From cdbc00de697d8e344b329ca3a83136843bccf07e Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:56:46 +0200 Subject: [PATCH 1/5] wip: gnopls base Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- contribs/gnopls/README.md | 158 + contribs/gnopls/doc/advanced.md | 57 + contribs/gnopls/doc/analyzers.md | 1035 +++ contribs/gnopls/doc/assets/assets.go | 7 + .../gnopls/doc/assets/browse-assembly.png | Bin 0 -> 779478 bytes .../gnopls/doc/assets/browse-free-symbols.png | Bin 0 -> 309890 bytes contribs/gnopls/doc/assets/browse-pkg-doc.png | Bin 0 -> 636585 bytes .../gnopls/doc/assets/code-action-doc.png | Bin 0 -> 47715 bytes .../doc/assets/convert-string-interpreted.png | Bin 0 -> 42250 bytes .../gnopls/doc/assets/convert-string-raw.png | Bin 0 -> 52665 bytes .../gnopls/doc/assets/diagnostic-analysis.png | Bin 0 -> 52655 bytes .../doc/assets/diagnostic-typeerror.png | Bin 0 -> 65041 bytes .../gnopls/doc/assets/document-highlight.png | Bin 0 -> 20231 bytes contribs/gnopls/doc/assets/documentlink.png | Bin 0 -> 22311 bytes .../doc/assets/extract-function-after.png | Bin 0 -> 37975 bytes .../doc/assets/extract-function-before.png | Bin 0 -> 35851 bytes .../doc/assets/extract-to-new-file-after.png | Bin 0 -> 28575 bytes .../doc/assets/extract-to-new-file-before.png | Bin 0 -> 34603 bytes .../gnopls/doc/assets/extract-var-after.png | Bin 0 -> 12035 bytes .../gnopls/doc/assets/extract-var-before.png | Bin 0 -> 27457 bytes .../gnopls/doc/assets/fill-struct-after.png | Bin 0 -> 31585 bytes .../gnopls/doc/assets/fill-struct-before.png | Bin 0 -> 30109 bytes .../gnopls/doc/assets/fill-switch-after.png | Bin 0 -> 34400 bytes .../gnopls/doc/assets/fill-switch-before.png | Bin 0 -> 15683 bytes .../doc/assets/fill-switch-enum-after.png | Bin 0 -> 47748 bytes .../doc/assets/fill-switch-enum-before.png | Bin 0 -> 16619 bytes contribs/gnopls/doc/assets/foldingrange.png | Bin 0 -> 47940 bytes contribs/gnopls/doc/assets/go.mod | 7 + contribs/gnopls/doc/assets/hover-basic.png | Bin 0 -> 43375 bytes contribs/gnopls/doc/assets/hover-doclink.png | Bin 0 -> 38293 bytes contribs/gnopls/doc/assets/hover-embed.png | Bin 0 -> 28530 bytes .../gnopls/doc/assets/hover-field-tag.png | Bin 0 -> 69972 bytes contribs/gnopls/doc/assets/hover-linkname.png | Bin 0 -> 25431 bytes .../gnopls/doc/assets/hover-size-field.png | Bin 0 -> 37260 bytes .../gnopls/doc/assets/hover-size-struct.png | Bin 0 -> 88030 bytes .../gnopls/doc/assets/hover-size-wasteful.png | Bin 0 -> 14369 bytes .../doc/assets/inlayhint-parameternames.png | Bin 0 -> 6675 bytes .../gnopls/doc/assets/invert-if-after.png | Bin 0 -> 29629 bytes .../gnopls/doc/assets/invert-if-before.png | Bin 0 -> 42334 bytes contribs/gnopls/doc/assets/outgoingcalls.png | Bin 0 -> 159640 bytes .../doc/assets/remove-unusedparam-after.png | Bin 0 -> 14775 bytes .../doc/assets/remove-unusedparam-before.png | Bin 0 -> 26457 bytes .../gnopls/doc/assets/rename-conflict.png | Bin 0 -> 66148 bytes contribs/gnopls/doc/assets/signature-help.png | Bin 0 -> 65416 bytes contribs/gnopls/doc/codelenses.md | 160 + contribs/gnopls/doc/command-line.md | 35 + contribs/gnopls/doc/contributing.md | 181 + contribs/gnopls/doc/daemon.md | 183 + contribs/gnopls/doc/design/architecture.svg | 1 + contribs/gnopls/doc/design/design.md | 436 ++ contribs/gnopls/doc/design/implementation.md | 172 + contribs/gnopls/doc/design/integrating.md | 89 + contribs/gnopls/doc/emacs.md | 188 + contribs/gnopls/doc/features/README.md | 65 + contribs/gnopls/doc/features/completion.md | 3 + contribs/gnopls/doc/features/diagnostics.md | 177 + contribs/gnopls/doc/features/modfiles.md | 9 + contribs/gnopls/doc/features/navigation.md | 253 + contribs/gnopls/doc/features/passive.md | 282 + contribs/gnopls/doc/features/templates.md | 49 + .../gnopls/doc/features/transformation.md | 677 ++ contribs/gnopls/doc/features/web.md | 151 + contribs/gnopls/doc/generate/generate.go | 795 ++ contribs/gnopls/doc/generate/generate_test.go | 28 + contribs/gnopls/doc/helix.md | 51 + contribs/gnopls/doc/inlayHints.md | 91 + contribs/gnopls/doc/inline-after.png | Bin 0 -> 30596 bytes contribs/gnopls/doc/inline-before.png | Bin 0 -> 27797 bytes contribs/gnopls/doc/refactor-inline.md | 163 + contribs/gnopls/doc/release/README | 10 + contribs/gnopls/doc/release/v0.16.0.md | 288 + contribs/gnopls/doc/release/v0.17.0.md | 56 + contribs/gnopls/doc/releases.md | 25 + contribs/gnopls/doc/semantictokens.md | 124 + contribs/gnopls/doc/settings.md | 563 ++ contribs/gnopls/doc/subl.md | 81 + contribs/gnopls/doc/troubleshooting.md | 48 + contribs/gnopls/doc/vim.md | 234 + contribs/gnopls/doc/workspace.md | 139 + contribs/gnopls/doc/zeroconfig.png | Bin 0 -> 35409 bytes contribs/gnopls/go.mod | 31 + contribs/gnopls/go.sum | 61 + contribs/gnopls/integration/govim/Dockerfile | 16 + contribs/gnopls/integration/govim/README.md | 47 + .../gnopls/integration/govim/artifacts.go | 67 + .../integration/govim/cloudbuild.harness.yaml | 21 + .../gnopls/integration/govim/cloudbuild.yaml | 51 + .../gnopls/integration/govim/run_local.sh | 96 + .../govim/run_tests_for_cloudbuild.sh | 28 + .../analysis/deprecated/deprecated.go | 267 + .../analysis/deprecated/deprecated_test.go | 16 + .../internal/analysis/deprecated/doc.go | 16 + .../analysis/deprecated/testdata/src/a/a.go | 17 + .../deprecated/testdata/src/a/a_test.go | 12 + .../internal/analysis/embeddirective/doc.go | 18 + .../analysis/embeddirective/embeddirective.go | 166 + .../embeddirective/embeddirective_test.go | 16 + .../embeddirective/testdata/src/a/embedText | 1 + .../testdata/src/a/import_missing.go | 17 + .../testdata/src/a/import_present.go | 129 + .../testdata/src/a/import_present_go120.go | 26 + .../internal/analysis/fillreturns/doc.go | 27 + .../analysis/fillreturns/fillreturns.go | 264 + .../analysis/fillreturns/fillreturns_test.go | 25 + .../analysis/fillreturns/testdata/src/a/a.go | 139 + .../fillreturns/testdata/src/a/a.go.golden | 139 + .../fillreturns/testdata/src/typeparams/a.go | 5 + .../testdata/src/typeparams/a.go.golden | 5 + .../analysis/fillstruct/fillstruct.go | 498 ++ .../analysis/fillstruct/fillstruct_test.go | 36 + .../analysis/fillstruct/testdata/src/a/a.go | 112 + .../analysis/fillstruct/testdata/src/b/b.go | 6 + .../testdata/src/typeparams/typeparams.go | 54 + .../internal/analysis/fillswitch/doc.go | 66 + .../analysis/fillswitch/fillswitch.go | 300 + .../analysis/fillswitch/fillswitch_test.go | 36 + .../analysis/fillswitch/testdata/src/a/a.go | 76 + .../analysis/fillswitch/testdata/src/b/b.go | 21 + .../analysis/infertypeargs/infertypeargs.go | 149 + .../infertypeargs/infertypeargs_test.go | 17 + .../infertypeargs/testdata/src/a/basic.go | 20 + .../testdata/src/a/basic.go.golden | 20 + .../infertypeargs/testdata/src/a/imported.go | 12 + .../testdata/src/a/imported.go.golden | 12 + .../testdata/src/a/imported/imported.go | 7 + .../testdata/src/a/notypechange.go | 26 + .../testdata/src/a/notypechange.go.golden | 26 + .../gnopls/internal/analysis/nonewvars/doc.go | 22 + .../internal/analysis/nonewvars/nonewvars.go | 89 + .../analysis/nonewvars/nonewvars_test.go | 17 + .../analysis/nonewvars/testdata/src/a/a.go | 16 + .../nonewvars/testdata/src/a/a.go.golden | 16 + .../nonewvars/testdata/src/typeparams/a.go | 6 + .../testdata/src/typeparams/a.go.golden | 6 + .../norangeoverfunc/norangeoverfunc.go | 50 + .../internal/analysis/noresultvalues/doc.go | 21 + .../analysis/noresultvalues/noresultvalues.go | 86 + .../noresultvalues/noresultvalues_test.go | 17 + .../noresultvalues/testdata/src/a/a.go | 9 + .../noresultvalues/testdata/src/a/a.go.golden | 9 + .../testdata/src/typeparams/a.go | 6 + .../testdata/src/typeparams/a.go.golden | 6 + .../analysis/simplifycompositelit/doc.go | 24 + .../simplifycompositelit.go | 205 + .../simplifycompositelit_test.go | 17 + .../simplifycompositelit/testdata/src/a/a.go | 234 + .../testdata/src/a/a.go.golden | 234 + .../src/generatedcode/generatedcode.go | 17 + .../src/generatedcode/generatedcode.go.golden | 17 + .../internal/analysis/simplifyrange/doc.go | 32 + .../analysis/simplifyrange/simplifyrange.go | 114 + .../simplifyrange/simplifyrange_test.go | 22 + .../simplifyrange/testdata/src/a/a.go | 16 + .../simplifyrange/testdata/src/a/a.go.golden | 16 + .../src/generatedcode/generatedcode.go | 18 + .../src/generatedcode/generatedcode.go.golden | 18 + .../src/rangeoverfunc/rangeoverfunc.go | 20 + .../src/rangeoverfunc/rangeoverfunc.go.golden | 20 + .../internal/analysis/simplifyslice/doc.go | 24 + .../analysis/simplifyslice/simplifyslice.go | 101 + .../simplifyslice/simplifyslice_test.go | 17 + .../simplifyslice/testdata/src/a/a.go | 70 + .../simplifyslice/testdata/src/a/a.go.golden | 70 + .../src/generatedcode/generatedcode.go | 72 + .../src/generatedcode/generatedcode.go.golden | 72 + .../testdata/src/typeparams/typeparams.go | 36 + .../src/typeparams/typeparams.go.golden | 36 + .../internal/analysis/stubmethods/doc.go | 38 + .../analysis/stubmethods/stubmethods.go | 403 + .../analysis/stubmethods/stubmethods_test.go | 17 + .../testdata/src/typeparams/implement.go | 15 + .../internal/analysis/undeclaredname/doc.go | 23 + .../undeclaredname/testdata/src/a/a.go | 28 + .../undeclaredname/testdata/src/a/channels.go | 13 + .../testdata/src/a/consecutive_params.go | 10 + .../testdata/src/a/error_param.go | 10 + .../undeclaredname/testdata/src/a/literals.go | 11 + .../testdata/src/a/operation.go | 11 + .../undeclaredname/testdata/src/a/selector.go | 10 + .../undeclaredname/testdata/src/a/slice.go | 9 + .../undeclaredname/testdata/src/a/tuple.go | 13 + .../testdata/src/a/unique_params.go | 11 + .../analysis/undeclaredname/undeclared.go | 360 + .../undeclaredname/undeclared_test.go | 17 + .../analysis/unusedparams/cmd/main.go | 13 + .../internal/analysis/unusedparams/doc.go | 34 + .../analysis/unusedparams/testdata/src/a/a.go | 87 + .../unusedparams/testdata/src/a/a.go.golden | 87 + .../testdata/src/typeparams/typeparams.go | 55 + .../src/typeparams/typeparams.go.golden | 55 + .../analysis/unusedparams/unusedparams.go | 308 + .../unusedparams/unusedparams_test.go | 17 + .../unusedvariable/testdata/src/assign/a.go | 74 + .../testdata/src/assign/a.go.golden | 59 + .../unusedvariable/testdata/src/decl/a.go | 30 + .../testdata/src/decl/a.go.golden | 24 + .../analysis/unusedvariable/unusedvariable.go | 315 + .../unusedvariable/unusedvariable_test.go | 24 + .../analysis/useany/testdata/src/a/a.go | 25 + .../useany/testdata/src/a/a.go.golden | 25 + .../gnopls/internal/analysis/useany/useany.go | 98 + .../internal/analysis/useany/useany_test.go | 17 + contribs/gnopls/internal/cache/analysis.go | 1678 ++++ contribs/gnopls/internal/cache/cache.go | 122 + contribs/gnopls/internal/cache/check.go | 2067 +++++ contribs/gnopls/internal/cache/constraints.go | 61 + .../gnopls/internal/cache/constraints_test.go | 126 + contribs/gnopls/internal/cache/debug.go | 12 + contribs/gnopls/internal/cache/diagnostics.go | 192 + contribs/gnopls/internal/cache/errors.go | 506 ++ contribs/gnopls/internal/cache/errors_test.go | 128 + contribs/gnopls/internal/cache/filemap.go | 151 + .../gnopls/internal/cache/filemap_test.go | 112 + contribs/gnopls/internal/cache/filterer.go | 83 + contribs/gnopls/internal/cache/fs_memoized.go | 171 + contribs/gnopls/internal/cache/fs_overlay.go | 79 + contribs/gnopls/internal/cache/imports.go | 254 + contribs/gnopls/internal/cache/keys.go | 54 + contribs/gnopls/internal/cache/load.go | 801 ++ .../internal/cache/metadata/cycle_test.go | 146 + .../gnopls/internal/cache/metadata/graph.go | 413 + .../internal/cache/metadata/metadata.go | 259 + .../internal/cache/methodsets/methodsets.go | 493 ++ contribs/gnopls/internal/cache/mod.go | 495 ++ contribs/gnopls/internal/cache/mod_tidy.go | 503 ++ contribs/gnopls/internal/cache/mod_vuln.go | 389 + contribs/gnopls/internal/cache/os_darwin.go | 59 + contribs/gnopls/internal/cache/os_windows.go | 56 + contribs/gnopls/internal/cache/package.go | 193 + contribs/gnopls/internal/cache/parse.go | 45 + contribs/gnopls/internal/cache/parse_cache.go | 419 + .../gnopls/internal/cache/parse_cache_test.go | 234 + .../gnopls/internal/cache/parsego/file.go | 102 + .../gnopls/internal/cache/parsego/parse.go | 967 +++ .../internal/cache/parsego/parse_test.go | 46 + contribs/gnopls/internal/cache/port.go | 204 + contribs/gnopls/internal/cache/port_test.go | 124 + contribs/gnopls/internal/cache/session.go | 1207 +++ .../gnopls/internal/cache/session_test.go | 402 + contribs/gnopls/internal/cache/snapshot.go | 2324 ++++++ contribs/gnopls/internal/cache/symbols.go | 201 + .../gnopls/internal/cache/testfuncs/match.go | 116 + .../gnopls/internal/cache/testfuncs/tests.go | 324 + .../gnopls/internal/cache/typerefs/doc.go | 151 + .../internal/cache/typerefs/packageset.go | 148 + .../internal/cache/typerefs/pkggraph_test.go | 244 + .../internal/cache/typerefs/pkgrefs_test.go | 406 + .../gnopls/internal/cache/typerefs/refs.go | 832 ++ .../internal/cache/typerefs/refs_test.go | 549 ++ contribs/gnopls/internal/cache/view.go | 1249 +++ contribs/gnopls/internal/cache/view_test.go | 175 + contribs/gnopls/internal/cache/workspace.go | 112 + contribs/gnopls/internal/cache/xrefs/xrefs.go | 193 + .../gnopls/internal/clonetest/clonetest.go | 152 + .../internal/clonetest/clonetest_test.go | 74 + .../gnopls/internal/cmd/call_hierarchy.go | 143 + .../gnopls/internal/cmd/capabilities_test.go | 178 + contribs/gnopls/internal/cmd/check.go | 110 + contribs/gnopls/internal/cmd/cmd.go | 949 +++ contribs/gnopls/internal/cmd/codeaction.go | 224 + contribs/gnopls/internal/cmd/codelens.go | 137 + contribs/gnopls/internal/cmd/definition.go | 137 + contribs/gnopls/internal/cmd/execute.go | 135 + contribs/gnopls/internal/cmd/folding_range.go | 71 + contribs/gnopls/internal/cmd/format.go | 75 + contribs/gnopls/internal/cmd/help_test.go | 90 + contribs/gnopls/internal/cmd/highlight.go | 81 + .../gnopls/internal/cmd/implementation.go | 86 + contribs/gnopls/internal/cmd/imports.go | 80 + contribs/gnopls/internal/cmd/info.go | 313 + .../gnopls/internal/cmd/integration_test.go | 1231 +++ contribs/gnopls/internal/cmd/links.go | 76 + contribs/gnopls/internal/cmd/parsespan.go | 106 + .../gnopls/internal/cmd/prepare_rename.go | 79 + contribs/gnopls/internal/cmd/references.go | 91 + contribs/gnopls/internal/cmd/remote.go | 164 + contribs/gnopls/internal/cmd/rename.go | 73 + .../gnopls/internal/cmd/semantictokens.go | 196 + contribs/gnopls/internal/cmd/serve.go | 146 + contribs/gnopls/internal/cmd/signature.go | 87 + contribs/gnopls/internal/cmd/span.go | 238 + .../gnopls/internal/cmd/spanformat_test.go | 55 + contribs/gnopls/internal/cmd/stats.go | 248 + contribs/gnopls/internal/cmd/subcommands.go | 59 + contribs/gnopls/internal/cmd/symbols.go | 115 + .../gnopls/internal/cmd/usage/api-json.hlp | 8 + contribs/gnopls/internal/cmd/usage/bug.hlp | 4 + .../internal/cmd/usage/call_hierarchy.hlp | 10 + contribs/gnopls/internal/cmd/usage/check.hlp | 8 + .../gnopls/internal/cmd/usage/codeaction.hlp | 85 + .../gnopls/internal/cmd/usage/codelens.hlp | 35 + .../gnopls/internal/cmd/usage/definition.hlp | 15 + .../gnopls/internal/cmd/usage/execute.hlp | 28 + contribs/gnopls/internal/cmd/usage/fix.hlp | 5 + .../internal/cmd/usage/folding_ranges.hlp | 8 + contribs/gnopls/internal/cmd/usage/format.hlp | 20 + contribs/gnopls/internal/cmd/usage/help.hlp | 10 + .../gnopls/internal/cmd/usage/highlight.hlp | 10 + .../internal/cmd/usage/implementation.hlp | 10 + .../gnopls/internal/cmd/usage/imports.hlp | 18 + .../gnopls/internal/cmd/usage/inspect.hlp | 8 + .../gnopls/internal/cmd/usage/licenses.hlp | 4 + contribs/gnopls/internal/cmd/usage/links.hlp | 12 + .../internal/cmd/usage/prepare_rename.hlp | 10 + .../gnopls/internal/cmd/usage/references.hlp | 14 + contribs/gnopls/internal/cmd/usage/remote.hlp | 8 + contribs/gnopls/internal/cmd/usage/rename.hlp | 20 + contribs/gnopls/internal/cmd/usage/semtok.hlp | 8 + contribs/gnopls/internal/cmd/usage/serve.hlp | 30 + .../gnopls/internal/cmd/usage/signature.hlp | 10 + contribs/gnopls/internal/cmd/usage/stats.hlp | 17 + .../gnopls/internal/cmd/usage/symbols.hlp | 7 + .../gnopls/internal/cmd/usage/usage-v.hlp | 89 + contribs/gnopls/internal/cmd/usage/usage.hlp | 86 + .../gnopls/internal/cmd/usage/version.hlp | 6 + .../gnopls/internal/cmd/usage/vulncheck.hlp | 13 + .../internal/cmd/usage/workspace_symbol.hlp | 13 + contribs/gnopls/internal/cmd/vulncheck.go | 47 + .../gnopls/internal/cmd/workspace_symbol.go | 89 + contribs/gnopls/internal/debug/info.go | 139 + contribs/gnopls/internal/debug/info_test.go | 50 + contribs/gnopls/internal/debug/log/log.go | 43 + contribs/gnopls/internal/debug/metrics.go | 58 + contribs/gnopls/internal/debug/rpc.go | 239 + contribs/gnopls/internal/debug/serve.go | 846 +++ .../gnopls/internal/debug/template_test.go | 156 + contribs/gnopls/internal/debug/trace.go | 320 + contribs/gnopls/internal/doc/api.go | 75 + contribs/gnopls/internal/doc/api.json | 1357 ++++ contribs/gnopls/internal/file/file.go | 62 + contribs/gnopls/internal/file/hash.go | 33 + contribs/gnopls/internal/file/kind.go | 68 + contribs/gnopls/internal/file/modification.go | 57 + .../gnopls/internal/filecache/filecache.go | 606 ++ .../internal/filecache/filecache_test.go | 265 + contribs/gnopls/internal/fuzzy/input.go | 183 + contribs/gnopls/internal/fuzzy/input_test.go | 141 + contribs/gnopls/internal/fuzzy/matcher.go | 439 ++ .../gnopls/internal/fuzzy/matcher_test.go | 307 + contribs/gnopls/internal/fuzzy/self_test.go | 39 + contribs/gnopls/internal/fuzzy/symbol.go | 309 + contribs/gnopls/internal/fuzzy/symbol_test.go | 253 + contribs/gnopls/internal/golang/add_import.go | 29 + contribs/gnopls/internal/golang/assembly.go | 133 + .../gnopls/internal/golang/call_hierarchy.go | 315 + .../gnopls/internal/golang/change_quote.go | 79 + .../internal/golang/change_signature.go | 617 ++ contribs/gnopls/internal/golang/code_lens.go | 216 + contribs/gnopls/internal/golang/codeaction.go | 655 ++ contribs/gnopls/internal/golang/comment.go | 201 + .../internal/golang/completion/builtin.go | 147 + .../internal/golang/completion/completion.go | 3385 +++++++++ .../golang/completion/deep_completion.go | 371 + .../golang/completion/deep_completion_test.go | 33 + .../internal/golang/completion/definition.go | 160 + .../internal/golang/completion/format.go | 444 ++ .../gnopls/internal/golang/completion/fuzz.go | 141 + .../internal/golang/completion/keywords.go | 154 + .../internal/golang/completion/labels.go | 112 + .../internal/golang/completion/literal.go | 602 ++ .../internal/golang/completion/package.go | 334 + .../golang/completion/package_test.go | 81 + .../golang/completion/postfix_snippets.go | 682 ++ .../internal/golang/completion/printf.go | 174 + .../internal/golang/completion/printf_test.go | 72 + .../internal/golang/completion/snippet.go | 126 + .../completion/snippet/snippet_builder.go | 111 + .../snippet/snippet_builder_test.go | 62 + .../internal/golang/completion/statements.go | 420 + .../gnopls/internal/golang/completion/util.go | 341 + .../internal/golang/completion/util_test.go | 28 + contribs/gnopls/internal/golang/definition.go | 407 + .../gnopls/internal/golang/diagnostics.go | 44 + .../gnopls/internal/golang/embeddirective.go | 195 + contribs/gnopls/internal/golang/extract.go | 1380 ++++ .../gnopls/internal/golang/extracttofile.go | 301 + contribs/gnopls/internal/golang/fix.go | 217 + .../gnopls/internal/golang/folding_range.go | 197 + contribs/gnopls/internal/golang/format.go | 340 + .../gnopls/internal/golang/format_test.go | 75 + .../gnopls/internal/golang/freesymbols.go | 416 + .../internal/golang/freesymbols_test.go | 132 + .../gnopls/internal/golang/gc_annotations.go | 226 + contribs/gnopls/internal/golang/highlight.go | 604 ++ contribs/gnopls/internal/golang/hover.go | 1587 ++++ contribs/gnopls/internal/golang/identifier.go | 183 + .../gnopls/internal/golang/identifier_test.go | 107 + .../gnopls/internal/golang/implementation.go | 543 ++ contribs/gnopls/internal/golang/inlay_hint.go | 354 + contribs/gnopls/internal/golang/inline.go | 136 + contribs/gnopls/internal/golang/inline_all.go | 276 + .../internal/golang/invertifcondition.go | 267 + .../gnopls/internal/golang/known_packages.go | 137 + contribs/gnopls/internal/golang/lines.go | 245 + contribs/gnopls/internal/golang/linkname.go | 145 + contribs/gnopls/internal/golang/origin.go | 30 + contribs/gnopls/internal/golang/pkgdoc.go | 889 +++ contribs/gnopls/internal/golang/references.go | 688 ++ contribs/gnopls/internal/golang/rename.go | 1438 ++++ .../gnopls/internal/golang/rename_check.go | 960 +++ contribs/gnopls/internal/golang/semtok.go | 999 +++ .../gnopls/internal/golang/signature_help.go | 233 + contribs/gnopls/internal/golang/snapshot.go | 98 + contribs/gnopls/internal/golang/stub.go | 334 + contribs/gnopls/internal/golang/symbols.go | 230 + .../gnopls/internal/golang/type_definition.go | 53 + .../gnopls/internal/golang/types_format.go | 546 ++ contribs/gnopls/internal/golang/util.go | 365 + .../internal/golang/workspace_symbol.go | 526 ++ .../internal/golang/workspace_symbol_test.go | 138 + contribs/gnopls/internal/label/keys.go | 37 + .../gnopls/internal/licenses/gen-licenses.sh | 38 + contribs/gnopls/internal/licenses/licenses.go | 146 + .../gnopls/internal/licenses/licenses_test.go | 47 + .../internal/lsprpc/autostart_default.go | 38 + .../gnopls/internal/lsprpc/autostart_posix.go | 96 + contribs/gnopls/internal/lsprpc/binder.go | 5 + .../gnopls/internal/lsprpc/binder_test.go | 199 + .../lsprpc/commandinterceptor_test.go | 61 + contribs/gnopls/internal/lsprpc/dialer.go | 114 + .../gnopls/internal/lsprpc/export_test.go | 142 + contribs/gnopls/internal/lsprpc/goenv.go | 34 + contribs/gnopls/internal/lsprpc/goenv_test.go | 133 + contribs/gnopls/internal/lsprpc/lsprpc.go | 533 ++ .../gnopls/internal/lsprpc/lsprpc_test.go | 375 + .../gnopls/internal/lsprpc/middleware_test.go | 223 + contribs/gnopls/internal/mod/code_lens.go | 172 + contribs/gnopls/internal/mod/diagnostics.go | 545 ++ contribs/gnopls/internal/mod/format.go | 32 + contribs/gnopls/internal/mod/hover.go | 380 + contribs/gnopls/internal/mod/inlayhint.go | 104 + contribs/gnopls/internal/progress/progress.go | 292 + .../gnopls/internal/progress/progress_test.go | 161 + .../internal/protocol/command/command_gen.go | 677 ++ .../protocol/command/commandmeta/meta.go | 264 + .../internal/protocol/command/gen/gen.go | 204 + .../internal/protocol/command/generate.go | 27 + .../internal/protocol/command/interface.go | 735 ++ .../protocol/command/interface_test.go | 33 + .../gnopls/internal/protocol/command/util.go | 79 + contribs/gnopls/internal/protocol/context.go | 65 + contribs/gnopls/internal/protocol/doc.go | 18 + contribs/gnopls/internal/protocol/edits.go | 175 + contribs/gnopls/internal/protocol/enums.go | 179 + .../internal/protocol/generate/README.md | 144 + .../internal/protocol/generate/generate.go | 118 + .../gnopls/internal/protocol/generate/main.go | 360 + .../internal/protocol/generate/main_test.go | 116 + .../internal/protocol/generate/output.go | 441 ++ .../internal/protocol/generate/tables.go | 274 + .../internal/protocol/generate/typenames.go | 181 + .../internal/protocol/generate/types.go | 167 + .../gnopls/internal/protocol/json_test.go | 140 + contribs/gnopls/internal/protocol/log.go | 136 + contribs/gnopls/internal/protocol/mapper.go | 443 ++ .../gnopls/internal/protocol/mapper_test.go | 449 ++ contribs/gnopls/internal/protocol/protocol.go | 313 + contribs/gnopls/internal/protocol/semantic.go | 58 + .../gnopls/internal/protocol/semtok/semtok.go | 108 + contribs/gnopls/internal/protocol/span.go | 100 + contribs/gnopls/internal/protocol/tsclient.go | 296 + .../internal/protocol/tsdocument_changes.go | 81 + .../internal/protocol/tsinsertreplaceedit.go | 40 + .../protocol/tsinsertreplaceedit_test.go | 44 + contribs/gnopls/internal/protocol/tsjson.go | 2130 ++++++ .../gnopls/internal/protocol/tsprotocol.go | 6727 +++++++++++++++++ contribs/gnopls/internal/protocol/tsserver.go | 1334 ++++ contribs/gnopls/internal/protocol/uri.go | 220 + contribs/gnopls/internal/protocol/uri_test.go | 134 + .../internal/protocol/uri_windows_test.go | 139 + .../gnopls/internal/server/assets/common.css | 116 + .../gnopls/internal/server/assets/common.js | 28 + .../gnopls/internal/server/assets/favicon.ico | Bin 0 -> 5686 bytes .../internal/server/assets/go-logo-blue.svg | 1 + .../gnopls/internal/server/call_hierarchy.go | 59 + .../gnopls/internal/server/code_action.go | 316 + contribs/gnopls/internal/server/code_lens.go | 66 + contribs/gnopls/internal/server/command.go | 1651 ++++ contribs/gnopls/internal/server/completion.go | 201 + contribs/gnopls/internal/server/counters.go | 28 + contribs/gnopls/internal/server/debug.go | 12 + contribs/gnopls/internal/server/definition.go | 61 + .../gnopls/internal/server/diagnostics.go | 990 +++ .../gnopls/internal/server/folding_range.go | 49 + contribs/gnopls/internal/server/format.go | 38 + contribs/gnopls/internal/server/general.go | 685 ++ contribs/gnopls/internal/server/highlight.go | 39 + contribs/gnopls/internal/server/hover.go | 59 + .../gnopls/internal/server/implementation.go | 36 + contribs/gnopls/internal/server/inlay_hint.go | 35 + contribs/gnopls/internal/server/link.go | 286 + contribs/gnopls/internal/server/prompt.go | 419 + .../gnopls/internal/server/prompt_test.go | 82 + contribs/gnopls/internal/server/references.go | 40 + contribs/gnopls/internal/server/rename.go | 96 + .../gnopls/internal/server/selection_range.go | 75 + contribs/gnopls/internal/server/semantic.go | 56 + contribs/gnopls/internal/server/server.go | 540 ++ .../gnopls/internal/server/signature_help.go | 48 + contribs/gnopls/internal/server/symbols.go | 62 + .../internal/server/text_synchronization.go | 421 ++ .../gnopls/internal/server/unimplemented.go | 159 + contribs/gnopls/internal/server/workspace.go | 141 + .../internal/server/workspace_symbol.go | 41 + contribs/gnopls/internal/settings/analysis.go | 218 + .../internal/settings/codeactionkind.go | 98 + contribs/gnopls/internal/settings/default.go | 149 + contribs/gnopls/internal/settings/settings.go | 1435 ++++ .../gnopls/internal/settings/settings_test.go | 242 + .../gnopls/internal/settings/staticcheck.go | 63 + contribs/gnopls/internal/settings/vet_test.go | 50 + .../internal/telemetry/cmd/stacks/stacks.go | 970 +++ contribs/gnopls/internal/telemetry/latency.go | 102 + .../internal/telemetry/telemetry_test.go | 232 + .../gnopls/internal/template/completion.go | 253 + .../internal/template/completion_test.go | 102 + .../gnopls/internal/template/highlight.go | 97 + .../internal/template/implementations.go | 218 + contribs/gnopls/internal/template/parse.go | 504 ++ .../gnopls/internal/template/parse_test.go | 238 + contribs/gnopls/internal/template/symbols.go | 231 + contribs/gnopls/internal/test/compare/text.go | 49 + .../gnopls/internal/test/compare/text_test.go | 28 + .../test/integration/bench/bench_test.go | 349 + .../test/integration/bench/codeaction_test.go | 69 + .../test/integration/bench/completion_test.go | 330 + .../test/integration/bench/definition_test.go | 46 + .../test/integration/bench/didchange_test.go | 142 + .../internal/test/integration/bench/doc.go | 40 + .../test/integration/bench/hover_test.go | 47 + .../integration/bench/implementations_test.go | 44 + .../test/integration/bench/imports_test.go | 89 + .../test/integration/bench/iwl_test.go | 72 + .../test/integration/bench/references_test.go | 44 + .../test/integration/bench/reload_test.go | 52 + .../test/integration/bench/rename_test.go | 49 + .../test/integration/bench/repo_test.go | 290 + .../test/integration/bench/stress_test.go | 92 + .../test/integration/bench/tests_test.go | 96 + .../test/integration/bench/typing_test.go | 63 + .../bench/workspace_symbols_test.go | 41 + .../integration/codelens/codelens_test.go | 411 + .../integration/codelens/gcdetails_test.go | 125 + .../completion/completion18_test.go | 121 + .../integration/completion/completion_test.go | 1166 +++ .../integration/completion/fixedbugs_test.go | 40 + .../completion/postfix_snippet_test.go | 762 ++ .../test/integration/debug/debug_test.go | 101 + .../integration/diagnostics/analysis_test.go | 127 + .../integration/diagnostics/builtin_test.go | 35 + .../diagnostics/diagnostics_test.go | 2209 ++++++ .../integration/diagnostics/golist_test.go | 71 + .../diagnostics/gopackagesdriver_test.go | 85 + .../diagnostics/invalidation_test.go | 141 + .../diagnostics/undeclared_test.go | 73 + .../gnopls/internal/test/integration/doc.go | 156 + .../gnopls/internal/test/integration/env.go | 358 + .../internal/test/integration/env_test.go | 68 + .../internal/test/integration/expectation.go | 829 ++ .../internal/test/integration/fake/client.go | 221 + .../internal/test/integration/fake/doc.go | 20 + .../internal/test/integration/fake/edit.go | 42 + .../test/integration/fake/edit_test.go | 96 + .../internal/test/integration/fake/editor.go | 1710 +++++ .../test/integration/fake/editor_test.go | 61 + .../test/integration/fake/glob/glob.go | 349 + .../test/integration/fake/glob/glob_test.go | 118 + .../internal/test/integration/fake/proxy.go | 35 + .../internal/test/integration/fake/sandbox.go | 285 + .../internal/test/integration/fake/workdir.go | 429 ++ .../test/integration/fake/workdir_test.go | 219 + .../test/integration/fake/workdir_windows.go | 21 + .../integration/inlayhints/inlayhints_test.go | 69 + .../integration/misc/call_hierarchy_test.go | 36 + .../test/integration/misc/codeactions_test.go | 120 + .../integration/misc/configuration_test.go | 255 + .../test/integration/misc/debugserver_test.go | 46 + .../test/integration/misc/definition_test.go | 644 ++ .../test/integration/misc/embed_test.go | 41 + .../test/integration/misc/extract_test.go | 67 + .../test/integration/misc/failures_test.go | 82 + .../test/integration/misc/fix_test.go | 162 + .../test/integration/misc/formatting_test.go | 394 + .../test/integration/misc/generate_test.go | 105 + .../test/integration/misc/highlight_test.go | 153 + .../test/integration/misc/hover_test.go | 724 ++ .../test/integration/misc/import_test.go | 127 + .../test/integration/misc/imports_test.go | 388 + .../test/integration/misc/link_test.go | 94 + .../test/integration/misc/misc_test.go | 72 + .../integration/misc/multiple_adhoc_test.go | 44 + .../test/integration/misc/prompt_test.go | 488 ++ .../test/integration/misc/references_test.go | 574 ++ .../test/integration/misc/rename_test.go | 921 +++ .../integration/misc/semantictokens_test.go | 238 + .../test/integration/misc/settings_test.go | 32 + .../test/integration/misc/shared_test.go | 58 + .../integration/misc/signature_help_test.go | 69 + .../test/integration/misc/staticcheck_test.go | 138 + .../test/integration/misc/vendor_test.go | 102 + .../test/integration/misc/vuln_test.go | 939 +++ .../test/integration/misc/webserver_test.go | 495 ++ .../integration/misc/workspace_symbol_test.go | 114 + .../test/integration/modfile/modfile_test.go | 1219 +++ .../integration/modfile/tempmodfile_test.go | 41 + .../internal/test/integration/options.go | 194 + .../internal/test/integration/regtest.go | 193 + .../internal/test/integration/runner.go | 435 ++ .../integration/template/template_test.go | 231 + .../test/integration/watch/setting_test.go | 85 + .../test/integration/watch/watch_test.go | 713 ++ .../test/integration/workspace/adhoc_test.go | 39 + .../test/integration/workspace/broken_test.go | 265 + .../workspace/directoryfilters_test.go | 207 + .../integration/workspace/fromenv_test.go | 76 + .../integration/workspace/goversion_test.go | 126 + .../integration/workspace/metadata_test.go | 238 + .../integration/workspace/misspelling_test.go | 80 + .../integration/workspace/modules_test.go | 161 + .../workspace/multi_folder_test.go | 128 + .../integration/workspace/packages_test.go | 482 ++ .../integration/workspace/quickfix_test.go | 458 ++ .../integration/workspace/standalone_test.go | 206 + .../test/integration/workspace/std_test.go | 73 + .../test/integration/workspace/vendor_test.go | 67 + .../integration/workspace/workspace_test.go | 1468 ++++ .../integration/workspace/zero_config_test.go | 327 + .../internal/test/integration/wrappers.go | 625 ++ contribs/gnopls/internal/test/marker/doc.go | 377 + .../internal/test/marker/marker_test.go | 2497 ++++++ .../testdata/callhierarchy/callhierarchy.txt | 92 + .../testdata/callhierarchy/issue64451.txt | 51 + .../testdata/callhierarchy/issue66923.txt | 15 + .../testdata/codeaction/change_quote.txt | 69 + .../codeaction/extract-variadic-63287.txt | 28 + .../testdata/codeaction/extract_method.txt | 256 + .../codeaction/extract_variable-67905.txt | 29 + .../testdata/codeaction/extract_variable.txt | 70 + .../codeaction/extract_variable_resolve.txt | 81 + .../testdata/codeaction/extracttofile.txt | 279 + .../testdata/codeaction/fill_struct.txt | 575 ++ .../codeaction/fill_struct_resolve.txt | 586 ++ .../testdata/codeaction/fill_switch.txt | 105 + .../codeaction/fill_switch_resolve.txt | 116 + .../codeaction/functionextraction.txt | 583 ++ .../functionextraction_issue44813.txt | 42 + .../marker/testdata/codeaction/grouplines.txt | 206 + .../codeaction/import-shadows-builtin.txt | 55 + .../marker/testdata/codeaction/imports.txt | 175 + .../testdata/codeaction/infertypeargs.txt | 25 + .../marker/testdata/codeaction/inline.txt | 24 + .../testdata/codeaction/inline_resolve.txt | 35 + .../marker/testdata/codeaction/invertif.txt | 218 + .../marker/testdata/codeaction/issue64558.txt | 14 + .../testdata/codeaction/removeparam.txt | 247 + .../codeaction/removeparam_formatting.txt | 55 + .../codeaction/removeparam_funcvalue.txt | 19 + .../codeaction/removeparam_imports.txt | 163 + .../codeaction/removeparam_issue65217.txt | 58 + .../codeaction/removeparam_method.txt | 126 + .../codeaction/removeparam_resolve.txt | 258 + .../codeaction/removeparam_satisfies.txt | 65 + .../codeaction/removeparam_witherrs.txt | 11 + .../marker/testdata/codeaction/splitlines.txt | 223 + .../marker/testdata/codelens/generate.txt | 9 + .../test/marker/testdata/codelens/test.txt | 32 + .../marker/testdata/completion/address.txt | 92 + .../test/marker/testdata/completion/anon.txt | 37 + .../marker/testdata/completion/append.txt | 61 + .../marker/testdata/completion/assign.txt | 47 + .../test/marker/testdata/completion/bad.txt | 68 + .../marker/testdata/completion/basic_lit.txt | 19 + .../marker/testdata/completion/builtins.txt | 118 + .../testdata/completion/casesensitive.txt | 24 + .../test/marker/testdata/completion/cast.txt | 17 + .../marker/testdata/completion/channel.txt | 36 + .../marker/testdata/completion/comment.txt | 81 + .../marker/testdata/completion/complit.txt | 104 + .../marker/testdata/completion/constant.txt | 20 + .../testdata/completion/danglingstmt.txt | 158 + .../test/marker/testdata/completion/deep.txt | 110 + .../test/marker/testdata/completion/deep2.txt | 65 + .../marker/testdata/completion/errors.txt | 33 + .../marker/testdata/completion/field_list.txt | 38 + .../marker/testdata/completion/foobarbaz.txt | 541 ++ .../marker/testdata/completion/func_rank.txt | 83 + .../marker/testdata/completion/func_sig.txt | 15 + .../testdata/completion/func_snippets.txt | 32 + .../marker/testdata/completion/func_value.txt | 33 + .../test/marker/testdata/completion/fuzzy.txt | 55 + .../testdata/completion/imported-std.txt | 61 + .../test/marker/testdata/completion/index.txt | 36 + .../testdata/completion/interfacerank.txt | 36 + .../marker/testdata/completion/issue51783.txt | 47 + .../marker/testdata/completion/issue56505.txt | 13 + .../marker/testdata/completion/issue59096.txt | 20 + .../marker/testdata/completion/issue60545.txt | 28 + .../marker/testdata/completion/issue62141.txt | 39 + .../marker/testdata/completion/issue62560.txt | 19 + .../marker/testdata/completion/issue62676.txt | 64 + .../marker/testdata/completion/keywords.txt | 166 + .../marker/testdata/completion/labels.txt | 55 + .../test/marker/testdata/completion/lit.txt | 49 + .../test/marker/testdata/completion/maps.txt | 29 + .../testdata/completion/multi_return.txt | 55 + .../testdata/completion/nested_complit.txt | 26 + .../marker/testdata/completion/postfix.txt | 131 + .../completion/postfix_placeholder.txt | 83 + .../marker/testdata/completion/printf.txt | 42 + .../marker/testdata/completion/range_func.txt | 23 + .../test/marker/testdata/completion/rank.txt | 212 + .../marker/testdata/completion/snippet.txt | 77 + .../completion/snippet_placeholder.txt | 83 + .../marker/testdata/completion/statements.txt | 134 + .../test/marker/testdata/completion/testy.txt | 61 + .../testdata/completion/type_assert.txt | 30 + .../marker/testdata/completion/type_mods.txt | 27 + .../testdata/completion/type_params.txt | 70 + .../testdata/completion/unimported-std.txt | 49 + .../marker/testdata/completion/unimported.txt | 88 + .../marker/testdata/completion/unresolved.txt | 16 + .../marker/testdata/completion/unsafe.txt | 24 + .../marker/testdata/completion/variadic.txt | 67 + .../marker/testdata/configuration/static.txt | 41 + .../test/marker/testdata/definition/cgo.txt | 62 + .../marker/testdata/definition/comment.txt | 34 + .../test/marker/testdata/definition/embed.txt | 275 + .../marker/testdata/definition/import.txt | 53 + .../test/marker/testdata/definition/misc.txt | 241 + .../marker/testdata/definition/standalone.txt | 45 + .../marker/testdata/diagnostics/addgowork.txt | 51 + .../marker/testdata/diagnostics/analyzers.txt | 82 + .../testdata/diagnostics/excludedfile.txt | 36 + .../marker/testdata/diagnostics/generated.txt | 21 + .../marker/testdata/diagnostics/initcycle.txt | 17 + .../testdata/diagnostics/issue56943.txt | 22 + .../testdata/diagnostics/issue59005.txt | 20 + .../testdata/diagnostics/issue60544.txt | 9 + .../testdata/diagnostics/issue60605.txt | 15 + .../testdata/diagnostics/issue64547.txt | 14 + .../testdata/diagnostics/issue67360.txt | 16 + .../testdata/diagnostics/osarch_suffix.txt | 46 + .../marker/testdata/diagnostics/parseerr.txt | 27 + .../diagnostics/range-over-func-67237.txt | 82 + .../testdata/diagnostics/rundespiteerrors.txt | 21 + .../testdata/diagnostics/stdversion.txt | 89 + .../testdata/diagnostics/strangefiles.txt | 22 + .../marker/testdata/diagnostics/typeerr.txt | 28 + .../testdata/diagnostics/useinternal.txt | 24 + .../marker/testdata/diagnostics/usemodule.txt | 52 + .../marker/testdata/fixedbugs/issue59318.txt | 20 + .../marker/testdata/fixedbugs/issue59944.txt | 33 + .../marker/testdata/fixedbugs/issue61543.txt | 13 + .../marker/testdata/fixedbugs/issue66109.txt | 25 + .../marker/testdata/fixedbugs/issue66250.txt | 17 + .../marker/testdata/fixedbugs/issue66876.txt | 27 + .../test/marker/testdata/foldingrange/a.txt | 154 + .../testdata/foldingrange/a_lineonly.txt | 163 + .../test/marker/testdata/foldingrange/bad.txt | 41 + .../test/marker/testdata/format/format.txt | 80 + .../marker/testdata/format/issue59554.txt | 34 + .../test/marker/testdata/format/noparse.txt | 27 + .../marker/testdata/highlight/controlflow.txt | 74 + .../marker/testdata/highlight/highlight.txt | 158 + .../testdata/highlight/highlight_kind.txt | 88 + .../marker/testdata/highlight/issue60435.txt | 15 + .../marker/testdata/highlight/issue68918.txt | 9 + .../marker/testdata/highlight/switchbreak.txt | 21 + .../test/marker/testdata/hover/basiclit.txt | 87 + .../test/marker/testdata/hover/comment.txt | 82 + .../test/marker/testdata/hover/const.txt | 154 + .../test/marker/testdata/hover/embed.txt | 57 + .../test/marker/testdata/hover/generics.txt | 117 + .../test/marker/testdata/hover/godef.txt | 426 ++ .../test/marker/testdata/hover/goprivate.txt | 28 + .../test/marker/testdata/hover/hover.txt | 33 + .../test/marker/testdata/hover/issues.txt | 22 + .../test/marker/testdata/hover/linkable.txt | 134 + .../testdata/hover/linkable_generics.txt | 148 + .../test/marker/testdata/hover/linkname.txt | 30 + .../test/marker/testdata/hover/methods.txt | 71 + .../test/marker/testdata/hover/sizeoffset.txt | 117 + .../test/marker/testdata/hover/std.txt | 83 + .../marker/testdata/hover/structfield.txt | 29 + .../marker/testdata/implementation/basic.txt | 73 + .../testdata/implementation/generics.txt | 31 + .../testdata/implementation/issue43655.txt | 22 + .../testdata/implementation/issue67041.txt | 37 + .../marker/testdata/inlayhints/inlayhints.txt | 405 + .../marker/testdata/inlayhints/issue67142.txt | 36 + .../test/marker/testdata/links/links.txt | 47 + .../test/marker/testdata/modfile/godebug.txt | 43 + .../marker/testdata/modfile/godebug_bad.txt | 17 + .../testdata/references/crosspackage.txt | 37 + .../marker/testdata/references/imports.txt | 17 + .../marker/testdata/references/interfaces.txt | 42 + .../testdata/references/intrapackage.txt | 36 + .../marker/testdata/references/issue58506.txt | 56 + .../marker/testdata/references/issue59851.txt | 29 + .../marker/testdata/references/issue60369.txt | 29 + .../marker/testdata/references/issue60622.txt | 22 + .../marker/testdata/references/issue60676.txt | 68 + .../marker/testdata/references/issue61618.txt | 36 + .../marker/testdata/references/issue67978.txt | 18 + .../marker/testdata/references/shadow.txt | 17 + .../test/marker/testdata/references/test.txt | 29 + .../marker/testdata/references/typeswitch.txt | 18 + .../test/marker/testdata/rename/bad.txt | 19 + .../test/marker/testdata/rename/basic.txt | 35 + .../test/marker/testdata/rename/conflict.txt | 59 + .../test/marker/testdata/rename/crosspkg.txt | 72 + .../test/marker/testdata/rename/doclink.txt | 183 + .../test/marker/testdata/rename/embed.txt | 33 + .../test/marker/testdata/rename/generics.txt | 188 + .../marker/testdata/rename/generics_basic.txt | 107 + .../marker/testdata/rename/issue39614.txt | 18 + .../marker/testdata/rename/issue42134.txt | 80 + .../marker/testdata/rename/issue43616.txt | 21 + .../marker/testdata/rename/issue57479.txt | 37 + .../marker/testdata/rename/issue60752.txt | 57 + .../marker/testdata/rename/issue60789.txt | 35 + .../marker/testdata/rename/issue61294.txt | 26 + .../marker/testdata/rename/issue61640.txt | 33 + .../marker/testdata/rename/issue61813.txt | 14 + .../marker/testdata/rename/issue67069.txt | 54 + .../test/marker/testdata/rename/methods.txt | 57 + .../test/marker/testdata/rename/prepare.txt | 62 + .../test/marker/testdata/rename/random.txt | 238 + .../test/marker/testdata/rename/shadow.txt | 36 + .../test/marker/testdata/rename/testy.txt | 41 + .../marker/testdata/rename/typeswitch.txt | 24 + .../marker/testdata/rename/unexported.txt | 25 + .../selectionrange/selectionrange.txt | 42 + .../marker/testdata/signature/generic.txt | 21 + .../marker/testdata/signature/issue63804.txt | 13 + .../marker/testdata/signature/signature.txt | 236 + .../marker/testdata/stubmethods/basic.txt | 20 + .../testdata/stubmethods/basic_resolve.txt | 31 + .../testdata/stubmethods/issue61693.txt | 26 + .../testdata/stubmethods/issue61830.txt | 24 + .../testdata/stubmethods/issue64078.txt | 36 + .../testdata/stubmethods/issue64114.txt | 37 + .../testdata/suggestedfix/embeddirective.txt | 22 + .../testdata/suggestedfix/issue65024.txt | 78 + .../testdata/suggestedfix/missingfunction.txt | 127 + .../testdata/suggestedfix/noresultvalues.txt | 18 + .../testdata/suggestedfix/self_assignment.txt | 19 + .../marker/testdata/suggestedfix/stub.txt | 365 + .../testdata/suggestedfix/undeclared.txt | 42 + .../testdata/suggestedfix/undeclaredfunc.txt | 19 + .../testdata/suggestedfix/unusedrequire.txt | 25 + .../suggestedfix/unusedrequire_gowork.txt | 48 + .../test/marker/testdata/symbol/basic.txt | 114 + .../test/marker/testdata/symbol/generic.txt | 23 + .../test/marker/testdata/token/comment.txt | 55 + .../test/marker/testdata/token/illformed.txt | 15 + .../test/marker/testdata/token/range.txt | 29 + .../test/marker/testdata/typedef/typedef.txt | 68 + .../test/marker/testdata/workfile/godebug.txt | 60 + .../marker/testdata/workfile/godebug_bad.txt | 22 + .../testdata/workspacesymbol/allscope.txt | 30 + .../workspacesymbol/caseinsensitive.txt | 26 + .../workspacesymbol/casesensitive.txt | 116 + .../testdata/workspacesymbol/issue44806.txt | 27 + .../workspacesymbol/workspacesymbol.txt | 72 + .../testdata/workspacesymbol/wsscope.txt | 29 + .../test/marker/testdata/zeroconfig/adhoc.txt | 49 + .../testdata/zeroconfig/dynamicports.txt | 118 + .../marker/testdata/zeroconfig/nested.txt | 72 + .../zeroconfig/nonworkspacemodule.txt | 79 + contribs/gnopls/internal/util/README.md | 7 + .../gnopls/internal/util/astutil/purge.go | 74 + .../internal/util/astutil/purge_test.go | 89 + contribs/gnopls/internal/util/astutil/util.go | 71 + .../gnopls/internal/util/browser/README.md | 1 + .../gnopls/internal/util/browser/browser.go | 67 + contribs/gnopls/internal/util/bug/bug.go | 145 + contribs/gnopls/internal/util/bug/bug_test.go | 91 + .../internal/util/constraints/constraint.go | 52 + contribs/gnopls/internal/util/frob/frob.go | 403 + .../gnopls/internal/util/frob/frob_test.go | 119 + .../internal/util/goversion/goversion.go | 95 + .../internal/util/goversion/goversion_test.go | 74 + .../internal/util/immutable/immutable.go | 43 + contribs/gnopls/internal/util/lru/lru.go | 179 + .../gnopls/internal/util/lru/lru_fuzz_test.go | 38 + .../gnopls/internal/util/lru/lru_nil_test.go | 19 + contribs/gnopls/internal/util/lru/lru_test.go | 153 + .../gnopls/internal/util/moremaps/maps.go | 66 + .../gnopls/internal/util/moreslices/slices.go | 20 + .../gnopls/internal/util/pathutil/util.go | 49 + .../gnopls/internal/util/persistent/map.go | 322 + .../internal/util/persistent/map_test.go | 352 + .../gnopls/internal/util/persistent/set.go | 78 + .../internal/util/persistent/set_test.go | 132 + .../internal/util/safetoken/safetoken.go | 127 + .../internal/util/safetoken/safetoken_test.go | 131 + .../internal/util/typesutil/typesutil.go | 36 + contribs/gnopls/internal/version/version.go | 29 + contribs/gnopls/internal/vulncheck/copier.go | 142 + .../vulncheck/govulncheck/govulncheck.go | 160 + .../internal/vulncheck/govulncheck/handler.go | 61 + .../vulncheck/govulncheck/jsonhandler.go | 46 + contribs/gnopls/internal/vulncheck/osv/osv.go | 240 + .../gnopls/internal/vulncheck/scan/command.go | 165 + .../internal/vulncheck/semver/semver.go | 45 + .../internal/vulncheck/semver/semver_test.go | 25 + contribs/gnopls/internal/vulncheck/types.go | 47 + .../gnopls/internal/vulncheck/vulntest/db.go | 233 + .../internal/vulncheck/vulntest/db_test.go | 76 + .../internal/vulncheck/vulntest/report.go | 176 + .../vulncheck/vulntest/report_test.go | 48 + .../internal/vulncheck/vulntest/stdlib.go | 23 + .../vulncheck/vulntest/stdlib_test.go | 24 + .../vulntest/testdata/GO-2020-0001.json | 50 + .../vulncheck/vulntest/testdata/report.yaml | 15 + contribs/gnopls/internal/work/completion.go | 161 + contribs/gnopls/internal/work/diagnostics.go | 92 + contribs/gnopls/internal/work/format.go | 30 + contribs/gnopls/internal/work/hover.go | 93 + contribs/gnopls/main.go | 36 + contribs/gnopls/release/release.go | 106 + 922 files changed, 161528 insertions(+) create mode 100644 contribs/gnopls/README.md create mode 100644 contribs/gnopls/doc/advanced.md create mode 100644 contribs/gnopls/doc/analyzers.md create mode 100644 contribs/gnopls/doc/assets/assets.go create mode 100644 contribs/gnopls/doc/assets/browse-assembly.png create mode 100644 contribs/gnopls/doc/assets/browse-free-symbols.png create mode 100644 contribs/gnopls/doc/assets/browse-pkg-doc.png create mode 100644 contribs/gnopls/doc/assets/code-action-doc.png create mode 100644 contribs/gnopls/doc/assets/convert-string-interpreted.png create mode 100644 contribs/gnopls/doc/assets/convert-string-raw.png create mode 100644 contribs/gnopls/doc/assets/diagnostic-analysis.png create mode 100644 contribs/gnopls/doc/assets/diagnostic-typeerror.png create mode 100644 contribs/gnopls/doc/assets/document-highlight.png create mode 100644 contribs/gnopls/doc/assets/documentlink.png create mode 100644 contribs/gnopls/doc/assets/extract-function-after.png create mode 100644 contribs/gnopls/doc/assets/extract-function-before.png create mode 100644 contribs/gnopls/doc/assets/extract-to-new-file-after.png create mode 100644 contribs/gnopls/doc/assets/extract-to-new-file-before.png create mode 100644 contribs/gnopls/doc/assets/extract-var-after.png create mode 100644 contribs/gnopls/doc/assets/extract-var-before.png create mode 100644 contribs/gnopls/doc/assets/fill-struct-after.png create mode 100644 contribs/gnopls/doc/assets/fill-struct-before.png create mode 100644 contribs/gnopls/doc/assets/fill-switch-after.png create mode 100644 contribs/gnopls/doc/assets/fill-switch-before.png create mode 100644 contribs/gnopls/doc/assets/fill-switch-enum-after.png create mode 100644 contribs/gnopls/doc/assets/fill-switch-enum-before.png create mode 100644 contribs/gnopls/doc/assets/foldingrange.png create mode 100644 contribs/gnopls/doc/assets/go.mod create mode 100644 contribs/gnopls/doc/assets/hover-basic.png create mode 100644 contribs/gnopls/doc/assets/hover-doclink.png create mode 100644 contribs/gnopls/doc/assets/hover-embed.png create mode 100644 contribs/gnopls/doc/assets/hover-field-tag.png create mode 100644 contribs/gnopls/doc/assets/hover-linkname.png create mode 100644 contribs/gnopls/doc/assets/hover-size-field.png create mode 100644 contribs/gnopls/doc/assets/hover-size-struct.png create mode 100644 contribs/gnopls/doc/assets/hover-size-wasteful.png create mode 100644 contribs/gnopls/doc/assets/inlayhint-parameternames.png create mode 100644 contribs/gnopls/doc/assets/invert-if-after.png create mode 100644 contribs/gnopls/doc/assets/invert-if-before.png create mode 100644 contribs/gnopls/doc/assets/outgoingcalls.png create mode 100644 contribs/gnopls/doc/assets/remove-unusedparam-after.png create mode 100644 contribs/gnopls/doc/assets/remove-unusedparam-before.png create mode 100644 contribs/gnopls/doc/assets/rename-conflict.png create mode 100644 contribs/gnopls/doc/assets/signature-help.png create mode 100644 contribs/gnopls/doc/codelenses.md create mode 100644 contribs/gnopls/doc/command-line.md create mode 100644 contribs/gnopls/doc/contributing.md create mode 100644 contribs/gnopls/doc/daemon.md create mode 100644 contribs/gnopls/doc/design/architecture.svg create mode 100644 contribs/gnopls/doc/design/design.md create mode 100644 contribs/gnopls/doc/design/implementation.md create mode 100644 contribs/gnopls/doc/design/integrating.md create mode 100644 contribs/gnopls/doc/emacs.md create mode 100644 contribs/gnopls/doc/features/README.md create mode 100644 contribs/gnopls/doc/features/completion.md create mode 100644 contribs/gnopls/doc/features/diagnostics.md create mode 100644 contribs/gnopls/doc/features/modfiles.md create mode 100644 contribs/gnopls/doc/features/navigation.md create mode 100644 contribs/gnopls/doc/features/passive.md create mode 100644 contribs/gnopls/doc/features/templates.md create mode 100644 contribs/gnopls/doc/features/transformation.md create mode 100644 contribs/gnopls/doc/features/web.md create mode 100644 contribs/gnopls/doc/generate/generate.go create mode 100644 contribs/gnopls/doc/generate/generate_test.go create mode 100644 contribs/gnopls/doc/helix.md create mode 100644 contribs/gnopls/doc/inlayHints.md create mode 100644 contribs/gnopls/doc/inline-after.png create mode 100644 contribs/gnopls/doc/inline-before.png create mode 100644 contribs/gnopls/doc/refactor-inline.md create mode 100644 contribs/gnopls/doc/release/README create mode 100644 contribs/gnopls/doc/release/v0.16.0.md create mode 100644 contribs/gnopls/doc/release/v0.17.0.md create mode 100644 contribs/gnopls/doc/releases.md create mode 100644 contribs/gnopls/doc/semantictokens.md create mode 100644 contribs/gnopls/doc/settings.md create mode 100644 contribs/gnopls/doc/subl.md create mode 100644 contribs/gnopls/doc/troubleshooting.md create mode 100644 contribs/gnopls/doc/vim.md create mode 100644 contribs/gnopls/doc/workspace.md create mode 100644 contribs/gnopls/doc/zeroconfig.png create mode 100644 contribs/gnopls/go.mod create mode 100644 contribs/gnopls/go.sum create mode 100644 contribs/gnopls/integration/govim/Dockerfile create mode 100644 contribs/gnopls/integration/govim/README.md create mode 100644 contribs/gnopls/integration/govim/artifacts.go create mode 100644 contribs/gnopls/integration/govim/cloudbuild.harness.yaml create mode 100644 contribs/gnopls/integration/govim/cloudbuild.yaml create mode 100755 contribs/gnopls/integration/govim/run_local.sh create mode 100755 contribs/gnopls/integration/govim/run_tests_for_cloudbuild.sh create mode 100644 contribs/gnopls/internal/analysis/deprecated/deprecated.go create mode 100644 contribs/gnopls/internal/analysis/deprecated/deprecated_test.go create mode 100644 contribs/gnopls/internal/analysis/deprecated/doc.go create mode 100644 contribs/gnopls/internal/analysis/deprecated/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/deprecated/testdata/src/a/a_test.go create mode 100644 contribs/gnopls/internal/analysis/embeddirective/doc.go create mode 100644 contribs/gnopls/internal/analysis/embeddirective/embeddirective.go create mode 100644 contribs/gnopls/internal/analysis/embeddirective/embeddirective_test.go create mode 100644 contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/embedText create mode 100644 contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_missing.go create mode 100644 contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_present.go create mode 100644 contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_present_go120.go create mode 100644 contribs/gnopls/internal/analysis/fillreturns/doc.go create mode 100644 contribs/gnopls/internal/analysis/fillreturns/fillreturns.go create mode 100644 contribs/gnopls/internal/analysis/fillreturns/fillreturns_test.go create mode 100644 contribs/gnopls/internal/analysis/fillreturns/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/fillreturns/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/fillreturns/testdata/src/typeparams/a.go create mode 100644 contribs/gnopls/internal/analysis/fillreturns/testdata/src/typeparams/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/fillstruct/fillstruct.go create mode 100644 contribs/gnopls/internal/analysis/fillstruct/fillstruct_test.go create mode 100644 contribs/gnopls/internal/analysis/fillstruct/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/fillstruct/testdata/src/b/b.go create mode 100644 contribs/gnopls/internal/analysis/fillstruct/testdata/src/typeparams/typeparams.go create mode 100644 contribs/gnopls/internal/analysis/fillswitch/doc.go create mode 100644 contribs/gnopls/internal/analysis/fillswitch/fillswitch.go create mode 100644 contribs/gnopls/internal/analysis/fillswitch/fillswitch_test.go create mode 100644 contribs/gnopls/internal/analysis/fillswitch/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/fillswitch/testdata/src/b/b.go create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/infertypeargs.go create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/infertypeargs_test.go create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/basic.go create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/basic.go.golden create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported.go create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported.go.golden create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported/imported.go create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/notypechange.go create mode 100644 contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/notypechange.go.golden create mode 100644 contribs/gnopls/internal/analysis/nonewvars/doc.go create mode 100644 contribs/gnopls/internal/analysis/nonewvars/nonewvars.go create mode 100644 contribs/gnopls/internal/analysis/nonewvars/nonewvars_test.go create mode 100644 contribs/gnopls/internal/analysis/nonewvars/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/nonewvars/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/nonewvars/testdata/src/typeparams/a.go create mode 100644 contribs/gnopls/internal/analysis/nonewvars/testdata/src/typeparams/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/norangeoverfunc/norangeoverfunc.go create mode 100644 contribs/gnopls/internal/analysis/noresultvalues/doc.go create mode 100644 contribs/gnopls/internal/analysis/noresultvalues/noresultvalues.go create mode 100644 contribs/gnopls/internal/analysis/noresultvalues/noresultvalues_test.go create mode 100644 contribs/gnopls/internal/analysis/noresultvalues/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/noresultvalues/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/noresultvalues/testdata/src/typeparams/a.go create mode 100644 contribs/gnopls/internal/analysis/noresultvalues/testdata/src/typeparams/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/simplifycompositelit/doc.go create mode 100644 contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit.go create mode 100644 contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit_test.go create mode 100644 contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/generatedcode/generatedcode.go create mode 100644 contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/generatedcode/generatedcode.go.golden create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/doc.go create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/simplifyrange.go create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/simplifyrange_test.go create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/testdata/src/generatedcode/generatedcode.go create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/testdata/src/generatedcode/generatedcode.go.golden create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/testdata/src/rangeoverfunc/rangeoverfunc.go create mode 100644 contribs/gnopls/internal/analysis/simplifyrange/testdata/src/rangeoverfunc/rangeoverfunc.go.golden create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/doc.go create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/simplifyslice.go create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/simplifyslice_test.go create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/testdata/src/generatedcode/generatedcode.go create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/testdata/src/generatedcode/generatedcode.go.golden create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go create mode 100644 contribs/gnopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden create mode 100644 contribs/gnopls/internal/analysis/stubmethods/doc.go create mode 100644 contribs/gnopls/internal/analysis/stubmethods/stubmethods.go create mode 100644 contribs/gnopls/internal/analysis/stubmethods/stubmethods_test.go create mode 100644 contribs/gnopls/internal/analysis/stubmethods/testdata/src/typeparams/implement.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/doc.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/channels.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/consecutive_params.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/error_param.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/literals.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/operation.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/selector.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/slice.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/tuple.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/unique_params.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/undeclared.go create mode 100644 contribs/gnopls/internal/analysis/undeclaredname/undeclared_test.go create mode 100644 contribs/gnopls/internal/analysis/unusedparams/cmd/main.go create mode 100644 contribs/gnopls/internal/analysis/unusedparams/doc.go create mode 100644 contribs/gnopls/internal/analysis/unusedparams/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/unusedparams/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go create mode 100644 contribs/gnopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden create mode 100644 contribs/gnopls/internal/analysis/unusedparams/unusedparams.go create mode 100644 contribs/gnopls/internal/analysis/unusedparams/unusedparams_test.go create mode 100644 contribs/gnopls/internal/analysis/unusedvariable/testdata/src/assign/a.go create mode 100644 contribs/gnopls/internal/analysis/unusedvariable/testdata/src/assign/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/unusedvariable/testdata/src/decl/a.go create mode 100644 contribs/gnopls/internal/analysis/unusedvariable/testdata/src/decl/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/unusedvariable/unusedvariable.go create mode 100644 contribs/gnopls/internal/analysis/unusedvariable/unusedvariable_test.go create mode 100644 contribs/gnopls/internal/analysis/useany/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/analysis/useany/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/analysis/useany/useany.go create mode 100644 contribs/gnopls/internal/analysis/useany/useany_test.go create mode 100644 contribs/gnopls/internal/cache/analysis.go create mode 100644 contribs/gnopls/internal/cache/cache.go create mode 100644 contribs/gnopls/internal/cache/check.go create mode 100644 contribs/gnopls/internal/cache/constraints.go create mode 100644 contribs/gnopls/internal/cache/constraints_test.go create mode 100644 contribs/gnopls/internal/cache/debug.go create mode 100644 contribs/gnopls/internal/cache/diagnostics.go create mode 100644 contribs/gnopls/internal/cache/errors.go create mode 100644 contribs/gnopls/internal/cache/errors_test.go create mode 100644 contribs/gnopls/internal/cache/filemap.go create mode 100644 contribs/gnopls/internal/cache/filemap_test.go create mode 100644 contribs/gnopls/internal/cache/filterer.go create mode 100644 contribs/gnopls/internal/cache/fs_memoized.go create mode 100644 contribs/gnopls/internal/cache/fs_overlay.go create mode 100644 contribs/gnopls/internal/cache/imports.go create mode 100644 contribs/gnopls/internal/cache/keys.go create mode 100644 contribs/gnopls/internal/cache/load.go create mode 100644 contribs/gnopls/internal/cache/metadata/cycle_test.go create mode 100644 contribs/gnopls/internal/cache/metadata/graph.go create mode 100644 contribs/gnopls/internal/cache/metadata/metadata.go create mode 100644 contribs/gnopls/internal/cache/methodsets/methodsets.go create mode 100644 contribs/gnopls/internal/cache/mod.go create mode 100644 contribs/gnopls/internal/cache/mod_tidy.go create mode 100644 contribs/gnopls/internal/cache/mod_vuln.go create mode 100644 contribs/gnopls/internal/cache/os_darwin.go create mode 100644 contribs/gnopls/internal/cache/os_windows.go create mode 100644 contribs/gnopls/internal/cache/package.go create mode 100644 contribs/gnopls/internal/cache/parse.go create mode 100644 contribs/gnopls/internal/cache/parse_cache.go create mode 100644 contribs/gnopls/internal/cache/parse_cache_test.go create mode 100644 contribs/gnopls/internal/cache/parsego/file.go create mode 100644 contribs/gnopls/internal/cache/parsego/parse.go create mode 100644 contribs/gnopls/internal/cache/parsego/parse_test.go create mode 100644 contribs/gnopls/internal/cache/port.go create mode 100644 contribs/gnopls/internal/cache/port_test.go create mode 100644 contribs/gnopls/internal/cache/session.go create mode 100644 contribs/gnopls/internal/cache/session_test.go create mode 100644 contribs/gnopls/internal/cache/snapshot.go create mode 100644 contribs/gnopls/internal/cache/symbols.go create mode 100644 contribs/gnopls/internal/cache/testfuncs/match.go create mode 100644 contribs/gnopls/internal/cache/testfuncs/tests.go create mode 100644 contribs/gnopls/internal/cache/typerefs/doc.go create mode 100644 contribs/gnopls/internal/cache/typerefs/packageset.go create mode 100644 contribs/gnopls/internal/cache/typerefs/pkggraph_test.go create mode 100644 contribs/gnopls/internal/cache/typerefs/pkgrefs_test.go create mode 100644 contribs/gnopls/internal/cache/typerefs/refs.go create mode 100644 contribs/gnopls/internal/cache/typerefs/refs_test.go create mode 100644 contribs/gnopls/internal/cache/view.go create mode 100644 contribs/gnopls/internal/cache/view_test.go create mode 100644 contribs/gnopls/internal/cache/workspace.go create mode 100644 contribs/gnopls/internal/cache/xrefs/xrefs.go create mode 100644 contribs/gnopls/internal/clonetest/clonetest.go create mode 100644 contribs/gnopls/internal/clonetest/clonetest_test.go create mode 100644 contribs/gnopls/internal/cmd/call_hierarchy.go create mode 100644 contribs/gnopls/internal/cmd/capabilities_test.go create mode 100644 contribs/gnopls/internal/cmd/check.go create mode 100644 contribs/gnopls/internal/cmd/cmd.go create mode 100644 contribs/gnopls/internal/cmd/codeaction.go create mode 100644 contribs/gnopls/internal/cmd/codelens.go create mode 100644 contribs/gnopls/internal/cmd/definition.go create mode 100644 contribs/gnopls/internal/cmd/execute.go create mode 100644 contribs/gnopls/internal/cmd/folding_range.go create mode 100644 contribs/gnopls/internal/cmd/format.go create mode 100644 contribs/gnopls/internal/cmd/help_test.go create mode 100644 contribs/gnopls/internal/cmd/highlight.go create mode 100644 contribs/gnopls/internal/cmd/implementation.go create mode 100644 contribs/gnopls/internal/cmd/imports.go create mode 100644 contribs/gnopls/internal/cmd/info.go create mode 100644 contribs/gnopls/internal/cmd/integration_test.go create mode 100644 contribs/gnopls/internal/cmd/links.go create mode 100644 contribs/gnopls/internal/cmd/parsespan.go create mode 100644 contribs/gnopls/internal/cmd/prepare_rename.go create mode 100644 contribs/gnopls/internal/cmd/references.go create mode 100644 contribs/gnopls/internal/cmd/remote.go create mode 100644 contribs/gnopls/internal/cmd/rename.go create mode 100644 contribs/gnopls/internal/cmd/semantictokens.go create mode 100644 contribs/gnopls/internal/cmd/serve.go create mode 100644 contribs/gnopls/internal/cmd/signature.go create mode 100644 contribs/gnopls/internal/cmd/span.go create mode 100644 contribs/gnopls/internal/cmd/spanformat_test.go create mode 100644 contribs/gnopls/internal/cmd/stats.go create mode 100644 contribs/gnopls/internal/cmd/subcommands.go create mode 100644 contribs/gnopls/internal/cmd/symbols.go create mode 100644 contribs/gnopls/internal/cmd/usage/api-json.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/bug.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/call_hierarchy.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/check.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/codeaction.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/codelens.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/definition.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/execute.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/fix.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/folding_ranges.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/format.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/help.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/highlight.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/implementation.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/imports.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/inspect.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/licenses.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/links.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/prepare_rename.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/references.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/remote.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/rename.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/semtok.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/serve.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/signature.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/stats.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/symbols.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/usage-v.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/usage.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/version.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/vulncheck.hlp create mode 100644 contribs/gnopls/internal/cmd/usage/workspace_symbol.hlp create mode 100644 contribs/gnopls/internal/cmd/vulncheck.go create mode 100644 contribs/gnopls/internal/cmd/workspace_symbol.go create mode 100644 contribs/gnopls/internal/debug/info.go create mode 100644 contribs/gnopls/internal/debug/info_test.go create mode 100644 contribs/gnopls/internal/debug/log/log.go create mode 100644 contribs/gnopls/internal/debug/metrics.go create mode 100644 contribs/gnopls/internal/debug/rpc.go create mode 100644 contribs/gnopls/internal/debug/serve.go create mode 100644 contribs/gnopls/internal/debug/template_test.go create mode 100644 contribs/gnopls/internal/debug/trace.go create mode 100644 contribs/gnopls/internal/doc/api.go create mode 100644 contribs/gnopls/internal/doc/api.json create mode 100644 contribs/gnopls/internal/file/file.go create mode 100644 contribs/gnopls/internal/file/hash.go create mode 100644 contribs/gnopls/internal/file/kind.go create mode 100644 contribs/gnopls/internal/file/modification.go create mode 100644 contribs/gnopls/internal/filecache/filecache.go create mode 100644 contribs/gnopls/internal/filecache/filecache_test.go create mode 100644 contribs/gnopls/internal/fuzzy/input.go create mode 100644 contribs/gnopls/internal/fuzzy/input_test.go create mode 100644 contribs/gnopls/internal/fuzzy/matcher.go create mode 100644 contribs/gnopls/internal/fuzzy/matcher_test.go create mode 100644 contribs/gnopls/internal/fuzzy/self_test.go create mode 100644 contribs/gnopls/internal/fuzzy/symbol.go create mode 100644 contribs/gnopls/internal/fuzzy/symbol_test.go create mode 100644 contribs/gnopls/internal/golang/add_import.go create mode 100644 contribs/gnopls/internal/golang/assembly.go create mode 100644 contribs/gnopls/internal/golang/call_hierarchy.go create mode 100644 contribs/gnopls/internal/golang/change_quote.go create mode 100644 contribs/gnopls/internal/golang/change_signature.go create mode 100644 contribs/gnopls/internal/golang/code_lens.go create mode 100644 contribs/gnopls/internal/golang/codeaction.go create mode 100644 contribs/gnopls/internal/golang/comment.go create mode 100644 contribs/gnopls/internal/golang/completion/builtin.go create mode 100644 contribs/gnopls/internal/golang/completion/completion.go create mode 100644 contribs/gnopls/internal/golang/completion/deep_completion.go create mode 100644 contribs/gnopls/internal/golang/completion/deep_completion_test.go create mode 100644 contribs/gnopls/internal/golang/completion/definition.go create mode 100644 contribs/gnopls/internal/golang/completion/format.go create mode 100644 contribs/gnopls/internal/golang/completion/fuzz.go create mode 100644 contribs/gnopls/internal/golang/completion/keywords.go create mode 100644 contribs/gnopls/internal/golang/completion/labels.go create mode 100644 contribs/gnopls/internal/golang/completion/literal.go create mode 100644 contribs/gnopls/internal/golang/completion/package.go create mode 100644 contribs/gnopls/internal/golang/completion/package_test.go create mode 100644 contribs/gnopls/internal/golang/completion/postfix_snippets.go create mode 100644 contribs/gnopls/internal/golang/completion/printf.go create mode 100644 contribs/gnopls/internal/golang/completion/printf_test.go create mode 100644 contribs/gnopls/internal/golang/completion/snippet.go create mode 100644 contribs/gnopls/internal/golang/completion/snippet/snippet_builder.go create mode 100644 contribs/gnopls/internal/golang/completion/snippet/snippet_builder_test.go create mode 100644 contribs/gnopls/internal/golang/completion/statements.go create mode 100644 contribs/gnopls/internal/golang/completion/util.go create mode 100644 contribs/gnopls/internal/golang/completion/util_test.go create mode 100644 contribs/gnopls/internal/golang/definition.go create mode 100644 contribs/gnopls/internal/golang/diagnostics.go create mode 100644 contribs/gnopls/internal/golang/embeddirective.go create mode 100644 contribs/gnopls/internal/golang/extract.go create mode 100644 contribs/gnopls/internal/golang/extracttofile.go create mode 100644 contribs/gnopls/internal/golang/fix.go create mode 100644 contribs/gnopls/internal/golang/folding_range.go create mode 100644 contribs/gnopls/internal/golang/format.go create mode 100644 contribs/gnopls/internal/golang/format_test.go create mode 100644 contribs/gnopls/internal/golang/freesymbols.go create mode 100644 contribs/gnopls/internal/golang/freesymbols_test.go create mode 100644 contribs/gnopls/internal/golang/gc_annotations.go create mode 100644 contribs/gnopls/internal/golang/highlight.go create mode 100644 contribs/gnopls/internal/golang/hover.go create mode 100644 contribs/gnopls/internal/golang/identifier.go create mode 100644 contribs/gnopls/internal/golang/identifier_test.go create mode 100644 contribs/gnopls/internal/golang/implementation.go create mode 100644 contribs/gnopls/internal/golang/inlay_hint.go create mode 100644 contribs/gnopls/internal/golang/inline.go create mode 100644 contribs/gnopls/internal/golang/inline_all.go create mode 100644 contribs/gnopls/internal/golang/invertifcondition.go create mode 100644 contribs/gnopls/internal/golang/known_packages.go create mode 100644 contribs/gnopls/internal/golang/lines.go create mode 100644 contribs/gnopls/internal/golang/linkname.go create mode 100644 contribs/gnopls/internal/golang/origin.go create mode 100644 contribs/gnopls/internal/golang/pkgdoc.go create mode 100644 contribs/gnopls/internal/golang/references.go create mode 100644 contribs/gnopls/internal/golang/rename.go create mode 100644 contribs/gnopls/internal/golang/rename_check.go create mode 100644 contribs/gnopls/internal/golang/semtok.go create mode 100644 contribs/gnopls/internal/golang/signature_help.go create mode 100644 contribs/gnopls/internal/golang/snapshot.go create mode 100644 contribs/gnopls/internal/golang/stub.go create mode 100644 contribs/gnopls/internal/golang/symbols.go create mode 100644 contribs/gnopls/internal/golang/type_definition.go create mode 100644 contribs/gnopls/internal/golang/types_format.go create mode 100644 contribs/gnopls/internal/golang/util.go create mode 100644 contribs/gnopls/internal/golang/workspace_symbol.go create mode 100644 contribs/gnopls/internal/golang/workspace_symbol_test.go create mode 100644 contribs/gnopls/internal/label/keys.go create mode 100755 contribs/gnopls/internal/licenses/gen-licenses.sh create mode 100644 contribs/gnopls/internal/licenses/licenses.go create mode 100644 contribs/gnopls/internal/licenses/licenses_test.go create mode 100644 contribs/gnopls/internal/lsprpc/autostart_default.go create mode 100644 contribs/gnopls/internal/lsprpc/autostart_posix.go create mode 100644 contribs/gnopls/internal/lsprpc/binder.go create mode 100644 contribs/gnopls/internal/lsprpc/binder_test.go create mode 100644 contribs/gnopls/internal/lsprpc/commandinterceptor_test.go create mode 100644 contribs/gnopls/internal/lsprpc/dialer.go create mode 100644 contribs/gnopls/internal/lsprpc/export_test.go create mode 100644 contribs/gnopls/internal/lsprpc/goenv.go create mode 100644 contribs/gnopls/internal/lsprpc/goenv_test.go create mode 100644 contribs/gnopls/internal/lsprpc/lsprpc.go create mode 100644 contribs/gnopls/internal/lsprpc/lsprpc_test.go create mode 100644 contribs/gnopls/internal/lsprpc/middleware_test.go create mode 100644 contribs/gnopls/internal/mod/code_lens.go create mode 100644 contribs/gnopls/internal/mod/diagnostics.go create mode 100644 contribs/gnopls/internal/mod/format.go create mode 100644 contribs/gnopls/internal/mod/hover.go create mode 100644 contribs/gnopls/internal/mod/inlayhint.go create mode 100644 contribs/gnopls/internal/progress/progress.go create mode 100644 contribs/gnopls/internal/progress/progress_test.go create mode 100644 contribs/gnopls/internal/protocol/command/command_gen.go create mode 100644 contribs/gnopls/internal/protocol/command/commandmeta/meta.go create mode 100644 contribs/gnopls/internal/protocol/command/gen/gen.go create mode 100644 contribs/gnopls/internal/protocol/command/generate.go create mode 100644 contribs/gnopls/internal/protocol/command/interface.go create mode 100644 contribs/gnopls/internal/protocol/command/interface_test.go create mode 100644 contribs/gnopls/internal/protocol/command/util.go create mode 100644 contribs/gnopls/internal/protocol/context.go create mode 100644 contribs/gnopls/internal/protocol/doc.go create mode 100644 contribs/gnopls/internal/protocol/edits.go create mode 100644 contribs/gnopls/internal/protocol/enums.go create mode 100644 contribs/gnopls/internal/protocol/generate/README.md create mode 100644 contribs/gnopls/internal/protocol/generate/generate.go create mode 100644 contribs/gnopls/internal/protocol/generate/main.go create mode 100644 contribs/gnopls/internal/protocol/generate/main_test.go create mode 100644 contribs/gnopls/internal/protocol/generate/output.go create mode 100644 contribs/gnopls/internal/protocol/generate/tables.go create mode 100644 contribs/gnopls/internal/protocol/generate/typenames.go create mode 100644 contribs/gnopls/internal/protocol/generate/types.go create mode 100644 contribs/gnopls/internal/protocol/json_test.go create mode 100644 contribs/gnopls/internal/protocol/log.go create mode 100644 contribs/gnopls/internal/protocol/mapper.go create mode 100644 contribs/gnopls/internal/protocol/mapper_test.go create mode 100644 contribs/gnopls/internal/protocol/protocol.go create mode 100644 contribs/gnopls/internal/protocol/semantic.go create mode 100644 contribs/gnopls/internal/protocol/semtok/semtok.go create mode 100644 contribs/gnopls/internal/protocol/span.go create mode 100644 contribs/gnopls/internal/protocol/tsclient.go create mode 100644 contribs/gnopls/internal/protocol/tsdocument_changes.go create mode 100644 contribs/gnopls/internal/protocol/tsinsertreplaceedit.go create mode 100644 contribs/gnopls/internal/protocol/tsinsertreplaceedit_test.go create mode 100644 contribs/gnopls/internal/protocol/tsjson.go create mode 100644 contribs/gnopls/internal/protocol/tsprotocol.go create mode 100644 contribs/gnopls/internal/protocol/tsserver.go create mode 100644 contribs/gnopls/internal/protocol/uri.go create mode 100644 contribs/gnopls/internal/protocol/uri_test.go create mode 100644 contribs/gnopls/internal/protocol/uri_windows_test.go create mode 100644 contribs/gnopls/internal/server/assets/common.css create mode 100644 contribs/gnopls/internal/server/assets/common.js create mode 100644 contribs/gnopls/internal/server/assets/favicon.ico create mode 100644 contribs/gnopls/internal/server/assets/go-logo-blue.svg create mode 100644 contribs/gnopls/internal/server/call_hierarchy.go create mode 100644 contribs/gnopls/internal/server/code_action.go create mode 100644 contribs/gnopls/internal/server/code_lens.go create mode 100644 contribs/gnopls/internal/server/command.go create mode 100644 contribs/gnopls/internal/server/completion.go create mode 100644 contribs/gnopls/internal/server/counters.go create mode 100644 contribs/gnopls/internal/server/debug.go create mode 100644 contribs/gnopls/internal/server/definition.go create mode 100644 contribs/gnopls/internal/server/diagnostics.go create mode 100644 contribs/gnopls/internal/server/folding_range.go create mode 100644 contribs/gnopls/internal/server/format.go create mode 100644 contribs/gnopls/internal/server/general.go create mode 100644 contribs/gnopls/internal/server/highlight.go create mode 100644 contribs/gnopls/internal/server/hover.go create mode 100644 contribs/gnopls/internal/server/implementation.go create mode 100644 contribs/gnopls/internal/server/inlay_hint.go create mode 100644 contribs/gnopls/internal/server/link.go create mode 100644 contribs/gnopls/internal/server/prompt.go create mode 100644 contribs/gnopls/internal/server/prompt_test.go create mode 100644 contribs/gnopls/internal/server/references.go create mode 100644 contribs/gnopls/internal/server/rename.go create mode 100644 contribs/gnopls/internal/server/selection_range.go create mode 100644 contribs/gnopls/internal/server/semantic.go create mode 100644 contribs/gnopls/internal/server/server.go create mode 100644 contribs/gnopls/internal/server/signature_help.go create mode 100644 contribs/gnopls/internal/server/symbols.go create mode 100644 contribs/gnopls/internal/server/text_synchronization.go create mode 100644 contribs/gnopls/internal/server/unimplemented.go create mode 100644 contribs/gnopls/internal/server/workspace.go create mode 100644 contribs/gnopls/internal/server/workspace_symbol.go create mode 100644 contribs/gnopls/internal/settings/analysis.go create mode 100644 contribs/gnopls/internal/settings/codeactionkind.go create mode 100644 contribs/gnopls/internal/settings/default.go create mode 100644 contribs/gnopls/internal/settings/settings.go create mode 100644 contribs/gnopls/internal/settings/settings_test.go create mode 100644 contribs/gnopls/internal/settings/staticcheck.go create mode 100644 contribs/gnopls/internal/settings/vet_test.go create mode 100644 contribs/gnopls/internal/telemetry/cmd/stacks/stacks.go create mode 100644 contribs/gnopls/internal/telemetry/latency.go create mode 100644 contribs/gnopls/internal/telemetry/telemetry_test.go create mode 100644 contribs/gnopls/internal/template/completion.go create mode 100644 contribs/gnopls/internal/template/completion_test.go create mode 100644 contribs/gnopls/internal/template/highlight.go create mode 100644 contribs/gnopls/internal/template/implementations.go create mode 100644 contribs/gnopls/internal/template/parse.go create mode 100644 contribs/gnopls/internal/template/parse_test.go create mode 100644 contribs/gnopls/internal/template/symbols.go create mode 100644 contribs/gnopls/internal/test/compare/text.go create mode 100644 contribs/gnopls/internal/test/compare/text_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/bench_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/codeaction_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/completion_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/definition_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/didchange_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/doc.go create mode 100644 contribs/gnopls/internal/test/integration/bench/hover_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/implementations_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/imports_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/iwl_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/references_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/reload_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/rename_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/repo_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/stress_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/tests_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/typing_test.go create mode 100644 contribs/gnopls/internal/test/integration/bench/workspace_symbols_test.go create mode 100644 contribs/gnopls/internal/test/integration/codelens/codelens_test.go create mode 100644 contribs/gnopls/internal/test/integration/codelens/gcdetails_test.go create mode 100644 contribs/gnopls/internal/test/integration/completion/completion18_test.go create mode 100644 contribs/gnopls/internal/test/integration/completion/completion_test.go create mode 100644 contribs/gnopls/internal/test/integration/completion/fixedbugs_test.go create mode 100644 contribs/gnopls/internal/test/integration/completion/postfix_snippet_test.go create mode 100644 contribs/gnopls/internal/test/integration/debug/debug_test.go create mode 100644 contribs/gnopls/internal/test/integration/diagnostics/analysis_test.go create mode 100644 contribs/gnopls/internal/test/integration/diagnostics/builtin_test.go create mode 100644 contribs/gnopls/internal/test/integration/diagnostics/diagnostics_test.go create mode 100644 contribs/gnopls/internal/test/integration/diagnostics/golist_test.go create mode 100644 contribs/gnopls/internal/test/integration/diagnostics/gopackagesdriver_test.go create mode 100644 contribs/gnopls/internal/test/integration/diagnostics/invalidation_test.go create mode 100644 contribs/gnopls/internal/test/integration/diagnostics/undeclared_test.go create mode 100644 contribs/gnopls/internal/test/integration/doc.go create mode 100644 contribs/gnopls/internal/test/integration/env.go create mode 100644 contribs/gnopls/internal/test/integration/env_test.go create mode 100644 contribs/gnopls/internal/test/integration/expectation.go create mode 100644 contribs/gnopls/internal/test/integration/fake/client.go create mode 100644 contribs/gnopls/internal/test/integration/fake/doc.go create mode 100644 contribs/gnopls/internal/test/integration/fake/edit.go create mode 100644 contribs/gnopls/internal/test/integration/fake/edit_test.go create mode 100644 contribs/gnopls/internal/test/integration/fake/editor.go create mode 100644 contribs/gnopls/internal/test/integration/fake/editor_test.go create mode 100644 contribs/gnopls/internal/test/integration/fake/glob/glob.go create mode 100644 contribs/gnopls/internal/test/integration/fake/glob/glob_test.go create mode 100644 contribs/gnopls/internal/test/integration/fake/proxy.go create mode 100644 contribs/gnopls/internal/test/integration/fake/sandbox.go create mode 100644 contribs/gnopls/internal/test/integration/fake/workdir.go create mode 100644 contribs/gnopls/internal/test/integration/fake/workdir_test.go create mode 100644 contribs/gnopls/internal/test/integration/fake/workdir_windows.go create mode 100644 contribs/gnopls/internal/test/integration/inlayhints/inlayhints_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/call_hierarchy_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/codeactions_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/configuration_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/debugserver_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/definition_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/embed_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/extract_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/failures_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/fix_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/formatting_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/generate_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/highlight_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/hover_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/import_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/imports_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/link_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/misc_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/multiple_adhoc_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/prompt_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/references_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/rename_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/semantictokens_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/settings_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/shared_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/signature_help_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/staticcheck_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/vendor_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/vuln_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/webserver_test.go create mode 100644 contribs/gnopls/internal/test/integration/misc/workspace_symbol_test.go create mode 100644 contribs/gnopls/internal/test/integration/modfile/modfile_test.go create mode 100644 contribs/gnopls/internal/test/integration/modfile/tempmodfile_test.go create mode 100644 contribs/gnopls/internal/test/integration/options.go create mode 100644 contribs/gnopls/internal/test/integration/regtest.go create mode 100644 contribs/gnopls/internal/test/integration/runner.go create mode 100644 contribs/gnopls/internal/test/integration/template/template_test.go create mode 100644 contribs/gnopls/internal/test/integration/watch/setting_test.go create mode 100644 contribs/gnopls/internal/test/integration/watch/watch_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/adhoc_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/broken_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/directoryfilters_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/fromenv_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/goversion_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/metadata_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/misspelling_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/modules_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/multi_folder_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/packages_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/quickfix_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/standalone_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/std_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/vendor_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/workspace_test.go create mode 100644 contribs/gnopls/internal/test/integration/workspace/zero_config_test.go create mode 100644 contribs/gnopls/internal/test/integration/wrappers.go create mode 100644 contribs/gnopls/internal/test/marker/doc.go create mode 100644 contribs/gnopls/internal/test/marker/marker_test.go create mode 100644 contribs/gnopls/internal/test/marker/testdata/callhierarchy/callhierarchy.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/callhierarchy/issue64451.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/callhierarchy/issue66923.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/change_quote.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/extract-variadic-63287.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/extract_method.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable-67905.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/extracttofile.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/fill_struct.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/fill_struct_resolve.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/fill_switch.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/fill_switch_resolve.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/functionextraction.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/functionextraction_issue44813.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/grouplines.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/import-shadows-builtin.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/imports.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/infertypeargs.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/inline.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/inline_resolve.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/invertif.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/issue64558.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_formatting.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_funcvalue.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_imports.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_issue65217.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_method.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_satisfies.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_witherrs.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codeaction/splitlines.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codelens/generate.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/codelens/test.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/address.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/anon.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/append.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/assign.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/bad.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/basic_lit.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/builtins.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/casesensitive.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/cast.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/channel.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/comment.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/complit.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/constant.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/danglingstmt.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/deep.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/deep2.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/errors.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/field_list.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/foobarbaz.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/func_rank.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/func_sig.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/func_snippets.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/func_value.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/fuzzy.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/imported-std.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/index.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/interfacerank.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/issue51783.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/issue56505.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/issue59096.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/issue60545.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/issue62141.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/issue62560.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/issue62676.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/keywords.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/labels.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/lit.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/maps.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/multi_return.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/nested_complit.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/postfix.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/postfix_placeholder.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/printf.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/range_func.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/rank.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/snippet.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/snippet_placeholder.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/statements.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/testy.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/type_assert.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/type_mods.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/type_params.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/unimported-std.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/unimported.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/unresolved.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/unsafe.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/completion/variadic.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/configuration/static.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/definition/cgo.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/definition/comment.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/definition/embed.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/definition/import.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/definition/misc.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/definition/standalone.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/addgowork.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/analyzers.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/excludedfile.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/generated.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/initcycle.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/issue56943.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/issue59005.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/issue60544.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/issue60605.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/issue64547.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/issue67360.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/osarch_suffix.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/parseerr.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/range-over-func-67237.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/rundespiteerrors.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/stdversion.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/strangefiles.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/typeerr.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/useinternal.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/diagnostics/usemodule.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue59318.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue59944.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue61543.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66109.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66250.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66876.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/foldingrange/a.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/foldingrange/bad.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/format/format.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/format/issue59554.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/format/noparse.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/highlight/controlflow.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/highlight/highlight.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/highlight/highlight_kind.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/highlight/issue60435.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/highlight/issue68918.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/highlight/switchbreak.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/basiclit.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/comment.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/const.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/embed.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/generics.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/godef.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/goprivate.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/hover.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/issues.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/linkable.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/linkable_generics.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/linkname.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/methods.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/sizeoffset.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/std.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/hover/structfield.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/implementation/basic.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/implementation/generics.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/implementation/issue43655.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/implementation/issue67041.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/inlayhints/inlayhints.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/inlayhints/issue67142.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/links/links.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/modfile/godebug.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/modfile/godebug_bad.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/crosspackage.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/imports.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/interfaces.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/intrapackage.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/issue58506.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/issue59851.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/issue60369.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/issue60622.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/issue60676.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/issue61618.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/issue67978.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/shadow.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/test.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/references/typeswitch.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/bad.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/basic.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/conflict.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/crosspkg.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/doclink.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/embed.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/generics.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/generics_basic.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue39614.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue42134.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue43616.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue57479.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue60752.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue60789.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue61294.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue61640.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue61813.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/issue67069.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/methods.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/prepare.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/random.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/shadow.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/testy.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/typeswitch.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/rename/unexported.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/selectionrange/selectionrange.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/signature/generic.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/signature/issue63804.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/signature/signature.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/stubmethods/basic.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/stubmethods/basic_resolve.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/stubmethods/issue61693.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/stubmethods/issue61830.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/stubmethods/issue64078.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/stubmethods/issue64114.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/embeddirective.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/issue65024.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/missingfunction.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/noresultvalues.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/self_assignment.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/stub.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/undeclared.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/undeclaredfunc.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/unusedrequire.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/suggestedfix/unusedrequire_gowork.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/symbol/basic.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/symbol/generic.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/token/comment.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/token/illformed.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/token/range.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/typedef/typedef.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/workfile/godebug.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/workfile/godebug_bad.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/workspacesymbol/allscope.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/workspacesymbol/caseinsensitive.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/workspacesymbol/casesensitive.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/workspacesymbol/issue44806.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/workspacesymbol/workspacesymbol.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/workspacesymbol/wsscope.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/zeroconfig/adhoc.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/zeroconfig/dynamicports.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/zeroconfig/nested.txt create mode 100644 contribs/gnopls/internal/test/marker/testdata/zeroconfig/nonworkspacemodule.txt create mode 100644 contribs/gnopls/internal/util/README.md create mode 100644 contribs/gnopls/internal/util/astutil/purge.go create mode 100644 contribs/gnopls/internal/util/astutil/purge_test.go create mode 100644 contribs/gnopls/internal/util/astutil/util.go create mode 100644 contribs/gnopls/internal/util/browser/README.md create mode 100644 contribs/gnopls/internal/util/browser/browser.go create mode 100644 contribs/gnopls/internal/util/bug/bug.go create mode 100644 contribs/gnopls/internal/util/bug/bug_test.go create mode 100644 contribs/gnopls/internal/util/constraints/constraint.go create mode 100644 contribs/gnopls/internal/util/frob/frob.go create mode 100644 contribs/gnopls/internal/util/frob/frob_test.go create mode 100644 contribs/gnopls/internal/util/goversion/goversion.go create mode 100644 contribs/gnopls/internal/util/goversion/goversion_test.go create mode 100644 contribs/gnopls/internal/util/immutable/immutable.go create mode 100644 contribs/gnopls/internal/util/lru/lru.go create mode 100644 contribs/gnopls/internal/util/lru/lru_fuzz_test.go create mode 100644 contribs/gnopls/internal/util/lru/lru_nil_test.go create mode 100644 contribs/gnopls/internal/util/lru/lru_test.go create mode 100644 contribs/gnopls/internal/util/moremaps/maps.go create mode 100644 contribs/gnopls/internal/util/moreslices/slices.go create mode 100644 contribs/gnopls/internal/util/pathutil/util.go create mode 100644 contribs/gnopls/internal/util/persistent/map.go create mode 100644 contribs/gnopls/internal/util/persistent/map_test.go create mode 100644 contribs/gnopls/internal/util/persistent/set.go create mode 100644 contribs/gnopls/internal/util/persistent/set_test.go create mode 100644 contribs/gnopls/internal/util/safetoken/safetoken.go create mode 100644 contribs/gnopls/internal/util/safetoken/safetoken_test.go create mode 100644 contribs/gnopls/internal/util/typesutil/typesutil.go create mode 100644 contribs/gnopls/internal/version/version.go create mode 100644 contribs/gnopls/internal/vulncheck/copier.go create mode 100644 contribs/gnopls/internal/vulncheck/govulncheck/govulncheck.go create mode 100644 contribs/gnopls/internal/vulncheck/govulncheck/handler.go create mode 100644 contribs/gnopls/internal/vulncheck/govulncheck/jsonhandler.go create mode 100644 contribs/gnopls/internal/vulncheck/osv/osv.go create mode 100644 contribs/gnopls/internal/vulncheck/scan/command.go create mode 100644 contribs/gnopls/internal/vulncheck/semver/semver.go create mode 100644 contribs/gnopls/internal/vulncheck/semver/semver_test.go create mode 100644 contribs/gnopls/internal/vulncheck/types.go create mode 100644 contribs/gnopls/internal/vulncheck/vulntest/db.go create mode 100644 contribs/gnopls/internal/vulncheck/vulntest/db_test.go create mode 100644 contribs/gnopls/internal/vulncheck/vulntest/report.go create mode 100644 contribs/gnopls/internal/vulncheck/vulntest/report_test.go create mode 100644 contribs/gnopls/internal/vulncheck/vulntest/stdlib.go create mode 100644 contribs/gnopls/internal/vulncheck/vulntest/stdlib_test.go create mode 100644 contribs/gnopls/internal/vulncheck/vulntest/testdata/GO-2020-0001.json create mode 100644 contribs/gnopls/internal/vulncheck/vulntest/testdata/report.yaml create mode 100644 contribs/gnopls/internal/work/completion.go create mode 100644 contribs/gnopls/internal/work/diagnostics.go create mode 100644 contribs/gnopls/internal/work/format.go create mode 100644 contribs/gnopls/internal/work/hover.go create mode 100644 contribs/gnopls/main.go create mode 100644 contribs/gnopls/release/release.go diff --git a/contribs/gnopls/README.md b/contribs/gnopls/README.md new file mode 100644 index 00000000000..6602e0c27a7 --- /dev/null +++ b/contribs/gnopls/README.md @@ -0,0 +1,158 @@ +# `gopls`, the Go language server + +[![PkgGoDev](https://pkg.go.dev/badge/golang.org/x/tools/gopls)](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 a wide variety of [IDE features](doc/features/README.md) +to any [LSP]-compatible editor. + + + +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. +Also, the gopls documentation for each feature describes whether it is +supported in each client editor. + +## Editors + +To get started with `gopls`, install an LSP plugin in your editor of choice. + +TODO: ensure that each editor has a local page (and move these to doc/clients/$EDITOR.md). +TODO: also, be more consistent about editor (e.g. Emacs) vs. client (e.g. eglot). + +* [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 + +* [Index of features](doc/features/README.md) +* [Command-line interface](doc/command-line.md) +* [Configuration settings](doc/settings.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/ diff --git a/contribs/gnopls/doc/advanced.md b/contribs/gnopls/doc/advanced.md new file mode 100644 index 00000000000..4c5e6015fd7 --- /dev/null +++ b/contribs/gnopls/doc/advanced.md @@ -0,0 +1,57 @@ +# Gopls: 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 +$ 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). + +[Go project]: https://go.googlesource.com/go diff --git a/contribs/gnopls/doc/analyzers.md b/contribs/gnopls/doc/analyzers.md new file mode 100644 index 00000000000..f78f1bdf732 --- /dev/null +++ b/contribs/gnopls/doc/analyzers.md @@ -0,0 +1,1035 @@ +# Gopls: Analyzers + + + +Gopls contains a driver for pluggable, modular static +[analyzers](https://pkg.go.dev/golang.org/x/tools/go/analysis#hdr-Analyzer), +such as those used by [go vet](https://pkg.go.dev/cmd/vet). + +Most analyzers report mistakes in your code; +some suggest "quick fixes" that can be directly applied in your editor. +Every time you edit your code, gopls re-runs its analyzers. +Analyzer diagnostics help you detect bugs sooner, +before you run your tests, or even before you save your files. + +This document describes the suite of analyzers available in gopls, +which aggregates analyzers from a variety of sources: + +- all the usual bug-finding analyzers from the `go vet` suite (e.g. `printf`; see [`go tool vet help`](https://pkg.go.dev/cmd/vet) for the complete list); +- a number of analyzers with more substantial dependencies that prevent them from being used in `go vet` (e.g. `nilness`); +- analyzers that augment compilation errors by suggesting quick fixes to common mistakes (e.g. `fillreturns`); and +- a handful of analyzers that suggest possible style improvements (e.g. `simplifyrange`). + +To enable or disable analyzers, use the [analyses](settings.md#analyses) setting. + +In addition, gopls includes the [`staticcheck` suite](https://staticcheck.dev/docs/checks), +though these analyzers are off by default. +Use the [`staticcheck`](settings.md#staticcheck`) setting to enable them, +and consult staticcheck's documentation for analyzer details. + + + + + + +## `appends`: check for missing values after append + + +This checker reports calls to append that pass +no values to be appended to the slice. + + s := []string{"a", "b", "c"} + _ = append(s) + +Such calls are always no-ops and often indicate an +underlying mistake. + +Default: on. + +Package documentation: [appends](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends) + + +## `asmdecl`: report mismatches between assembly files and Go declarations + + + +Default: on. + +Package documentation: [asmdecl](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/asmdecl) + + +## `assign`: check for useless assignments + + +This checker reports assignments of the form x = x or a[i] = a[i]. +These are almost always useless, and even when they aren't they are +usually a mistake. + +Default: on. + +Package documentation: [assign](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign) + + +## `atomic`: check for common mistakes using the sync/atomic package + + +The atomic checker looks for assignment statements of the form: + + x = atomic.AddUint64(&x, 1) + +which are not atomic. + +Default: on. + +Package documentation: [atomic](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic) + + +## `atomicalign`: check for non-64-bits-aligned arguments to sync/atomic functions + + + +Default: on. + +Package documentation: [atomicalign](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomicalign) + + +## `bools`: check for common mistakes involving boolean operators + + + +Default: on. + +Package documentation: [bools](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/bools) + + +## `buildtag`: check //go:build and // +build directives + + + +Default: on. + +Package documentation: [buildtag](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/buildtag) + + +## `cgocall`: detect some violations of the cgo pointer passing rules + + +Check for invalid cgo pointer passing. +This looks for code that uses cgo to call C code passing values +whose types are almost always invalid according to the cgo pointer +sharing rules. +Specifically, it warns about attempts to pass a Go chan, map, func, +or slice to C, either directly, or via a pointer, array, or struct. + +Default: on. + +Package documentation: [cgocall](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall) + + +## `composites`: check for unkeyed composite literals + + +This analyzer reports a diagnostic for composite literals of struct +types imported from another package that do not use the field-keyed +syntax. Such literals are fragile because the addition of a new field +(even if unexported) to the struct will cause compilation to fail. + +As an example, + + err = &net.DNSConfigError{err} + +should be replaced by: + + err = &net.DNSConfigError{Err: err} + + +Default: on. + +Package documentation: [composites](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/composite) + + +## `copylocks`: check for locks erroneously passed by value + + +Inadvertently copying a value containing a lock, such as sync.Mutex or +sync.WaitGroup, may cause both copies to malfunction. Generally such +values should be referred to through a pointer. + +Default: on. + +Package documentation: [copylocks](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/copylock) + + +## `deepequalerrors`: check for calls of reflect.DeepEqual on error values + + +The deepequalerrors checker looks for calls of the form: + + reflect.DeepEqual(err1, err2) + +where err1 and err2 are errors. Using reflect.DeepEqual to compare +errors is discouraged. + +Default: on. + +Package documentation: [deepequalerrors](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/deepequalerrors) + + +## `defers`: report common mistakes in defer statements + + +The defers analyzer reports a diagnostic when a defer statement would +result in a non-deferred call to time.Since, as experience has shown +that this is nearly always a mistake. + +For example: + + start := time.Now() + ... + defer recordLatency(time.Since(start)) // error: call to time.Since is not deferred + +The correct code is: + + defer func() { recordLatency(time.Since(start)) }() + +Default: on. + +Package documentation: [defers](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers) + + +## `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. + +Default: on. + +Package documentation: [deprecated](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated) + + +## `directive`: check Go toolchain directives such as //go:debug + + +This analyzer checks for problems with known Go toolchain directives +in all Go source files in a package directory, even those excluded by +//go:build constraints, and all non-Go source files too. + +For //go:debug (see https://go.dev/doc/godebug), the analyzer checks +that the directives are placed only in Go source files, only above the +package comment, and only in package main or *_test.go files. + +Support for other known directives may be added in the future. + +This analyzer does not check //go:build, which is handled by the +buildtag analyzer. + + +Default: on. + +Package documentation: [directive](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/directive) + + +## `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. + +Default: on. + +Package documentation: [embed](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective) + + +## `errorsas`: report passing non-pointer or non-error values to errors.As + + +The errorsas analysis reports calls to errors.As where the type +of the second argument is not a pointer to a type implementing error. + +Default: on. + +Package documentation: [errorsas](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas) + + +## `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. + +Default: on. + +Package documentation: [fillreturns](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns) + + +## `framepointer`: report assembly that clobbers the frame pointer before saving it + + + +Default: on. + +Package documentation: [framepointer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer) + + +## `httpresponse`: check for mistakes using HTTP responses + + +A common mistake when using the net/http package is to defer a function +call to close the http.Response Body before checking the error that +determines whether the response is valid: + + resp, err := http.Head(url) + defer resp.Body.Close() + if err != nil { + log.Fatal(err) + } + // (defer statement belongs here) + +This checker helps uncover latent nil dereference bugs by reporting a +diagnostic for such mistakes. + +Default: on. + +Package documentation: [httpresponse](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/httpresponse) + + +## `ifaceassert`: detect impossible interface-to-interface type assertions + + +This checker flags type assertions v.(T) and corresponding type-switch cases +in which the static type V of v is an interface that cannot possibly implement +the target interface T. This occurs when V and T contain methods with the same +name but different signatures. Example: + + var v interface { + Read() + } + _ = v.(io.Reader) + +The Read method in v has a different signature than the Read method in +io.Reader, so this assertion cannot succeed. + +Default: on. + +Package documentation: [ifaceassert](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert) + + +## `infertypeargs`: 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 + } + + +Default: on. + +Package documentation: [infertypeargs](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs) + + +## `loopclosure`: check references to loop variables from within nested functions + + +This analyzer reports places where a function literal references the +iteration variable of an enclosing loop, and the loop calls the function +in such a way (e.g. with go or defer) that it may outlive the loop +iteration and possibly observe the wrong value of the variable. + +Note: An iteration variable can only outlive a loop iteration in Go versions <=1.21. +In Go 1.22 and later, the loop variable lifetimes changed to create a new +iteration variable per loop iteration. (See go.dev/issue/60078.) + +In this example, all the deferred functions run after the loop has +completed, so all observe the final value of v [ +## `lostcancel`: check cancel func returned by context.WithCancel is called + + +The cancellation function returned by context.WithCancel, WithTimeout, +and WithDeadline must be called or the new context will remain live +until its parent context is cancelled. +(The background context is never cancelled.) + +Default: on. + +Package documentation: [lostcancel](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel) + + +## `nilfunc`: check for useless comparisons between functions and nil + + +A useless comparison is one like f == nil as opposed to f() == nil. + +Default: on. + +Package documentation: [nilfunc](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc) + + +## `nilness`: check for redundant or impossible nil comparisons + + +The nilness checker inspects the control-flow graph of each function in +a package and reports nil pointer dereferences, degenerate nil +pointers, and panics with nil values. A degenerate comparison is of the form +x==nil or x!=nil where x is statically known to be nil or non-nil. These are +often a mistake, especially in control flow related to errors. Panics with nil +values are checked because they are not detectable by + + if r := recover(); r != nil { + +This check reports conditions such as: + + if f == nil { // impossible condition (f is a function) + } + +and: + + p := &v + ... + if p != nil { // tautological condition + } + +and: + + if p == nil { + print(*p) // nil dereference + } + +and: + + if p == nil { + panic(p) + } + +Sometimes the control flow may be quite complex, making bugs hard +to spot. In the example below, the err.Error expression is +guaranteed to panic because, after the first return, err must be +nil. The intervening loop is just a distraction. + + ... + err := g.Wait() + if err != nil { + return err + } + partialSuccess := false + for _, err := range errs { + if err == nil { + partialSuccess = true + break + } + } + if partialSuccess { + reportStatus(StatusMessage{ + Code: code.ERROR, + Detail: err.Error(), // "nil dereference in dynamic method call" + }) + return nil + } + +... + +Default: on. + +Package documentation: [nilness](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilness) + + +## `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 + +Default: on. + +Package documentation: [nonewvars](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars) + + +## `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 } + +Default: on. + +Package documentation: [noresultvalues](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues) + + +## `printf`: check consistency of Printf format strings and arguments + + +The check applies to calls of the formatting functions such as +[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of +those functions such as [log.Printf]. It reports a variety of +mistakes such as syntax errors in the format string and mismatches +(of number and type) between the verbs and their arguments. + +See the documentation of the fmt package for the complete set of +format operators and their operand types. + +Default: on. + +Package documentation: [printf](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf) + + +## `shadow`: check for possible unintended shadowing of variables + + +This analyzer check for shadowed variables. +A shadowed variable is a variable declared in an inner scope +with the same name and type as a variable in an outer scope, +and where the outer variable is mentioned after the inner one +is declared. + +(This definition can be refined; the module generates too many +false positives and is not yet enabled by default.) + +For example: + + func BadRead(f *os.File, buf []byte) error { + var err error + for { + n, err := f.Read(buf) // shadows the function variable 'err' + if err != nil { + break // causes return of wrong value + } + foo(buf) + } + return err + } + +Default: off. Enable by setting `"analyses": {"shadow": true}`. + +Package documentation: [shadow](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow) + + +## `shift`: check for shifts that equal or exceed the width of the integer + + + +Default: on. + +Package documentation: [shift](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shift) + + +## `sigchanyzer`: check for unbuffered channel of os.Signal + + +This checker reports call expression of the form + + signal.Notify(c <-chan os.Signal, sig ...os.Signal), + +where c is an unbuffered channel, which can be at risk of missing the signal. + +Default: on. + +Package documentation: [sigchanyzer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer) + + +## `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. + +This analyzer ignores generated code. + +Default: on. + +Package documentation: [simplifycompositelit](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit) + + +## `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. + +This analyzer ignores generated code. + +Default: on. + +Package documentation: [simplifyrange](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange) + + +## `simplifyslice`: check for slice simplifications + + +A slice expression of the form: + + s[a:len(s)] + +will be simplified to: + + s[a:] + +This is one of the simplifications that "gofmt -s" applies. + +This analyzer ignores generated code. + +Default: on. + +Package documentation: [simplifyslice](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice) + + +## `slog`: check for invalid structured logging calls + + +The slog checker looks for calls to functions from the log/slog +package that take alternating key-value pairs. It reports calls +where an argument in a key position is neither a string nor a +slog.Attr, and where a final key is missing its value. +For example,it would report + + slog.Warn("message", 11, "k") // slog.Warn arg "11" should be a string or a slog.Attr + +and + + slog.Info("message", "k1", v1, "k2") // call to slog.Info missing a final value + +Default: on. + +Package documentation: [slog](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog) + + +## `sortslice`: check the argument type of sort.Slice + + +sort.Slice requires an argument of a slice type. Check that +the interface{} value passed to sort.Slice is actually a slice. + +Default: on. + +Package documentation: [sortslice](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sortslice) + + +## `stdmethods`: check signature of methods of well-known interfaces + + +Sometimes a type may be intended to satisfy an interface but may fail to +do so because of a mistake in its method signature. +For example, the result of this WriteTo method should be (int64, error), +not error, to satisfy io.WriterTo: + + type myWriterTo struct{...} + func (myWriterTo) WriteTo(w io.Writer) error { ... } + +This check ensures that each method whose name matches one of several +well-known interface methods from the standard library has the correct +signature for that interface. + +Checked method names include: + + Format GobEncode GobDecode MarshalJSON MarshalXML + Peek ReadByte ReadFrom ReadRune Scan Seek + UnmarshalJSON UnreadByte UnreadRune WriteByte + WriteTo + +Default: on. + +Package documentation: [stdmethods](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdmethods) + + +## `stdversion`: report uses of too-new standard library symbols + + +The stdversion analyzer reports references to symbols in the standard +library that were introduced by a Go release higher than the one in +force in the referring file. (Recall that the file's Go version is +defined by the 'go' directive its module's go.mod file, or by a +"//go:build go1.X" build tag at the top of the file.) + +The analyzer does not report a diagnostic for a reference to a "too +new" field or method of a type that is itself "too new", as this may +have false positives, for example if fields or methods are accessed +through a type alias that is guarded by a Go version constraint. + + +Default: on. + +Package documentation: [stdversion](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion) + + +## `stringintconv`: check for string(int) conversions + + +This checker flags conversions of the form string(x) where x is an integer +(but not byte or rune) type. Such conversions are discouraged because they +return the UTF-8 representation of the Unicode code point x, and not a decimal +string representation of x as one might expect. Furthermore, if x denotes an +invalid code point, the conversion cannot be statically rejected. + +For conversions that intend on using the code point, consider replacing them +with string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the +string representation of the value in the desired base. + +Default: on. + +Package documentation: [stringintconv](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv) + + +## `structtag`: check that struct field tags conform to reflect.StructTag.Get + + +Also report certain struct tags (json, xml) used with unexported fields. + +Default: on. + +Package documentation: [structtag](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/structtag) + + +## `stubmethods`: detect missing methods and fix with stub implementations + + +This analyzer detects type-checking errors due to missing methods +in assignments from concrete types to interface types, and offers +a suggested fix that will create a set of stub methods so that +the concrete type satisfies the interface. + +For example, this function will not compile because the value +NegativeErr{} does not implement the "error" interface: + + func sqrt(x float64) (float64, error) { + if x < 0 { + return 0, NegativeErr{} // error: missing method + } + ... + } + + type NegativeErr struct{} + +This analyzer will suggest a fix to declare this method: + + // Error implements error.Error. + func (NegativeErr) Error() string { + panic("unimplemented") + } + +(At least, it appears to behave that way, but technically it +doesn't use the SuggestedFix mechanism and the stub is created by +logic in gopls's golang.stub function.) + +Default: on. + +Package documentation: [stubmethods](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods) + + +## `testinggoroutine`: report calls to (*testing.T).Fatal from goroutines started by a test + + +Functions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and +Skip{,f,Now} methods of *testing.T, must be called from the test goroutine itself. +This checker detects calls to these functions that occur within a goroutine +started by the test. For example: + + func TestFoo(t *testing.T) { + go func() { + t.Fatal("oops") // error: (*T).Fatal called from non-test goroutine + }() + } + +Default: on. + +Package documentation: [testinggoroutine](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/testinggoroutine) + + +## `tests`: check for common mistaken usages of tests and examples + + +The tests checker walks Test, Benchmark, Fuzzing and Example functions checking +malformed names, wrong signatures and examples documenting non-existent +identifiers. + +Please see the documentation for package testing in golang.org/pkg/testing +for the conventions that are enforced for Tests, Benchmarks, and Examples. + +Default: on. + +Package documentation: [tests](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/tests) + + +## `timeformat`: check for calls of (time.Time).Format or time.Parse with 2006-02-01 + + +The timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm) +format. Internationally, "yyyy-dd-mm" does not occur in common calendar date +standards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended. + +Default: on. + +Package documentation: [timeformat](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat) + + +## `undeclaredname`: suggested fixes for "undeclared name: <>" + + +This checker provides suggested fixes for type errors of the +type "undeclared name: <>". It will either insert a new statement, +such as: + + <> := + +or a new function declaration, such as: + + func <>(inferred parameters) { + panic("implement me!") + } + +Default: on. + +Package documentation: [undeclaredname](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/undeclaredname) + + +## `unmarshal`: report passing non-pointer or non-interface values to unmarshal + + +The unmarshal analysis reports calls to functions such as json.Unmarshal +in which the argument type is not a pointer or an interface. + +Default: on. + +Package documentation: [unmarshal](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unmarshal) + + +## `unreachable`: check for unreachable code + + +The unreachable analyzer finds statements that execution can never reach +because they are preceded by an return statement, a call to panic, an +infinite loop, or similar constructs. + +Default: on. + +Package documentation: [unreachable](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable) + + +## `unsafeptr`: check for invalid conversions of uintptr to unsafe.Pointer + + +The unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer +to convert integers to pointers. A conversion from uintptr to +unsafe.Pointer is invalid if it implies that there is a uintptr-typed +word in memory that holds a pointer value, because that word will be +invisible to stack copying and to the garbage collector. + +Default: on. + +Package documentation: [unsafeptr](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr) + + +## `unusedparams`: check for unused parameters of functions + + +The unusedparams analyzer checks functions to see if there are +any parameters that are not being used. + +To ensure soundness, it ignores: + - "address-taken" functions, that is, functions that are used as + a value rather than being called directly; their signatures may + be required to conform to a func type. + - exported functions or methods, since they may be address-taken + in another package. + - unexported methods whose name matches an interface method + declared in the same package, since the method's signature + may be required to conform to the interface type. + - functions with empty bodies, or containing just a call to panic. + - parameters that are unnamed, or named "_", the blank identifier. + +The analyzer suggests a fix of replacing the parameter name by "_", +but in such cases a deeper fix can be obtained by invoking the +"Refactor: remove unused parameter" code action, which will +eliminate the parameter entirely, along with all corresponding +arguments at call sites, while taking care to preserve any side +effects in the argument expressions; see +https://github.com/golang/tools/releases/tag/gopls%2Fv0.14. + +Default: on. + +Package documentation: [unusedparams](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams) + + +## `unusedresult`: check for unused results of calls to some functions + + +Some functions like fmt.Errorf return a result and have no side +effects, so it is always a mistake to discard the result. Other +functions may return an error that must not be ignored, or a cleanup +operation that must be called. This analyzer reports calls to +functions like these when the result of the call is ignored. + +The set of functions may be controlled using flags. + +Default: on. + +Package documentation: [unusedresult](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult) + + +## `unusedvariable`: check for unused variables and suggest fixes + + + +Default: off. Enable by setting `"analyses": {"unusedvariable": true}`. + +Package documentation: [unusedvariable](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedvariable) + + +## `unusedwrite`: checks for unused writes + + +The analyzer reports instances of writes to struct fields and +arrays that are never read. Specifically, when a struct object +or an array is copied, its elements are copied implicitly by +the compiler, and any element write to this copy does nothing +with the original object. + +For example: + + type T struct { x int } + + func f(input []T) { + for i, v := range input { // v is a copy + v.x = i // unused write to field x + } + } + +Another example is about non-pointer receiver: + + type T struct { x int } + + func (t T) f() { // t is a copy + t.x = i // unused write to field x + } + +Default: on. + +Package documentation: [unusedwrite](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedwrite) + + +## `useany`: check for constraints that could be simplified to "any" + + + +Default: off. Enable by setting `"analyses": {"useany": true}`. + +Package documentation: [useany](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/useany) + + diff --git a/contribs/gnopls/doc/assets/assets.go b/contribs/gnopls/doc/assets/assets.go new file mode 100644 index 00000000000..139bd2ffef9 --- /dev/null +++ b/contribs/gnopls/doc/assets/assets.go @@ -0,0 +1,7 @@ +// 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 assets is an empty package to appease "go test ./...", +// as run by our CI builders, which doesn't like an empty module. +package assets diff --git a/contribs/gnopls/doc/assets/browse-assembly.png b/contribs/gnopls/doc/assets/browse-assembly.png new file mode 100644 index 0000000000000000000000000000000000000000..93ae8d215e7516458e0f72753a4f9be6b07f770d GIT binary patch literal 779478 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

5&U0XZ9^vd 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 z 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�>9dTQtuC1(!( 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/browse-free-symbols.png b/contribs/gnopls/doc/assets/browse-free-symbols.png new file mode 100644 index 0000000000000000000000000000000000000000..c521f52dcc73d7fcef66f2e589684637562706c1 GIT binary patch literal 309890 zcmZ^~1ymf*vN*hWfFOb38VDL-acA)moP^*mi@OA8mtY|{gy8NF+zIaPPJ-Lw?(!|a zd*6Ha|IYiS&z$b5uBx8yt?4e?5EUgEEDTZ%004j``$6gx0D$ZO03cGoK!rmN7}JR0 z6|%XwqBsCh5s7(kgaUu2Hu>;L5diR{2LPY}0KhFA1l<7uT-gDDT|)psFaZD{a!6_X zC=Bn&0&B{eDJlY(;q4ay6a+i~61;@~F8~Bmz%w^^3m}I;_TTMK2#o*6K?DGTEdj{? z#?gk?&zBhdex~_XjT{pMK!ZQw!SARHg#Y25f%spP$PO7u|J6pMhT{RmRV8I*;kBxX z6BumgY+>)Bo@Emz+Jaq-sNHRC?3@MNMQHxP5QMj%!9W`7e^6YkMQAh?Rj4KHoxs$*Z0v08 zG$0IWYHDF8Q!~L&Qt$sw4u2A%v2by55Cj6<+}zmQxY+ES%z+#N0s=sGP9P^ID;$H> z*~8Ao$eq>Bnf6~y{)>+k*xAI%(!s^j-j4d2uaU96tBVK?&GSJ2ef{e_!S0s-Gm@S2 zzo!MCAn>^d$ic=A{J+RtEY1FZ$ew%tMfT6U{xzKNGc!RoXRwo`y{#?S&IR;8D=z%c zNdHgg|GekF2vscI!8V#wmT*dE_?$qTJUo2=Ci~xA|C?0O-p1ZZ)xpRF4C44V$$xgO63m;}tP z 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�za6(u}7?ym*|Dho`5wagHDyX~okE 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

i%;ZCC~uRS=j9wHnfX!+ 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

%4`Ip*!@rHD^i?wvbRZD)Bb3dJ9n3)Kc4 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/browse-pkg-doc.png b/contribs/gnopls/doc/assets/browse-pkg-doc.png new file mode 100644 index 0000000000000000000000000000000000000000..32db6167c2b305656bc3fa2a5be63d4830848ded GIT binary patch literal 636585 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 z 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

P0qjrGTs+s$6iP)pObH{zWTZ`zG( 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%0A-Prh*6lH*_&)X%%^wix*ZLk&G$AHRZHAc^@+b3R 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/code-action-doc.png b/contribs/gnopls/doc/assets/code-action-doc.png new file mode 100644 index 0000000000000000000000000000000000000000..2f2e6e476b83ffa72d02cf768a1429da77a7c66c GIT binary patch literal 47715 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/convert-string-interpreted.png b/contribs/gnopls/doc/assets/convert-string-interpreted.png new file mode 100644 index 0000000000000000000000000000000000000000..6bb7f2a9b352252b52575f663e002089f2a5be98 GIT binary patch literal 42250 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/convert-string-raw.png b/contribs/gnopls/doc/assets/convert-string-raw.png new file mode 100644 index 0000000000000000000000000000000000000000..24dea626eb18ef279d8f1293169933c949de1ddb GIT binary patch literal 52665 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 Gp$Py64Hbw0 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/diagnostic-analysis.png b/contribs/gnopls/doc/assets/diagnostic-analysis.png new file mode 100644 index 0000000000000000000000000000000000000000..5a934d0d6e6ebb1a9de4a4a94e28469ce21d7de4 GIT binary patch literal 52655 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 F{SOMP4L$$> literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/diagnostic-typeerror.png b/contribs/gnopls/doc/assets/diagnostic-typeerror.png new file mode 100644 index 0000000000000000000000000000000000000000..8f78228893c48b694fc092c061f28c2a2bbde47b GIT binary patch literal 65041 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

O&H) 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 NAQJN8Wukii{|}h2cT4~P literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/document-highlight.png b/contribs/gnopls/doc/assets/document-highlight.png new file mode 100644 index 0000000000000000000000000000000000000000..ded4564c02779fbbba2f8f8ac2fb135a984faf4c GIT binary patch literal 20231 zcmZ^~1AJu9);1a&6Wg{X?%39Z6Wg{qv2EM7?TMXCY}+?;{^xzqx!=8C_pf`eT~%wX zU0v0+cdh4Hq4Kg~@G#ggKtMq765_&&KtRCuU$P?<#MgHUOl!{90n9`|MgRz?HX8O# z5B%$#&_G;K1_;QV90X*c%yHJDA!yx{(RS00Dtmnf*|6RFjeBGO)3t*E6)yH==j7 zvi++91aRg060M9J^$1<9EUg{5TzQE9)q?9w{#(pIO!%)Rjut$`YBKVKLN@kBglzPT z^o+#3FocAJ0DD7YE=6I{f2)6;@erFjI@)qEFu1t5(7UkE+t`~hFmZBnGB7eTFf-GA zwV-owvv$;TrL%S*`Nzor*bz2zFt9hXbu_cFCj4txPv6GLk%yT0ucQBc{d1j0u4e!7 zWbN?pZhdu-;cpEC6Fnot|DT$pneqQc?QhLLYX9ojKkfj3jd2OtSlZYt+3Fb>@iGDa zW&gj6{&R8vw3at>HL_F}HnaK~gRhS8GJXHf_;0QMv+Dm;Rdz747qYSX(sbng&nW&| z`M)dwPsM*3R{M`nW>&WUm(TyI`Y+YLUFMRrH~Vs@_xCsd+q?fy*}vNZ82%2(e+|$- zqyMkcui@u~0WkdURN{rP#qQPv0^$df5El613Vf~w>4_$S@>%>Sh&;>vP4ehlnQvYQ z$gHqDzW^}RYy!0;8rrwoTuK6eLZ}eOg0R_GV%At7=s*~ ze?>~vl5JFZOgDHRU&%+be>52Fjo|*R`b&vlZ;$m2n0iC?%MyRCZ{J8F)4v2kKWtgx zUF=Pt!q

>EWcXZv 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 DEtg^I literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/fill-struct-after.png b/contribs/gnopls/doc/assets/fill-struct-after.png new file mode 100644 index 0000000000000000000000000000000000000000..61662287e10e831b94e432d251d36fdb96f75937 GIT binary patch literal 31585 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

;C+6*JAi`i@krvf;T13> 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/fill-struct-before.png b/contribs/gnopls/doc/assets/fill-struct-before.png new file mode 100644 index 0000000000000000000000000000000000000000..fd544921a6d829d949465374c5b62e63a47161ac GIT binary patch literal 30109 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 z z1nvKYN<2Qf3F7q8rOKkgy%*_zCmxFLv02L-cL}MT-_kf#=-m$<>!0T8j2Ov#53Bd( z8B@4$>|;Kc%dTduqM`K~!jwX69BkClV^_WmR#8x?5OLHR9Uaf!9<9i`KaU8^z>L`0 z@E%tNIxLAa7OSG~V4n9oqb_elxW7Un^rGfmfo3dlu>n!Jj&}8%z%Kmg?RAzqu$L88$e$`}*PoI)hKZ0CS?{)#1@Xtcg{3rv)Y{_U!bi%ZNBh=!1uPEb!%kqLXO!Fp-HZqdK{m8dC6E)M4a zCrcA`#w!h(;@z<0PP^WKO5Ich^t3_nTDquF+tUjf8R6;a=^^RaPY;u!>rUzv+t{KT{>Wla3!MqNG0ntGlKW1m*Ct1^TjKq8+&`OQGJYi z4b?8~yU#DW42lz{3Hi@dicnD}0DG3^%NLp$6V!Ftx)^@A&%*tJ~0*Tm4r>!y|@-To*Dju%ni zG?@FfJJ!sQ$0ZxyKM}+~^M!xrmGdberW>JtidBz;!W~XoNaAbYQ>}e>2eZLW zTY@c_YUB`WKK^>GP&wGQy||p`v^oThNza0^5}ZM*kxj`6NIQ9oZa!vEj8DYPTN)DX z2?)8O<8qZQ)26tVbvL(0uvIKocz}v3afH<$`&p$)bF+XB^HfPx)K{miGEqRs=7lp) zA5*q@C2n&-_c`O`d-wxM7>;Vbn1$cV_S#Z6!U?adYQe0x?`L+A)>T$pxjlt&bD27s z%!Ysx1(@TL&`GWd7j^^vzG2J1lo!0{uiwtCI|p}3?zUVy>Wbua6*>Z)C3-<}4t4b? zEpm#16<6RDvIUkzPmZn4&O4N9`G9@yqm&$d?;=Q{lcj*GX;p9o4wBssr~AW3?SO*+ zSf;^&`LylpIX$`h>9C}PLu9HOpFhdlPg|F$7HL|;2@KvXQHd#(tDa@ZBIPELG8owE z3kZVj9&%Ht*dsWiLbm4l7hfB9iNg7JDE4Uo?|}GYL^Q=TJ?JP zFE7{M+czPvLdVPo;w*t6pLzpk)@tc&RNr8$KlL4R8X>1Oiw<#=N-&1_XI$J`rb;qh1QFC@JcQhVOsI+wF3!eW?wuL#dM2^ySX2 zbEbKw&?`j$h{g&#yJ@`|bHhYKf5ju>j29x^nQrjY%}X3Kuojmr!FAt9@FFPQ2=c`J z+9WJ8CL36124!b|Q_(QXJcXFPY_!=wOFwZ%8~^fdy@$nGql3nh{1iY^Mk2X^wZ$b#p= znTP)L^JG<&A_`Aqd8S6*s$dJ192I1gKLd!p*Ikt!svHiI}ex}gv(YP%|JLTRzN z7V#Z63NGyPZL=42oFV+!xE7Vs7Jy?ti06P@lSQy%c9{7Wrya<9ECZh; zS2Tp=vT$MehMs%Ja{pXWY@21pjQe4{%*0`S&<0~`=E@He)spHt>q*n^7xS2$(@}G| zLyP5-iqz0wdZy%mO`*`Qa+xf_z^NP#A#ic+XPrCXQAv;53wd*d%q|R?Ib8-I&A3(Y z0*OIBW6|?8M}^y47E1_`0MxH0@wo@!!{*tvqUxokng5KyVZjHjiH?pQzAXC+=UuR! zH}uh4iW5Ob6p14&KXIeacroAjfTGX}*@}cCXP}c(o2eSeOoJ3z zsRh12#39vy<~(+lvu%*|5n`8FJjr~gS$JevT{Z+abz=()Cq70O*fg-p!3lMkzA+aF z^sd#kUV7Xwv>hP9yj}5&ZNzIn`Y0Y;dz3^XrOc}S#X4?p*U$F=mlPe!2zQ@!EMH#d zI|ZF)-+om+liG2O<@h#{Wzjsg7SNy_!@2bGD&}0k$#2pWtsXAsPs@@5~WdQ$F8Y$kRsT`#=jCjTWG9vsgM1Sw~l)z$V?}yrISHaEW9Vm0|pul3AkAEJR ze#dla0Zi8M^sFr5;4VIl8)2rfYEzdj9y>NO*VM*7 z#F<)ILKWV=^c?PGnBm*g;AVFV0On?D;yaECzf;>(Az45@&1oLa%op`T{iW5yf3o`j zRBE|=-0#;4$}LHBqdwR(jj{j+pmEO)^U&a`!+YPq`)e`84TT{_=o|(UQ@uY2t~{~O z(z1TWz}g;Yj(B*F%{wC|ApsK>7Ea=@)zepGStuB#>FA_qkuO8L8I?;er={-MfHJ=n zBzVOu2Z$2CRGe+FK6=%?4$fN%$Q=_qJf4DAH@pTmy2=bQC6><<9}k|{+~&H+IG<}L z@6Fl6`xbg5l9-asV(f6-JqQj&tWiZh5dJfVWh6GUs3kdR7Com=NP~rloxL<5n&p}P zKQ^{(UI(nu_ja%SIx*IyQ*$kX*EI_e2sw&yM7Ohn+XaNMU=qE#CCa%*?D< zy2kNnju-&Yq?CCg;^ozPs$6qWRSu<+MClQ^T6QN7p*$*+gedCp_KCyD9O8Nqt!D+Q zv`jcZD_njy9c-Zrod)(!y{3KWSl0GcKdTy8xUO+>{QF`18EhUMOogs06vvBs=@ws$ z(rIXkUq8MYxk{+lRrRoU>}xPY6C8%^6 zCKL>G&I?B56wejNIWo8^DJi9oELl}?Vc^8rKAkk^&-{?PsrT54SBXwa0$RJ~!uscz zvjDPR3)Fm`ck3e^50zAuHwVJ$9WP6ULJzlXA^+J_bi_7Fs3pJXJ-1nTuIKFzZgc&< z1yiLTsmW6Den&F8JYV!rmZ&h~<>mc4C}j27 zm?>35=2<%Y4vV5_9j+L1m80s34wUBK>4S}-==~&Yso4xaFDI9R{NbsR3drT2#d4D- z{gLpWu+4ccFctDDt_E+YrCWkPrw^jJt$EO8Ziv1m6@7Ju8s|M*P(mY0gz%}ic$73t|2x99Tnb!qd|}q%(e+@k>%|#-Z0!}>rvK}G z-HHFq0=8>tsN??66mq7|tD3;P<~5sC4f*Wi;@GcZpU{^vy(1)aL19l9(C{H6`Nh5J zMMMoX*|V2^WcW{619~ZjrJwBSVfCH5^XIW^-5#N8-L%kLtWBUUuC(I}XwNg)H@G)c zw+SCrrsFD6Qf49GM5$fVq6rn!{a^pXQgdw_ua6k4_M;?_KD z2-TbMwUUMl3^g6x3FXE|AwKU9r$Xk=+Q4R)Tv}34j0aXU(DDg|UERM9C!fKjM1B=J zKz#b42rJKbq{vDZphpe)?kqgcOG)-Ym~BZJqOaHZkWNE)k)^u3Va8?8i}m+?kaYpA z5nukZue+1(zwQ?k9RC6_PQ&vW|wrRk4{o5{P98-qP27n^D*>zcVb zcG#~>Dc79aI!B4q1nOmHGs*MO^9$E5Q@ghQvntp==D48WQA&6{ABiX^Accjc-c-@x zs_Z!6eyihE=oR;~HYwgtLa+PmK`tQZANOs6(687=F!x&KZh(I1BqtAH0%rVz=LiCK zl$!nnrhQy|s=-G(1--83cMbgumH7N*{0etG#QELIXM}4H!Qg3P^FK zIUk9;(tBnFKYVO|Xl!TrT&$#KHaW1V(wS8nF(K$Ill%#a$^ew)Wh$AD@?wi!e zD5pC<(b>=ms&*JPyL=Be_M+oklPABUEjr%9(QE|ybWVe!=uMFGqo zP+wo2!a>ove_e5bvXijaG1m?IBJvv8bx9&*OxU9iwI(PWZ~Nt|RcKWe;vd@wUN}qj zJs@FT(_?p~@^ldT8c|Rn<)Wj*cwd|?KcgvtT+Nw~%G*5J%kSJQF`DwsPY{>UULHCo z$WI}omA3HMefh@baE72z>s9Pzy~@)Ze1#n}1kOvab8_0Oy$G$7`ShFpT_gv`15DA9 z{S4o|bhFmUZV&TW_Ps}}o5hfS|GC%s`zuB1N}4d?pxFH`RvcLY&T4=s5SbRB8PiZ` zv>C`t9;4mild}Mx=5@mC*_(JvUVRM$!bJW1w*Y7vy6Wgepdxz$^CEzm=SZ+aa0ct- z@#HR6cf*bJI#lKFaXUxPLBA^e5fUvlR#5)f5Pc*Q7Dqr;=+O@?2G}eaulNYXPO6_& zmpQYp_F+s`-3JU0-sGL#9=$VvO{B|^p2?2npQ;sp=|{9w^esdOKDl<&vw+NLok}QQ z9gmJUTo*jwCialocG{(TC=j}Y??+`pSqJ6Ue1O#(>&mvoCgF7fRX(K`ao%CJh40E} ze#ybx=^Se&lUYKq9SrMeup2CKhZgBh5L^VWASG# z1}kMXaBAR5Fif=-aFFD=mMR%-f*zn?LuM+U@F!4OW)qs!*H1ULuDl2u08Z@8BzGk6 zC27oW7FJqMONF^9RlsY{U@4IZEm=kKPfkwwac@@B^sEda3J2;d;ckQWzZRAp4t-LL zkB2i?>Yn#^;HWo;SpMcVrm|mWjSr7YC{wf^p5#>4?!aZW?bB3-fY&9s4IC*eJ2a`P ztno?ChZq?dH3WD%{)oxX1q_gg>?w)%*x{jmzYJ^18i+r{{)mW^$Q!fO^!aF(Z9*ne zyt7vZcu+hvY-_QL8=QWFp!elfI&$@?;7Czv=powcu)j4c&c`m0BM7N zetRTUo5*zIlTwGYfCfXeD71Es8na_j+p*~JsAVYx4E(HVu|3hXEa$;wSj7(u-GYa& zJ8`>*EJ}!>pIB-l%0_P9A#tc@e>@~jK%w`m5zr8+c&P70KVd?JfZlpn4kPd+*mre=EMN+!NrMtWS z>o?x_{r%6G!#Uiucb+>l&)k{$JaJuTR^R44gh`zzk`=|<=|v+VLe&6LF!V)!!s!Om zL>VuzywmK}{%tb1mXNS8)UXQ$9I)KxrPb_Q)MbZTA@{7m;ZTI8LIn5)23LLcZ4ye!P9v3yrL4ujqI-AK|~%r66I?F!UVVDo4STT5W0GX60U(d__ zTLUm;Lh6l9#|2}#F3%-^2dKwBqFF{k{f;|(kIiRgv4Dm#HM;Ofiq%;33ZwY;_}hE^lqH zS%P+`AR%Rj);~Jwga#P2J)dx`$NTN|uq!=qi{jh&pvq z&r0QO;5*s`cs5vvP z3rn@$=lWUxFxay(DzGe|T6_63YOIcq|IIc-Q|pY7T>TI;GxHn!MZW!V@1F?cNpfKn z`Gu6&K+y)L`ukCC30vQGvCyy7RY+K;Y4Y80ms7_&?+2q-epec$OlQqj)z$Sf^M{YQ zSGxI*4ol`z&ssDV+V1NzjM$7n))gUgXfP?vP$LWlpTXen%M0iURd&EyAuR_`?-nFJ zpw)rfd>cpUdGj0}kDHeo#k845RU;!YF7z+};l*jMdTKtW+yzxj^tIe|B%4&bkWHl)z=*P{y>L#_fT`9kZU%>{6BBoJkBhf9(`rbK z)Og6yYz4``ePHpq^-rcRrn+c%SmQ&H7HI}33kfPTXbfcJZ>>dBU`?3P^|9%bbH7q& zN}O)5CLt%DeyboJ`iL)8E2ev@gX-=<>F~(uwVwiuOR)QNwTi7Cl@p8YZHXO>DlhC! zRT&L$6RO;eZVTa*yR$E))g=FZi(^w#xjwr1j+W_lEjoeqIhcZueZzO~-q(Cjf96YmX!q4=*~# z3|kU&x$XX#eWzUAHrIQtN^4`YI0tYz*KNbV@a{IRwB{t?|j6cW(qW*B~(V;p?vCvZ0&hX^-2|X zIh@4yxfV=om@}%%*u5i9UJu>)3s>S+1 zMNPF<>dd<`wP6xs&MohdB8BAgSpJ0zESA+@s?UtxhtB(1Qdk`)rNa#lDlLO<>Aki% zjdL9@(O9Ib@u9}Fz1_xgJtap@3X5nH|4&VKd=F#l{&qLAeSdM_Q^A_9ST8|67 zPui-=GoQNwCU|nj>s#9uKP3|fG-)}6v6k@&sy&=!RqRs}msTFmve~xyTv_dXFZ_~; zH)yUWjijpdqF{){Ost-0b92*VXR>r2Z0$Xq!DHRR6dA+wB@8W(V!rLPu1)~I3#i+q z3SgBy*S@trVj07>=iu{Il`Kc`Gh&J$lJ81Tg8-TPP0zVf!1PHJHWwQ@e6<<0DPc?di^4gU;#-1@r5TQADw%B; z55tX?cE0I}a4jIGXuxCVdQ{nT8EMVCdvhbe`!gpvXyyVzTN#>{H#^KNb(fsf%go2K z{!)0Y1R@oJl;sMdS5fylIc?>774q9N`aZaMd3oM$&xwhRH-4mh4_R9PjQD(iof-=~ z8aCl>A@q(huI;@~CLo*uqO{3O}w*Z-@I%)4Ga~9FQq~{QX$Q(Nr~Z z_sbUQDvrBNg*)YqvK-xef?i+5Dy(B})o-bS4vyL>=QPuI@rRjxxTrtp-O%clTcE4( zPjxj2b=8Y$F$>)s$1p%|enhwk5!%l<@?ohe^Q}_|M`G7v|KJxV(8NMrGyKqglL15154Q?e^ zKoAZG?Fqu8(q+3*>wH;lfXMIXQ*mbkQks#G5v@AQgvFcl-Qu6g)jfrF=HX0 zf*~f^-Wc-H{%KAeBkkPwjCR87+v(0S+WT{+X4G-vJ*6%_^(`i{ibu}!5|EY+K<_dm z{JDn9>tvz%fUDo(S`D`8ipZ+^jK-ijw+p&IFBqcl_pCQ~USStvxK)91$TYFPm)MslMrsgL~Zr={rF zVsh&UHgKvhjg@{yn4p|NU{#)sjiLnirDDvQP}1X`siu{8xn~;1&s({T>1cqT?dJTx zB6Jv{uLEh?&80MukvG77HBdBL(O1kiBlWCWcZePF{@zw#2AMZ%jW^Rr9m4YgWURVX zP^D%8aM#H6St>Ky%vw_T2P$1BeJZ>0{f~o;OfXp;0v+kYpT+L>>X1Rd)}Vq98ertn zF~?Z}J}L(38Tbc~F0GtmN}WVK86Wz!al%-6p$p^4Pt?w5={MY}qdk<@J>pHC*WbsN z$Wq{WAQgSc&(U7I@-Uo%DGkER{oS%%fY)m5JND_tT<1r@{z%Vi=CubBHj0+Y`_@5s zI_l2022+HKhxQAuy=#mFCX|k$Ugov6?m?|a=^s60!JW4Zo$uf8H{x6*rA8pU2YBMX ziZvC#LKC;VhtW{Zkg<7MS?7t_Wo&QEH{v*Zkcav$;1jTlz_#osX6|tQ#I&B( zkaSH;$wc5!5}^B*X4AHH$PN))A9Iv)ztT8uM@bLzsqlplC$A{=Y7lk zOAi!_di{{)wslm*`)E%I( zG4*owH*QD4YBlt2Y7};TnohNcyY$-1?z)Uw;c9LKG#{ggQ)1P|p6acb>EPxDK@AHv z{d1&B2QvrntD<|T%1ELMpbVO%v9X3j5m?TPW&BYSVN|hhBkUc|^z(@mxSPG~5Z|4l zNcnj*=$yi0UPTb8wis&N`w@WKsp8k=mv;6OTi#i_trjL3!W2gXnnG ze+;YI*eK>yR8+V^-A^gZb42{`2?;|oUk#HKx)0$SxZIt3v8LK#qlaal*&0OnG3*_m zEN)y%wcK2N?}nj+BR2~L6<^K-GI6a5fQFVmrCN}Nr5AxpkiM{zNlsT~E{swNYlAOM zFCnX{Qw8a6bsGR-OB>Oc*q2Y5)3<_j6AWl)G$v+1==wMSakT?0>Eou+0I7?H_8SN< zQ@pMT-Qf0E>?NlXU9%8Y8r%iTyF%lmHO=lbG|!*fS}+G((s@v+tbpamqHj>w>vNY+ z(yYI4&79krv0I9J6}~7~zH%M?gcc)L43$t&`G6#fwmuUl`bES~zC+*kWG17I{u@S>*##OfJ~6Ka`cZ=2V(hi zlxGDwF=;d%X1#fOpo)ckJzL#R-~!^=^(uwoQYk3j@QO?X#_!{(AZI%W1lORm4S zPhvXf>xId{=M0H&@}gk-bFh@nWfmwiPUo@t^}gQ1_oY6?rYAg=T{V!mKZK9jPEZF0 zQvKf3Y1A*Aa{m3YMGI1pDBm(|l+ldb2i&5Pi8TYuAHPq$$HZhot7uS6Wd=me@C(}5 zFU{EvPu@>eUZ^(3a$tO{Lt;dO+TdidJZ{zSQfHc@z6eT<>!hOrr4dF*W^vc{KKvN$ zjous`%Gw_eZJMBA54hbOq+)G-`FP^KpITmY0>NeF4s7^g6 zS>kz;5yU_$yv_N<#+m1D@)|T0gs?RdRXimpBf}9CY#nkU7x4Q=Wj=M!K_|UY4?te< ziHbgUamN!mBgE1A7`C142jAovl5ORiK@d^D)$Zm5rJ^hZ7_HEo1{{qcZ>o}ixD!## zaX`N8DH5_f7;;3l?TuDcTg!Ox1v@9(Akw(}3bxVDo$t-GF2`-+eHy!cYg%A-7 zAZ|FHTsrfk`6LnnIy>tT?J-BwCv!Xo|HVE3NjdpJ6s24Fd-va-LH}Gc#Qu{t5=DBt z>whi>iggxpf^e20ow)~AC!01Rx+}4VnkR|qzJK^quf<$rNLbm*dN`&GaPg1%5$fN$ zE*8kD@gBmCak9uj<7h>YJ9<53n$F7vRb`hpM>c$i90Jjn zEXnM3R{cNlT9*Zr8wU!Mn<{BPq;JsDV|fS2VfagR|GFOu;j=CB_>#|S8wT{jw~v%cH4_n*N` zjZqSu2k4*P(})Zlj3i>ip3ie5U}pKDqS8fNR(4YPJINST`wdDGgK}ya-we62mlwbP z;yC?-%oa*AOTWOP?; z+F6dh@a#&ztNLgR(fAdhY@ojftFZ9v>FMdC^YgHj6e9cfa}Z+d4|l$E?fZyJhM>+& zv~+Z(W9~bX@^`>6x;P5akshFQ7-39jO6;GSZ(f7swkl>CE zQSb8|x&V;HYG17CvwMG|nMytJ%H9f{Mp)a?k)!M0ECCi4)@-W>M_=4)mcfaUEWslC ziCXV->kqy}=hC$CH!nndo62Q@H)q=+pbBmIXzlvXAjQ?avAieq^)?tjK0cX3UV!jt z6f!C*342M8f2PY2!XvZCdjA4gzO?MDoGROgm}xWgC~N4nnqh-AoECt$+l(MgnH<%BTmRi7)nXE7ce}d4C+PE?&2nIE7r=GYN z;^;6O$>g7qkdbnnD3p)#{-ZKtIEXGGE+h7i6JkYZZxcGf)>7-c7Pnn=7Z;bQbr2XF zxwT~(`B5U#eQ%a3-fEh%SFGOQ7x~(9!QIuBXJ}ZM{H}_-dqd0&Gb3YQK|w*eENUd? z^Mu@aC!MvAi}f~X`fH{BKwy-%OiPMMjcdXujcB=?u&}VYxLC4xN-YdiGMNYmlpx|r zL=Y3P7ufT|pN>&SWmGEVExp;R9W^}?00%HyW||@_%x*0wCnt$s+##PLh??QaS6>4j zt}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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/fill-switch-after.png b/contribs/gnopls/doc/assets/fill-switch-after.png new file mode 100644 index 0000000000000000000000000000000000000000..33d1bd34c4a335b0ccda8d836e4b3bc97ca20d40 GIT binary patch literal 34400 zcmbTeb9`M*+cun}u^QWMjK;PaH*C=C*tYG)b{pHaZJUj4f4jZf>%PC|ecpfGonQ8> zJ!{UvT5Hc7=W!mhL*!+}5nyp(K|nwdBqc-?K|sKWfX92#kid7!rxQ+K0&XNIBM1Ug z8435S3jzF0q$i;$0|Meo0RrOf4+8QC%<|p`0dZgi0Xh5%0>YI90)lCs)}+7#JYi{| zCTS=m140cvh6VuxMFDvSJOTy&fdrX=fd4)Q0Rh$oe1XRVf2hWiyZv{Vrl}#!I3mBTpo3Wotmh zM$bskNWuq8L`1}6t8d7qC?fX99QcWs#Ms{6nu~$K$;pY{iG|+E)`)?LlarHyk(q&+ znGRTj&d$ZsUe}q<(vI|>O8(X(Vqm9dYhrD0Vr5D6t6p7zm4iJm3CXX9{(b$^PXlL@ z|FmRj_h(qZ0W$nL!@xw($nd|J*_#;tU(9};`N!<{xc+I5=T~K1!dB*1wo2BzdIo$< zJiqJz@2vlH?w``~Ce8-t>LMlIxHrrIYfu`L{(l;yAbQ6C@9uo-Ku1Lq=Z3m(V)N+}v>Rn; zD|@H$pJZWB1-ZPG!l>G6w?O=wL|5M8B`0<|*kzTF!;huWoMr79*r zY-?DJVg(=D{tOoc<(P}!ECyW+Lt={3 zHuIlbY>HIC4%%31Kq$%Awo;2ii6Tov$zPwMTV|y^owv5XR?3P<$Qg-FQ%AyHpEU%* zd*#iFhY!o>)mU9XQ>eIp4xwskU~+H9d@|`YV%^|qViXOZWSTnOXm5_NH@h?!YPrCjrY1)vujo#fmY`iOd$ zOU$y2&HX;bE4$(GK3h4H8)+sRs-$Ez^fGaNJI+VKOJJyxA6nkY?ggsQ8E7eS@GXb%9Sg zAMxXFKH;bjI3u7eB*tfxvA;j{XiAx_nl;U`SO%MObTmAI$RaWe_sfiYa8;1Pz-Y2W zX)J{Jc>$}fKQU(EQUO&(bNtu0(3>3m&>Yld@0ERX z?`QGt-9ryKb1VNX$lBYwIP=SHj=_S>PdWFd7nbBRoGFOvz|kIGy0guJ-XB=&d&KY- z{NUpB_@aA;={d4h^bl9<3<9PqMI+PVkrSs$j!$Sv*$O*P!no(FuQyV4OIK-}+l*OW z;>vsLVR-{J+B)9Nq&kYjA2!Qox2@wIFjs2gxi>b~ON4Y<86yC)TwjKEI7Rq$OPiXika41(*p8qEGu;P*E`^O~jj-&I_s0mPiF~w)*Z^j zy$6eDL&Omw!ZPy$H?o4DF&z81_4>1#erQ^h)N3WpzKq_TVk5fiOI=v=o-a9M>t!4`aBsCQZ1f3fdc-h zK}z2UNTUwLfHk)$ooJMyfQ37e*BpWG0sEvm^6P6O5CJ`t?47fVH8TGo`ap%zUbQ{7 zZHCtq(>3qq5$3`4v}>c<&yBpVuo>D|zbt{x*yP@ixj;SuHJJA!mCnJS?)h?;77H=G zgx`$~5tIINEswpK9+89*ebjLFq4Ys9CPB!@m9A3-s{>K4ra?kXpGQ z6}p-`)3#?}VzNC9DxU`B^mVq)m48@01ATvQ0cgHH-2vYgvU0vHV7KI=!G6n|Hjn9$ zdeX`(G=_&al%+~Q1zE;8i;Sp@w@deEi*_m~d)eu;W9s=XSB?9Xi}QOzA{c0h&?$wO z;>4Q=n-B+l?L#-jL-KLpRF}ml>Tcy9&|U2%7w_%-{*2*B50frPTTP4pwk44N6`h*ch-8C zC%QMl76{$s6&g+V?mu@ks1wRvwHpWVutFj0Q7DI;Ot?A^n^ig43#n+txO&)7_IiXQ zmXf^EbsI2WTr=fD8#%t1MtcJ%jiMc43;7@s8d zJp}o+7tZ!Il<}F=2ai$FQHj{($@Um6knl5!LgvJv=atl;-FkxY`^4Htd=&E(s0N=6APipfb?}?D%^U&QvUKO=C(2Ho3+zmP@iNZ zhR-KHTHjvu-!Nwe%%@mPAUvHO=L=hzBSn;Gp0 zdbKdb^Wh)J&={B8j$^ZuBGwZadLBHyptl$aUI;x@UUIbq4|NO;a+P&d=GcC|Q$BFC zoub+LdT8f{_9U;qpDXu$!}V3?O;me?9JRLUJ0RBGf_aGL$DR>wT^W7F-DQ-h6nuzc zIXZpzF=@VkWnl|~IuCViVv|(slzdwJ6C<@^EQG#6p`ZphcAK5J9BzgS!l9`{U>SU5 z+oyE|D?IgBA0{WV&D=pWX8pQ%LpybHYdw{3rNWkCDQU^yXQmq^$`k1^ za>EUWs4T&$rMbL>R3K^!JF*SVh(jM)dG|zdC4%&fl}yNq*&}Q}6VsF2&q^VY8?b%a zg~MmJ#G5PAqKS+q6;Ju>hRDcMA4ljVdiqCfk({v?JTCHqcn@SM0{=fiILi|{>2;-ChfrC= z;HLpe4>y+Z5<+0LAB(I1Ou9%3|NT42n!})w7eaAwagbN8c1$f_uFly(h`0KuRBAnK zlTICRkBRm#%si_#5}56d=$y1*f^MsK;E%ktV1VlCNsk@p`+!=dqFv6z@we>liAiU` z1ti|$BR(YGYm1~u2K_rOCf6Ev7Cznuaz;;#<~wh$b&%MSr+ zm}F3>PrQLpu4|D$08Mm~g%#mZpV{VzyQBzf>IS}-zC|~YHj8YfKdnc3>|*RFPnCG6 z*zhNrU2FvfvXW}MOwDM;xNhQ&ovePNATDJ*T}3yZeRAxiHXGAbw zMRwwS3QRMVRx*lrol<9YA_;}P-e+_Me~ifdc6^zH41qeOki&LQ)f7K&uv5plHztoJ zy1JOSF0OlM;EZtKr*bwXwjZXnfwG1|?wf=Kr_Nw2n}Nw<%=M!;?St{_(8q1lu<_^; z3fWFj=8Wznclm8Rrs2Rjv}|k|PC%a2Vcv=D!A;a2MTfE)J1m1yQaEz6b#l$b{R%IV z59%q@s6R1N-&~F$YqDwvntAq!MuTkGu~}w{?#4;<%OaQ}!ir|^?`5eL`j<1N`n*!E zQhOR-)1jtl$Q&wNer)u;eok7APcyne+P6*qUb-yC{mGKawMI_t2)T<_;e&|x+*#Jw zsrzYhft{|ji4hE=_k=x}T;^pO36riw4~<+o{i%@ zvNfJQ${?*@)kx`JZFh2x{^JhHL--U+2^57p`e8Z;7bfs7sc+xC%SEUn96|7GuAkXe zXzAM^m|LI$`{ux@I@b6uMNLnQkOtg2aqHbC^2lKGC7)FxR2Ta&u(o<0uulS(59J{5 z{5WvA*@|HQ4N~R?BS)hKL8^*NZthQuWBB1ood`2`YIkNsC$~*g z`_-==YnYAPR@%LglmRxZY?OvNT=jh_){%SH~ zi#Ny-NIZJf9UPD9aOlO~nDcGJh*0$i zZn`%Cy8R4!8^5>;!h3IH?Yyy?zv^3>$QC>9EZa|%nhU_hj132i5%T4j&c}S;|Mre= zGzXwpLZK@ldiD$Z^Lk0R$d?LoNERE_DS7Okl+Nw0imA-e3RBoWRTmkmE(bRBiZ6W& zYU3bm*Lqwvce-n%( zj;4k}%j7%@t{*P*%RTFpO&$h#7Whm&&RA@{&z}vHgxtQkg^=og`o2-bgX>E1S!}#t zEzUMK+}^yjH%y#ox(?KWwrR4YC$%71l}O{G32|oq)Y^T^&tsgA(~#fOjlOJeaphZ- zo94i8U~$^& z$I}nI2sGoyE;Kd{XniZDipN}Va}!$TgRgnWs#IHE9^@-vKsk*3Hsso8F_JspcFFhM zu#McwHBT$)gN(Rj*#TsN$(PrT1NF0wg?sgZ=PRO;lj9AyGknv;8p$6(*Lx*7xh$lt zu}U7-V)!}tMonTo`eKOJZsxiso+UcNY=JLBqBqQGG%#^asgys~ z9kKl7JTN6cv9o`hU%vd#`5C6A)r5Gcr+o>!QOU4u5f1kYw{mNNK%|v+T5wqZ?Ev$V z#a-q5Iqxkli7E!l;pz*hM$J?ApUMy7kwbPYQ{lx1OPz}l)m|LF`|r3XX;To) z-{i+X!1b79)6cSxJcTdk`2cTwX{X;@b39A|ZeMC#y;dMIK0%PgrXNa1Jp*+z@%;M6 z*R>fYPE|aV9A3@5+V>-(A}~#H_$bA?g2`hLr@s84nfa}4UdH+Z4NbQZcU#Aqf>*)n zHNsH@r?jf(Tvm6PWLbMKpkDL3b8(~rxDKA`WdYle5l4KvG?MPii8La7s>Nouzv zgcP$4nlGe|3!$lWzLdqnX%w ziB#g%6E`Isiv$KnnXK#;X7C7pd~$dgPCAi3{*gwdq@yolw%IR-#ETIu0^CPTMj!)+ zQBn{Ah3#FJ7ZoqrbSFDTV3q{d<_Ggo7$YOB+5^^oSCK3u(m6-R1RD1)EH+ze#3%IR>s8(+9B6x3H z?NA}flIJ!zog>-PQMuz_tvO-R(u|vR;up^qMi(=x<3!~|atDwmuHDkrjcx@RD)atf zt#B#L{u_J#I>jGLN1ez&(7-IqK8Gr8y}tPG0ulMo!PROrKH~;ay$cS5}@N^5dyZEY)Hxw!I(`F*ko%X^J zi=bdq$hxJF<&4XHBq7B5q;Ry|C2dt?y4VP}w1LOQPNlUadgyfZdi+bmPt1ZBol<`{ ztL{6_y?bm2Xc8iWUh)AxTPZcADbZswx7u$d%$?H&q9%gGTYPVbh@frf_?#l8`To*R zDlzUz$YDPbUr(VLZQ>ApElST$x)s$x#UNaUOtF&k~{qMJIHNz$9qh8Ebcs93AXolmwm7LlI z>Oak4< zAIKlcy|n(V+|eWA3Brbq_znjGJJy`*5w%a0hbq{B;hCaO-oHfQ;Nbuo8T@_ix7 zxvo%{@!uqqgn}!@zPu*+gN<@K%@7BI%`MgfoUAXEi3WJ*Shq|}M9nDV5lW+I+Lr(# z${{4U1c!5u7Ewm(M*U#>=&Gz={e;s24n+N$dGwzd%815TH zdil;Ae|!jYdg(;Ce1AGA9mwyv>4Psq8(jJvz@zp|O-oB1S3%I|T++FLZ~+_R5uKaE zmCgSs!Y4tm-9|O;kbe7g54E1H4&@i3<|zLvNRRTC07)aUbr9;tU45jA3bsW`OF(9H zr;>EFElW@*A)pA5ON^9nS2Aq#X=sEk7HESODJ?eVt5a@s7q@T3y#};ZcgsJx-=(oX zU#LEYNroxM1WSd-nDDdfxFB9b$L)RaqbTN*B9U(xqYj&s4V%VBZx5!K|1z|PzPD9# z3NKV6=<8qOQ=M*yKD*?uO2UnRITu=~y!BZVe2|>jS9C7Ql343vH)l6^Jh)#@y#DLn zqSm1^nvM|DyPdH@TK|5@m%^c5<;(V$TU^h#7bjZvDsWIx&RAnlC@G6VTWk4YJoYP85` zkL$Sb@dt*33>{ zGG5}-(<7Ejs*1zAjl{B;h$jkQpkKK7aGcKI5=KxK6^tMnP8h$Bxei=tAEp;fa=1@h z==*Y7*pl~4)ZCr7^xPdE?HXVE2eRCXM!Xf|6|IX`7@{evMxu`Pk6MeZDHIw|0?5xU zmI;uH(C==}-aNd=1k?SCa|xTk9r|WE<)qBkRh!#Ynp^BS5#Js!WLV3|&`?Tt=+a3^ zNiXiVBfDk6H4~tvv0iZU1!f3%UB6C#7v=D1+G^I_qR&1PYHSvGXiI;a_lm}}8`LlO zLk0FVs6UR)&d3b~>O|Ez7OS(4gSP57wp$ZVW(T{Sjm3D!#Z$}47b5Qsl_84;y;^;_ z_tKrgdA!05uTC=rx5Ng9G-M;r>pt9D_h!BYebA9XZFiR=?O4?r3(WeGjfvUoCSP$# zxtus8Fx}SWFK0moT}iJ<2B44_p`#Epw2oQsdK|c#eKcNvqkn+1*ySheY<%$|;{O8G zUg3CXEx9K*U@wp=YpOoHCOq?zToC}z5TSzXH**Oh`1O!)*D?_N20Im> zrWw(e^%*50S&C4Uy!UdRZ&aU&lAqvJ5;Afu;yJ(Mb<=uQfTy=g0M_DVS)!FptBOZQ zs-5h~3Aqf)^#O-)>V%>eo>0)2D!NA>`_p;gRFlVH*bPUj13~GkhKh;Wv{L}|$HByA zl`Z*WUCEs0pp?jFPTLEr*Xon@q>5)rU~neXnWTF-cF8Ds!#LCU_IsL{9ATu$yR-ghS4-B*29yoH|s?A$56us9wockdB5aeHX}# zD@Mm47lkhN(AIjob-H8#`z7%SaN09rDB+- zpagU&3#*_-B*B&h{|H7ejBIFX0z6#qUIqsT7sz3*1R^)+A{)=@;5D03OR=2zcCXhzp7%vdvZ zp9k5yp9M`wgXh$tDp`E!i1*h=6ey9lRx9(W8|Hp$ zQFF?T7Mu8-BO9ChdS#D2LEhiZLWFhKb`1(C>r9Xy6v>4BcrLddN@$P^42obyi5grF z6LuDx1c%64u*|B<#_`b~P4^D-U^qb{D1{cKZTrLd>>Mm-#d)3~w+~z``K=E-2eY4f$bi3ni}J7WDqa?WA4{Do z0<4VVcM8j^DF(j)6sl;VzfTD*gcLT!=jNRaWf=RJh4{w71Pzkh$N9!b1mx|vu^k#* zcpQQ1)Kl!QH)WE|UnHYbywyU@Nt3*uCe=@r8|avD;w+LY*0}XH(1e+Uf?{(!(gqvv z{QOzux;K^{s}h}`;VMN}OdsvA(cqp9#lwR#n&B?rq;FjJ=@-?=SwT`4{5YW_6{n>T zl`oy>XpIQRcF>e}hTE(C5!mIez4@q zs+f{!dORC6T|z++YQ!w9jaO}FZkO_v{|NX3m&V{Uh;`R^Fd%1p5~rQbn@b~;9B5Wj z{RJF=Xvq!z#{F7tQ<;*7MMUD=nsOIqupXLB2&+IGHvfy1-mx~1d=h|yX>@{(SOLfa zMq`?V1NCl_&vv>@6bmy54+5}__tr4Jv3(n07*us5o>u^p87U;v^m(4XM3Z+>jCl(r zBsD{K8t$n`hxjB$xAOft!t{M|Lw?qU=Kab1yBZ%q+J&vKgr-Tdxo5_^JM6Wz@YFsI zM3ZvZ<@q)gJA^h1&X&X>%{$)i-c7onq5(N-()UwDN8*ol_!L7Q_+P2#R*cEDm(mSd zBz8?2SUuKL__#FEhotbK!r7nO^S(|_b$zF{CbW*KJpM-JWgH@PGm`FCqUl@Fr1u{1yv%HDQ2~9jM6QwfTHH;0wsY;zGe^!_S zdF!XX8*~A#*+gH_@$k(13Qb<8goTBpqoTaQ;HG;<>YJK!KR)?C|GcisW+g7GpYUY% zENip%h#F}bDYoUTaZZ}+h6)|-T_LI=knk*1Gv!*_ASf!)voH(GLgbCY^ML)>{L^$8 z(}}kGVdTo$?rr%7=TPUa&6hC~BkBAhwyR6oc&eXW&f|m8u=~TF)P*!Z){bpq$&Zg& zmc~0#LR=#o*s@B#d~BaD z2tHyKpxHs9Iom4;0z7dHh%$@hPUEw2Y1e3#Y~oV2!enWM6_-g$WJi+6(d{G3TJcqw zj<4^^5X&4+Q&r+mCR>^{DgEc}D@14u^g@p~Gi}?Jn097#m|o`7o|7x!++uI%liib! zjv{-~u`*dfM{BdYYK{EP9RsnY`kH{l+t-duR{X{1>wEn8!9^SGtPXw_8AyD8)285lT?qFi~Kkf3=Z1`b zIQuL-A2EKoz)Z4#D)=QP`^X2r05N@^fvv;zH*;bis7agJCnR3~t2QH@xCeG*>VKBn zAes?-gDzh4j*p1Y9O4418eo-*bP9@0%{n^gafunQZk3qI^4kCybg`m7sAoY{Fqm}Y z=^nJOiC$|E8kS4Tl0Uj)hsZ&fYyaU)25^ZBXSo|um%mG*pkWHVH5>n^QPxLv3CFqy4XJ5_4| z^YTTcl@2z6X9e67r`z)r*OhL%X9tKtZPi4b1!z0esbWyQot^UVY?lUE*}bPJ6J+rp zQJv-&a7$P)A!#l4I~Y-#cE*L}rBotg3xfzC!O^&geS)hed2D{1gJ_|We{B0jeSWzY z&f?wDfR-4|{45(G0l~LO{?~<)rifU-71rrc$KBj?&XeNPJZfB6-wiZ0A_rqRSY;8; z8$mbusEM(RxWpHnqe6rcO4uI9Ed`eK7$}no+o`xn&nKl=NFd9=@e=7|bD2DEy(L-6 z+q~dBvRCFq!L=s*)Twd2uU`7G<7Gm&P*zs`!Q?ff%XaUeZliQ+AbMD_dI+ zdGUZ=EhG^OhHjA8Yq}W#wW72Sqy@%TnS_sf6)s}hfRNeBJS{TKSF=!VC!o&cJy4_P z@>tM{Yp-W4tGIjTTkY`WI%h3$mEcr(qgwWnKnt*_S^NS0g1rT{p9@sx%Fy`c zmR32j4nE(H>^00mV)ZgeV}2s!`v`4gIu|l2F1yj9$LS)RwIy~q#od}u4r}Tt;=!E2 z9gFL=G0*jcux+?FfeZMdWvQX~uF&5HB|_i_1<|`@_zvFmGqKQxF833ko8VS=!5Cz5 zD9R8T3kA95hBqMpVE;&aZMKM6!~4+l8oAk>i7B0~0A!JO{06`+{A5)S4^@$?vRb&d z$T=3xDqPn!(lC~jBad!C#2oN_Vqp|G2)u+8YMnD!j1al(_r1xb6C_ZCLhAtROV})i zU(Yu>)hfP%Ivy_&FQbM?EZNiOI*z0c)jsxUc^{AYDC4aXN?52?pYlTpwSkA`#UiGqq69RpY( zZYU^_z|TbeBa&sO0EuvUuLDhg{`<($fJB!trDYg6r?Rj|26q?{V2PfDWooeuD`z zpulvs84^nGy`dceFk+Wr` zF5za|Ml6`*7@oG}Mid<4L<%cPW|YIX4G)(Ru0Oo)+~*{knx~w5@e_UibaaC^Q5|Y2 zye1bv*7#fW3{)!zie`~;T?Mv^3}YrF7sr;Nm--R!S5_H5c{@r>k?UuG9)N`?b$ow7(HurkiQn0UB4kOh?FTJoq5pV&01XEhTSvUWT2AJ8S|+6sfAw87OyHD| zOH)&`Xog+i$MQ_!0P&JwZail>uq6}p9pu+v9-p;!L4|cP{J=l@Lo6R$$lqY2V;aaQ zZKpF?>ArLlBBeSCsxb#{35*aQqh1gwNajxxXYsn$XJNWy6QFedh&RyedG{+wwYKl! z>%0J~<(Ik(coZ#krTWP@z$$FDw28R zS&h5|yE>3J@<2Fmh$|E`+s{`KJ=;Mzu#V6dIi1u1S*BCVX~V6@h-`qT@vHXgIhQsb zAFkgrVhogW)Q#Tqg-))l3?Z6xk%X-+^YX`b6QFm>F}A(=ta**`ZwRc z=I}_=bI?E;h~}MUNoSUdZTLS;m{y-n&wK1#5ugwWf+}Qh z{1J@(0%MvqKoy22O^yrRKkMADJp=MUwDw)76XriT4JtsxMiq%Gj(_(HkY)jQ9*6|^ z{+%3wC#*|>n9jB|>Zi>gun9~AM7?2xVIe4S;J|9wBO+pd^bXfn)q>JdS{dl+DFpqM zBbG6MMYu8~9l@d7|G8=%4q`y? zJO2Qx8L?^c@f&o;D-{nEj^l0VFqVo6zzE;%%MDOj0NXFX&uV30v{5qWt5);0!|IH9 z8UfHkw0-3xz0I=`My+HiCs~@ywl-c8F~m=O{9d?#$Kc-EY<(~zrVwpZo?Y+LXLGUu zDI!*cUTkWH5Whw6cjC4*2jWJ!;D}lT-&WrqLwh!0h%vZpO9x)Zkc1r zZvZr;*00rC8Ea}LvPiX7yq4^41wv%hU3%0>`zjlY6wyfMv0g(A2Dmiq9v%ALGt86T zPJlavkH4^R!J&thxgQ1sBOrTNW{1IP$$xoi8bGHafYnA2JGzZg>4nB zgKoca{M9{qf#dLkx)jW-i8wY%xBP%_cRk2u0>V^|Xlqz4;iip7Zo`hgrmhLQ16#bc z1r9<^h(;7v5(cZaCmi2azt3n_VV6u7SNSvUN|7KfXb&VStd^#Wf1#?o15AXN-_{lY z4FjXAzaI(!1yEic9Yx?um;Kq@t{L32FKrELNxg+xLar;Zcn*i6s@#CHLc&F=-=~+w z0TolVh0;T>0lAuu8dFo_>Xty8A4zcsp8%D+-L4iiL+2%5CD>yop%aIJrV{o3Q2DEz z|3HlXDHnLKS^d?eyz&lx!)cks!-cBz^$}uyuq*|*KP@HQ-ne4OXHTKo)ll+E*9c=Hc<@TDRW8y=B{7dmJ{oDy9o~ z>p$Nd4^;E}19W?U(Q3i}1?kH2h1s+cbQF}4S~C;F_HWBk6=96q{TvYtLd37oA4HK9 zl9-&E!(cHS>DcJ>tNhvt23((o>nMlCm+JVlxt%rpn1DE!T4#*h#dU%GLTLXARe=^j zzNPBU;57)v#{@}SO}Txa!#SZ7jF}KnFUk`b8{XdVJZza`$rkmkEAC_1yy_n8#dk_G zu;|>T;5v^aGML8&j@slsBEM8ByLmxuaUptqZgxzxgQ{HFfn&)jML{M)h>${(pZeiT z2`D18KmjN9p!~|-dM9xQW*ccx(ms|E&*PndpdvL8ej-&;Jpf~7f{+n6B@q(EHk^M~ zLL49lkP$dV)%VpuZBmU=El50&tM{T9zqYyGLUxIA3kdFAOL}Q2-nRC44JhKW+*Ls! zK{;r|zT}9tmn}WRm_pYOr~7|H%jgD%zu|KG^A-KUp*8%q=xMUrJzPB$Oe@dLZUSUg z6gUqv>}hMWH?+8?CW*OLS}wtj=tCd|2{c)+`p;G3zHNnPi2@=C#MiZ{6G4X%9V>Zd zo<~Pi%Y2f9pMwh?5`A&b@cSPm6C%@-$0V59*kOwR+0 zJ=Rm80PkVyK-x*Mdk&w;ja-Q}qC<&FE4V7s-W73lWXqFH*Jw<-`m>Cxg8q6ghU8(H zjJio^l^|tecD9#W&8!)3qsY(+d7lYNhV>u{bcGB_D9V$3xx0h1t<lq9CP>L=v!MystfJ)ISnnho!W7IV|(&Ed~LC3E&!$KPZUVWp+VSawFU_%a{u- zrZxMFsCI!$+pH`_*uGL)m3U3k9m8Uhc>D098J{g#?CK=W?V@=3>*Kedcl-#Y0lh&YA^kGxQTDcXLS9)Px2`_1uvJ z3nM;d^5N_1>N3yyZ3zUch2wK$e5tn-)z>E@At5oHM!4)P!LnsvXo3YJ1qg+|-^Qjl zAlZoTkp*|sh!G9L@LA!vZ)m{Mq)xCOjgOTc01`nC#YxkJh2g%klJxHP=z#S%A%cVU zvv?a4wD2PEG_HD!Y{y&#tJ1s=(S(^~SADx<7Sz`=ab^R5V>tyUEQQ(9WrqJKSS!2* ze$;u(Zydx4#$Jpi)mOZ3ya&LZLg)@+g}!47s*vk(6CqC*3-Z3*U-i{KH*l5!k5oyp zL4F@hxReW&z{nxJ|B}5dU8MO5^AvNkj;PL7TRe$i>BFmR0pAX3pFvKR6P|dca96-s9Roy(TEh zdGsvdPG0c~Dm3Y`$%M2CXi%+)xu~3{5;LLH z!NO(;lBl}D;ALeksr_3#!Nr-NUGq!;a>L=q*}J`pP;42BAy)lk|JmNvRV~wpN1PRY z){mQ}e`pm~AasR+ZhiL9Y}p-2kbI$(fR^f!_H>KW{n<~gJDS9L?DB~aaDJrJ0*#Lr zR}|b@%KQK7BLHyzuRcPeui&up3leGL^`a?wXh3_43|mxL1H&ANMVM}0M6^b0Y>n99 zgVzgY_FEdQ(GeBC!iKqp$Z~dW%}@Q1MZP{EpK5#lB}YgavEwcS02ywSi;Kl$JS%C_fh4X67s&1hrGjAPnwIkPviu{?N5PYRl0 z+WyaeXaodNAtB$iL0#?b$2dj2;PwjH1y}%bCURuV6iO`Ig@c)|?{^{np9U#$G}t7> zXaz+KQ>5xfNf6mH*qN+|IQPvDaACo+DA?OgX6Xq>M$Qf28ChFZsujw9-b5Goj}2?d zS}B8l#&zX^l^`FMRcLxJk|_H?SqZJ-qekhEL93_x&vV{T`Ahpc5P`s?^Up@m-*eYz zApHZCt%h(FV-H&70AHNj4I30=jZ1F zj*fLJQf+k>YG+HN0u0)~ZHd_*+d*#Baasolh;4P^!})Io7Y*i1-JtwO(!5)ynE;z^ zS-ZI7oloDEHzw=3KhpW`s0El{Q?TRSK7P7~C;U2bH2;q68=`ID_0ZEC2Lwb`ue4lu zsVb%Z@q*Qq_-=Sa9}GU9n>1P?@!oy%&@A&AXBKAHPnmZoEJ|^@3t8hOB$$7$kS06$ z0+{V;R=exFWn(;Ko)?A2Vsj5M8%t~wc6XLKA`UjVD1$n{8@8DsSej5uS6fIv2hRp; z4nx=L4Xtk;k3Mr+odXqK3R|>6O`^0?C8zd%sMF?DVMIvA{KT@pR~i1Y_kcvL&~ZLR zWl@BpO!~hOfmxA_JR%~tIX2@{)KC>q-K>b+@ZO~%96@ctKl^!Y;w4iY2guV6wSBfQ z+a55J>ccRXAZvVnr29!OH|Ya?jf;zZ!c+^u_6~?G;e^V~m~Ll9UHL-oe2$NE=TumV zy)i1=FX~#CjuwGz4)K0+_)60LuBtn5*`2MJ{h{F&5200YOnN^a{BB^V|h)W*tJXO{{CLzSfHe+ zxv^0&pQQn&t6h^yXGgYEhP;yKPW+crq`_1vz*EG?f5iC{3OY3{4ZYy;cLSi*=U>9R zevsEs5K8hH^C+rm_kC>{G3w4?<9TPI@l)sUdFz?%PK!y#SA&1uh@iyg$Iw-KCC_Jc zf|sUly6>o4?V9Ctu|V0;3Lm3(h=UA1?0}Y4flh3p_R-Y8@KZKe)F=MH_g>nxlX$bc1d@EgpN@f> z%=LG(Jkg2ahKfnGePdf8i|hR?0G@}5q`i26O8h5Vrp0vCH_As$^Up10zidV+8325!ELrVv7Fh4wCO)HD}vvzaKs%9vmmq6 zD$M}R%#(Z!DAh$9crNTI2@ns1dyv53Hwt_)w#qKMU}C+ohkI$fk`+ zVBC$-0TFU+&|7{61s7p?y@U=`MNM_Gel`~;PRIumWn?laUpoh)mkUMIj+GqUIB`7` zIxXd())WroBR(>iumQiUIOll|?k)x72te~1b63#?YkYbP(oy49dc5C0gpk6vpfc+e6f znTHt-(-0q~iI4z37`$L*KZ~K`hpc)D0mrh-l#$ zZ-tJfft7w+s;t7V^4ic4&ZH zHx%s;=96D+Dq)r$ofEeg*hU4K1`jJ;rf*CTc728tYZ#ZgdYIJ(-+JyDq z6!pjH*T2_NWdQmZCNeBf<~#FuD>#>!KZ%||YAmpV$c?A~E_XK{$|*{sGcEz>LcSZ^ z$FAgc5)>Xy4S>{4>|>s6Xm`aY$YUK`*){6OtkVbH*I_{NPNXX0RID>|3J zb|A~?v3hePJF%raE}P$%FG3_k*utU|o{JoUlq3?15mH0&;^N{<9kW_YohclTsFz4t zgPy=}|7yaSkft_LsXF=(rV() zcR^Gr)fz2$pl$-hU$X}1Jl=d1Y&M5wkX@!n_OFu_TdF)z6l&Oo9xmdeGkZjj>{NmB zC1P&-W+)aP?udZHZQz0ecPUA)x4O_YYq=L!_Itg7j`;tPgbZrYp)lSbEBFpx{SQe9 zbOqkKx@Y81d-&9m=CCKtV5@KW-U{vvL0le$%$@_ZD(ofK7@m@Va-zlPR~i?~hn!9& z(YW|{6apQTY58EUu)`|SZIfV&{V)$V>Hsr72EC`N3(8z9-|ZiwjNadH{SDWvOydy> zXip;RB^JNtOU>@UQmMYZ56#}Tj>BZ+f2$Ja)ytqB@h<;xniVN-D~E&fgPdJkopYjO zTT^@DJDbp?sWjoTL|izCv8iYCMVxZ%8o1=#AKbNTPPYQ3z`Ig%WD@nPpnui>l$A_g zmzivb4_B59UuT&8Cj~I)_?wqN{r9}&f5SQ${*PD(X3+nDb!b**lcQIbi8>hj|7hEf^5lqlAQiG?fBG*;0NR)cxxnQM6%=e<@u5 zsbJ~NuPg(E5L6HJZ%R@2E2U8N-0Z znLlryp?|t*QIqu-31RpJ+c31x7P@^rpAxgXk!?MA?-L&@Jn^xM-@rz%#<)%E7V+x{ zHC4`-_kyQq-m{rq*JCA!i)|UJ`OQ|KqZi<(AQjtJE8an9{y53gWZXht2#-GWfIWy%n*|u(kcq!?Mcq}>f$=BO#Pw5Bj96#@yI-7FWeSN28t*DMi5D%I0J6$xF_{DQEG_ncY;mbAO#)4d! z?NcwWC`Z}C2)Y5W$#t2*RZA_f2;`cw*ukK3O=5lpy>vYnTJ&SHuXAFu&`OqZcjw@H zxihMjwa&m7I00Lr{>e!<*9&SeCD6(sAg@2f zxCA{==6Y)$^+q@C1XWg%v12KWx0AS@@|xxe9Vc3;(Cn=s%xbOkIUN3gLB3UW=M+A1 zwsIWbMhGu3Ut|ldh^(DbNvy3T3Y5I=Vu%i+SE<~HC&^tlyCBJ7<^muHN9pb;t#y|#CWxEzs8t8Jnk}r6=qXp>X?~2}D z-G>|AE?W0i-RZVJfn{R{#>~e%cunE&G4{SaxNk5ha}fg6V!yJ94HL^>*@PkcA|$4k z8olF(fwhf|s&&s(rbfWE>vlyars**TzZ8JAuP(k_I*RX=OR9)<)H!3+tydslJfU%L zx)5rE*AQRCK0S}q+~Z&q&yq024PTQM^sD{4&`8>QxalHUVL?Iv zx}W8`ZJKd0^;WZn0Db$wT{KlAOzWH24&!SpoR3jR5Ay_8lbPW<;}jVTExQ1blVs75 z5bD_=)%^x#cPvqINi#_aBPB3sDVPN2sS`a9V4L)PO=|qes&Q(Q zjxTW40C2&_MhIy5l&|Y;vhKar!51%ySDintGw$Nx?}Fs=S0kTSXW-vv^P z%Un|Y{kvP&ufkJqTE-|EE}zJmnNg^zslS$#lngdT{7K_n1bzwg{>@9;buzV??RvGr z8z0k^xAFSyt)D&dAnS3cU__Jnv`{1LZp!0Cf^dUB;^`WrjC%Sb8lAgbzaP_-F*7kS z094s~iw%rBW0|zqgC?v7-QWeq#p!k5Y2CQxNiDL|s;~B&# z)E3ucPy)K&&rdagXi7nBLRF8=Bvmbg!zvl~+3vQot7}d;PTA|~%(I%|GX`;H(1?hP zE;EE%2$Jw>^~xR^roZl}X9RW(rGm0z(7W@`TNP2K#1!{Dp9~daxwXYf%`9l*Gq93Dq-3n3d8Qt8f6&$8cXago1owc@gMl zBaV>{u5d&>Ns&;|_jnW1B+*z!7R)!Wq!ne2w|i9S^2jAlWymPwj*7?K^;({(6o(Nt z?uZd@P>>yKRbq!{bM_{j*aJ%?Jr(rKDMEThLRtRw^^?HzDyABbagWPyNF?w+73mmk zfkZO-QnbG3f($!~u$U zseImC-QC^BU@Egoud9OcCA|94%NA9@S02FGM7fH7pA~PT#^5 z7U7%uBx%fJC?XctbSh!rJY;fsg>+vz3As)=3;5~Jg8-}CEzW8za~mYs7<#E$662r7 zE@DxD{@$5#JA>!*+--+EQpBzOp$Rel{kfzgx{Z6VR)3Q$Tzo8?#X}V_QCduqWz#>h z7kF%@H{X*PlS2aog}s~C*nVFE(Elq6RB8(Ir9fgRAHxPnLQ>*?OJ0_l4%g*J3}UQ8 z79blRhx}C8Dc#_Y?YVC<7DEZlUdpG;wgc`jcLO&dq(n!+{u>91LKNUY0TZIqh_eR> zHJ&e;e~Pa#+D~}C*gzECSG0T3;&$mP2uIgXo~yQlXf>q4Fr~kAAYEn3dhznerMJ~S z(9Z*8y;bT+TFiY(7c+ML_Bu1FWd8OpAvX7PIB6Kdn2299_KY^x*H%R!7j`HM1~s@W zv|MwJp%^3IESNbQ%(DZac-#z;FI!E7dl<1YtZHEj%5v6Qs`Q8IjAJD_({CJikAtPn z{j(CanqgZ(VFKzA+2`n15i#|BDRnr9YRN?!G62&`v2SHXxBp7Hc--ap_1*cUEeg_o z0n8;FO}`hspluN(g(W}Tl<*sWOsqB?!My?WL8&yC=!`;8fbo;iVaP9+!M9xE!=z~e zEhn9~VdG4_tTAvqKM<-m`_^lj8;^-SVn{XkzY)Bu>=!MtKxEOxWn_HXGw-=lq$v~P z>%CTsy?B?Vf_izP2tMwG4`gk_fzH&J`Q;_u?}bc~`Lv=sVQzEhjluRK9u4XDAoTEt zfalZ@{}Y{Ae4YC}&V10IczFtgw0}O?)_4fszc?J<2x4KfH zA7|m`$M0wP61Z5UgDcZjd2Jr<%mH6ZaTx>+@%$M+s@SGSz;=z5m)+x;t+%xSKi7}~ zVHBEl=(9Ace~ZsMSt8aMpT=iLn}%jL8Hwkfom7O>dJ)`zjq-1OP_|CeKWZ)PL6hXW;zP1N>$V z$C^<7WS>u*%$pKo<;2&#Npwdml8XhfW2T^`i^Q)Zzbzz8>_{vnPqloVgwOOPh*?;P; z`9Wyx*;-;mMp06nF^@&OudqxH>pWyp*p~q1{OX&Qi9@y1n-7#ogR~=PNs&`uBN~2c zrlbc#AiknwVhx9?r)aIwUW{xRV16uP=quZ$B@oyz&H9P?lQfL(-8a>5hUqI4Wpt`{ zaU9ap(o_r#A*5dQynp%zqV!gO3RdEC((?K>NPOqWh>ZP*A-Rg)*CxX8PBa3c?X!_q zx|Ajx#Db*aXX{>VdpGC!O+1z zp6$C%0>X6T4BiTIwP&g0=i4gYuk11H#4M7ptl~(59SNww^hi zb_B{)D~S2|iTa}nXhSa{e_wtOB@+Fwu+W^5awo5_=s79|eUmC{>of2nu3kdHo*iu7 z!50oJ+iXvcmb`bl7{<=Cv1dMe+-Emcr{Q(kC)#hm{rtvsAFGzrR9lSwkQ0{igwI)3 za6~qTcMqj4+Xyf&mkorn&(9oJlh?dtIE;7AzV9cn4?YO0B6*uYA&%p>cz`xPx&*Hy zAq3jpJR@ze0Q0NHy~cxS&pr@#|Aa^iI7qyTK(m3ctT2(*_-R1T>}S|H8{UNgUnPE= zr|G$vc#MsKx3s+z;_gQf=7;l;gX7&@V}yyJ_{>Hjh&cw)8*!*P^VA0y$L)QjgD9Z} zQ%=(g3LJVWq_#UBC7>%kQw;F1-;K7nhKfWd1_O z7xteJOX*yd?KH+f{@gzb-ZI#0^-lP!JQXc*6do&P4-0Eh9>Zv6T2JCr*+CcXZKyN6 z==UM2+%qc5>VeXF-%w!L5`V9!``35^I@YNSlTr?<7?FhF!Ym#c);ys+tFgHI1uCD# z!l#f)7G#U)!ur-M=<;AesiX6{_i$pm1M1V^GY^H@T~0}~A*sRQEf@j24uN*xN8AE?Dqm&PgSq{UMUCF6tEJ1y zb^9t}EUrgaMNT0_Rxh14QFpM6E+Ju_G!e&yYx}DSbOh192n~AUwn>V3$`6>BF~IAC zm2~&|pD}mv9wffrFC&5rj4@fxkCF>9gw7ewbc(*S$9+Dus}Jy>a`U@gRD*5Q1Ctc-+AWv=%)eOA9(w+V|$~RyOsu z?hvtySkpzxNlrw1b^28_rTa9A+4dwnk<_%w`@~Scyd9b&H0tc1f-FYd&AB{$j}fX0 z0B+_>jm&d%a}uHFjDG?>o>c!UCbFi|=HYXsUy>ft>LUd9j1MuH@s~;np|R5RFV6(Q-4Hm)2KRF#=Ck0YSlM4blwLZud>=gx+$p!P$*jK_$cV z^(zfLTC4T%1|z7u1$*vmLdNGf4&4E{FbZJlkODo7ZcbCIqE6U<`b;4&*SUp(+#dJm zcBT03qcF{rU{4E=zBm16{{0m^WT54K5@dJDzhnFjS_unqWCq8Q;ZN8Dk2L6NP}0~n zz5hH9fcVWBg9hRk6%&)Q3WYw2F*bE_t-ADI!LM0mNxOS#!(C`;#81+WT>_o6Wy8#Bc$DT}zbE4Uwf&J(D6 z45s^2tIclBs!cBG)T`G_>|H9_FA7WB(QghJiMqOjpi#*4 zVgVw1gxA?)b^##3}7$-)gW@kh{Hk($~qN?yt zZiiC)!=w$H@BLfs2HpDZyT`FyNCa%rl9c5E{u35y;$sI#q0VlU^njVUOi7-S2ni78T)ayvJj0*uuHXuE~d3NhJL4{`*aM{OAB=t6+$w)4RjQfw@9;?zWx z=z#Be--FbB8PwgQr$DW#tccFk)Xa#bxfJalE3Wlt-_LeGXjM5AZ0(&`Ycx6ELqQbPIRT4Ji_5Bp*1$y7MeRtb%f7Uvb7B+C3C3l*0r;@mj;X= zLXM8KIos+4_fJ+CgYxnW9WrtCGrN22BCx;|ahq*-I9i!4re@_eHSaMb6*(6wKNDmT z{Yb4uf+?h=7ByA$E@TCe_?|;;&~!Ee6pIqw;f=XLcp=dE^)rn+MGV!-NJmZCMPnff zY6j}W{EBK@<>a^vr`jGXb!A1;u^+3;IC)AexyXkF3|D*C-Rpc-v~Z1?86-p>gav;l zCArJg;EN5!HKpO!JPxLc&2Xuqu5e zM@5r-AUxICub3dccyDR4uM~}k$jHH39g-ftpAal4v}+r9YY39X&wMN9I*azcgD7G{ z%kXV#rrH}A98O2hkc`h>F-cJ&C9Ui|B#A9|UXFscg7j%Z1)96T2kWOh@DBTy<1mFh zr6k3VYhrPx6jRUk9T{#`NLXG<9w1~inOHTsUnRmmlf1jiW!u2*s5WMSKb{ez!oZW$ z?N1I+eesP+40-{hwCfd|O&S$tF1G87)cYEpxW+7MdCRq2?EE^tIDDu$GBSC9j17vA zsYAkWfxjeOwNz90wFc~TjWEV}^Xtzq@0}e%KC^Wpd%vXgv+mrjR)4dChPXw))%ZT< zU0vQ1<4mha{(Lby#^fyo4}WY0!i}inLzvJP9B8sbK(&9#m7>R=<5-@{UG9f1B_sTx zQQI%rP-)|bdru*Y&RfM(hc4rFPb)#^d**f4ReQGQCr2Zm&EbK;_pm441hVwag{`-5 z72?BZ3V{g7wrkhflj*bVWW24-_SYABa}8U}0!Zt%&C?-=seZH%&Y`7B-6BR*!N+qR zsO=Ay`E_unvS7X6G@kmTP9be)_}*yxaA?RKzFZOKAW8P#-R3xq%S{+=m{axiML?5ubqeiS3&$H{z;Ki-r_v!IMHWi&>R$=nbEcLBZhMoG~{03qR>{*7Bu^X+u7$ zL3a@2_NGPtm&41RuNFe<@rZd>WWm9GOjK9iHEovLo)^4AAc|>W-x~}LX8K4GyswW+ za(hcAI>Lspjw;(2|b0-*)?m#a~n;z@Iq?ilQaLD9W&9 zH^K4~wNMdF=c~Y_ur(IDrzjCqvt}Q2fbQB-`n{zoUN!o_O8!XLdb26#Kvdred62#< zyw0Xu>-O^~_PeBL@Px{cb^D1sc=Mpu4v~PeV^86HLTN-# zCrf|6gpF8B=cQb~s^H)(JD(HwMa5H*eCKqz{;YQ+?D&(nZCsp7b@sGpy9YV)Hai5z z;8|ZFCOZOjs-?bRjpjP7)XODzyi4?8z zYlP3SclaSsAtd<-o=fUrtaLqb$Cje8Zn>#V#-FP;JS1S@G*k;7CHYE{*tvLKSb6SG zcHIdhUb!)n6Y-J0>)}B*;kb$Jvu?fHt54mymjY3245NF+y&pm~pB6v=L_v7#M4O;N zt&?oP*xjkd9{|@fD?A|hcI(}^wGNCYrZ*{hV4xWY2D0A@33-X%U>6M@rUO<^^{4=o zt$rIRt*QWxy0T{^Zc|mqJl)6j=LgrI`C45Oa^u`Vb|g=ZMYjyUHqUWT4H;bz6X>fw zLg=2*+b-QRgQlaMEC&0%GjfiPte}Zu>u0@dwr4#^Eh1pj;ijXSQDeo3XQyuBR?b4? zq5T$iy!0PuXP{3PTu>o%Hl_>0)5e3e?gLLxY?`bs-rgS2GcOqiBdsS*Qwknb@{`!S z!l3AUF>aI4?0~~{F-N!&tRS|SCN!Uz!pnGc#ekIL=}nKeK%8K z?QoMJM3st2QHf*b8t62cL?9LAwRTN{JJd0GJk3Js7K-ns^J z>!eo?T`H=oD_`g${(B~X7AJ2x)A4kiBEbRzY(K$*zw>F2@ndYZwem4e?1jUgB`bc9 zL7n8>8-=$HS5?@PhgDzjEu9CcFII&;1wm+*@f18b0|s1svMQXKG`wjqIK1d_KSH=i zW}W-G5(aW_OWCUGwsUxR592Jh1j_SCDBL|y$)=nQ0$X3?q-LE=bHkj zm!c%2BVP;=Z*P87#4YFZ0(o-au(dvoK66mlbNBAE%Nf7lV`wA&W|z|-kKdzEDkr#| zS6kIx<*yj?ernK<@J;nTc)mZ&Eqd#)Fm=}spS)Oh$YgHINXYE{IP z09An6hEgBrgiDK_^8AA6zN7_{$hV9($lMa}3Qwbgl3U$YK4X}-eYJWyNWFR2J8-Qh z#(f?*rvs$QN#UR7&sY+O+D-H? zN^ZmS19)tf!yJsv4=wC#%zN=o~WeTWK{6V3`Q=GO!-TOkBvLpWYPpEVVb zbU?wsJBKqrAb3NdV4;x2)&>Q$y{Y?==1A)uQZu|KNE(!j5U+^S06B*^>?P9HX7O5jdN$q4O0s9wi}n_nXJ~pO+0Xhe=wV;V;~3G@5{A*9Oav9 ze{Q^UUfP55@xhSu*YjJFE)T?;kMA)!`<>fY@4T^HP4N8eVt2aNn); z;U>t>Z^wRTM30mX@fmF|K5(CPGyt&bBFR&$zIt5MzcdZ@&}&1%#A!;pHJ!@utGrO7 zzIiwqAQ!?(`zZ;l#M0aDh`S*7xXF=#i3tlqX0ys{@WUP&5uBw?GeO@jnoo4UeWSX& zKsm~zWVf`P-)%+nZm*|iEfg{7)Ub!|7}z=i0f$kyRR^9@V&4LKv+dTN1Evt1h=)jq z2+XMeDwNadr-bBIBg$K|A%n$lDk-#8IxTOz_j7Y(oprL^>vUr!gsl{PhCL?h(JZRl zS@H9z5&EDWXhMt`bjzNQb3^oh#KMshJ>-Q1^YuetHzynR2!Gw3qCeNSJvkx{f!`yM z{fde=SmH=KV`>fF&x`*Fp)I+(D5KE<3Q;#aplk08Yo=TFN{C#$AFTomCKSoo$UESi~E zYAUldncnq!_;!vGb^T-$@j4ce@%HS!X8fnv_(9XhCaaB!iD-}#W2$l*bSB*SDes%* zms&&YBWq<)dEpOyM2K_o<6?#NUoIz$<&5WH1-5rA12$F)9QQQe_ZZOO;_ipf#!gvt zxX5aY57)M-DLH&fctuOeGVY2tLJk}cxOA_wN#WAeMxu_oI7Pt!_HZ`)JS=#@rtv** z7-QQIER^dc55s1}m%ZDlr1ia^C_gwxX(7SWIhR@Y>%~G)RjGR0CrT8oD~r@gn2*Gv$6M(#Ii@{AsZmm?{-G2VwdU zB2f6k`OyG$Pc1G=DO`-~Ir&|-i;yNh;oaEzd)j*^^R4AmP zb7DtUmaItHGKX`%(g`)LkonptHhxh~u4$_?2}wngotW#8K^AyPpnm>YxFd^x8;VZx9OojH zPKA53ypZP-5|3eH+(2w_oD-xxhU2CC?b;b5iRtT3<3{b&@}w>mK|v7Tis0m=RM#%b zXa)&+NJ0yCUAgP6+FeMU%?S-1O-DYc?(D|e0V3nG8|_?6V=20(h zZV>F;3&vWM+Y_oDsx-dl-I|{)ph`t43z$!IlrQC?G|)saP1JqSR2kQsHJLbi=DQ`s;&M6xT zB3dbyC+Ss3`0=I-lsFcOKJ(_PQk18)WbZ`8f^|k=k*4gju{M591<9<#AA(*5UY-zZ z%C^b^gE&e$gQR(Mg(eZ+4@pY-(^YIuvE|j@%p=*^TJ>@mX~f2=%}=SB6WA;Dpy7KA z%FAF%&v+0s>&15$kn4ygg?gCcVm=^i=oVEdQ1N>*EFlXU8oT5f5X3zvN8RO1LSWx> zZ^WMlT{u`yJBH15AiP60S|8Pm&c7DwQk!N1cGxJ3*!U7d6rP|v`?@tozKjWnMd{?_ zn$$U}-sh`XUw*-h1;H{H_5-`ZF6SY2&lA6RS|CTQikNd|c0iWLInMsmBvixDg~O3M zS-GCC1V<4fHZo~(#Sp?!FTcAzR_1&XIc`tB{16Q7>p%#a37eabrlfAKm_*toP*5byMsKj`bh_{OkiWyb8 z_c81p7b)+f=J9A|HVgX)QRp>isAzmkNGNmXakHawi9_hkIr`8%XSR^?7}~exGSTID z<>G=qS)Z4E;H`{^L@g3coxxPiT%sbL~fz_{6)WPs{qwjpdbs$AjsDb**6opf!SxrF-S8` zfw)E&I3yN9h*ekedsBt=a~L zTQh1mk{6vEq-$7P(}25Oh2CFM!cyv|yp3U?lo~Qs9^MZITDx)X@@VKV4fzzBhi#Lyh-ZmhPlop;mu`6 zq#T*QC_Y0V-VX4BAIl%gHXW7_62{w__y|9+UMfe{QjH}Agx$=<71Bi?4tnO~)hRJ9 zz9k1f-W&jBc*G)kOGXS!BZd0W%W)UZ$V z{l-_#zk%6$?#^CERtI?jbz%Qa?OY}IYH?f-tu}N;oN_HuoS|F5t36gPxFARo*Sipg z!B}FflT5Xw71Q4VmW_ZY7}n9Zm0xlmx}QX#xxpL~sbb;*4-sNY*$y`;_fjo;UKo=H z*XKx;55AUmsrr>N@xunN3&q$NY~RS_B#NRU4H7?%qUDR67({-muU1YfLjm%&_quFi z(0WHj-~ffUU#+T`5Te|;i^V#UERVPHRK}ePwY%F9(yR#p+inw8DiJR}S(bF?Cq2hIMXrgo93!08; zQxkna{KDAIgly`4kq6y^?tr(D?@?E-e2pX#9e$vwnAeB`3d&>9Wrn7bSNQDxIBpQw zfLNSZ)NKamg92xO(Eo#DUs#C6aHh|46?D+|gtFSv4$eeUZ6-hQ=7(*sEB$i;D zVy-jdy~6ok`A5)D=nv(Bg+;)ow?z`c-G!$NzGZxP=?dGVIk-dD@y@SlL#_N@v)m|G) zXD<_cXH&OY+b&$@HxN-x(k|-32&r{^A}ZVR?ZX!4KLc5}lW-kPa&d%=rF;u2h+4S| zlEc#OdR&=rsxm?^VuGCc5>gr=))&}}hl`P9?+dO;;d2?^kD!WgKS(alfE9&;E1S^rvwjzLVy2~96yDhJ&af_%ap-DBG))F%I zV@pQ5)-{a!eL`JP*wKhU0R`aV7euH_l*`}x5mF)zNI&B3u# z&PJZ*h<_guXFiQlRinY-j+hRlcJn(;$X1<+x@co6U`nu~$cn#rcnAjr1H*yvRY2H! z84~dN>*@-GB4J^vwT1gBzwmUA*2_iw4AmOgX3nl128(5_c<@Y3G}Ye5*iSwTr$jyQ z7SF4W0Zw)8g4jSu8!H`^hWQGzF0dSs9skF^1Q{c~r$LeaC~NL&(WTZ$5WenkiooIeZ>@V9n3t5}GE!+jNl- zB~Qn-7B+@MM4Ct#GT{i0SKa(1IXcz2hO=GzfpbenBI&b8hFpb(AdB~bstV^!Bb!iU z`hyIv_XQgn0#3`&fk09OI1iCYHoovhsja4EowvNfkG_O*m@IQ|QZp0Dp)a;gMLthv z+1`*9n)LwyYtAUtsc5oza|a6=!huSgB|l%{A8_QS`k(VsMRm6>*}03C)8VzM=Du zQv@rOCMC^t7M(TEU^P)wD^lg>4g4;)p>3-Z{<6cNDXL6lYiz;ct84J{HN8yRsE?l$ zL#@^&_eh!EW#5|&i0eIa{8xqJqkWks4aF(%5^9um#q2Q2 z%4)b#O!me1P!49P+33M6&x)h_YCL*;En7+-pb8r*cc!c-KMWpeCP0>jI*NXm%AtFS zbCjt6<^f%#d3UG(7v}B62IWDR#kTZ<{|ns8(*x+RG8_@n$Zslx9}Kpz9|p2f$vN|1 zpjn_E8v7{7GB8q7MHjH4a$!1H3w^u+dC-29u4O-oz@Ys!oQ!1n7lO9T_UnR`I93~~ zM)kS7ACQ8Qala{)GQj_bee@3vWQGV-jGFlV{{DIg{$zEwl}ge3i&2|@ABMs2O5{I4 zr?ess+JQB6zJBxpBSfH0DypEgZo>WBCO=G2iYmV+cO!CeHd_Qnh)>gw5%~XhVut>6ySMttV%1jK=xqu84o`a8Unca8T5o8Fad6!4s2J_kLYse zbU+m|e81XxZo7qW?g0~{lcMz4+XxPXflONGT#ZrJ@-hJ+d4>G74TI4D;`x6o)(zE* zTD*pti1~w$p1~iuB=MBhrlYtL($ayvo;RIaL-D~=u?Y#_fJ`oAfY1|OwL;V6Vt0@d zsNCOgNK2r6q@r_t1!}`_Q`Vn-HWna-b)y}J}MEOpuhKo<{K{Nv(KtE;P#RSNVL%z%K(IRRn`)RF33 zVtH~S?%%tG3&emh(|aL*mOoJklumd`uQn(?(1GGn#;5 zGC4Wf4J-5$iHIos0WbuHLA!%O)+dVb?G)WnGX67IRslYT;%X)I?5{?0k>s@AgkGc( zyC`yC{URt~m;04xXu&JApbl>;^3q0n7mhb?K-VNi1eLScXVZa``BM1XT@^#LBc{lk?-quVKAo#l@&V#6CUKwT4Zo%(QL2`ZXN zhLBZF!G?rw=wltfhKv7$46r-(51}FTM*G}qcR&IEs|nOZ=;J`|1&+~}7C^sl<-dbI zCVdZe}5bX z|D-BVPKSZ{`3ZFU$Q`%=|FxVH2=hQVqm9;%pkxgvI#w-2f#pa>z~BGKQ|MPC4XT|c z38}w|ys1^>z&xqU!1_yE0_J4_Mo>svV$aW7g4&93jkqR|EzvYj0#`KQXv}}lBa@Ti zH+ksCB>1=Izym|NlZpu02=1Rz@X!1Xv`YZs2(3T literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/fill-switch-before.png b/contribs/gnopls/doc/assets/fill-switch-before.png new file mode 100644 index 0000000000000000000000000000000000000000..f25af03b9c8424ae2313e61838e31a187e7c8cc3 GIT binary patch literal 15683 zcmbumWmud|(>91DxCRYAxH}{Shr!*Q;BJGvy9Edm9D=($1PC&?yE{RHJM1L){eI80 z@9zHEnPcYatGlbJy}Hioj!=}BKt+0w1O)|!DkUkV37thHF3yKhHoJ6WX6)pa!^p7)KF0V!B9{SkSPBHC@2?JD5%5FP*A+7P*8Xd8Ldiu zkOVtZO(`=uIVgHa7y$|f8Vd>*5`u<&p~5Vo;Qof8pdg$eAGm~2D0s*d3-Z&=f&Nd< z9GL&f30;;0`#)i*kUvDiDxy+Skf(|<$kf!%$->@Q#k{u~3JS*7QdQGgQ%;uG*xr`e z(8S)zl-b?Z;SUKEpF1xkXlv?hNak*9W9P){&JXyTgBKG16U_o3`Bd=W@cq(1qdLKk&*F%Ow4$d#l-(5hrICvES#Mkcv)E7+}xPmIGF80<}7R&v9 zI_RI?{~HVGegPytmj5%91dtrw|1gAt`nW75CamfXeWC~Nj?;(xGVLWvMIMZe&~ZB< z0xwJnlcx}gZHYvmN1GQepagwR7YP%HfGY+EETBb@#L0h?4<{+Hc z%kR(wFh9fLLyPD1Q*@cAZL?safB7fEe=zd(EhNDo@@mBgBdgB!f^`l>@J|GU(Ed-t z|4%|iDic#nGiaX2*6POU%6l3c8x#Wrx8}A_M?^>EulN)P18}M71iu)oBnyMuXbxwt zX&WNTnn=r!iKS;tY(uE$)LGqJ=&$QbR(gkORWr9jT)D+RG*O`?N~B`5upiu|>@FfK z`XX<(*>#ucCp~GfTM+3+U>2hyq^j>xR*Y2q-lMqOJlUd3|LF3}De&auVVYJx*D8}D z4;(;~`)pzoTwYFu;^W?_?$;lfZN^U!Qmu0X9;|p`cvn>Q4Esy!T$7DisEl;Ct2LIw ztb92rM=$++xmF|T_{8>?+g9nAs*VDOr)-t=TSu4Z{*0O@$O@%~jRCEE`dOw22t;q~QL^zYSYc~pLMBJ;Y~ zL~;n{YW{d}%P%rdIWXCK*I!)^?)NfS3a@>_+EvR|@;ItE9LQhITRtGKwqF-vakQ{L z5~#JF#^#puG0k-n^b@na*K5j4U;tOB(tU5PZyoMz{Aqr<&Gu`u_0B4NbN()Vt)(`7 zKV<=MRk(Tbm0O+J7!E%G0Ecp+mztjTb7}-j@4c(Ioi8(*u0U|}jC;OWL8%Tm2U}2t zLyINSEC<%Bq->0wDdE>jV^eg6qKB_D4A(>}lT+V6+A!H(A0E)N^6-RYW^#q1UZ(V| z`?kSvZcc_rN3-O)U%~qmtl7j6vEqsP=?Z%;{icN> zQ>jyf(65=ts>CE|K}U1BXGMBfnMwn2E#)Q2baMu=^-na>GR1@{gr_1LS<)v`p3q= zJ3+dzf>Q%8TNF?DdZi=pW}8r1b2GE;O~b0POS;X^=@A^=t@fdHo%{#`o~V&meWPw? zDKd^%k8133V3VaZJu(pa;P7B*O$2v4iQLlXo{LddE)7gkEARRlrWcOKFk`T+jJU5I zbH<_9@K#_uU;C$KM@O~H0lAb>*p?xr%S*#OO^L(&thVS0H^G#AVFEFu7+irOpvj~l zV$%|JlFq38WK{KyQYP}RrnTSDCtp|`9^HC$zcHW1={+Wc+X*7d37a+->Jlm21Iq!! zb529s4g@nQvl(4nJS_*gVeFRWG3#Dh2~@=9tFgIyh~)F1^`+^NKo_4^6#0*Ab!8v6U;A!}j|A@oUGiCrdE%bBeKjEs{E-?Ce4 zefXZekIG@bnbo=pC&Lt9JoCBZP4_^XMvkE#DT+N0ih{E*3d2q1*HY|yHm?mvll|R| z6b`{{3-J2E&NOB$_wJ9ua?Ki4!KZ3c3W>L$gX9f(Xd+}2Cnd9}W5@fZEb3hC%=u+&1uNk6y4TK5h;8y8{+op26`PvA|(&J)H zG4BQ(W>~Kr`J%Z%izr6i>=u>JMwZO`#kCa1oA*~19eO&RbBV$48`PRTgQ~g<{BE&Y z4KepD_R`T6c#Orw@UB%vloUcsN-&qWWJKg+2`(;DE3UeF7o&xe0G~0m`U*;PX|-Ck z!0!~!FcF(jNM_vp{Nnt20ukcGZo zCNm!Cb-WKanwQb$Bu*~@Uk$l{ypq}je4eKgYX#yZL8@?-M9=SRNj*`rDtI@yUQT`= zE#bX^iK@!-lRcyYuJ-0DG!1b+YtB0cW=0WiQS{7oW1EDyycf`~P~kg28Qyld?2-B= z=cB4x+3^GEp5Pl;MiyF2XRb3Vq5Sw$Q)o$MjAXX(g`l}BQ+)&5K=M( z6!*xArRSN6_^lEt-Ed;xSDN)jFVk6&4n6hd{gZ?O9UGqM(E(t<#LhD0YcuWj9e_FV z5{t(Sc*tePlo+2fPN&rjGO5z99U321dY9BH@&?O;&N8967%3hq;yhE)KJAg*@nB8g z?qXvs4#;n7tynkm#hLikl@>fEtgl>~s=y9ZH;G1RBs;3C&VkxHg%J!$WEVfAIC8Cb zcy1X!1;3F@aNJE>nN~P#`P^XwOwKmbIh1}M(JEgW7Eh=IoR|2nX`fIpYNy%L&5S&! z{>@@%P^z@dK==7~}@B8%mPKa?N*fNA<8H(~}F^s;d=UR3>dC%qi5qcpMw3 z8H{>^`HNAJ--tSUdB3-awOmgjCA-Ro4K(Q|-zdSG!4)Ex{&9|j`?bA>ApHQXCudqp zhBmE*nJ-m~-#J7SG54zpP z9;R>aou%+=+z!7`sg-xms0}4W8dYV4i6`@Zlbh2am;%dO^SIsA9;T(_Rx-=WGWV+3 zPppR`2XfOGU3Jm6zm!C(&Pr!M_GkfC!a}dBwF#@cQAK8b?0JPtkM$J}Q9SP`Ofs1} zGZ}Q7@^FHiP+D3FjU3G}J?L%g3HU^)-Kunho&0Qoo< zY19e~ENnw$re|5D&PbIP=F6l8sO`Rwx@3E0z~I!WR~Ki~805$9*{vE=PdZLo1Gj=}exeFWsaJ=x%-g&G`? zr`W9Wn-R^W6#(sejC&%T!^eIn<0Ae=K0mwL?^e#`5-Q$PlX93Q@m&2bjq-qv8N=1+ z1Xt>K?1#yt0EuaXRlj8uUOT-c^pvYseWc<^gSkCRQGfhXB)jBrDtZ<isQ|^4W+ZUFw%uz;3t1BvU5~<@`71l(anBp~}v4Opwv`CST5{G{$ep z>mD4R=Udq>-b7d;X#p+qM1hospEx~j-@a!~Ezl~z#Fr;Ut}3c)CG6G%Gq<-)u5V`b&@ zT$Xc_veS*~5>zw2{AU0nC zMzL#c>CC~Yu&D%Q)=EnV+Y7IjBGnmuJA6@>-l4GnUdn?DcfTX1Y+{D`y*X!GYd`+V zDgp`f8%^f+DNBMRdzJBtkLj4j*9fdyvk!vMZ~t0-0cmkUyF|BKuTSp5|Js-6P!JOl zy$iUb^4BWd5SH*CJQV)X7ibGTD4IBNL}&0kKMKYV=(mN(ww2f$+nASJ3zO#K+Hnd8J z^JC`ST6p91l5s;~aWh2-ur zf*P+;KWAdmq>6I#_UcHJbUHbe&Crs(A08b5qot*l#9JYI9xH_lv3qGFgpx+IuC1&Y z!z0)F6~$E3^sX)akjwUxy@ksQ<>jPwbToes>E04e`O8Wy-b84Gkw9I zQ$r6AIpO)~BGusyGu4~zlj$=6H3M!Up@NVegN6$M4P`tAtk}haF*_y(XT9jIFK9PW<(N}LuNlOp?IVo}O zbAZl1{PipF( zs2X6-F{(^m_t7ZOZ(~uW=v}U%2d#Lu_DW{NK@7}^FpBGlZy`8Otv5jxDtbQj)%9iH zT~X)k=g=UgDC2p9Nk`Hdwq}~PQTXj=8}wzRt&sk`!aXdBo>Z}JwHp0jX^JFzq;g6Z zNqkdmT--RVxE80AvQ-T82;vqS#}VQ#go8&FGxIC73{Zd#&E^l6+ zclEv%W)JYj2z`E(m>EDOp#v3-KDcsYO{rzqlfk6%I5TJ1X3n%~CYjm3#5X;Jr z(WxXkyVt?{{c?leYmwW9^5iAG!AQ74s}uzS!`;z^Xd*ldY0r&Q z+lJBr?6b~|(ycIJEmTC8`fCGjoU1!r#bHyh-*@JVp9$KLo@fW3G-~Fnwic3EFg<=T zKDA!Ib5gqeo$WUN`4>fnX~n?$Jxn1$5nwTI*1L)VTpwIAk)z~v-%AIN5BGJt3@gsJ zAugfeZf1Dq8y4wH%7piAo(a6m=9U5m;o_w0TvTjR=)_7g-SgBQ$HjfNwPWh^;>L_Q zdf05Ju$R;WFeD&1v5@nBB7W|Z;KQi}Fq8*rzz6q*%H$G&bKNOr#+q~3fbd#ecIVc-XdOE@Y9>~v9FdW;!V|_mQ2A;SR*VZ*_x2eqys+w{R zxpm!hwXoO^;%~Y=tRPG;d0^H+Z?gMPnd0E~5AW2D#QIfwe`ABuO| zq=Xww6{QWO#r8VLI`_feTAHF)nPrKFMLXiA!t>)1{^54NU9N@A>v zAfBe)T~q@w5$P`mi{j|kYPi3$x5iHhSy&xbmcXJs+Xt)rjS!QFt-fW>B|g)R8h|O5nVm0nL9{xwU;vg$#yakW*$?p;C$; zi@76IZph2Mc$e}T{+2iI1}rB*8b%q>uIp^45qmrfV9+p>ir3}$6*~Pzv$aLgVS@9{ zSF6HY57PmlKNn4AiB<4E>+n@`ay9H0g9;>&69O=qO zAJRVctSk_b)8*1Krr9wypy9E@G++kMlQY1a#%;e@aBC%baG|}wdb&U&JfB@HZ7MA` z@H`YCbVU2PgqEZL=4_f-YJ0(9;K2VI&bLrbaisT2L$6agSdy$WkhS4=1(C&CEaK` z6BSXUK0bX%HrxP|2g)1mUnT_^94)jiQj(zJ5xdXJP;3PB^C`^yJ|1GfHH<|j28$>% zY=|IO`WsLKy_0}6KN3A1)~v4XB-_sV#ue(gmD!-PtA3+;R>QXjXT&h-Bj39zWoB0g zCMu$A-8&R(=BmV_GMEmM$w;^v)gl+o0R5l8mt%_u=8^`se=nIt(HY0ur=q6IJ|Q6LPV8 zMYI^kyDuyz7(nz*@jPD6Zhg!_949`?*1S6~KX6K={Aa1WB?+tQSc%W$p?h$~2mkUJ zljg?;MK=N*PvJuTv<9_(u2XKI%tA5Q)JX5NyIZw=FN9hSvHoa+ASRtgFcT}QqWQbc zl1=vY;m&|D(9p=}cw!}g38KM!zzw<6Q8s%4SArZWsJ9oGVa@D z;k~<42agSRH!QGzj1yDX$M(*ZBxdj;NEPlTz{awr$SS9+;8hP%O+1b1O|Knlwo}L} zLuj-h@0q4^5883QBlzgC4%BoRM|D@n$gj#^@v>de)zViJOo=;AcCz}Z?@cP_h0-%K z`)MY3cSb@S1W5R9-#z$~kV4-eRv>XNXZ6dZ_GC_rT?d|VcT=f#0bNh zQ&UsjjSOp^tMk1wb#32LtN!sR8}ws$W_&9HxyGVb2T*O=?Cp+dF{Ls>z??9bX7&lT z1D=dV5T2$NW^+i=P8e-O?5GgJ!$Mk&p2XoIZxQyM0|Mcw*JgJaSqW)Il2bDek*Hxn4fvO5}J2E^3mw-$b}av!e&IpQF{E zLx;EXi_;w(?EM2jRW4+VpxC_r=0frM<#Xk0)i@&+5fj{D&Hs)0<@*dnmcqa;e|E$E z{O0QEEATsS42I_bQ}N>3O%5^0R$F78c`CCkxYGKN2=!y6@95)J+@%+1-K=F=U4B?d zCF-Z)_Om#9^~CpDQ^UTmngWu1YaZAZ6y$iuv7y$G{SbO{ZoS}l7(W0O|K+gGTd7tqg5`@W219`K2Vu0)AY)hMVMgn>@rm{9)J zdnxWWa_^P83L9u(8w{Q$T`kp;kP^Hc;n1u(b=t+~IJ)lXxv3g9jzsZ3UtnNg?Qh{) zJG(Q`{H9ILcfw0en!$W&F6WEOpi!whzYhm7?6Aod3zBEAi(zCv$MHIrccT=7q5QI{ z`$`QWdCMxXF;yi|$>?^UXh$-2V`u3s5!sAR5`VWP1DdL^wc=W5!1T!I4>}r34Lcpq z2RI8l3mVO~o`jP7p_x0pjM}0FalyrgX<;ZaN^hJGjwC~eqrQ0Pf$nZFiQ;&b4D+UD z=K{c@_$lm&sJ1>KURt{s6djV7J+Fg(;}|S?ltEa|KNyrWyfOyeW=U2w7E_3k!i=Wny2V1?Z||lns1_;zq+6Dw=yto2Ds*(+hje z`ZwbcW_8od1d1TuV<|&p*lu_I!_TTdfhPyns>_vgF{{Bd3{LE z$cUn@u6}!Nc{xE4a46h0RSP=1-+tAYb<%5|eI$jT0khA3L*8$rg|5lFvJ0yOY+81g zVuV7zLtFFcdf)sDO5DN(9LCW7z%%dlVgJYH&FAn3hj2*wst^uYMIpcfI&{!pIXB~w zsL%lZy;XrJ;o(-9?L9gPySI3>5DtY<_dA!PxsZ?$AWqZBC>snHX~6NZO>b{683)I7 zPWO9G^Vz9!xHz~S;~~k}$Canh5`D1c1VS?wg`x}whJ6%t$2}DlRY7tFTQ)U?Vz7Tl z2R{)Jk;8NADbFFc<)J*m&f9j3gt)$=-T~AgJwq#PsB=@Kzu*HbbZv2J@eou*6sacM zf&CA5{Wn2iX<-fltz94|w_&3)>n8udp8Z)0MY>x0=F2pf$0@;3?d$J*xc~a;t#DwH z0A=A8YS&Uf7i&w^{J6Yp|F+GFDI7geJ=+GZGWgSWOU=CFW|4(A-z{@oRvW}~`^U*& zjDw&WS>D?Ow|SiNP`pxs|0!5pwX{VW`a?G5zjFI9SuHY?rbfSFimCr{_p9Jhx2MBc ztgrd^d;!FX5G7O~o4bdq78SsAS~@WhT6Tld%mrcIY>qIoM1U*4VMnG&H#{U;UIQ2$ z=_C~06~GTzitvw$l8LycJ3l`M4=J~7oZ--~wz)~XzC1e~%`uhh(BSIVLXh8Z|85f? za)1u!kdSW{+r`B~ZE3%CuiQKcF3bBus^#Lx2MDCya(6v%;CQ~(w*_?DUFa8YFaur^ z>Y@uivC_VF1;B>K#&$AnZ^&E0!@P~3m2g64&C9-1ZBl}c&2X$DmOR&Dhq=Xv_AJu2 zv0)q;9o;-VMdRS$h9vfS~aoaA1H!Y=ZO%=$< zs#~dkP{KjL7gKMGOCtxALNkXD815VTM%b$9BG?6C(jo`cnU5QdXYnh?gdfgo7=*;f zV=gsV7eVYtWj0LlL~1!97PJoUd-kzx!MMOc7%Q4J*nv5c7ke`wfz60WVY zv$#-HLh0cu#bDIw4?6W00cN9V-AhaFH7a$xf4sg#MMtA7(!8?5n36+$Z6~}5ae5L% zR%yeKah21GL54@xvKUUAk&$7-Bc{q>KK9il=SpTr$fVxz=Uc_hxeA@(ydN(QGFY0B z%2Aal?rr`I6Yib*E`jdu;UVQSRP;b8=0DW78eZ(}?QP=48T$LTgUfQhj7R;+N{dP( zhuH|ei=e8!3oHywwNW2Z(0Y&DN%af~96b)Irq|~!4pTTQT$t;l`4khO??|YqNkN4C zp7oTaGc&5?T6J&Ny)R=@6~+>KMY}O}MDmTX02EjX3JNVnq_wq@;U<#Iy3J&_L+CA} zgAYrMw&}@7aYGRwN)!rw((06BaTub)9i|KAG#jjv>UY9*N&TK23q001Uv!hWqC z(ZImKa+i{tdg#8YzMgYoX-S$NKn$P)ByVMd$nbW~w+CFZ2!BZcfhEJmw!5i}>{zv( z;gWK)viUf8`1rJXiHCnYInu)ZgQeIG&Ec5EQV0|63XXZ&o!%`d)np7IFocqC(MfcT z7Ah+%+2a845#?aiq7l54)iyjaqOS@j;v!y@q7<~mFJaAa%-DH)bvDZil2qLD76v}34douw9 zIP-y)9Qe_==rflMoC z>atplEs%K5TY!*~E2XQeE4y>%6!xZsi278$X@@g8g$w_HQg~B{*P$Y#k5A{;|MG82#Om$9oV%!kvc}4Bx$57 zj6Gf-1x_DdMEzI&Z`ZlDF(G~g2&{F4@wv9tB_%^4^{?K1M2A%S@ht5;{I8}dcKPEU zXq6eG8G+1>=&t85D2Re~8+Gsft8Hd@AxF)uR;l*inf*~N8xZD$hc9hrd`1vm06AVp z4DlT_oF-f%{*~qMO5PO}c=p4(pQ1AkGrSnjfix~FYz#paEucS~vl+Uv93?cC;a`Qm z!USP`iaWJ}{*O}f?}n6w0EJk?33IoP`pAF~>Z8BBDxgPcc2 z%DqjEN)8iAPD3_EWj43>bW7KswLiEjy21I>A#9&vs4u+X;H8q%?ZZ^zES61uT&Lhg)Frr@dNz!LN z4XDJ_?EznO*i1Fp6^-`}gb&mBmpPS+NO$0L)X^_ycb&X)FsmEt{W~g>K1fjL;_&ou zItlbvy?RMyUEbyL*X%F+(7c`&czW2(K-y%z--P-y>w3J7ao2S7v%X6}^ZL@ag4>31 zl+98TD%9CJD$20w_uNu;{ZLZS*r*TN^`x&uV^jB%!HaV3@CtmrZJ?v5XV2u=+edl5 zu0z|$g1$V}t9Ltmmo)(vd)on@*?5{xn_%y5wS$!zZ|g`1{a-S*$(cBi1y2M6veC1q z=!>|saQEkC%19bdKLw0F#!~RG7|0Zrb7SwCi{Jc&3m}wgnv?rIm~o!^`0ExtHkzmz zdpJ!%@#rj~3wqCeA`ls5UPY@`FkF#qu4nVGZO?bxOZH)Lc`JfPy?8i^M)z)w_P#4ry4WBU0QRDtxrM)`@tresa($*yUj`a9t-yowE zyApfQ=T_YppJ8Q$)VB%tUGMdc{Vcu;u1`REO@y|m+xB1I>2EvdULS~4f;fO$F730Q zb_ab>fSq=Hn}FzcJPSD=y?mpL!lIZap&N0x)9d3~mC##gAQGC_{ZG)Xj1%*y5C0~w z!&{@<@}4BZc#yoTW!Ksemf;Uy6fZ&sY+T$PFjx{Rup#6Pa|keJ^^Pb1@aSFGc7^44 z6Ya?;DM@j?BHn|m8Nv^QAQekx>(`%oTKJ1qb?O}mhD?pb9HexC?j)!O{-kSO`r6(* zpznknClVbGY>lLd3D*|-(JL`pJs0(?97)DcFSp;_4}X-IejFVv_b`rBCvECOU2y9q znoJ(@(NHI>y~xZ?JX#mF{_z&X=v;Z9hq~T{7oVCITc(o|uwsq$Ry8Et@@cuur;J7z*p3S^HjpNhWCcYNeF!wpa zI3)%N^&>WmopNVIyN>g?F%A}^FcC3<#?IdCD;Y-{-_Kk(lg7CX2g_+#%|{gT%}lz- zmXsT3jfa*$IMT^BDotj0HpVxD3c`ySl(Sd;afG8>2m;;*ccm-DL9O;8pRPn(ScHx1 z3C|-UvmX7dQj+*G7+3^bbE9EfR$Q;*fR!v86`HkG^4v-psJ|tU0nu0kT9cZ^1oV2Rm(|qP>va5soUMFtX=VRK1a{cmBnlspK&@u>VvimI`E#1W0w8n1v-ss_)liblYFZ0hojSCrGx^ByCmV1(E8GM)`KXY~>>;Dd2U zgIKV5@6&ax=oE;;S~Gr80VP=sh)!S^lnu=6RH=F0p_1Z`L_2X~&Sr?P>giF#8t62t zoL)tMVtpiYVMc|IOm?yWbAuuK28%J}Vf( zz?})*=DKiJy8)Wb!GM_it068VDSFh_P*)l!1^n5MW$99 zx@wsKfYCBxZ5oeUSkv`e;6iA7v+8i)FZw*bL3RO$!-R}WN*~V&tqvDbDj9H~n&4Ku z!|j^P=EpLx2!Y#C1XT=ZfPWGIDH`|n#_z`G*NgRRwZKieJ1QEWCw5O4Jkd%(9M-V> z(nI4WS=7ENOS))KIt8;m`K;P2OfW6>qAQFq7P^C$caJ5PTV&g}Dt6Dm%$?Nd6hLxNxuwdnk*AnTu*4ZM7L#Aj&3SYn^G@Bxrb6a3@RHv- zQ=9ujRinU(Qkg*p`=~|5RxK8T!JBJ3^wQouS38wT=bxsg@Bg>gAe#AJnnZl$a6bxDYxIr1CGv!a7R zarNUxH+X7CxA$v7?K~Z;#&nY27jYSfPnu)WA|D!URVc#ME}aA91ZEvtW<}U}H_qW| z@2c;aSxw~xE{G+CZ0wm-HI~z^YyHfW9O$F^$SX4e#aIC?F{un71S%6A8=DK7j$YTxGJ&Bu%r>;nw;6rhB8 zHtP%n8azibJ-)=t(ZPx2KeSh)*&#)nF)xz^QG-KhAMxhq>@caMi~zWEzO^24o>AfQ z!6hY!_Z{WjCa}AA5m?MqTbh$ClR*Q5c$G=tLjXwwk1p}>>nFUWo!rhb<*?Ml@ZskG z&xD=JwNrF;#~foy@7SVjH-B_7D^Kq`H?lPdEN1abTf8i6Lr+1L*dcnW4nv+LD5$wW8)$9`wAJ2o#O>@x+Txw;N-j1TU-_}0FQakWbmL{65! zOaVKO+aDD+%c~DNpRQS&KP(tOO^{p=y)-$RTdK)?9VPgry2teVz*E8A69i*u;Tv7P z-IvFwh%w9a>Vpg0NGhi8%1n)VRuO`U`IyU*@}^&Imyjp?_oK4~x#N^o`&(j23kEU2 zMGwK82!ZP834=j@E7+vTd{;n-HL7I0;DLawu7Rq=ztT}bhQo2)>vQh-M28SHL}!x$ zLq&@rVNXB5=_QL7~OZq$i18gI`mcRpO}7R3KZ}az!p6#*Pt&$)eF+t zJGQWi^lmg|!L`4`Nygp7V&J9W<*8K(V~WyJJcgzhy8Yr)u0W=Cehkk~ z;_?SagvcQs(=cY9b>hI_o`aJ&O25h^qC9_bsiXzK^)1sNbBd2-E2(BM=R(C4Ze5F{ z*o#YvOc$uA6Sf{641Isoj1Bx|(xsbJc{7wKVb3l#Jj9Gc@dnmYF~_6v@as@{ol$M+ z7{gCn4w&}{_|obu;WWmeTpt?ThA}*Q2F;rjw9UiC%1L$_f`CWZfT1rq<65hv^QjVA zQJ6Uz)VonBc~coZUxC*2^>(GWKl_Vb{K>nLXJK81@13ofVM4Xlxo)G~O`@Sb`y9b3 z+vx_%T|$|By8DVGWy;8aR!xhe&HS&GIuRyh=m`KB{fuV_{AhzEs+fvfq|h*RFRPvu z`i1cCiMkwa09ha#(#1rB7fXx4Nf1?(VLYac{a6JqlvJw9)D4Lb;}c8bWGt9F67lfw)KPFiApWI^J`EA(-x; zF~H+EMatm|^)|Uky?zk|*$f zu=&?arwmrd_~(Sb~a51&3!K)eso(9jEQZY*2d z+nB_}6)HSjTxwW2I0=0~`wSkj)N&Nzn4`b=fnpRU8O-}nu8de%SQ}?&EQ3R%qjG?Y zu}^2<=coJ2UEdvWyj4CJN1kPuOAMvVR8m1V>`xIPC`hM-KO+SpAz^T6=vD}x{+Ipl zBTAFq5G>Gi?HfmF)6OvLx#*?N4w`BwLE%n{+qj9Ee!k|P3ttdoA;4!qC!+YIA3PdS zSGM0vNl2u?u;uf^HF$W~m?sBU+xzdOKL~JRi}1w-h^y@B$-6i5Ehs04%*EhjxhdtB z>&a5drF^zPOkWh9Odh-QSmbx;SqD(yKm2n52ww`mCMSpP?3jF6R!GdDwPK~B0wyb@ zaU#;lXZTlCFoEeg{<|T%5W;!Xu%+io-snz<8y=cCnwpwQ@sJCD{q~JcDiVfuWMl+I zJO=L#;s0PWIj$5r&9H+;fZ9CZ4{UG9eHdsB4UKc+MsF@lg2NXT46|Wr;lSI!01_+A zTXIE3MY2_&dJQoT6=W?fY-d9V;ODTw+c21%6v%k?ufqdH#2*}9&LCH-yjKVT>`l`j z^5BM-$rIH);*I$K?c)vv41!+(%8>dibw+dOsycca#qxhIMLL{8V6IlT1*h?U#qNqh zAlD3`n)m;pOI;m*kgGMrasIzZRTsk7cMu#51k-2rZ~j}ffLB;d>vHKLD#M*Ww}_>H L@?w=Dp9B9dUbJ7p literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/fill-switch-enum-after.png b/contribs/gnopls/doc/assets/fill-switch-enum-after.png new file mode 100644 index 0000000000000000000000000000000000000000..564be1779763b2d39de3d71bd693985f67b0c009 GIT binary patch literal 47748 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 E4;>Gr82|tP literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/fill-switch-enum-before.png b/contribs/gnopls/doc/assets/fill-switch-enum-before.png new file mode 100644 index 0000000000000000000000000000000000000000..85150347fb0e1e7acc79e4a7d040bbfbcbd4a81c GIT binary patch literal 16619 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/foldingrange.png b/contribs/gnopls/doc/assets/foldingrange.png new file mode 100644 index 0000000000000000000000000000000000000000..19e7645b2668a4a0711b671b647e338dfca33b80 GIT binary patch literal 47940 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

V_h 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 B;ky6; literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/hover-doclink.png b/contribs/gnopls/doc/assets/hover-doclink.png new file mode 100644 index 0000000000000000000000000000000000000000..dcee92b2d98aee5ded9997044502b0d9ba10afef GIT binary patch literal 38293 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/hover-embed.png b/contribs/gnopls/doc/assets/hover-embed.png new file mode 100644 index 0000000000000000000000000000000000000000..4d877a283da75ace35f8ca7d859e22fabd5ba75f GIT binary patch literal 28530 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˳l*NWRYb;McOsCqUnYJk^sa^b)f+QtrnehlRH;6 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 YRgo`6Zf1LP@J=vJ&{;C;i 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/hover-size-wasteful.png b/contribs/gnopls/doc/assets/hover-size-wasteful.png new file mode 100644 index 0000000000000000000000000000000000000000..6d907fb446cfc5b50cb6b43f1afa2a5217572c31 GIT binary patch literal 14369 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/inlayhint-parameternames.png b/contribs/gnopls/doc/assets/inlayhint-parameternames.png new file mode 100644 index 0000000000000000000000000000000000000000..83d934e1ca6fff08c6775cb01a344cb40ea49dd5 GIT binary patch literal 6675 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 Fe*i?ASOWk6 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/invert-if-after.png b/contribs/gnopls/doc/assets/invert-if-after.png new file mode 100644 index 0000000000000000000000000000000000000000..d66dc8e92f75e43d0bf48314db56803b183dd375 GIT binary patch literal 29629 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 MR}icDW*GcG01O>5#Q*>R literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/invert-if-before.png b/contribs/gnopls/doc/assets/invert-if-before.png new file mode 100644 index 0000000000000000000000000000000000000000..48581d2f3d87a58612ed0764e622bedd0d2834a2 GIT binary patch literal 42334 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%0D4=toGbH)B0(3$oce&ovLxX*zWNoKHyQe3)fm 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/remove-unusedparam-before.png b/contribs/gnopls/doc/assets/remove-unusedparam-before.png new file mode 100644 index 0000000000000000000000000000000000000000..4e49c4294fbf0dd921544a15715ddc9919ed8edb GIT binary patch literal 26457 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 Lu=6%e%?4MZujO;;+wDv&89{eyeAKVFAVvl5C@H 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/signature-help.png b/contribs/gnopls/doc/assets/signature-help.png new file mode 100644 index 0000000000000000000000000000000000000000..ca5377874758a2d011739ae53e76275d4250e6f3 GIT binary patch literal 65416 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%0A|zOj1TYi;3n(Z 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 + +## `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 + + diff --git a/contribs/gnopls/doc/command-line.md b/contribs/gnopls/doc/command-line.md new file mode 100644 index 00000000000..4f825e21b89 --- /dev/null +++ b/contribs/gnopls/doc/command-line.md @@ -0,0 +1,35 @@ +# Gopls: Command-line interface + +The `gopls` command provides a number of subcommands that expose much +of the server's functionality. However, the interface is currently +**experimental** and **subject to change at any point.** +It is not efficient, complete, flexible, or officially supported. + +Its primary use is as a debugging aid. +For example, this command reports the location of references to the +symbol at the specified file/line/column: + +``` +$ gopls references ./gopls/main.go:35:8 +Log: Loading packages... +Info: Finished loading packages. +/home/gopher/xtools/go/packages/gopackages/main.go:27:7-11 +/home/gopher/xtools/gopls/internal/cmd/integration_test.go:1062:7-11 +/home/gopher/xtools/gopls/internal/test/integration/bench/bench_test.go:59:8-12 +/home/gopher/xtools/gopls/internal/test/integration/regtest.go:140:8-12 +/home/gopher/xtools/gopls/main.go:35:7-11 +``` + +See golang/go#63693 for a discussion of its future. + +Learn about available commands and flags by running `gopls help`. + +Positions within files are specified as `file.go:line:column` triples, +where the line and column start at 1, and columns are measured in +bytes of the UTF-8 encoding. +Alternatively, positions may be specified by the byte offset within +the UTF-8 encoding of the file, starting from zero, for example +`file.go:#1234`. +(When working in non-ASCII files, beware that your editor may report a +position's offset within its file using a different measure such as +UTF-16 codes, Unicode code points, or graphemes). diff --git a/contribs/gnopls/doc/contributing.md b/contribs/gnopls/doc/contributing.md new file mode 100644 index 00000000000..007c5793073 --- /dev/null +++ b/contribs/gnopls/doc/contributing.md @@ -0,0 +1,181 @@ +# Gopls: 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). + + + +[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" + +## Documentation + +Each CL that adds or changes a feature should include, in addition to +a test that exercises the new behavior: + +- a **release note** that briefly explains the change, and +- **comprehensive documentation** in the [index of features](features/README.md). + +The release note should go in the file named for the forthcoming +release, for example [release/v0.16.0.md](release/v0.16.0.md). (Create +the file if your feature is the first to be added after a release.) + + diff --git a/contribs/gnopls/doc/daemon.md b/contribs/gnopls/doc/daemon.md new file mode 100644 index 00000000000..0844bc062e7 --- /dev/null +++ b/contribs/gnopls/doc/daemon.md @@ -0,0 +1,183 @@ +# Gopls: Running 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=` flag, and then pass `-remote=` 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-.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-.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;"`. Any gopls forwarder passing the same value for `` +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. diff --git a/contribs/gnopls/doc/design/architecture.svg b/contribs/gnopls/doc/design/architecture.svg new file mode 100644 index 00000000000..6c554d5670c --- /dev/null +++ b/contribs/gnopls/doc/design/architecture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/contribs/gnopls/doc/design/design.md b/contribs/gnopls/doc/design/design.md new file mode 100644 index 00000000000..6e6e7c3bb15 --- /dev/null +++ b/contribs/gnopls/doc/design/design.md @@ -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. + +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 + + +* 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* + + + +## 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] +| | 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.
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.
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.
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.
It also needs exported symbol information for all the packages it discovers.
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
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 diff --git a/contribs/gnopls/doc/design/implementation.md b/contribs/gnopls/doc/design/implementation.md new file mode 100644 index 00000000000..12d655c0b5e --- /dev/null +++ b/contribs/gnopls/doc/design/implementation.md @@ -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. + + +![Gopls architecture](architecture.svg) + +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. + + + +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 diff --git a/contribs/gnopls/doc/design/integrating.md b/contribs/gnopls/doc/design/integrating.md new file mode 100644 index 00000000000..2d8e01a76c0 --- /dev/null +++ b/contribs/gnopls/doc/design/integrating.md @@ -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 diff --git a/contribs/gnopls/doc/emacs.md b/contribs/gnopls/doc/emacs.md new file mode 100644 index 00000000000..3b6ee80d05a --- /dev/null +++ b/contribs/gnopls/doc/emacs.md @@ -0,0 +1,188 @@ +# Gopls: Using 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) +``` + +Use `M-x eglot-upgrade-eglot` to upgrade to the latest version of +Eglot. + +### 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 PATH ` 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/ diff --git a/contribs/gnopls/doc/features/README.md b/contribs/gnopls/doc/features/README.md new file mode 100644 index 00000000000..41449de7f20 --- /dev/null +++ b/contribs/gnopls/doc/features/README.md @@ -0,0 +1,65 @@ +# Gopls: Index of features + +This page provides an index of all supported features of gopls that +are accessible through the [language server protocol](https://microsoft.github.io/language-server-protocol/) (LSP). +It is intended for: +- **users of gopls** learning its capabilities so that they get the most out of their editor; +- **editor maintainers** adding or improving Go support in an LSP-capable editor; and +- **contributors to gopls** trying to understand how it works. + +In an ideal world, Go users would not need to know that gopls or even +LSP exists, as their LSP-enabled editors would implement every facet +of the protocol and expose each feature in a natural and discoverable +way. In reality, editors vary widely in their support for LSP, so +unfortunately these documents necessarily involve many details of the +protocol. + +We also list [settings](../settings.md) that affect each feature. + +Most features are illustrated with reference to VS Code, but we will +briefly mention whether each feature is supported in other popular +clients, and if so, how to find it. We welcome contributions, edits, +and updates from users of any editor. + +Contributors should [update this documentation](../contributing.md#documentation) +when making significant changes to existing features or when adding new ones. + +- [Passive](passive.md): features that are always on and require no special action + - [Hover](passive.md#hover): information about the symbol under the cursor + - [Signature Help](passive.md#signature-help): type information about the enclosing function call + - [Document Highlight](passive.md#document-highlight): highlight identifiers referring to the same symbol + - [Inlay Hint](passive.md#inlay-hint): show implicit names of struct fields and parameter names + - [Semantic Tokens](passive.md#semantic-tokens): report syntax information used by editors to color the text + - [Folding Range](passive.md#folding-range): report text regions that can be "folded" (expanded/collapsed) in an editor + - [Document Link](passive.md#document-link): extracts URLs from doc comments, strings in current file so client can linkify +- [Diagnostics](diagnostics.md): compile errors and static analysis findings +- [Navigation](navigation.md): navigation of cross-references, types, and symbols + - [Definition](navigation.md#definition): go to definition of selected symbol + - [Type Definition](navigation.md#type-definition): go to definition of type of selected symbol + - [References](navigation.md#references): list references to selected symbol + - [Implementation](navigation.md#implementation): show "implements" relationships of selected type + - [Document Symbol](navigation.md#document-symbol): outline of symbols defined in current file + - [Symbol](navigation.md#symbol): fuzzy search for symbol by name + - [Selection Range](navigation.md#selection-range): select enclosing unit of syntax + - [Call Hierarchy](navigation.md#call-hierarchy): show outgoing/incoming calls to the current function +- [Completion](completion.md): context-aware completion of identifiers, statements +- [Code transformation](transformation.md): fixes and refactorings + - [Formatting](transformation.md#formatting): format the source code + - [Rename](transformation.md#rename): rename a symbol or package + - [Organize imports](transformation.md#organize-imports): organize the import declaration + - [Extract](transformation.md#extract): extract selection to a new file/function/variable + - [Inline](transformation.md#inline): inline a call to a function or method + - [Miscellaneous rewrites](transformation.md#miscellaneous-rewrites): various Go-specific refactorings +- [Web-based queries](web.md): commands that open a browser page + - [Package documentation](web.md#doc): browse documentation for current Go package + - [Free symbols](web.md#freesymbols): show symbols used by a selected block of code + - [Assembly](web.md#assembly): show listing of assembly code for selected function +- Support for non-Go files: + - [Template files](templates.md): files parsed by `text/template` and `html/template` + - [go.mod and go.work files](modfiles.md): Go module and workspace manifests +- [Command-line interface](../command-line.md): CLI for debugging and scripting (unstable) + +You can find this page from within your editor by executing the +`gopls.doc.features` [code action](transformation.md#code-actions), +which opens it in a web browser. +In VS Code, you can find it on the Quick fix menu. diff --git a/contribs/gnopls/doc/features/completion.md b/contribs/gnopls/doc/features/completion.md new file mode 100644 index 00000000000..46991aab05e --- /dev/null +++ b/contribs/gnopls/doc/features/completion.md @@ -0,0 +1,3 @@ +# Gopls: Completion + +TODO(golang/go#62022): document diff --git a/contribs/gnopls/doc/features/diagnostics.md b/contribs/gnopls/doc/features/diagnostics.md new file mode 100644 index 00000000000..f58a6465d1d --- /dev/null +++ b/contribs/gnopls/doc/features/diagnostics.md @@ -0,0 +1,177 @@ +# Gopls: Diagnostics + +Gopls continuously annotates all your open files of source code with a +variety of diagnostics. Every time you edit a file or make a +configuration change, gopls asynchronously recomputes these +diagnostics and sends them to the client using the LSP +[`publishDiagnostics`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_publishDiagnostics) +notification, giving you real-time feedback that reduces the cost of +common mistakes. + +Diagnostics come from two main sources: compilation errors and analysis findings. + +- **Compilation errors** are those that you would obtain from running `go + build`. Gopls doesn't actually run the compiler; that would be too + slow. Instead it runs `go list` (when needed) to compute the + metadata of the compilation, then processes those packages in a similar + manner to the compiler front-end: reading, scanning, and parsing the + source files, then type-checking them. Each of these steps can + produce errors that gopls will surface as a diagnostic. + + The `source` field of the LSP `Diagnostic` record indicates where + the diagnostic came from: those with source `"go list"` come from + the `go list` command, and those with source `"compiler"` come from + gopls' parsing or type checking phases, which are similar to those + used in the Go compiler. + + ![A diagnostic due to a type error](../assets/diagnostic-typeerror.png) + + The example above shows a `string + int` addition, causes the type + checker to report a `MismatchedTypes` error. The diagnostic contains + a link to the documentation about this class of type error. + +- **Analysis findings** come from the [**Go analysis + framework**](https://golang.org/x/tools/go/analysis), the system + used by `go vet` to apply a variety of additional static checks to + your Go code. The best-known example is the [`printf` + analyzer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf), + which reports calls to [`fmt.Printf`](https://pkg.go.dev/fmt#Printf) + where the format "verb" doesn't match the argument, such as + `fmt.Printf("%d", "three")`. + + Gopls provides dozens of analyzers aggregated from a variety of + suites; see [Analyzers](../analyzers.md) for the complete list. The + `source` field of each diagnostic produced by an analyzer records + the name of the analyzer that produced it. + + ![A diagnostic due to an analysis finding](../assets/diagnostic-analysis.png) + + The example above shows a `printf` formatting mistake. The diagnostic contains + a link to the documentation for the `printf` analyzer. + +## Recomputation of diagnostics + +Diagnostics are automatically recomputed each time the source files +are edited. + +Compilation errors in open files are updated after a very short delay +(tens of milliseconds) after each file change, potentially after every keystroke. +This ensures rapid feedback of syntax and type errors while editing. + +Compilation and analysis diagnostics for the whole workspace are much +more expensive to compute, so they are usually recomputed after a +short idle period (around 1s) following an edit. + +The [`diagnosticsDelay`](../settings.md#diagnosticsDelay) setting determines +this period. +Alternatively, diagnostics may be triggered only after an edited file +is saved, using the +[`diagnosticsTrigger`](../settings.md#diagnosticsTrigger) setting. + +Gopls does not currently support "pull-based" diagnostics, which are +computed synchronously when requested by the client; see golang/go#53275. + + +## Quick fixes + +Each analyzer diagnostic may suggest one or more alternative +ways to fix the problem by editing the code. +For example, when a `return` statement has too few operands, +the [`fillreturns`](../analyzers.md#fillreturns) analyzer +suggests a fix that heuristically fills in the missing ones +with suitable values. Applying the fix eliminates the compilation error. + +![An analyzer diagnostic with two alternative fixes](../assets/remove-unusedparam-before.png) + +The screenshot above shows VS Code's Quick Fix menu for an "unused +parameter" analysis diagnostic with two alternative fixes. +(See [Remove unused parameter](transformation.md#remove-unused-parameter) for more detail.) + +Suggested fixes that are indisputably safe are [code +actions](transformation.md#code-actions) whose kind is +`"source.fixAll"`. +Many client editors have a shortcut to apply all such fixes. + + +TODO(adonovan): audit all the analyzers to ensure that their +documentation is up-to-date w.r.t. any fixes they suggest. + +Settings: + +- The [`diagnosticsDelay`](../settings.md#diagnosticsDelay) setting determines + the idle period after an edit before diagnostics are recomputed. +- The [`diagnosticsTriggerr`](../settings.md#diagnosticsTrigger) setting determines + what events cause recomputation of diagnostics. +- The [`linkTarget`](../settings.md#linkTarget) setting specifies + the base URI for Go package links in the Diagnostic.CodeDescription field. + +Client support: +- **VS Code**: Each diagnostic appears as a squiggly underline. + Hovering reveals the details, along with any suggested fixes. +- **Emacs + eglot**: Each diagnostic appears as a squiggly underline. + Hovering reveals the details. Use `M-x eglot-code-action-quickfix` + to apply available fixes; it will prompt if there are more than one. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls check file.go` + + diff --git a/contribs/gnopls/doc/features/modfiles.md b/contribs/gnopls/doc/features/modfiles.md new file mode 100644 index 00000000000..775be987ade --- /dev/null +++ b/contribs/gnopls/doc/features/modfiles.md @@ -0,0 +1,9 @@ +# Gopls: Support for go.mod and go.work files + +TODO: document these features for go.{mod,work} files: +- hover +- vulncheck +- add dependency +- update dependency +- diagnostics + diff --git a/contribs/gnopls/doc/features/navigation.md b/contribs/gnopls/doc/features/navigation.md new file mode 100644 index 00000000000..00371341a7c --- /dev/null +++ b/contribs/gnopls/doc/features/navigation.md @@ -0,0 +1,253 @@ +# Gopls: Navigation features + +This page documents gopls features for navigating your source code. + + + +## Definition + +The LSP [`textDocument/definition`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) +request returns the location of the declaration of the symbol under the cursor. +Most editors provide a command to navigate directly to that location. + +A definition query also works in these unexpected places: + +- On an **import path**, it returns the list of locations, of + each package declaration in the files of the imported package. +- On a **package declaration**, it returns the location of + the package declaration that provides the documentation of that package. +- On a symbol in a **[`go:linkname` directive](https://pkg.go.dev/cmd/compile)**, + it returns the location of that symbol's declaration. +- On a **[doc link](https://tip.golang.org/doc/comment#doclinks)**, it returns + (like [`hover`](passive.md#hover)) the location of the linked symbol. +- On a file name in a **[`go:embed` directive](https://pkg.go.dev/embed)**, + it returns the location of the embedded file. +- On the declaration of a non-Go function (a `func` with no body), + it returns the location of the assembly implementation, if any, + + + +Client support: +- **VS Code**: Use [Go to Definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition) (`F12` or `⌘`-click). + If the cursor is already at the declaration, the request is instead interpreted as "Go to References". +- **Emacs + eglot**: use [`M-x xref-find-definitions`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html). +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls definition file.go:#offset` + +## References + +The LSP [`textDocument/references`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) +request returns the locations of all identifiers that refer to the symbol under the cursor. + +The references algorithm handles various parts of syntax as follows: + +- The references to a **symbol** report all uses of that symbol. + In the case of exported symbols this may include locations in other packages. +- The references to a **package declaration** are all the + direct imports of the package, along with all the other package + declarations in the same package. +- It is an error to request the references to a **built-in symbol** + such as `int` or `append`, + as they are presumed too numerous to be of interest. +- The references to an **interface method** include references to + concrete types that implement the interface. Similarly, the + references to a **method of a concrete type** include references to + corresponding interface methods. +- An **embedded field** `T` in a struct type such as `struct{T}` is + unique in Go in that it is both a reference (to a type) and a + definition (of a field). + The `references` operation reports only the references to it [as a field](golang/go#63521). + To find references to the type, jump to the type declararation first. + +Be aware that a references query returns information only about the +build configuration used to analyze the selected file, so if you ask +for the references to a symbol defined in `foo_windows.go`, the result +will never include the file `bar_linux.go`, even if that file refers +to a symbol of the same name; see golang/go#65755. + +Clients can request that the the declaration be included among the +references; most do. + +Client support: +- **VS Code**: Use [`Go to References`](https://code.visualstudio.com/docs/editor/editingevolved#_peek) to quickly "peek" at the references, + or `Find all References` to open the references panel. +- **Emacs + eglot**: Via [`xref` package](https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html): use `M-x xref-find-references`. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls references file.go:#offset` + +## Implementation + +The LSP +[`textDocument/implementation`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_implementation) +request queries the "implements" relation between interfaces and concrete types: + +- When invoked on a reference to an **interface type**, it returns the + location of the declaration of each type that implements + the interface. +- When invoked on a **concrete type**, + it returns the locations of the matching interface types. +- When invoked on an **interface method**, it returns the corresponding + methods of the types that satisfy the interface. +- When invoked on a **concrete method**, + it returns the locations of the matching interface methods. + +Only non-trivial interfaces are considered; no implementations are +reported for type `any`. + +Within the same package, all matching types/methods are reported. +However, across packages, only exported package-level types and their +methods are reported, so local types (whether interfaces, or struct +types with methods due to embedding) may be missing from the results. + + +Generic types are currently not fully supported; see golang/go#59224. + +Client support: +- **VS Code**: Use [Go to Implementations](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-implementation) (`⌘F12`). +- **Emacs + eglot**: Use `M-x eglot-find-implementation`. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls implementation file.go:#offset` + + +## Type Definition + +The LSP +[`textDocument/typeDefinition`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_typeDefinition) +request returns the location of the type of the selected symbol. + +For example, if the selection is the name `buf` of a local variable of +type `*bytes.Buffer`, a `typeDefinition` query will return the +location of the type `bytes.Buffer`. +Clients typically navigate to that location. + +Type constructors such as pointer, array, slice, channel, and map are +stripped off the selected type in the search for a named type. For +example, if x is of type `chan []*T`, the reported type definition +will be that of `T`. +Similarly, if the symbol's type is a function with one "interesting" +(named, non-`error`) result type, the function's result type is used. + +Gopls currently requires that a `typeDefinition` query be applied to a +symbol, not to an arbitrary expression; see golang/go#67890 for +potential extensions of this functionality. + + +Client support: +- **VS Code**: Use [Go to Type Definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-implementation). +- **Emacs + eglot**: Use `M-x eglot-find-typeDefinition`. +- **Vim + coc.nvim**: ?? +- **CLI**: not supported + +## Document Symbol + +The `textDocument/documentSymbol` LSP query reports the list of +top-level declarations in this file. Clients may use this information +to present an overview of the file, and an index for faster navigation. + +Gopls responds with the +[`DocumentSymbol`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbol) +type if the client indicates +[`hierarchicalDocumentSymbolSupport`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbolClientCapabilities); +otherwise it returns a +[`SymbolInformation`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#symbolInformation). + +Client support: +- **VS Code**: Use the [Outline view](https://code.visualstudio.com/docs/getstarted/userinterface#_outline-view) for navigation. +- **Emacs + eglot**: Use [`M-x imenu`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html#Imenu) to jump to a symbol. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls links file.go` + + +## Symbol + +The +[`workspace/symbol`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_symbol) +LSP query searches an index of all the symbols in the workspace. + +The default symbol matching algorithm (`fastFuzzy`), inspired by the +popular fuzzy matcher [FZF](https://github.com/junegunn/fzf), attempts +a variety of inexact matches to correct for misspellings or abbreviations in your +query. For example, it considers `DocSym` a match for `DocumentSymbol`. + + + +Settings: +- The [`symbolMatcher`](../settings.md#symbolMatcher) setting controls the algorithm used for symbol matching. +- The [`symbolStyle`](../settings.md#symbolStyle) setting controls how symbols are qualified in symbol responses. +- The [`symbolScope`](../settings.md#symbolScope) setting determines the scope of the query. +- The [`directoryFilters`](../settings.md#directoryFilters) setting specifies directories to be excluded from the search. + +Client support: +- **VS Code**: Use ⌘T to open [Go to Symbol](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-symbol) with workspace scope. (Alternatively, use Ctrl-Shift-O, and add a `@` prefix to search within the file or a `#` prefix to search throughout the workspace.) +- **Emacs + eglot**: Use [`M-x xref-find-apropos`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Looking-Up-Identifiers.html) to show symbols that match a search term. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls links file.go` + + +## Selection Range + +The +[`textDocument/selectionRange`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_selectionRange) +LSP query returns information about the lexical extent of each piece +of syntax enclosing the current selection. +Clients may use it to provide an operation to expand the selection +to successively larger expressions. + +Client support: +- **VSCode**: Use `⌘⇧^→` to expand the selection or `⌘⇧^←` to contract it again; watch this [video](https://www.youtube.com/watch?v=dO4SGAMl7uQ). +- **Emacs + eglot**: Not standard. Use `M-x eglot-expand-selection` defined in [this configuration snippet](https://github.com/joaotavora/eglot/discussions/1220#discussioncomment-9321061). +- **Vim + coc.nvim**: ?? +- **CLI**: not supported + +## Call Hierarchy + +The LSP CallHierarchy mechanism consists of three queries that +together enable clients to present a hierarchical view of a portion of +the static call graph: + +- [`textDocument/prepareCallHierarchy`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_prepareCallHierarchy) returns a list of [items](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyItem) for a given position, each representing a named function or method enclosing the position; +- [`callHierarchyItem/incomingCalls`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchy_incomingCalls) returns the set of call sites that call the selected item; and +- [`callHierarchy/outgoingCalls`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchy_incomingCalls) returns the set of functions called by the selected item. + +Invoke the command while selecting the name in a function declaration. + +Dynamic calls are not included, because it is not analytically +practical to detect them. So, beware that the results may not be +exhaustive, and perform a [References](#references) query if necessary. + +The hierarchy does not consider a nested function distinct from its +enclosing named function. (Without the ability to detect dynamic +calls, it would make little sense do so.) + +The screenshot below shows the outgoing call tree rooted at `f`. The +tree has been expanded to show a path from `f` to the `String` method +of `fmt.Stringer` through the guts of `fmt.Sprint:` + + + +Caveats: +- In some cases dynamic function calls are (erroneously) included in + the output; see golang/go#68153. + +Client support: +- **VS Code**: `Show Call Hierarchy` menu item (`⌥⇧H`) opens [Call hierarchy view](https://code.visualstudio.com/docs/cpp/cpp-ide#_call-hierarchy) (note: docs refer to C++ but the idea is the same for Go). +- **Emacs + eglot**: Not standard; install with `(package-vc-install "https://github.com/dolmens/eglot-hierarchy")`. Use `M-x eglot-hierarchy-call-hierarchy` to show the direct incoming calls to the selected function; use a prefix argument (`C-u`) to show the direct outgoing calls. There is no way to expand the tree. +- **CLI**: `gopls call_hierarchy file.go:#offset` shows outgoing and incoming calls. diff --git a/contribs/gnopls/doc/features/passive.md b/contribs/gnopls/doc/features/passive.md new file mode 100644 index 00000000000..23a1938cbeb --- /dev/null +++ b/contribs/gnopls/doc/features/passive.md @@ -0,0 +1,282 @@ +# Gopls: Passive features + +This page documents the fundamental LSP features of gopls that may be +described as "passive", since many editors use them to continuously +provide information about your source files without requiring any +special action. + +See also [Code Lenses](../codelenses.md), some of which annotate your +source code with additional information and may thus also be +considered passive features. + + +## Hover + +The LSP [`textDocument/hover`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover) +query returns a description of the code currently under the cursor, such +as its name, kind, type, value (for a constant), abbreviated +declaration (for a type), doc comment (if any), and a link to the +symbol's documentation on `pkg.go.dev`. The client may request either +plain text or Markdown. + + + +Depending on the selection, the response may include additional information. +For example, hovering over a type shows its declared methods, +plus any methods promoted from embedded fields. + +**Doc links**: A doc comment may refer to another symbol using square +brackets, for example `[fmt.Printf]`. Hovering over one of these +[doc links](https://go.dev/doc/comment#doclinks) reveals +information about the referenced symbol. + + + +**Struct size/offset info**: for declarations of struct types, +hovering over the name reveals the struct's size in bytes: + + + +And hovering over each field name shows the size and offset of that field: + + + +This information may be useful when optimizing the layout of your data +structures, or when reading assembly files or stack traces that refer +to each field by its cryptic byte offset. + +In addition, Hover reports the percentage of wasted space due to +suboptimal ordering of struct fields, if this figure is 20% or higher: + + + +In the struct above, alignment rules require each of the two boolean +fields (1 byte) to occupy a complete word (8 bytes), leading to (7 + +7) / (3 * 8) = 58% waste. +Placing the two booleans together would save a word. +(In most structures clarity is more important than compactness, so you +should reorder fields to save space only in data structures that have +been shown by profiling to be very frequently allocated.) + +**Embed directives**: hovering over the file name pattern in +[`//go:embed` directive](https://pkg.go.dev/embed), for example +`*.html`, reveals the list of file names to which the wildcard +expands. + + + + +**Linkname directives**: a [`//go:linkname` directive](https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives) creates a linker-level alias for another symbol. +Hovering over the directive shows information about the other symbol. + + + +The hover information for symbols from the standard library added +after Go 1.0 states the Go release that added the symbol. + +Settings: +- The [`hoverKind`](../settings.md#hoverKind) setting controls the verbosity of documentation. +- The [`linkTarget`](../settings.md#linkTarget) setting specifies + the base URI for Go package links + +Caveats: +- It is an unfortunate limitation of the LSP that a `Hover` request + currently includes only a position but not a selection, as this + means it is impossible to request information about the type and + methods of, say, the `f(x)` portion of the larger expression + `f(x).y`. Please upvote microsoft/language-server-protocol#1466 if + you would like to see this addressed. + +Client support: +- **VS Code**: enabled by default. Displays rendered Markdown in a panel near the cursor. +- **Emacs + eglot**: enabled by default. Displays a one-line summary in the echo area. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls definition file.go:#start-#end` includes information from a Hover query. + + +## Signature Help + +The LSP [`textDocument/signatureHelp`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_signatureHelp) +query returns information about the innermost function call enclosing +the cursor or selection, including the signature of the function and +the names, types, and documentation of each parameter. + +Clients may provide this information to help remind the user of the +purpose of each parameter and their order, while reading or editing a +function call. + + + +Call parens are not necessary if the cursor is within an identifier +that denotes a function or method. For example, Signature Help at +`once.Do(initialize‸)` will describe `initialize`, not `once.Do`. + +Client support: +- **VS Code**: enabled by default. + Also known as "[parameter hints](https://code.visualstudio.com/api/references/vscode-api#SignatureHelpProvider)" in the [IntelliSense settings](https://code.visualstudio.com/docs/editor/intellisense#_settings). + Displays signature and doc comment alongside Hover information. +- **Emacs + eglot**: enabled by default. Displays signature in the echo area. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls signature file.go:#start-#end` + + +## Document Highlight + +The LSP [`textDocument/documentHighlight`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentHighlight) +query reports a set of source ranges that should be highlighted based +on the current cursor position or selection, to emphasize the +relationship between them. + +Each of the following parts of syntax forms a set so that if you +select any one member, gopls will highlight the complete set: + +- each identifier that refers to the same symbol (as in the screenshot below); +- a named result variable and all its corresponding operands of `return` statements; +- the `for`, `break`, and `continue` tokens of the same loop; +- the `switch` and `break` tokens of the same switch statement; +- the `func` keyword of a function and all of its `return` statements. + +More than one of these rules may be activated by a single selection, +for example, by an identifier that is also a return operand. + +Different occurrences of the same identifier may be color-coded to distinguish +"read" from "write" references to a given variable symbol. + + + +Client support: +- **VS Code**: enabled by default. Triggered by cursor motion, or single click. + (Note: double clicking activates a simple syntax-oblivious textual match.) +- **Emacs + eglot**: enabled by default. Triggered by cursor motion or selection. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls signature file.go:#start-#end` + + +## Inlay Hint + +The LSP [`textDocument/inlayHint`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint) +query returns a set of annotations to be spliced into the current file +that reveal implicit information. + + + +Examples: + +- In a function call `f(1, 2)`, hints provide the + names of the parameters (`parameterNames`), as in the screenshot above. +- In a call to a generic function, hints provide the type arguments + (`functionTypeParameters`). +- In an assignment `x, y = 1, 2`, hints provide the types of the + variables (`assignVariableTypes`). +- In a struct literal such as `Point2D{1, 2}`, hints provide the field + names (`compositeLiteralFields`). +- In a nested composite literal `T{{...}}`, a hint provides the type of + the inner literal, `{...}` (`compositeLiteralTypes`). +- In a `for k, v := range x {}` loop, hints provide the types of the + variables k and v (`rangeVariableTypes`). +- For a constant expression (perhaps using `iota`), a hint provides + its computed value (`constantValues`). + +See [Inlay hints](../inlayHints.md) for a complete list with examples. + + + +Settings: +- The [`hints`](../settings.md#hints) setting indicates the desired set of hints. + To reduce distractions, its default value is empty. + To enable hints, add one or more of the identifiers above to the hints + map. For example: + ```json5 + "hints": {"parameterNames": true} + ``` + +Client support: +- **VS Code**: in addition to the `hints` configuration value, VS Code provides a graphical + configuration menu ("Preferences: Open Settings (UI)" the search for "Go Inlay Hints") + for each supported kind of inlay hint. +- **Emacs + eglot**: disabled by default. Needs `M-x eglot-inlay-hints-mode` plus the configuration [described here](https://www.reddit.com/r/emacs/comments/11bqzvk/emacs29_and_eglot_inlay_hints/) +- **Vim + coc.nvim**: ?? +- **CLI**: not supported + +## Semantic Tokens + +The LSP [`textDocument/semanticTokens`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens) +query reports information about all the tokens in the current file, or +a portion of it. +The client may use this information to provide syntax highlighting +that conveys semantic distinctions between, for example, functions and +types, constants and variables, or library functions and built-ins. +Gopls also reports a modifier for the top-level constructor of each symbols's type, one of: +`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, `invalid`. +The client specifies the sets of types and modifiers it is interested in. + +Settings: +- The [`semanticTokens`](../settings.md#semanticTokens) setting determines whether + gopls responds to semantic token requests. This option allows users to disable + semantic tokens even when their client provides no client-side control over the + feature. Because gopls' semantic-tokens algorithm depends on type checking, + which adds a tangible latency, this feature is currently disabled by default + to avoid any delay in syntax highlighting; see golang/go#45313, golang/go#47465. +- The experimental [`noSemanticString`](../settings.md#noSemanticString) and + [`noSemanticNumber`](../settings.md#noSemanticNumber) settings cause the server + to exclude the `string` and `number` kinds from the response, as some clients + may do a more colorful job highlighting these tokens; see golang/go#45753. + +Client Support: +- **VS Code**: See [Semantic Highlighting Guide](https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide). +- **Emacs + eglot**: Not supported; see joaotavora/eglot#615. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls semtok file.go` + +For internal details of gopls' implementation of semantic tokens, +see [semantic tokens](../semantictokens.md). + +## Folding Range + +The LSP [`textDocument/foldingRange`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_foldingRange) +query reports the list of regions in the current file that may be +independently collapsed or expanded. For example, it may be convenient +to collapse large comments or functions when studying some code so +that more of it fits in a single screen. + + + +The protocol [allows](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#foldingRangeClientCapabilities) clients to indicate whether they prefer +fine-grained ranges such as matched pairs of brackets, or only ranges +consisting of complete lines. + +Client support: +- **VS Code**: displayed in left margin. Toggle the chevrons (`∨` and `>`) to collapse or expand. +- **Emacs + eglot**: not supported. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls folding_ranges file.go` + +## Document Link + +The LSP [`textDocument/documentLink`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink) +query uses heuristics to extracts URLs from doc comments and string +literals in the current file so that the client can present them as +clickable links. + + + +In addition to explicit URLs, gopls also turns string literals in +import declarations into links to the pkg.go.dev documentation for the +imported package. + +Settings: +- The [`importShortcut`](../settings.md#importShortcut) setting determines + what kind of link is returned for an `import` declaration. +- The [`linkTarget`](../settings.md#linkTarget) setting specifies + the base URI for Go package links. + +Client support: +- **VS Code**: Hovering over a link displays a "Follow link (cmd+click)" popup. +- **Emacs + eglot**: not currently used. +- **Vim + coc.nvim**: ?? +- **CLI**: `gopls links file.go` diff --git a/contribs/gnopls/doc/features/templates.md b/contribs/gnopls/doc/features/templates.md new file mode 100644 index 00000000000..a71a2ea181c --- /dev/null +++ b/contribs/gnopls/doc/features/templates.md @@ -0,0 +1,49 @@ +# Gopls: Support for template files + +Gopls provides some support for Go template files, that is, files that +are parsed by [`text/template`](https://pkg.go.dev/text/template) or +[`html/template`](https://pkg.go.dev/html/template). + +## Enabling template support + +Gopls recognizes template files based on their file extension, which +may be configured by the +[`templateExtensions`](../settings.md#templateExtensions) setting. If +this list is empty, template support is disabled. (This is the default +value, since Go templates don't have a canonical file extension.) + +Additional configuration may be necessary to ensure that your client +chooses the correct language kind when opening template files. +Gopls recogizes both `"tmpl"` and `"gotmpl"` for template files. +For example, in `VS Code` you will also need to add an +entry to the +[`files.associations`](https://code.visualstudio.com/docs/languages/identifiers) +mapping: +```json +"files.associations": { + ".mytemplate": "gotmpl" +}, +``` + + +## Features +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. + +TODO: also ++ Hover ++ SemanticTokens ++ Symbol search ++ DocumentHighlight + + diff --git a/contribs/gnopls/doc/features/transformation.md b/contribs/gnopls/doc/features/transformation.md new file mode 100644 index 00000000000..bf5df29c01b --- /dev/null +++ b/contribs/gnopls/doc/features/transformation.md @@ -0,0 +1,677 @@ +# Gopls: Code transformation features + +This document describes gopls' features for code transformation, which +include a range of behavior-preserving changes (refactorings, +formatting, simplifications), code repair (fixes), and editing support +(filling in struct literals and switch statements). + +Code transformations are not a single category in the LSP: +- A few, such as Formatting and Rename, are primary operations in the + protocol. +- Some transformations are exposed through [Code Lenses](../codelenses.md), + which return _commands_, arbitrary server + operations invoked for their side effects through a + [`workspace/executeCommand`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_executeCommand) request; + however, no current code lenses are transformations of Go syntax. + +- Most transformations are defined as *code actions*. + +## Code Actions + +A **code action** is an action associated with a portion of the file. +Each time the selection changes, a typical client makes a +[`textDocument/codeAction`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction) +request for the set of available actions, then updates its UI +elements (menus, icons, tooltips) to reflect them. +The VS Code manual describes code actions as +"[Quick fixes + Refactorings](https://code.visualstudio.com/docs/editor/refactoring#_code-actions-quick-fixes-and-refactorings)". + +A `codeAction` request delivers the menu, so to speak, but it does +not order the meal. Once the user chooses an action, one of two things happens. +In trivial cases, the action itself contains an edit that the +client can directly apply to the file. +But in most cases the action contains a command, +similar to the command associated with a code lens. +This allows the work of computing the patch to be done lazily, only +when actually needed. (Most aren't.) +The server may then compute the edit and send the client a +[`workspace/applyEdit`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_applyEdit) +request to patch the files. +Not all code actions' commands have an `applyEdit` side effect: some +may change the state of the server, for example to toggle a variable +or to cause the server to send other requests to the client, +such as a `showDocument` request to open a report in a web browser. + +The main difference between code lenses and code actions is this: +- a `codeLens` request obtains commands for the entire file. + Each command specifies its applicable source range, + and typically appears as an annotation on that source range. +- a `codeAction` request obtains commands only for a particular range: the current selection. + All the commands are presented together in a menu at that location. + +Each action has a _kind_, +which is a hierarchical identifier such as `refactor.inline.call`. +Clients may filter actions based on their kind. +For example, VS Code has: +two menus, "Refactor..." and "Source action...", each populated by +different kinds of code actions (`refactor` and `source`); +a lightbulb icon that triggers a menu of "quick fixes" (of kind `quickfix`); +and a "Fix All" command that executes all code actions of +kind `source.fixAll`, which are those deemed unambiguously safe to apply. + +Gopls reports some code actions twice, with two different kinds, so +that they appear in multiple UI elements: simplifications, +for example from `for _ = range m` to `for range m`, +have kinds `quickfix` and `source.fixAll`, +so they appear in the "Quick Fix" menu and +are activated by the "Fix All" command. + + + +Many transformations are computed by [analyzers](../analyzers.md) +that, in the course of reporting a diagnostic about a problem, +also suggest a fix. +A `codeActions` request will return any fixes accompanying diagnostics +for the current selection. + + + + +Caveats: +- Many of gopls code transformations are limited by Go's syntax tree + representation, which currently records comments not in the tree + but in a side table; consequently, transformations such as Extract + and Inline are prone to losing comments. This is issue + golang/go#20744, and it is a priority for us to fix in 2024. + +- Generated files, as identified by the conventional + [DO NOT EDIT](https://go.dev/s/generatedcode) comment, + are not offered code actions for transformations. + + +Client support for code actions: + +- **VS Code**: Depending on their kind, code actions are found in + the "Refactor..." menu (`^⇧R`), + the "Source action..." menu, + the 💡 (light bulb) icon's menu, or + the "Quick fix" (`⌘.`) menu. + The "Fix All" command applies all actions of kind `source.fixAll`. +- **Emacs + eglot**: Code actions are invisible. + Use `M-x eglot-code-actions` to select one from those that are + available (if there are multiple) and execute it. + Some action kinds have filtering shortcuts, + e.g. [`M-x eglot-code-action-{inline,extract,rewrite}`](https://joaotavora.github.io/eglot/#index-M_002dx-eglot_002dcode_002daction_002dinline). +- **CLI**: `gopls codeaction -exec -kind k,... -diff file.go:#123-#456` executes code actions of the specified + kinds (e.g. `refactor.inline`) on the selected range, specified using zero-based byte offsets, and displays the diff. + +## Formatting + +The LSP +[`textDocument/formatting`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting) +request returns edits that format a file. +Gopls applies Go's canonical formatting algorithm, +[`go fmt`](https://pkg.go.dev/cmd/gofmt). +LSP formatting options are ignored. + +Most clients are configured to format files and organize imports +whenever a file is saved. + +Settings: +- The [`gofumpt`](../settings.md#gofumpt) setting causes gopls to use an + alternative formatter, [`github.com/mvdan/gofumpt`](https://pkg.go.dev/mvdan.cc/gofumpt). + +Client support: +- **VS Code**: Formats on save by default. Use `Format document` menu item (`⌥⇧F`) to invoke manually. +- **Emacs + eglot**: Use `M-x eglot-format-buffer` to format. Attach it to `before-save-hook` to format on save. For formatting combined with organize-imports, many users take the legacy approach of setting `"goimports"` as their `gofmt-command` using [go-mode](https://github.com/dominikh/go-mode.el), and adding `gofmt-before-save` to `before-save-hook`. An LSP-based solution requires code such as https://github.com/joaotavora/eglot/discussions/1409. +- **CLI**: `gopls format file.go` + +## Organize imports + +A `codeActions` request in a file whose imports are not organized will +return an action of the standard kind `source.organizeImports`. +Its command has the effect of organizing the imports: +deleting existing imports that are duplicate or unused, +adding new ones for undefined symbols, +and sorting them into the conventional order. + +The addition of new imports is based on heuristics that depend on +your workspace and the contents of your GOMODCACHE directory; they may +sometimes make surprising choices. + +Many editors automatically organize imports and format the code before +saving any edited file. + +Some users dislike the automatic removal of imports that are +unreferenced because, for example, the sole line that refers to the +import is temporarily commented out for debugging; see golang/go#54362. + +Settings: + +- The [`local`](../settings.md#local) setting is a comma-separated list of + prefixes of import paths that are "local" to the current file and + should appear after standard and third-party packages in the sort order. + +Client support: +- **VS Code**: automatically invokes `source.organizeImports` before save. + To disable it, use the snippet below, and invoke the "Organize Imports" command manually as needed. + ``` + "[go]": { + "editor.codeActionsOnSave": { "source.organizeImports": false } + } + ``` +- **Emacs + eglot**: Use `M-x eglot-code-action-organize-imports` to invoke manually. + Many users of [go-mode](https://github.com/dominikh/go-mode.el) use these lines to + organize imports and reformat each modified file before saving it, but this + approach is based on the legacy + [`goimports`](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) tool, not gopls: + ```lisp + (setq gofmt-command "goimports") + (add-hook 'before-save-hook 'gofmt-before-save) + ``` +- **CLI**: `gopls fix -a file.go:#offset source.organizeImports` + + +## `refactor.rename`: Rename + +The LSP +[`textDocument/rename`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename) +request renames a symbol. + +Renaming is a two-stage process. The first step, a +[`prepareRename`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_prepareRename) query, returns the current +name of the identifier under the cursor (if indeed there is one). +The client then displays a dialog prompting the user to choose a new +name by editing the old one. The second step, `rename` proper, applies +the changes. (This simple dialog support is unique among LSP +refactoring operations; see microsoft/language-server-protocol#1164.) + +Gopls' renaming algorithm takes great care to detect situations in +which renaming might introduce a compilation error. +For example, changing a name may cause a symbol to become "shadowed", +so that some existing references are no longer in scope. Gopls will +report an error, stating the pair of symbols and the shadowed reference: + + + +As another example, consider renaming a method of a concrete type. +Renaming may cause the type to no longer satisfy the same interfaces +as before, which could cause the program to fail to compile. +To avoid this, gopls inspects each conversion (explicit or implicit) +from the affected type to an interface type, and checks whether it +would remain valid after the renaming. If not, it aborts the renaming +with an error. + +If you intend to rename both the original method and the corresponding +methods of any matching interface types (as well as any methods of +types matching them in turn), you can indicate this by invoking the +rename operation on the interface method. + +Similarly, gopls will report an error if you rename a field of a +struct that happens to be an "anonymous" field that embeds a type, +since that would require a larger renaming involving the type as well. +If that is what you intend, you can again indicate this by +invoking the rename operation on the type. + +Renaming should never introduce a compilation error, but it may +introduce dynamic errors. For example, in a method renaming, if there +is no direct conversion of the affected type to the interface type, +but there is an intermediate conversion to a broader type (such as `any`) followed by a +type assertion to the interface type, then gopls may proceed to rename +the method, causing the type assertion to fail at run time. +Similar problems may arise with packages that use reflection, such as +`encoding/json` or `text/template`. There is no substitute for good +judgment and testing. + +Some tips for best results: +- There is currently no special support for renaming all receivers of + a family of methods at once, so you will need to rename one receiver + one at a time (golang/go#41892). +- The safety checks performed by the Rename algorithm require type + information. If the program is grossly malformed, there may be + insufficient information for it to run (golang/go#41870), + and renaming cannot generally be used to fix a type error (golang/go#41851). + When refactoring, we recommend working in small steps, repairing any + problems as you go, so that as much as possible of the program + compiles at each step. +- Sometimes it may be desirable for a renaming operation to change the + reference structure of the program, for example to intentionally + combine two variables x and y by renaming y to x. + The renaming tool is too strict to help in this case (golang/go#41852). + + + +For the gory details of gopls' rename algorithm, you may be interested +in the latter half of this 2015 GothamGo talk: +[Using go/types for Code Comprehension and Refactoring Tools](https://www.youtube.com/watch?v=p_cz7AxVdfg). + +Client support: +- **VS Code**: Use "[Rename symbol](https://code.visualstudio.com/docs/editor/editingevolved#_rename-symbol)" menu item (`F2`). +- **Emacs + eglot**: Use `M-x eglot-rename`, or `M-x go-rename` from [go-mode](https://github.com/dominikh/go-mode.el). +- **Vim + coc.nvim**: Use the `coc-rename` command. +- **CLI**: `gopls rename file.go:#offset newname` + + +
+## `refactor.extract`: Extract function/method/variable + +The `refactor.extract` family of code actions all return commands that +replace the selected expression or statements with a reference to a +newly created declaration that contains the selected code: + + + +- **`refactor.extract.function`** replaces one or more complete statements by a + call to a new function named `newFunction` whose body contains the + statements. The selection must enclose fewer statements than the + entire body of the existing function. + + ![Before extracting a function](../assets/extract-function-before.png) + ![After extracting a function](../assets/extract-function-after.png) + +- **`refactor.extract.method`** is a variant of "Extract function" offered when + the selected statements belong to a method. The newly created function + will be a method of the same receiver type. + +- **`refactor.extract.variable`** replaces an expression by a reference to a new + local variable named `x` initialized by the expression: + + ![Before extracting a var](../assets/extract-var-before.png) + ![After extracting a var](../assets/extract-var-after.png) + +If the default name for the new declaration is already in use, gopls +generates a fresh name. + +Extraction is a challenging problem requiring consideration of +identifier scope and shadowing, control +flow such as `break`/`continue` in a loop or `return` in a +function, cardinality of variables, and even subtle issues of style. +In each case, the tool will try to update the extracted statements +as needed to avoid build breakage or behavior changes. +Unfortunately, gopls' Extract algorithms are considerably less +rigorous than the Rename and Inline operations, and we are aware of a +number of cases where it falls short, including: + +- https://github.com/golang/go/issues/66289 +- https://github.com/golang/go/issues/65944 +- https://github.com/golang/go/issues/64821 +- https://github.com/golang/go/issues/63394 +- https://github.com/golang/go/issues/61496 +- https://github.com/golang/go/issues/50851 + +The following Extract features are planned for 2024 but not yet supported: + +- **Extract constant** is a variant of "Extract variable" to be + offered when the expression is constant; see golang/go#37170. +- **Extract parameter struct** will replace two or more parameters of a + function by a struct type with one field per parameter; see golang/go#65552. + + +- **Extract interface for type** will create a declaration of an + interface type with all the methods of the selected concrete type; + see golang/go#65721 and golang/go#46665. + + + +## `refactor.extract.toNewFile`: Extract declarations to new file + +(Available from gopls/v0.17.0) + +If you select one or more top-level declarations, gopls will offer an +"Extract declarations to new file" code action that moves the selected +declarations into a new file whose name is based on the first declared +symbol. +Import declarations are created as needed. +Gopls also offers this code action when the selection is just the +first token of the declaration, such as `func` or `type`. + +![Before: select the declarations to move](../assets/extract-to-new-file-before.png) +![After: the new file is based on the first symbol name](../assets/extract-to-new-file-after.png) + + + +## `refactor.inline.call`: Inline call to function + +For a `codeActions` request where the selection is (or is within) a +call of a function or method, gopls will return a command of kind +`refactor.inline.call`, whose effect is to inline the function call. + +The screenshots below show a call to `sum` before and after inlining: + +![Before: select Refactor... Inline call to sum](../inline-before.png) +![After: the call has been replaced by the sum logic](../inline-after.png) + +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, 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. +Calls to generic functions are not yet supported +(golang/go#63352), though we plan to fix that. + +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. + +Here are 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. + +## `refactor.rewrite`: Miscellaneous rewrites + +This section covers a number of transformations that are accessible as +code actions whose kinds are children of `refactor.rewrite`. + +### `refactor.rewrite.removeUnusedParam`: Remove unused parameter + +The [`unusedparams` analyzer](../analyzers.md#unusedparams) reports a +diagnostic for each parameter that is not used within the function body. +For example: +```go +func f(x, y int) { // "unused parameter: x" + fmt.Println(y) +} +``` + +It does _not_ report diagnostics for address-taken functions, which +may need all their parameters, even unused ones, in order to conform +to a particular function signature. +Nor does it report diagnostics for exported functions, +which may be address-taken by another package. +(A function is _address-taken_ if it is used other than in call position, `f(...)`.) + +In addition to the diagnostic, it suggests two possible fixes: + +1. rename the parameter to `_` to emphasize that it is unreferenced (an immediate edit); or +2. delete the parameter altogether, using a `ChangeSignature` command, updating all callers. + +Fix \#2 uses the same machinery as "Inline function call" (see above) +to ensure that the behavior of all existing calls is preserved, even +when the argument expression for the deleted parameter has side +effects, as in the example below. + +![The parameter x is unused](../assets/remove-unusedparam-before.png) +![The parameter x has been deleted](../assets/remove-unusedparam-after.png) + +Observe that in the first call, the argument `chargeCreditCard()` was +not deleted because of potential side effects, whereas in the second +call, the argument 2, a constant, was safely deleted. + +### `refactor.rewrite.changeQuote`: Convert string literal between raw and interpreted + +When the selection is a string literal, gopls offers a code action +to convert the string between raw form (`` `abc` ``) and interpreted +form (`"abc"`) where this is possible: + +![Convert to interpreted](../assets/convert-string-interpreted.png) +![Convert to raw](../assets/convert-string-raw.png) + +Applying the code action a second time reverts back to the original +form. + +### `refactor.rewrite.invertIf`: Invert 'if' condition + +When the selection is within an `if`/`else` statement that is not +followed by `else if`, gopls offers a code action to invert the +statement, negating the condition and swapping the `if` and and `else` +blocks. + +![Before "Invert if condition"](../assets/invert-if-before.png) +![After "Invert if condition"](../assets/invert-if-after.png) + + + +### `refactor.rewrite.{split,join}Lines`: Split elements into separate lines + +When the selection is within a bracketed list of items such as: + +- the **elements** of a composite literal, `[]T{a, b, c}`, +- the **arguments** of a function call, `f(a, b, c)`, +- the **groups of parameters** of a function signature, `func(a, b, c int, d, e bool)`, or +- its **groups of results**, `func() (x, y string, z rune)`, + +gopls will offer the "Split [items] into separate lines" code +action, which would transform the forms above into these forms: + +```go +[]T{ + a, + b, + c, +} + +f( + a, + b, + c, +) + +func( + a, b, c int, + d, e bool, +) + +func() ( + x, y string, + z rune, +) +``` +Observe that in the last two cases, each +[group](https://pkg.go.dev/go/ast#Field) of parameters or results is +treated as a single item. + +The opposite code action, "Join [items] into one line", undoes the operation. +Neither action is offered if the list is already full split or joined, +respectively, or trivial (fewer than two items). + +These code actions are not offered for lists containing `//`-style +comments, which run to the end of the line. + + + +### `refactor.rewrite.fillStruct`: Fill struct literal + +When the cursor is within a struct literal `S{}`, gopls offers the +"Fill S" code action, which populates each missing field of the +literal that is accessible. + +It uses the following heuristic to choose the value assigned to each +field: it finds candidate variables, constants, and functions that are +assignable to the field, and picks the one whose name is the closest +match to the field name. +If there are none, it uses the zero value (such as `0`, `""`, or +`nil`) of the field's type. + +In the example below, a +[`slog.HandlerOptions`](https://pkg.go.dev/golang.org/x/exp/slog#HandlerOptions) +struct literal is filled in using two local variables (`level` and +`add`) and a function (`replace`): + +![Before "Fill slog.HandlerOptions"](../assets/fill-struct-before.png) +![After "Fill slog.HandlerOptions"](../assets/fill-struct-after.png) + +Caveats: + +- This code action requires type information for the struct type, so + if it is defined in another package that is not yet imported, you + may need to "organize imports" first, for example by saving the + file. +- Candidate declarations are sought only in the current file, and only + above the current point. Symbols declared beneath the current point, + or in other files in the package, are not considered; see + golang/go#68224. + +### `refactor.rewrite.fillSwitch`: Fill switch + +When the cursor is within a switch statement whose operand type is an +_enum_ (a finite set of named constants), or within a type switch, +gopls offers the "Add cases for T" code action, which populates the +switch statement by adding a case for each accessible named constant +of the enum type, or, for a type switch, by adding a case for each +accessible named non-interface type that implements the interface. +Only missing cases are added. + +The screenshots below show a type switch whose operand has the +[`net.Addr`](https://pkg.go.dev/net#Addr) interface type. The code +action adds one case per concrete network address type, plus a default +case that panics with an informative message if an unexpected operand +is encountered. + +![Before "Add cases for Addr"](../assets/fill-switch-before.png) +![After "Add cases for Addr"](../assets/fill-switch-after.png) + +And these screenshots illustrate the code action adding cases for each +value of the +[`html.TokenType`](https://pkg.go.dev/golang.org/x/net/html#TokenType) +enum type, which represents the various types of token from +which HTML documents are composed: + +![Before "Add cases for Addr"](../assets/fill-switch-enum-before.png) +![After "Add cases for Addr"](../assets/fill-switch-enum-after.png) diff --git a/contribs/gnopls/doc/features/web.md b/contribs/gnopls/doc/features/web.md new file mode 100644 index 00000000000..46a9f91477b --- /dev/null +++ b/contribs/gnopls/doc/features/web.md @@ -0,0 +1,151 @@ +# Gopls: Web-based features + +The LSP +[`window.showDocument`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showDocument) request +allows the server to instruct the client to open a file in the editor +or a web page in a browser. It is the basis for a number of gopls +features that report information about your program through a web +interface. + +We recognize that a web interface is not ideal for everyone: some +users prefer a full-screen editor layout and dislike switching +windows; others may work in a text-only terminal without a window +system, perhaps over remote ssh or on the Linux console. +Unfortunately, the LSP lacks several natural kinds of extensibility, +including the ability for servers to define: + +- queries that [generalize a References + query](https://github.com/microsoft/language-server-protocol/issues/1911), + displaying results using similar UI elements; +- commands that [produce a stream of + text](https://github.com/joaotavora/eglot/discussions/1402), like a + typical shell command or compiler, that the client can redirect to + the editor's usual terminal-like UI element; or +- refactoring operations that, like Rename, [prompt the + user](https://github.com/microsoft/language-server-protocol/issues/1164) + for additional information. + +The web-based UI can help fill these gaps until such time as the LSP +provides standard ways of implementing these features. + +Gopls' web server listens on a `localhost` port. For security, all its +endpoints include a random string that serves as an authentication +token. The client, provided authenticated URLs by the server, will be +able to access your source code, but arbitrary processes running on +your machine will not. +Restarting the gopls process causes this secret to change, rendering +all existing previous URLs invalid; existing pages will display a banner +indicating that they have become disconnected. + +TODO: combine the web server and the debug server; see golang/go#68229. + +Gopls supports two-way communication between the web browser and the +client editor. All of the web-based reports contain links to +declarations in your source code. Clicking on one of these links +causes gopls to send a `showDocument` request to your editor to open +the relevant source file at the appropriate line. This works even when +your source code has been modified but not saved. +(VS Code users: please upvote microsoft/vscode#208093 if you would +like your editor to raise its window when handling this event.) + + +## `source.doc`: Browse package documentation + +In any Go source file, a code action request returns a command to +"Browse package documentation". This command opens a browser window +showing the documentation for the current Go package, presented using +a similar design to https://pkg.go.dev. + +This allows you to preview the documentation for your packages, even +internal ones that may be unpublished externally. Reloading the page +updates the documentation to reflect your changes. It is not necessary +to save modified Go source files. + + + +Clicking on the link for a package-level symbol or method, which in +`pkg.go.dev` would ordinarily take you to a source-code viewer such as +GitHub or Google Code Search, causes your editor to navigate to the +relevant source file and line. + +Client support: +- **VS Code**: Use the "Source Action... > Browse documentation for package P" menu. +- **Emacs + eglot**: Use `M-x go-browse-doc` in [go-mode](https://github.com/dominikh/go-mode.el). +- **Vim + coc.nvim**: ?? + + + +## `source.freesymbols`: Browse free symbols + +When studying code, either to understand it or to evaluate a different +organization or factoring, it is common to need to know what the +"inputs" are to a given chunk of code, either because you are +considering extracting it into its own function and want to know what +parameters it would take, or just to understand how one piece of a long +function relates to the preceding pieces. + +If you select a chunk of code, and invoke the "Browse free symbols" +[code action](transformation.md#code-actions), your editor will +open a browser displaying a report on the free symbols of the +selection. A symbol is "free" if it is referenced from within the +selection but defined outside of it. In essence, these are the inputs +to the selected chunk. + + + +The report classifies the symbols into imported, local, and +package-level symbols. The imported symbols are grouped by package, +and link to the documentation for the package, as described above. +Each of the remaining symbols is presented as a link that causes your +editor to navigate to its declaration. + +TODO: explain dotted paths. + +Client support: +- **VS Code**: Use the "Source Action... > Browse free symbols" menu. +- **Emacs + eglot**: Use `M-x go-browse-freesymbols` in [go-mode](https://github.com/dominikh/go-mode.el). +- **Vim + coc.nvim**: ?? + + + +## `source.assembly`: Browse assembly + +When you're optimizing the performance of your code or investigating +an unexpected crash, it may sometimes be helpful to inspect the +assembly code produced by the compiler for a given Go function. + +If you position the cursor or selection within a function f, +gopls offers the "Browse assembly for f" [code action](transformation.md#code-actions). +This opens a web-based listing of the assembly for the function, plus +any functions nested within it. + +Each time you edit your source and reload the page, the current +package is recompiled and the listing is updated. It is not necessary +to save your modified files. + +The compiler's target architecture is the same as the one gopls uses +when analyzing the file: typically, this is your machine's GOARCH, but +when viewing a file with a build tag, such as one named `foo_amd64.go` +or containing the comment `//go:build amd64`, the tags determine the +architecture. + +Each instruction is displayed with a link that causes your editor to +navigate to the source line responsible for the instruction, according +to the debug information. + + + +The example above shows the arm64 assembly listing of +[`time.NewTimer`](https://pkg.go.dev/time#NewTimer). +Observe that the indicated instruction links to a source location +inside a different function, `syncTimer`, because the compiler +inlined the call from `NewTimer` to `syncTimer`. + +Browsing assembly is not yet supported for generic functions, package +initializers (`func init`), or functions in test packages. +(Contributions welcome!) + +Client support: +- **VS Code**: Use the "Source Action... > Browse GOARCH assembly for f" menu. +- **Emacs + eglot**: Use `M-x go-browse-assembly` in [go-mode](https://github.com/dominikh/go-mode.el). +- **Vim + coc.nvim**: ?? diff --git a/contribs/gnopls/doc/generate/generate.go b/contribs/gnopls/doc/generate/generate.go new file mode 100644 index 00000000000..994933a3681 --- /dev/null +++ b/contribs/gnopls/doc/generate/generate.go @@ -0,0 +1,795 @@ +// 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/analyzers.md -- from linking gopls/internal/settings.DefaultAnalyzers +// gopls/doc/inlayHints.md -- from loading gopls/internal/settings.InlayHint +// gopls/internal/doc/api.json -- all of the above in a single value, for 'gopls api-json' +// +// Run it with this command: +// +// $ cd gopls/internal/doc && go generate +// +// TODO(adonovan): move this package to gopls/internal/doc/generate. +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "go/ast" + "go/token" + "go/types" + "maps" + "os" + "os/exec" + "path/filepath" + "reflect" + "regexp" + "slices" + "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/settings" + "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) { + 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/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.Lenses, err = loadLenses(settingsPkg, defaults.Codelenses) + if err != nil { + return nil, err + } + api.Hints, err = loadHints(settingsPkg) + if err != nil { + return nil, err + } + + 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 + } + + // Edge case for "analyses": populate its enum keys from + // the analyzer list, since its map keys are strings, not enums. + // Also, set its EnumKeys.ValueType for historical reasons. + for _, opt := range opts { + if opt.Name == "analyses" { + opt.EnumKeys.ValueType = "bool" + 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), + }) + } + } + } + + catName := strings.TrimSuffix(category.Type().Name(), "Options") + api.Options[catName] = opts + } + 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) // TODO(adonovan): do this only once at toplevel. + 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 { + if values, ok := enums[m.Key()]; 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) + + enumKeys.ValueType = m.Elem().String() // e.g. bool + + // 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 := make(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) + } + + // linksInHover is a one-off edge case (true | false | "gopls") + // that doesn't warrant a general solution (e.g. struct tag). + enums[pkg.Types.Scope().Lookup("LinksInHoverEnum").Type()] = []doc.EnumValue{ + {Value: "false", Doc: "false: do not show links"}, + {Value: "true", Doc: "true: show links to the `linkTarget` domain"}, + {Value: `"gopls"`, Doc: "`\"gopls\"`: show links to gopls' internal documentation viewer"}, + } + + 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) +} + +// 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 { + for _, source := range slices.Sorted(maps.Keys(sources)) { + 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 +} + +// loadHints derives and returns the inlay hints metadata from the settings.InlayHint type. +func loadHints(settingsPkg *packages.Package) ([]*doc.Hint, error) { + enums, err := loadEnums(settingsPkg) // TODO(adonovan): call loadEnums exactly once + if err != nil { + return nil, err + } + inlayHint := settingsPkg.Types.Scope().Lookup("InlayHint").Type() + var hints []*doc.Hint + for _, enumVal := range enums[inlayHint] { + name, _ := strconv.Unquote(enumVal.Value) + hints = append(hints, &doc.Hint{ + Name: name, + Doc: enumVal.Doc, + }) + } + return hints, nil +} + +func lowerFirst(x string) string { + if x == "" { + return x + } + return strings.ToLower(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, "\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, "\n", opt.Name) + + // heading + // + // We do not display the undocumented dotted-path alias + // (h.title + "." + opt.Name) used by VS Code only. + fmt.Fprintf(&buf, "### `%s %s`\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 rewriteAnalyzers(prevContent []byte, api *doc.API) ([]byte, error) { + var buf bytes.Buffer + for _, analyzer := range api.Analyzers { + fmt.Fprintf(&buf, "\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: +// +// +// +func replaceSection(content []byte, sectionName string, replacement []byte) ([]byte, error) { + re := regexp.MustCompile(fmt.Sprintf(`(?s)\n(.*?)`, 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" + } +} diff --git a/contribs/gnopls/doc/generate/generate_test.go b/contribs/gnopls/doc/generate/generate_test.go new file mode 100644 index 00000000000..da3c6792d8f --- /dev/null +++ b/contribs/gnopls/doc/generate/generate_test.go @@ -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 ./...") + } +} diff --git a/contribs/gnopls/doc/helix.md b/contribs/gnopls/doc/helix.md new file mode 100644 index 00000000000..209ffdaaa81 --- /dev/null +++ b/contribs/gnopls/doc/helix.md @@ -0,0 +1,51 @@ +# Gopls: Using 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 +``` + + diff --git a/contribs/gnopls/doc/inlayHints.md b/contribs/gnopls/doc/inlayHints.md new file mode 100644 index 00000000000..0e84d43f1da --- /dev/null +++ b/contribs/gnopls/doc/inlayHints.md @@ -0,0 +1,91 @@ +# Gopls: Inlay hints + +Inlay hints are helpful annotations that the editor can optionally +display in-line in the source code, such as the names of parameters in +a function call. This document describes the inlay hints available +from `gopls`. + + + +## **assignVariableTypes** + +`"assignVariableTypes"` controls 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** + +`"compositeLiteralFields"` 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** + +`"compositeLiteralTypes"` controls 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** + +`"constantValues"` controls 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** + +`"functionTypeParameters"` 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** + +`"parameterNames"` controls inlay hints for parameter names: +```go + parseInt(/* str: */ "123", /* radix: */ 8) +``` + + +**Disabled by default. Enable it by setting `"hints": {"parameterNames": true}`.** + +## **rangeVariableTypes** + +`"rangeVariableTypes"` controls 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}`.** + + diff --git a/contribs/gnopls/doc/inline-after.png b/contribs/gnopls/doc/inline-after.png new file mode 100644 index 0000000000000000000000000000000000000000..843a8454136bd247591ab406d34b1ffcfdda90ce GIT binary patch literal 30596 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/refactor-inline.md b/contribs/gnopls/doc/refactor-inline.md new file mode 100644 index 00000000000..cdddeb29e6e --- /dev/null +++ b/contribs/gnopls/doc/refactor-inline.md @@ -0,0 +1,163 @@ + + + +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`. + + +![Before: select Refactor... Inline call to sum](inline-before.png) +![After: the call has been replaced by the sum logic](inline-after.png) + +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. diff --git a/contribs/gnopls/doc/release/README b/contribs/gnopls/doc/release/README new file mode 100644 index 00000000000..e489c33f183 --- /dev/null +++ b/contribs/gnopls/doc/release/README @@ -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`). diff --git a/contribs/gnopls/doc/release/v0.16.0.md b/contribs/gnopls/doc/release/v0.16.0.md new file mode 100644 index 00000000000..1bcb5ec3d06 --- /dev/null +++ b/contribs/gnopls/doc/release/v0.16.0.md @@ -0,0 +1,288 @@ +# gopls/v0.16.0 + + +``` +go install golang.org/x/tools/gopls@v0.16.0-pre.2 +``` + +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 golang/go#65917 if +you have concerns about this change. + +## Configuration changes + +- The experimental `allowImplicitNetworkAccess` setting is deprecated (but not + yet removed). Please comment on golang/go#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 the new language features of Go 1.23, +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 documentation" code action that opens a +local web page displaying the generated documentation for Go packages +and symbols in a form similar to https://pkg.go.dev. +The package or symbol is chosen based on the current selection. + +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. + + + +As in `pkg.go.dev`, the heading for each symbol contains a link to the +source code of its declaration. In `pkg.go.dev`, these links would refer +to a source code page on a site such as GitHub or Google Code Search. +However, in gopls' internal viewer, clicking on one of these links will +cause your editor to navigate to the declaration. +(This feature requires that your LSP client honors the `showDocument` downcall.) + + + +Editor support: +- VS Code: use the "Source action > Browse documentation for func fmt.Println" menu item. + Note: source links navigate the editor but don't yet raise the window yet. + Please upvote microsoft/vscode#208093 and microsoft/vscode#207634 (temporarily closed). +- Emacs: requires eglot v1.17. Use `M-x go-browse-doc` from github.com/dominikh/go-mode.el. + +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 +approximately the set of parameters that would be needed if the block +were extracted into its own function. + +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. + + + +Editor support: +- VS Code: use the `Source action > Browse free symbols` menu item. +- Emacs: requires eglot v1.17. Use `M-x go-browse-freesymbols` from github.com/dominikh/go-mode.el. + +### Browse assembly + +Gopls offers a third web-based code action, "Browse assembly for f", +which displays an assembly listing of the declaration of the function +f enclosing the selected code, plus any nested functions such as +function literals or deferred calls. + +Gopls invokes the compiler to generate the report; +reloading the page updates the report. + +The machine architecture is determined by the 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. + + + +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. + + +Editor support: +- VS Code: use the "Source action > Browse assembly for f" menu item. +- Emacs: requires eglot v1.17. Use `M-x go-browse-assembly` from github.com/dominikh/go-mode.el. + +### `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. + +Consider the go.mod file and Go file below. +The declaration of `var `alias refers to a type, `types.Alias`, +introduced in go1.22, but the file belongs to a module that requires +only go1.21, so the analyzer reports a diagnostic: + +``` +module example.com +go 1.21 +``` + +```go +package p + +import "go/types" + +var alias types.Alias // types.Alias requires go1.22 or later (module is go1.21) +``` + +When an individual file is build-tagged for a release of Go other than +than module's version, the analyzer will apply appropriate checks for +the file's version. + +### 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 + +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: + + + +In the struct above, alignment rules require each of the two boolean +fields (1 byte) to occupy a complete word (8 bytes), leading to (7 + +7) / (3 * 8) = 58% waste. +Placing the two booleans together would save a word. + +This information may be helpful when making space optimizations to +your data structures, or when reading assembly code. + +Also, hovering over a reference to a field with a struct tag now also +display the tag: + + + +### Hover and "Go to Definition" work on symbols in doc comments + +Go 1.19 added support for [doc links](https://go.dev/doc/comment#links), +allowing the doc comment for one symbol to reference another. + +Gopls' Hover and Definition operations now treat these links just +like identifiers, so hovering over one will display information about +the symbol: + + + +Similarly, "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 diff --git a/contribs/gnopls/doc/release/v0.17.0.md b/contribs/gnopls/doc/release/v0.17.0.md new file mode 100644 index 00000000000..c57522973db --- /dev/null +++ b/contribs/gnopls/doc/release/v0.17.0.md @@ -0,0 +1,56 @@ + + +# Configuration Changes + +The `fieldalignment` analyzer, previously disabled by default, has +been removed: it is redundant with the hover size/offset information +displayed by v0.16.0 and its diagnostics were confusing. + +The kind (identifiers) of all of gopls' code actions have changed +to use more specific hierarchical names. For example, "Inline call" +has changed from `refactor.inline` to `refactor.inline.call`. +This allows clients to request particular code actions more precisely. +The user manual now includes the identifier in the documentation for each code action. + +# New features + +## Extract declarations to new file + +Gopls now offers another code action, +"Extract declarations to new file" (`refactor.extract.toNewFile`), +which moves selected code sections to a newly created file within the +same package. The created filename is chosen as the first {function, type, +const, var} name encountered. In addition, import declarations are added or +removed as needed. + +The user can invoke this code action by selecting a function name, the keywords +`func`, `const`, `var`, `type`, or by placing the caret on them without selecting, +or by selecting a whole declaration or multiple declrations. + +In order to avoid ambiguity and surprise about what to extract, some kinds +of paritial selection of a declration cannot invoke this code action. + +## Standard library version information in Hover + +Hovering over a standard library symbol now displays information about the first +Go release containing the symbol. For example, hovering over `errors.As` shows +"Added in go1.13". + +## Semantic token modifiers of top-level constructor of types +The semantic tokens response now includes additional modifiers for the top-level +constructor of the type of each symbol: +`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, and `invalid`. +Editors may use this for syntax coloring. + +## SignatureHelp for ident and values. + +Now, function signature help can be used on any identifier with a function +signature, not just within the parentheses of a function being called. + +## Jump to assembly definition + +A Definition query on a reference to a function jumps to the +function's Go `func` declaration. If the function is implemented in C +or assembly, the function has no body. Executing a second Definition +query (while already at the Go declaration) will navigate you to the +assembly implementation. diff --git a/contribs/gnopls/doc/releases.md b/contribs/gnopls/doc/releases.md new file mode 100644 index 00000000000..c4220e41116 --- /dev/null +++ b/contribs/gnopls/doc/releases.md @@ -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*..0` | +| Jan-Mar | `v*..*` | +| Apr | `v*..0` | +| Apr-Jun | `v*..*` | +| Jul | `v*..0` | +| Jul-Sep | `v*..*` | +| Oct | `v*..0` | +| Oct-Dec | `v*..*` | + +For more background on this policy, see https://go.dev/issue/55267. diff --git a/contribs/gnopls/doc/semantictokens.md b/contribs/gnopls/doc/semantictokens.md new file mode 100644 index 00000000000..f17ea7f06d8 --- /dev/null +++ b/contribs/gnopls/doc/semantictokens.md @@ -0,0 +1,124 @@ +# Gopls: Semantic Tokens + +TODO(adonovan): this doc is internal, not for end users. +Move it closer to the code in golang or protocol/semtok. + +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`. It also reports +a modifier for the top-level constructor of the object's type, one of: +`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, `invalid`. +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. diff --git a/contribs/gnopls/doc/settings.md b/contribs/gnopls/doc/settings.md new file mode 100644 index 00000000000..db6092a980c --- /dev/null +++ b/contribs/gnopls/doc/settings.md @@ -0,0 +1,563 @@ +# Gopls: Settings + +This document describes gopls' configuration settings. + +Gopls settings are defined by a 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. + + + + + + +* [Build](#build) +* [Formatting](#formatting) +* [UI](#ui) + * [Completion](#completion) + * [Diagnostic](#diagnostic) + * [Documentation](#documentation) + * [Inlayhint](#inlayhint) + * [Navigation](#navigation) + + +## Build + + +### `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: `[]`. + + +### `env map[string]string` + +env adds environment variables to external commands run by `gopls`, most notably `go list`. + +Default: `{}`. + + +### `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"]`. + + +### `templateExtensions []string` + +templateExtensions gives the extensions of file names that are treated +as template files. (The extension +is the part of the file name after the final dot.) + +Default: `[]`. + + +### `memoryMode string` + +**This setting is experimental and may be deleted.** + +obsolete, no effect + +Default: `""`. + + +### `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`. + + +### `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`. + + +### `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"]`. + + +## Formatting + + +### `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. + +It is used when tidying imports (during an LSP Organize +Imports request) or when inserting new ones (for example, +during completion); an LSP Formatting request merely sorts the +existing imports. + +Default: `""`. + + +### `gofumpt bool` + +gofumpt indicates if we should run gofumpt formatting. + +Default: `false`. + + +## UI + + +### `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}`. + + +### `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`. + + +### `noSemanticString bool` + +**This setting is experimental and may be deleted.** + +noSemanticString turns off the sending of the semantic token 'string' + +Default: `false`. + + +### `noSemanticNumber bool` + +**This setting is experimental and may be deleted.** + +noSemanticNumber turns off the sending of the semantic token 'number' + +Default: `false`. + + +## Completion + + +### `usePlaceholders bool` + +placeholders enables placeholders for function parameters or struct +fields in completion responses. + +Default: `false`. + + +### `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"`. + + +### `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"`. + + +### `experimentalPostfixCompletions bool` + +**This setting is experimental and may be deleted.** + +experimentalPostfixCompletions enables artificial method snippets +such as "someSlice.sort!". + +Default: `true`. + + +### `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`. + + +## Diagnostic + + +### `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: `{}`. + + +### `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`. + + +### `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}`. + + +### `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"`. + + +### `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"`. + + +### `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"`. + + +### `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`. + + +## Documentation + + +### `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"`. + + +### `linkTarget string` + +linkTarget is the base URL for links to Go package +documentation returned by LSP operations such as Hover and +DocumentLinks and in the CodeDescription field of each +Diagnostic. + +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"`. + + +### `linksInHover enum` + +linksInHover controls the presence of documentation links in hover markdown. + +Must be one of: + +* false: do not show links +* true: show links to the `linkTarget` domain +* `"gopls"`: show links to gopls' internal documentation viewer + +Default: `true`. + + +## Inlayhint + + +### `hints map[enum]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: `{}`. + + +## Navigation + + +### `importShortcut enum` + +importShortcut specifies whether import statements should link to +documentation or go to definitions. + +Must be one of: + +* `"Both"` +* `"Definition"` +* `"Link"` + +Default: `"Both"`. + + +### `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"`. + + +### `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"`. + + +### `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"`. + + +### `verboseOutput bool` + +**This setting is for debugging purposes only.** + +verboseOutput enables additional debug logging. + +Default: `false`. + + diff --git a/contribs/gnopls/doc/subl.md b/contribs/gnopls/doc/subl.md new file mode 100644 index 00000000000..37ddf9f5a96 --- /dev/null +++ b/contribs/gnopls/doc/subl.md @@ -0,0 +1,81 @@ +# Gopls: Using 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:
+``` +{ + "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 diff --git a/contribs/gnopls/doc/troubleshooting.md b/contribs/gnopls/doc/troubleshooting.md new file mode 100644 index 00000000000..5c064fd2cad --- /dev/null +++ b/contribs/gnopls/doc/troubleshooting.md @@ -0,0 +1,48 @@ +# Gopls: 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 ` 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. diff --git a/contribs/gnopls/doc/vim.md b/contribs/gnopls/doc/vim.md new file mode 100644 index 00000000000..e71482115ea --- /dev/null +++ b/contribs/gnopls/doc/vim.md @@ -0,0 +1,234 @@ +# Gopls: Using Vim or 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) + +## vim-go + +Use [vim-go] ver 1.20+, with the following configuration: + +```vim +let g:go_def_mode='gopls' +let g:go_info_mode='gopls' +``` + +## LanguageClient-neovim + +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() +``` + +## Ale + +Use [ale]: + +```vim +let g:ale_linters = { + \ 'go': ['gopls'], + \} +``` + +see [this issue][ale-issue-2179] + +## vim-lsp + +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 gd (lsp-definition) + "autocmd FileType go nmap ,n (lsp-next-error) + "autocmd FileType go nmap ,p (lsp-previous-error) +augroup END +``` + +## vim-lsc + +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). + +## coc.nvim + +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') +``` + +## govim + +In vim classic only, use the experimental [`govim`], simply follow the [install steps][govim-install]. + +## Neovim v0.5.0+ + +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. + +### Installation + +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' . +``` + +### Configuration + +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, + }, + }, +}) +``` + +### Imports and Formatting + +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 +}) +``` + +### Omnifunc + +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 + vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc') +end +require('lspconfig').gopls.setup({ + on_attach = on_attach +}) +``` + +### Additional Links + +* [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 diff --git a/contribs/gnopls/doc/workspace.md b/contribs/gnopls/doc/workspace.md new file mode 100644 index 00000000000..766175dd3b1 --- /dev/null +++ b/contribs/gnopls/doc/workspace.md @@ -0,0 +1,139 @@ +# Gopls: 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: + +![Zero Config gopls](zeroconfig.png) + +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. diff --git a/contribs/gnopls/doc/zeroconfig.png b/contribs/gnopls/doc/zeroconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..49d4f8ead741c5999737582884c2f095c7c38960 GIT binary patch literal 35409 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 b4O|82styj8_|B)@0Y2oVm8D7~OalK8hv0q> literal 0 HcmV?d00001 diff --git a/contribs/gnopls/go.mod b/contribs/gnopls/go.mod new file mode 100644 index 00000000000..759670af3cd --- /dev/null +++ b/contribs/gnopls/go.mod @@ -0,0 +1,31 @@ +module golang.org/x/tools/gopls + +// go 1.23.1 fixes some bugs in go/types Alias support. +// (golang/go#68894 and golang/go#68905). +go 1.23.1 + +require ( + github.com/google/go-cmp v0.6.0 + github.com/jba/templatecheck v0.7.0 + golang.org/x/mod v0.21.0 + golang.org/x/sync v0.8.0 + golang.org/x/telemetry v0.0.0-20240829154258-f29ab539cc98 + golang.org/x/text v0.18.0 + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d + 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.7.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.25.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + +) + +replace golang.org/x/tools => ../ diff --git a/contribs/gnopls/go.sum b/contribs/gnopls/go.sum new file mode 100644 index 00000000000..2819e487d71 --- /dev/null +++ b/contribs/gnopls/go.sum @@ -0,0 +1,61 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +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/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +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/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +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.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= +mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= +mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= diff --git a/contribs/gnopls/integration/govim/Dockerfile b/contribs/gnopls/integration/govim/Dockerfile new file mode 100644 index 00000000000..75944ebf9ca --- /dev/null +++ b/contribs/gnopls/integration/govim/Dockerfile @@ -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 diff --git a/contribs/gnopls/integration/govim/README.md b/contribs/gnopls/integration/govim/README.md new file mode 100644 index 00000000000..444bc13da21 --- /dev/null +++ b/contribs/gnopls/integration/govim/README.md @@ -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 diff --git a/contribs/gnopls/integration/govim/artifacts.go b/contribs/gnopls/integration/govim/artifacts.go new file mode 100644 index 00000000000..db375a21e41 --- /dev/null +++ b/contribs/gnopls/integration/govim/artifacts.go @@ -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=] + +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 +} diff --git a/contribs/gnopls/integration/govim/cloudbuild.harness.yaml b/contribs/gnopls/integration/govim/cloudbuild.harness.yaml new file mode 100644 index 00000000000..13b0a34c3cf --- /dev/null +++ b/contribs/gnopls/integration/govim/cloudbuild.harness.yaml @@ -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 diff --git a/contribs/gnopls/integration/govim/cloudbuild.yaml b/contribs/gnopls/integration/govim/cloudbuild.yaml new file mode 100644 index 00000000000..5efc93b0f8d --- /dev/null +++ b/contribs/gnopls/integration/govim/cloudbuild.yaml @@ -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}' diff --git a/contribs/gnopls/integration/govim/run_local.sh b/contribs/gnopls/integration/govim/run_local.sh new file mode 100755 index 00000000000..b5c284fa1e1 --- /dev/null +++ b/contribs/gnopls/integration/govim/run_local.sh @@ -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 < /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 diff --git a/contribs/gnopls/internal/analysis/deprecated/deprecated.go b/contribs/gnopls/internal/analysis/deprecated/deprecated.go new file mode 100644 index 00000000000..1a8c4c56766 --- /dev/null +++ b/contribs/gnopls/internal/analysis/deprecated/deprecated.go @@ -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 +} diff --git a/contribs/gnopls/internal/analysis/deprecated/deprecated_test.go b/contribs/gnopls/internal/analysis/deprecated/deprecated_test.go new file mode 100644 index 00000000000..89bf3bea252 --- /dev/null +++ b/contribs/gnopls/internal/analysis/deprecated/deprecated_test.go @@ -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") +} diff --git a/contribs/gnopls/internal/analysis/deprecated/doc.go b/contribs/gnopls/internal/analysis/deprecated/doc.go new file mode 100644 index 00000000000..0d96b86b302 --- /dev/null +++ b/contribs/gnopls/internal/analysis/deprecated/doc.go @@ -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 diff --git a/contribs/gnopls/internal/analysis/deprecated/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/deprecated/testdata/src/a/a.go new file mode 100644 index 00000000000..7ffa07dc517 --- /dev/null +++ b/contribs/gnopls/internal/analysis/deprecated/testdata/src/a/a.go @@ -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." diff --git a/contribs/gnopls/internal/analysis/deprecated/testdata/src/a/a_test.go b/contribs/gnopls/internal/analysis/deprecated/testdata/src/a/a_test.go new file mode 100644 index 00000000000..bf88d395b00 --- /dev/null +++ b/contribs/gnopls/internal/analysis/deprecated/testdata/src/a/a_test.go @@ -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() +} diff --git a/contribs/gnopls/internal/analysis/embeddirective/doc.go b/contribs/gnopls/internal/analysis/embeddirective/doc.go new file mode 100644 index 00000000000..bfed47f14f4 --- /dev/null +++ b/contribs/gnopls/internal/analysis/embeddirective/doc.go @@ -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 diff --git a/contribs/gnopls/internal/analysis/embeddirective/embeddirective.go b/contribs/gnopls/internal/analysis/embeddirective/embeddirective.go new file mode 100644 index 00000000000..1b0b89711c2 --- /dev/null +++ b/contribs/gnopls/internal/analysis/embeddirective/embeddirective.go @@ -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 +} diff --git a/contribs/gnopls/internal/analysis/embeddirective/embeddirective_test.go b/contribs/gnopls/internal/analysis/embeddirective/embeddirective_test.go new file mode 100644 index 00000000000..22e43af78ed --- /dev/null +++ b/contribs/gnopls/internal/analysis/embeddirective/embeddirective_test.go @@ -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") +} diff --git a/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/embedText b/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/embedText new file mode 100644 index 00000000000..5e1c309dae7 --- /dev/null +++ b/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/embedText @@ -0,0 +1 @@ +Hello World \ No newline at end of file diff --git a/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_missing.go b/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_missing.go new file mode 100644 index 00000000000..4b21dc60449 --- /dev/null +++ b/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_missing.go @@ -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) +} diff --git a/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_present.go b/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_present.go new file mode 100644 index 00000000000..a124a583f75 --- /dev/null +++ b/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_present.go @@ -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" diff --git a/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_present_go120.go b/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_present_go120.go new file mode 100644 index 00000000000..2eaad23c4b0 --- /dev/null +++ b/contribs/gnopls/internal/analysis/embeddirective/testdata/src/a/import_present_go120.go @@ -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) +} diff --git a/contribs/gnopls/internal/analysis/fillreturns/doc.go b/contribs/gnopls/internal/analysis/fillreturns/doc.go new file mode 100644 index 00000000000..584aec47db9 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillreturns/doc.go @@ -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 diff --git a/contribs/gnopls/internal/analysis/fillreturns/fillreturns.go b/contribs/gnopls/internal/analysis/fillreturns/fillreturns.go new file mode 100644 index 00000000000..5e18c1c6642 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillreturns/fillreturns.go @@ -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/gopls/internal/fuzzy" + "golang.org/x/tools/internal/analysisinternal" +) + +//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 +} diff --git a/contribs/gnopls/internal/analysis/fillreturns/fillreturns_test.go b/contribs/gnopls/internal/analysis/fillreturns/fillreturns_test.go new file mode 100644 index 00000000000..45c7846a7dc --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillreturns/fillreturns_test.go @@ -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") +} diff --git a/contribs/gnopls/internal/analysis/fillreturns/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/fillreturns/testdata/src/a/a.go new file mode 100644 index 00000000000..7ab0ff167d8 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillreturns/testdata/src/a/a.go @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/fillreturns/testdata/src/a/a.go.golden b/contribs/gnopls/internal/analysis/fillreturns/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..f007a5f374b --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillreturns/testdata/src/a/a.go.golden @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/fillreturns/testdata/src/typeparams/a.go b/contribs/gnopls/internal/analysis/fillreturns/testdata/src/typeparams/a.go new file mode 100644 index 00000000000..8454bd2ce4f --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillreturns/testdata/src/typeparams/a.go @@ -0,0 +1,5 @@ +package fillreturns + +func hello[T any]() int { + return +} diff --git a/contribs/gnopls/internal/analysis/fillreturns/testdata/src/typeparams/a.go.golden b/contribs/gnopls/internal/analysis/fillreturns/testdata/src/typeparams/a.go.golden new file mode 100644 index 00000000000..8454bd2ce4f --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillreturns/testdata/src/typeparams/a.go.golden @@ -0,0 +1,5 @@ +package fillreturns + +func hello[T any]() int { + return +} diff --git a/contribs/gnopls/internal/analysis/fillstruct/fillstruct.go b/contribs/gnopls/internal/analysis/fillstruct/fillstruct.go new file mode 100644 index 00000000000..b42b8232f22 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillstruct/fillstruct.go @@ -0,0 +1,498 @@ +// 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/fuzzy" + "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/typeparams" + "golang.org/x/tools/internal/typesinternal" +) + +// 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, typesinternal.NameRelativeTo(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, typesinternal.NameRelativeTo(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 +} diff --git a/contribs/gnopls/internal/analysis/fillstruct/fillstruct_test.go b/contribs/gnopls/internal/analysis/fillstruct/fillstruct_test.go new file mode 100644 index 00000000000..e0ad83de83b --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillstruct/fillstruct_test.go @@ -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") +} diff --git a/contribs/gnopls/internal/analysis/fillstruct/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/fillstruct/testdata/src/a/a.go new file mode 100644 index 00000000000..4a16a803379 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillstruct/testdata/src/a/a.go @@ -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 `fillstruct.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 `ast.BasicLit literal has missing fields` +} + +var _ = []ast.BasicLit{{}} // want "ast.BasicLit literal has missing fields" + +type unsafeStruct struct { + foo unsafe.Pointer +} + +var _ = unsafeStruct{} // want `unsafeStruct literal has missing fields` diff --git a/contribs/gnopls/internal/analysis/fillstruct/testdata/src/b/b.go b/contribs/gnopls/internal/analysis/fillstruct/testdata/src/b/b.go new file mode 100644 index 00000000000..a4b394605aa --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillstruct/testdata/src/b/b.go @@ -0,0 +1,6 @@ +package fillstruct + +type B struct { + ExportedInt int + unexportedInt int +} diff --git a/contribs/gnopls/internal/analysis/fillstruct/testdata/src/typeparams/typeparams.go b/contribs/gnopls/internal/analysis/fillstruct/testdata/src/typeparams/typeparams.go new file mode 100644 index 00000000000..24e8a930dc2 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillstruct/testdata/src/typeparams/typeparams.go @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/fillswitch/doc.go b/contribs/gnopls/internal/analysis/fillswitch/doc.go new file mode 100644 index 00000000000..076c3a1323d --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillswitch/doc.go @@ -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 diff --git a/contribs/gnopls/internal/analysis/fillswitch/fillswitch.go b/contribs/gnopls/internal/analysis/fillswitch/fillswitch.go new file mode 100644 index 00000000000..7b1a7e8cbe5 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillswitch/fillswitch.go @@ -0,0 +1,300 @@ +// 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" + "golang.org/x/tools/internal/typesinternal" +) + +// 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: "Add cases for " + types.TypeString(namedType, typesinternal.NameRelativeTo(pkg)), + 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: "Add cases for " + types.TypeString(namedType, typesinternal.NameRelativeTo(pkg)), + 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 +} diff --git a/contribs/gnopls/internal/analysis/fillswitch/fillswitch_test.go b/contribs/gnopls/internal/analysis/fillswitch/fillswitch_test.go new file mode 100644 index 00000000000..bf70aa39648 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillswitch/fillswitch_test.go @@ -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") +} diff --git a/contribs/gnopls/internal/analysis/fillswitch/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/fillswitch/testdata/src/a/a.go new file mode 100644 index 00000000000..6fa33ec8ffd --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillswitch/testdata/src/a/a.go @@ -0,0 +1,76 @@ +// 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 altb "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 altb.TypeB + switch b { // want `Add cases for b.TypeB` + case altb.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 { + } +} diff --git a/contribs/gnopls/internal/analysis/fillswitch/testdata/src/b/b.go b/contribs/gnopls/internal/analysis/fillswitch/testdata/src/b/b.go new file mode 100644 index 00000000000..6e40a8186e7 --- /dev/null +++ b/contribs/gnopls/internal/analysis/fillswitch/testdata/src/b/b.go @@ -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 b + +type TypeB int + +const ( + TypeBOne TypeB = iota + TypeBTwo + TypeBThree +) + +type ExportedInterface interface { + isExportedInterface() +} + +type notExportedType struct{} + +func (notExportedType) isExportedInterface() {} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs.go b/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs.go new file mode 100644 index 00000000000..9a514ad620c --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs.go @@ -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 +} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs_test.go b/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs_test.go new file mode 100644 index 00000000000..25c88e84f29 --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs_test.go @@ -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") +} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/basic.go b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/basic.go new file mode 100644 index 00000000000..1c3d88ba1ad --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/basic.go @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/basic.go.golden b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/basic.go.golden new file mode 100644 index 00000000000..72348ff7750 --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/basic.go.golden @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported.go b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported.go new file mode 100644 index 00000000000..fc1f763df6c --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported.go @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported.go.golden b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported.go.golden new file mode 100644 index 00000000000..6099545bbab --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported.go.golden @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported/imported.go b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported/imported.go new file mode 100644 index 00000000000..f0610a8b4ca --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/imported/imported.go @@ -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) {} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/notypechange.go b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/notypechange.go new file mode 100644 index 00000000000..c304f1d0d2a --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/notypechange.go @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/notypechange.go.golden b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/notypechange.go.golden new file mode 100644 index 00000000000..93c6f707c32 --- /dev/null +++ b/contribs/gnopls/internal/analysis/infertypeargs/testdata/src/a/notypechange.go.golden @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/nonewvars/doc.go b/contribs/gnopls/internal/analysis/nonewvars/doc.go new file mode 100644 index 00000000000..b0bef847e32 --- /dev/null +++ b/contribs/gnopls/internal/analysis/nonewvars/doc.go @@ -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 diff --git a/contribs/gnopls/internal/analysis/nonewvars/nonewvars.go b/contribs/gnopls/internal/analysis/nonewvars/nonewvars.go new file mode 100644 index 00000000000..b9c9b4d6f48 --- /dev/null +++ b/contribs/gnopls/internal/analysis/nonewvars/nonewvars.go @@ -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 :=" +} diff --git a/contribs/gnopls/internal/analysis/nonewvars/nonewvars_test.go b/contribs/gnopls/internal/analysis/nonewvars/nonewvars_test.go new file mode 100644 index 00000000000..49e19db2f0c --- /dev/null +++ b/contribs/gnopls/internal/analysis/nonewvars/nonewvars_test.go @@ -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") +} diff --git a/contribs/gnopls/internal/analysis/nonewvars/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/nonewvars/testdata/src/a/a.go new file mode 100644 index 00000000000..97d8fcde127 --- /dev/null +++ b/contribs/gnopls/internal/analysis/nonewvars/testdata/src/a/a.go @@ -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) +} diff --git a/contribs/gnopls/internal/analysis/nonewvars/testdata/src/a/a.go.golden b/contribs/gnopls/internal/analysis/nonewvars/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..17197e56458 --- /dev/null +++ b/contribs/gnopls/internal/analysis/nonewvars/testdata/src/a/a.go.golden @@ -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) +} diff --git a/contribs/gnopls/internal/analysis/nonewvars/testdata/src/typeparams/a.go b/contribs/gnopls/internal/analysis/nonewvars/testdata/src/typeparams/a.go new file mode 100644 index 00000000000..b381c9c0924 --- /dev/null +++ b/contribs/gnopls/internal/analysis/nonewvars/testdata/src/typeparams/a.go @@ -0,0 +1,6 @@ +package nonewvars + +func hello[T any]() int { + var z T + z := 1 // want "no new variables on left side of :=" +} diff --git a/contribs/gnopls/internal/analysis/nonewvars/testdata/src/typeparams/a.go.golden b/contribs/gnopls/internal/analysis/nonewvars/testdata/src/typeparams/a.go.golden new file mode 100644 index 00000000000..3a511730124 --- /dev/null +++ b/contribs/gnopls/internal/analysis/nonewvars/testdata/src/typeparams/a.go.golden @@ -0,0 +1,6 @@ +package nonewvars + +func hello[T any]() int { + var z T + z = 1 // want "no new variables on left side of :=" +} diff --git a/contribs/gnopls/internal/analysis/norangeoverfunc/norangeoverfunc.go b/contribs/gnopls/internal/analysis/norangeoverfunc/norangeoverfunc.go new file mode 100644 index 00000000000..aa58e89d75b --- /dev/null +++ b/contribs/gnopls/internal/analysis/norangeoverfunc/norangeoverfunc.go @@ -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 +} diff --git a/contribs/gnopls/internal/analysis/noresultvalues/doc.go b/contribs/gnopls/internal/analysis/noresultvalues/doc.go new file mode 100644 index 00000000000..87df2093e8d --- /dev/null +++ b/contribs/gnopls/internal/analysis/noresultvalues/doc.go @@ -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 diff --git a/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues.go b/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues.go new file mode 100644 index 00000000000..a5cd424a762 --- /dev/null +++ b/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues.go @@ -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 ()") +} diff --git a/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues_test.go b/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues_test.go new file mode 100644 index 00000000000..e9f1a36ab6f --- /dev/null +++ b/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues_test.go @@ -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") +} diff --git a/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/a/a.go new file mode 100644 index 00000000000..3daa7f7c767 --- /dev/null +++ b/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/a/a.go @@ -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` diff --git a/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/a/a.go.golden b/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..5e93aa41354 --- /dev/null +++ b/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/a/a.go.golden @@ -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` diff --git a/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/typeparams/a.go b/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/typeparams/a.go new file mode 100644 index 00000000000..f8aa43665cb --- /dev/null +++ b/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/typeparams/a.go @@ -0,0 +1,6 @@ +package noresult + +func hello[T any]() { + var z T + return z // want `no result values expected|too many return values` +} diff --git a/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/typeparams/a.go.golden b/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/typeparams/a.go.golden new file mode 100644 index 00000000000..963e3f4e1ad --- /dev/null +++ b/contribs/gnopls/internal/analysis/noresultvalues/testdata/src/typeparams/a.go.golden @@ -0,0 +1,6 @@ +package noresult + +func hello[T any]() { + var z T + return // want `no result values expected|too many return values` +} diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/doc.go b/contribs/gnopls/internal/analysis/simplifycompositelit/doc.go new file mode 100644 index 00000000000..bda74c7db3f --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/doc.go @@ -0,0 +1,24 @@ +// 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. +// +// This analyzer ignores generated code. +package simplifycompositelit diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit.go b/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit.go new file mode 100644 index 00000000000..6511477d254 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit.go @@ -0,0 +1,205 @@ +// 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) { + // Gather information whether file is generated or not + generated := make(map[*token.File]bool) + for _, file := range pass.Files { + if ast.IsGenerated(file) { + generated[pass.Fset.File(file.Pos())] = true + } + } + + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)} + inspect.Preorder(nodeFilter, func(n ast.Node) { + if _, ok := generated[pass.Fset.File(n.Pos())]; ok { + return // skip checking if it's generated code + } + + 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)) +) diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit_test.go b/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit_test.go new file mode 100644 index 00000000000..4445a0cbb2f --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit_test.go @@ -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", "generatedcode") +} diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/a/a.go new file mode 100644 index 00000000000..14e0fa3ae79 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/a/a.go @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/a/a.go.golden b/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..6bfed45a5d2 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/a/a.go.golden @@ -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" +} diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/generatedcode/generatedcode.go b/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/generatedcode/generatedcode.go new file mode 100644 index 00000000000..7b11dc5ba47 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/generatedcode/generatedcode.go @@ -0,0 +1,17 @@ +// 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. + +// Code generated with somegen DO NOT EDIT. + +package testdata + +type T struct { + x, y int +} + +var _ = [42]T{ + T{}, // No simplification fix is offered in generated code. + T{1, 2}, // No simplification fix is offered in generated code. + T{3, 4}, // No simplification fix is offered in generated code. +} diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/generatedcode/generatedcode.go.golden b/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/generatedcode/generatedcode.go.golden new file mode 100644 index 00000000000..7b11dc5ba47 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/testdata/src/generatedcode/generatedcode.go.golden @@ -0,0 +1,17 @@ +// 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. + +// Code generated with somegen DO NOT EDIT. + +package testdata + +type T struct { + x, y int +} + +var _ = [42]T{ + T{}, // No simplification fix is offered in generated code. + T{1, 2}, // No simplification fix is offered in generated code. + T{3, 4}, // No simplification fix is offered in generated code. +} diff --git a/contribs/gnopls/internal/analysis/simplifyrange/doc.go b/contribs/gnopls/internal/analysis/simplifyrange/doc.go new file mode 100644 index 00000000000..3d1145e0b09 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/doc.go @@ -0,0 +1,32 @@ +// 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. +// +// This analyzer ignores generated code. +package simplifyrange diff --git a/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange.go b/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange.go new file mode 100644 index 00000000000..4071d1b6e8a --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange.go @@ -0,0 +1,114 @@ +// 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 simplifyrange + +import ( + "bytes" + _ "embed" + "go/ast" + "go/printer" + "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: "simplifyrange", + Doc: analysisinternal.MustExtractDoc(doc, "simplifyrange"), + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, + URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange", +} + +func run(pass *analysis.Pass) (interface{}, error) { + // Gather information whether file is generated or not + generated := make(map[*token.File]bool) + for _, file := range pass.Files { + if ast.IsGenerated(file) { + generated[pass.Fset.File(file.Pos())] = true + } + } + + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.RangeStmt)(nil), + } + inspect.Preorder(nodeFilter, func(n ast.Node) { + if _, ok := generated[pass.Fset.File(n.Pos())]; ok { + return // skip checking if it's generated code + } + + var copy *ast.RangeStmt // shallow-copy the AST before modifying + { + x := *n.(*ast.RangeStmt) + copy = &x + } + end := newlineIndex(pass.Fset, copy) + + // Range statements of the form: for i, _ := range x {} + var old ast.Expr + if isBlank(copy.Value) { + old = copy.Value + copy.Value = nil + } + // Range statements of the form: for _ := range x {} + if isBlank(copy.Key) && copy.Value == nil { + old = copy.Key + copy.Key = nil + } + // Return early if neither if condition is met. + if old == nil { + return + } + pass.Report(analysis.Diagnostic{ + Pos: old.Pos(), + End: old.End(), + Message: "simplify range expression", + SuggestedFixes: suggestedFixes(pass.Fset, copy, end), + }) + }) + return nil, nil +} + +func suggestedFixes(fset *token.FileSet, rng *ast.RangeStmt, end token.Pos) []analysis.SuggestedFix { + var b bytes.Buffer + printer.Fprint(&b, fset, rng) + stmt := b.Bytes() + index := bytes.Index(stmt, []byte("\n")) + // If there is a new line character, then don't replace the body. + if index != -1 { + stmt = stmt[:index] + } + return []analysis.SuggestedFix{{ + Message: "Remove empty value", + TextEdits: []analysis.TextEdit{{ + Pos: rng.Pos(), + End: end, + NewText: stmt[:index], + }}, + }} +} + +func newlineIndex(fset *token.FileSet, rng *ast.RangeStmt) token.Pos { + var b bytes.Buffer + printer.Fprint(&b, fset, rng) + contents := b.Bytes() + index := bytes.Index(contents, []byte("\n")) + if index == -1 { + return rng.End() + } + return rng.Pos() + token.Pos(index) +} + +func isBlank(x ast.Expr) bool { + ident, ok := x.(*ast.Ident) + return ok && ident.Name == "_" +} diff --git a/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange_test.go b/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange_test.go new file mode 100644 index 00000000000..50a600e03bf --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange_test.go @@ -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 simplifyrange_test + +import ( + "go/build" + "slices" + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/analysis/simplifyrange" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + analysistest.RunWithSuggestedFixes(t, testdata, simplifyrange.Analyzer, "a", "generatedcode") + if slices.Contains(build.Default.ReleaseTags, "go1.23") { // uses iter.Seq + analysistest.RunWithSuggestedFixes(t, testdata, simplifyrange.Analyzer, "rangeoverfunc") + } +} diff --git a/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/a/a.go new file mode 100644 index 00000000000..49face1e968 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/a/a.go @@ -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 testdata + +import "log" + +func m() { + maps := make(map[string]string) + for k, _ := range maps { // want "simplify range expression" + log.Println(k) + } + for _ = range maps { // want "simplify range expression" + } +} diff --git a/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/a/a.go.golden b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..ec8490ab337 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/a/a.go.golden @@ -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 testdata + +import "log" + +func m() { + maps := make(map[string]string) + for k := range maps { // want "simplify range expression" + log.Println(k) + } + for range maps { // want "simplify range expression" + } +} diff --git a/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/generatedcode/generatedcode.go b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/generatedcode/generatedcode.go new file mode 100644 index 00000000000..36b935c77eb --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/generatedcode/generatedcode.go @@ -0,0 +1,18 @@ +// 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. + +// Code generated with somegen DO NOT EDIT. + +package testdata + +import "log" + +func mgeneratedcode() { + maps := make(map[string]string) + for k, _ := range maps { // No simplification fix is offered in generated code. + log.Println(k) + } + for _ = range maps { // No simplification fix is offered in generated code. + } +} diff --git a/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/generatedcode/generatedcode.go.golden b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/generatedcode/generatedcode.go.golden new file mode 100644 index 00000000000..36b935c77eb --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/generatedcode/generatedcode.go.golden @@ -0,0 +1,18 @@ +// 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. + +// Code generated with somegen DO NOT EDIT. + +package testdata + +import "log" + +func mgeneratedcode() { + maps := make(map[string]string) + for k, _ := range maps { // No simplification fix is offered in generated code. + log.Println(k) + } + for _ = range maps { // No simplification fix is offered in generated code. + } +} diff --git a/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/rangeoverfunc/rangeoverfunc.go b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/rangeoverfunc/rangeoverfunc.go new file mode 100644 index 00000000000..154e2829143 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/rangeoverfunc/rangeoverfunc.go @@ -0,0 +1,20 @@ +// 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 testdata + +import "iter" + +func _(seq1 iter.Seq[int], seq2 iter.Seq2[int, int]) { + // range-over-func is (once again) consistent with other types (#65236) + for _ = range "" { // want "simplify range expression" + } + for _ = range seq1 { // want `simplify range expression` + } + for _, v := range seq2 { // silence + _ = v + } + for _, _ = range seq2 { // want `simplify range expression` + } +} diff --git a/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/rangeoverfunc/rangeoverfunc.go.golden b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/rangeoverfunc/rangeoverfunc.go.golden new file mode 100644 index 00000000000..508c752bca6 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyrange/testdata/src/rangeoverfunc/rangeoverfunc.go.golden @@ -0,0 +1,20 @@ +// 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 testdata + +import "iter" + +func _(seq1 iter.Seq[int], seq2 iter.Seq2[int, int]) { + // range-over-func is (once again) consistent with other types (#65236) + for range "" { // want "simplify range expression" + } + for range seq1 { // want `simplify range expression` + } + for _, v := range seq2 { // silence + _ = v + } + for range seq2 { // want `simplify range expression` + } +} diff --git a/contribs/gnopls/internal/analysis/simplifyslice/doc.go b/contribs/gnopls/internal/analysis/simplifyslice/doc.go new file mode 100644 index 00000000000..4c6808acd53 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/doc.go @@ -0,0 +1,24 @@ +// 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 simplifyslice defines an Analyzer that simplifies slice statements. +// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go +// https://golang.org/cmd/gofmt/#hdr-The_simplify_command +// +// # Analyzer simplifyslice +// +// simplifyslice: check for slice simplifications +// +// A slice expression of the form: +// +// s[a:len(s)] +// +// will be simplified to: +// +// s[a:] +// +// This is one of the simplifications that "gofmt -s" applies. +// +// This analyzer ignores generated code. +package simplifyslice diff --git a/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice.go b/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice.go new file mode 100644 index 00000000000..dc99580b07e --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice.go @@ -0,0 +1,101 @@ +// 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 simplifyslice + +import ( + "bytes" + _ "embed" + "fmt" + "go/ast" + "go/printer" + "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: "simplifyslice", + Doc: analysisinternal.MustExtractDoc(doc, "simplifyslice"), + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, + URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice", +} + +// Note: We could also simplify slice expressions of the form s[0:b] to s[:b] +// but we leave them as is since sometimes we want to be very explicit +// about the lower bound. +// An example where the 0 helps: +// x, y, z := b[0:2], b[2:4], b[4:6] +// An example where it does not: +// x, y := b[:n], b[n:] + +func run(pass *analysis.Pass) (interface{}, error) { + // Gather information whether file is generated or not + generated := make(map[*token.File]bool) + for _, file := range pass.Files { + if ast.IsGenerated(file) { + generated[pass.Fset.File(file.Pos())] = true + } + } + + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.SliceExpr)(nil), + } + inspect.Preorder(nodeFilter, func(n ast.Node) { + if _, ok := generated[pass.Fset.File(n.Pos())]; ok { + return // skip checking if it's generated code + } + + expr := n.(*ast.SliceExpr) + // - 3-index slices always require the 2nd and 3rd index + if expr.Max != nil { + return + } + s, ok := expr.X.(*ast.Ident) + // the array/slice object is a single, resolved identifier + if !ok || s.Obj == nil { + return + } + call, ok := expr.High.(*ast.CallExpr) + // the high expression is a function call with a single argument + if !ok || len(call.Args) != 1 || call.Ellipsis.IsValid() { + return + } + fun, ok := call.Fun.(*ast.Ident) + // the function called is "len" and it is not locally defined; and + // because we don't have dot imports, it must be the predefined len() + if !ok || fun.Name != "len" || fun.Obj != nil { + return + } + arg, ok := call.Args[0].(*ast.Ident) + // the len argument is the array/slice object + if !ok || arg.Obj != s.Obj { + return + } + var b bytes.Buffer + printer.Fprint(&b, pass.Fset, expr.High) + pass.Report(analysis.Diagnostic{ + Pos: expr.High.Pos(), + End: expr.High.End(), + Message: fmt.Sprintf("unneeded: %s", b.String()), + SuggestedFixes: []analysis.SuggestedFix{{ + Message: fmt.Sprintf("Remove '%s'", b.String()), + TextEdits: []analysis.TextEdit{{ + Pos: expr.High.Pos(), + End: expr.High.End(), + NewText: []byte{}, + }}, + }}, + }) + }) + return nil, nil +} diff --git a/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice_test.go b/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice_test.go new file mode 100644 index 00000000000..7fc5f9af451 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice_test.go @@ -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 simplifyslice_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/analysis/simplifyslice" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + analysistest.RunWithSuggestedFixes(t, testdata, simplifyslice.Analyzer, "a", "generatedcode", "typeparams") +} diff --git a/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/a/a.go new file mode 100644 index 00000000000..20792105dd7 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/a/a.go @@ -0,0 +1,70 @@ +// 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 + +var ( + a [10]byte + b [20]float32 + s []int + t struct { + s []byte + } + + _ = a[0:] + _ = a[1:10] + _ = a[2:len(a)] // want "unneeded: len\\(a\\)" + _ = a[3:(len(a))] + _ = a[len(a)-1 : len(a)] // want "unneeded: len\\(a\\)" + _ = a[2:len(a):len(a)] + + _ = a[:] + _ = a[:10] + _ = a[:len(a)] // want "unneeded: len\\(a\\)" + _ = a[:(len(a))] + _ = a[:len(a)-1] + _ = a[:len(a):len(a)] + + _ = s[0:] + _ = s[1:10] + _ = s[2:len(s)] // want "unneeded: len\\(s\\)" + _ = s[3:(len(s))] + _ = s[len(a) : len(s)-1] + _ = s[0:len(b)] + _ = s[2:len(s):len(s)] + + _ = s[:] + _ = s[:10] + _ = s[:len(s)] // want "unneeded: len\\(s\\)" + _ = s[:(len(s))] + _ = s[:len(s)-1] + _ = s[:len(b)] + _ = s[:len(s):len(s)] + + _ = t.s[0:] + _ = t.s[1:10] + _ = t.s[2:len(t.s)] + _ = t.s[3:(len(t.s))] + _ = t.s[len(a) : len(t.s)-1] + _ = t.s[0:len(b)] + _ = t.s[2:len(t.s):len(t.s)] + + _ = t.s[:] + _ = t.s[:10] + _ = t.s[:len(t.s)] + _ = t.s[:(len(t.s))] + _ = t.s[:len(t.s)-1] + _ = t.s[:len(b)] + _ = t.s[:len(t.s):len(t.s)] +) + +func _() { + s := s[0:len(s)] // want "unneeded: len\\(s\\)" + _ = s +} + +func m() { + maps := []int{} + _ = maps[1:len(maps)] // want "unneeded: len\\(maps\\)" +} diff --git a/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/a/a.go.golden b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..45c791421c5 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/a/a.go.golden @@ -0,0 +1,70 @@ +// 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 + +var ( + a [10]byte + b [20]float32 + s []int + t struct { + s []byte + } + + _ = a[0:] + _ = a[1:10] + _ = a[2:] // want "unneeded: len\\(a\\)" + _ = a[3:(len(a))] + _ = a[len(a)-1:] // want "unneeded: len\\(a\\)" + _ = a[2:len(a):len(a)] + + _ = a[:] + _ = a[:10] + _ = a[:] // want "unneeded: len\\(a\\)" + _ = a[:(len(a))] + _ = a[:len(a)-1] + _ = a[:len(a):len(a)] + + _ = s[0:] + _ = s[1:10] + _ = s[2:] // want "unneeded: len\\(s\\)" + _ = s[3:(len(s))] + _ = s[len(a) : len(s)-1] + _ = s[0:len(b)] + _ = s[2:len(s):len(s)] + + _ = s[:] + _ = s[:10] + _ = s[:] // want "unneeded: len\\(s\\)" + _ = s[:(len(s))] + _ = s[:len(s)-1] + _ = s[:len(b)] + _ = s[:len(s):len(s)] + + _ = t.s[0:] + _ = t.s[1:10] + _ = t.s[2:len(t.s)] + _ = t.s[3:(len(t.s))] + _ = t.s[len(a) : len(t.s)-1] + _ = t.s[0:len(b)] + _ = t.s[2:len(t.s):len(t.s)] + + _ = t.s[:] + _ = t.s[:10] + _ = t.s[:len(t.s)] + _ = t.s[:(len(t.s))] + _ = t.s[:len(t.s)-1] + _ = t.s[:len(b)] + _ = t.s[:len(t.s):len(t.s)] +) + +func _() { + s := s[0:] // want "unneeded: len\\(s\\)" + _ = s +} + +func m() { + maps := []int{} + _ = maps[1:] // want "unneeded: len\\(maps\\)" +} diff --git a/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/generatedcode/generatedcode.go b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/generatedcode/generatedcode.go new file mode 100644 index 00000000000..a291600d11f --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/generatedcode/generatedcode.go @@ -0,0 +1,72 @@ +// 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. + +// Code generated with somegen DO NOT EDIT. + +package testdata + +var ( + a [10]byte + b [20]float32 + s []int + t struct { + s []byte + } + + _ = a[0:] + _ = a[1:10] + _ = a[2:len(a)] // No simplification fix is offered in generated code. + _ = a[3:(len(a))] + _ = a[len(a)-1 : len(a)] // No simplification fix is offered in generated code. + _ = a[2:len(a):len(a)] + + _ = a[:] + _ = a[:10] + _ = a[:len(a)] // No simplification fix is offered in generated code. + _ = a[:(len(a))] + _ = a[:len(a)-1] + _ = a[:len(a):len(a)] + + _ = s[0:] + _ = s[1:10] + _ = s[2:len(s)] // No simplification fix is offered in generated code. + _ = s[3:(len(s))] + _ = s[len(a) : len(s)-1] + _ = s[0:len(b)] + _ = s[2:len(s):len(s)] + + _ = s[:] + _ = s[:10] + _ = s[:len(s)] // No simplification fix is offered in generated code. + _ = s[:(len(s))] + _ = s[:len(s)-1] + _ = s[:len(b)] + _ = s[:len(s):len(s)] + + _ = t.s[0:] + _ = t.s[1:10] + _ = t.s[2:len(t.s)] + _ = t.s[3:(len(t.s))] + _ = t.s[len(a) : len(t.s)-1] + _ = t.s[0:len(b)] + _ = t.s[2:len(t.s):len(t.s)] + + _ = t.s[:] + _ = t.s[:10] + _ = t.s[:len(t.s)] + _ = t.s[:(len(t.s))] + _ = t.s[:len(t.s)-1] + _ = t.s[:len(b)] + _ = t.s[:len(t.s):len(t.s)] +) + +func _() { + s := s[0:len(s)] // No simplification fix is offered in generated code. + _ = s +} + +func m() { + maps := []int{} + _ = maps[1:len(maps)] // No simplification fix is offered in generated code. +} diff --git a/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/generatedcode/generatedcode.go.golden b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/generatedcode/generatedcode.go.golden new file mode 100644 index 00000000000..a291600d11f --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/generatedcode/generatedcode.go.golden @@ -0,0 +1,72 @@ +// 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. + +// Code generated with somegen DO NOT EDIT. + +package testdata + +var ( + a [10]byte + b [20]float32 + s []int + t struct { + s []byte + } + + _ = a[0:] + _ = a[1:10] + _ = a[2:len(a)] // No simplification fix is offered in generated code. + _ = a[3:(len(a))] + _ = a[len(a)-1 : len(a)] // No simplification fix is offered in generated code. + _ = a[2:len(a):len(a)] + + _ = a[:] + _ = a[:10] + _ = a[:len(a)] // No simplification fix is offered in generated code. + _ = a[:(len(a))] + _ = a[:len(a)-1] + _ = a[:len(a):len(a)] + + _ = s[0:] + _ = s[1:10] + _ = s[2:len(s)] // No simplification fix is offered in generated code. + _ = s[3:(len(s))] + _ = s[len(a) : len(s)-1] + _ = s[0:len(b)] + _ = s[2:len(s):len(s)] + + _ = s[:] + _ = s[:10] + _ = s[:len(s)] // No simplification fix is offered in generated code. + _ = s[:(len(s))] + _ = s[:len(s)-1] + _ = s[:len(b)] + _ = s[:len(s):len(s)] + + _ = t.s[0:] + _ = t.s[1:10] + _ = t.s[2:len(t.s)] + _ = t.s[3:(len(t.s))] + _ = t.s[len(a) : len(t.s)-1] + _ = t.s[0:len(b)] + _ = t.s[2:len(t.s):len(t.s)] + + _ = t.s[:] + _ = t.s[:10] + _ = t.s[:len(t.s)] + _ = t.s[:(len(t.s))] + _ = t.s[:len(t.s)-1] + _ = t.s[:len(b)] + _ = t.s[:len(t.s):len(t.s)] +) + +func _() { + s := s[0:len(s)] // No simplification fix is offered in generated code. + _ = s +} + +func m() { + maps := []int{} + _ = maps[1:len(maps)] // No simplification fix is offered in generated code. +} diff --git a/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go new file mode 100644 index 00000000000..a1a29d42deb --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go @@ -0,0 +1,36 @@ +// 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 testdata + +type List[E any] []E + +// TODO(suzmue): add a test for generic slice expressions when https://github.com/golang/go/issues/48618 is closed. +// type S interface{ ~[]int } + +var ( + a [10]byte + b [20]float32 + p List[int] + + _ = p[0:] + _ = p[1:10] + _ = p[2:len(p)] // want "unneeded: len\\(p\\)" + _ = p[3:(len(p))] + _ = p[len(a) : len(p)-1] + _ = p[0:len(b)] + _ = p[2:len(p):len(p)] + + _ = p[:] + _ = p[:10] + _ = p[:len(p)] // want "unneeded: len\\(p\\)" + _ = p[:(len(p))] + _ = p[:len(p)-1] + _ = p[:len(b)] + _ = p[:len(p):len(p)] +) + +func foo[E any](a List[E]) { + _ = a[0:len(a)] // want "unneeded: len\\(a\\)" +} diff --git a/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden new file mode 100644 index 00000000000..ce425b72276 --- /dev/null +++ b/contribs/gnopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden @@ -0,0 +1,36 @@ +// 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 testdata + +type List[E any] []E + +// TODO(suzmue): add a test for generic slice expressions when https://github.com/golang/go/issues/48618 is closed. +// type S interface{ ~[]int } + +var ( + a [10]byte + b [20]float32 + p List[int] + + _ = p[0:] + _ = p[1:10] + _ = p[2:] // want "unneeded: len\\(p\\)" + _ = p[3:(len(p))] + _ = p[len(a) : len(p)-1] + _ = p[0:len(b)] + _ = p[2:len(p):len(p)] + + _ = p[:] + _ = p[:10] + _ = p[:] // want "unneeded: len\\(p\\)" + _ = p[:(len(p))] + _ = p[:len(p)-1] + _ = p[:len(b)] + _ = p[:len(p):len(p)] +) + +func foo[E any](a List[E]) { + _ = a[0:] // want "unneeded: len\\(a\\)" +} diff --git a/contribs/gnopls/internal/analysis/stubmethods/doc.go b/contribs/gnopls/internal/analysis/stubmethods/doc.go new file mode 100644 index 00000000000..e1383cfc7e7 --- /dev/null +++ b/contribs/gnopls/internal/analysis/stubmethods/doc.go @@ -0,0 +1,38 @@ +// 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 stubmethods defines a code action for missing interface methods. +// +// # Analyzer stubmethods +// +// stubmethods: detect missing methods and fix with stub implementations +// +// This analyzer detects type-checking errors due to missing methods +// in assignments from concrete types to interface types, and offers +// a suggested fix that will create a set of stub methods so that +// the concrete type satisfies the interface. +// +// For example, this function will not compile because the value +// NegativeErr{} does not implement the "error" interface: +// +// func sqrt(x float64) (float64, error) { +// if x < 0 { +// return 0, NegativeErr{} // error: missing method +// } +// ... +// } +// +// type NegativeErr struct{} +// +// This analyzer will suggest a fix to declare this method: +// +// // Error implements error.Error. +// func (NegativeErr) Error() string { +// panic("unimplemented") +// } +// +// (At least, it appears to behave that way, but technically it +// doesn't use the SuggestedFix mechanism and the stub is created by +// logic in gopls's golang.stub function.) +package stubmethods diff --git a/contribs/gnopls/internal/analysis/stubmethods/stubmethods.go b/contribs/gnopls/internal/analysis/stubmethods/stubmethods.go new file mode 100644 index 00000000000..f4c30aadd7d --- /dev/null +++ b/contribs/gnopls/internal/analysis/stubmethods/stubmethods.go @@ -0,0 +1,403 @@ +// 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 stubmethods + +import ( + "bytes" + _ "embed" + "fmt" + "go/ast" + "go/format" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/util/typesutil" + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/typesinternal" +) + +//go:embed doc.go +var doc string + +var Analyzer = &analysis.Analyzer{ + Name: "stubmethods", + Doc: analysisinternal.MustExtractDoc(doc, "stubmethods"), + Run: run, + RunDespiteErrors: true, + URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods", +} + +// TODO(rfindley): remove this thin wrapper around the stubmethods refactoring, +// and eliminate the stubmethods analyzer. +// +// Previous iterations used the analysis framework for computing refactorings, +// which proved inefficient. +func run(pass *analysis.Pass) (interface{}, error) { + for _, err := range pass.TypeErrors { + var file *ast.File + for _, f := range pass.Files { + if f.Pos() <= err.Pos && err.Pos < f.End() { + file = f + break + } + } + // Get the end position of the error. + _, _, end, ok := typesinternal.ReadGo116ErrorData(err) + if !ok { + var buf bytes.Buffer + if err := format.Node(&buf, pass.Fset, file); err != nil { + continue + } + end = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos) + } + if diag, ok := DiagnosticForError(pass.Fset, file, err.Pos, end, err.Msg, pass.TypesInfo); ok { + pass.Report(diag) + } + } + + return nil, nil +} + +// MatchesMessage reports whether msg matches the error message sought after by +// the stubmethods fix. +func MatchesMessage(msg string) bool { + return strings.Contains(msg, "missing method") || strings.HasPrefix(msg, "cannot convert") || strings.Contains(msg, "not implement") +} + +// DiagnosticForError computes a diagnostic suggesting to implement an +// interface to fix the type checking error defined by (start, end, msg). +// +// If no such fix is possible, the second result is false. +func DiagnosticForError(fset *token.FileSet, file *ast.File, start, end token.Pos, msg string, info *types.Info) (analysis.Diagnostic, bool) { + if !MatchesMessage(msg) { + return analysis.Diagnostic{}, false + } + + path, _ := astutil.PathEnclosingInterval(file, start, end) + si := GetStubInfo(fset, info, path, start) + if si == nil { + return analysis.Diagnostic{}, false + } + qf := typesutil.FileQualifier(file, si.Concrete.Obj().Pkg(), info) + iface := types.TypeString(si.Interface.Type(), qf) + return analysis.Diagnostic{ + Pos: start, + End: end, + Message: msg, + Category: FixCategory, + SuggestedFixes: []analysis.SuggestedFix{{ + Message: fmt.Sprintf("Declare missing methods of %s", iface), + // No TextEdits => computed later by gopls. + }}, + }, true +} + +const FixCategory = "stubmethods" // recognized by gopls ApplyFix + +// StubInfo represents a concrete type +// that wants to stub out an interface type +type StubInfo struct { + // Interface is the interface that the client wants to implement. + // When the interface is defined, the underlying object will be a TypeName. + // Note that we keep track of types.Object instead of types.Type in order + // to keep a reference to the declaring object's package and the ast file + // in the case where the concrete type file requires a new import that happens to be renamed + // in the interface file. + // TODO(marwan-at-work): implement interface literals. + Fset *token.FileSet // the FileSet used to type-check the types below + Interface *types.TypeName + Concrete *types.Named + Pointer bool +} + +// GetStubInfo determines whether the "missing method error" +// can be used to deduced what the concrete and interface types are. +// +// TODO(adonovan): this function (and its following 5 helpers) tries +// to deduce a pair of (concrete, interface) types that are related by +// an assignment, either explicitly or through a return statement or +// function call. This is essentially what the refactor/satisfy does, +// more generally. Refactor to share logic, after auditing 'satisfy' +// for safety on ill-typed code. +func GetStubInfo(fset *token.FileSet, info *types.Info, path []ast.Node, pos token.Pos) *StubInfo { + for _, n := range path { + switch n := n.(type) { + case *ast.ValueSpec: + return fromValueSpec(fset, info, n, pos) + case *ast.ReturnStmt: + // An error here may not indicate a real error the user should know about, but it may. + // Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring + // it. However, event.Log takes a context which is not passed via the analysis package. + // TODO(marwan-at-work): properly log this error. + si, _ := fromReturnStmt(fset, info, pos, path, n) + return si + case *ast.AssignStmt: + return fromAssignStmt(fset, info, n, pos) + case *ast.CallExpr: + // Note that some call expressions don't carry the interface type + // because they don't point to a function or method declaration elsewhere. + // For eaxmple, "var Interface = (*Concrete)(nil)". In that case, continue + // this loop to encounter other possibilities such as *ast.ValueSpec or others. + si := fromCallExpr(fset, info, pos, n) + if si != nil { + return si + } + } + } + return nil +} + +// fromCallExpr tries to find an *ast.CallExpr's function declaration and +// analyzes a function call's signature against the passed in parameter to deduce +// the concrete and interface types. +func fromCallExpr(fset *token.FileSet, info *types.Info, pos token.Pos, call *ast.CallExpr) *StubInfo { + // Find argument containing pos. + argIdx := -1 + var arg ast.Expr + for i, callArg := range call.Args { + if callArg.Pos() <= pos && pos <= callArg.End() { + argIdx = i + arg = callArg + break + } + } + if arg == nil { + return nil + } + + concType, pointer := concreteType(arg, info) + if concType == nil || concType.Obj().Pkg() == nil { + return nil + } + tv, ok := info.Types[call.Fun] + if !ok { + return nil + } + sig, ok := aliases.Unalias(tv.Type).(*types.Signature) + if !ok { + return nil + } + var paramType types.Type + if sig.Variadic() && argIdx >= sig.Params().Len()-1 { + v := sig.Params().At(sig.Params().Len() - 1) + if s, _ := v.Type().(*types.Slice); s != nil { + paramType = s.Elem() + } + } else if argIdx < sig.Params().Len() { + paramType = sig.Params().At(argIdx).Type() + } + if paramType == nil { + return nil // A type error prevents us from determining the param type. + } + iface := ifaceObjFromType(paramType) + if iface == nil { + return nil + } + return &StubInfo{ + Fset: fset, + Concrete: concType, + Pointer: pointer, + Interface: iface, + } +} + +// fromReturnStmt analyzes a "return" statement to extract +// a concrete type that is trying to be returned as an interface type. +// +// For example, func() io.Writer { return myType{} } +// would return StubInfo with the interface being io.Writer and the concrete type being myType{}. +func fromReturnStmt(fset *token.FileSet, info *types.Info, pos token.Pos, path []ast.Node, ret *ast.ReturnStmt) (*StubInfo, error) { + // Find return operand containing pos. + returnIdx := -1 + for i, r := range ret.Results { + if r.Pos() <= pos && pos <= r.End() { + returnIdx = i + break + } + } + if returnIdx == -1 { + return nil, fmt.Errorf("pos %d not within return statement bounds: [%d-%d]", pos, ret.Pos(), ret.End()) + } + + concType, pointer := concreteType(ret.Results[returnIdx], info) + if concType == nil || concType.Obj().Pkg() == nil { + return nil, nil + } + funcType := enclosingFunction(path, info) + if funcType == nil { + return nil, fmt.Errorf("could not find the enclosing function of the return statement") + } + if len(funcType.Results.List) != len(ret.Results) { + return nil, fmt.Errorf("%d-operand return statement in %d-result function", + len(ret.Results), + len(funcType.Results.List)) + } + iface := ifaceType(funcType.Results.List[returnIdx].Type, info) + if iface == nil { + return nil, nil + } + return &StubInfo{ + Fset: fset, + Concrete: concType, + Pointer: pointer, + Interface: iface, + }, nil +} + +// fromValueSpec returns *StubInfo from a variable declaration such as +// var x io.Writer = &T{} +func fromValueSpec(fset *token.FileSet, info *types.Info, spec *ast.ValueSpec, pos token.Pos) *StubInfo { + // Find RHS element containing pos. + var rhs ast.Expr + for _, r := range spec.Values { + if r.Pos() <= pos && pos <= r.End() { + rhs = r + break + } + } + if rhs == nil { + return nil // e.g. pos was on the LHS (#64545) + } + + // Possible implicit/explicit conversion to interface type? + ifaceNode := spec.Type // var _ myInterface = ... + if call, ok := rhs.(*ast.CallExpr); ok && ifaceNode == nil && len(call.Args) == 1 { + // var _ = myInterface(v) + ifaceNode = call.Fun + rhs = call.Args[0] + } + concType, pointer := concreteType(rhs, info) + if concType == nil || concType.Obj().Pkg() == nil { + return nil + } + ifaceObj := ifaceType(ifaceNode, info) + if ifaceObj == nil { + return nil + } + return &StubInfo{ + Fset: fset, + Concrete: concType, + Interface: ifaceObj, + Pointer: pointer, + } +} + +// fromAssignStmt returns *StubInfo from a variable assignment such as +// var x io.Writer +// x = &T{} +func fromAssignStmt(fset *token.FileSet, info *types.Info, assign *ast.AssignStmt, pos token.Pos) *StubInfo { + // The interface conversion error in an assignment is against the RHS: + // + // var x io.Writer + // x = &T{} // error: missing method + // ^^^^ + // + // Find RHS element containing pos. + var lhs, rhs ast.Expr + for i, r := range assign.Rhs { + if r.Pos() <= pos && pos <= r.End() { + if i >= len(assign.Lhs) { + // This should never happen as we would get a + // "cannot assign N values to M variables" + // before we get an interface conversion error. + // But be defensive. + return nil + } + lhs = assign.Lhs[i] + rhs = r + break + } + } + if lhs == nil || rhs == nil { + return nil + } + + ifaceObj := ifaceType(lhs, info) + if ifaceObj == nil { + return nil + } + concType, pointer := concreteType(rhs, info) + if concType == nil || concType.Obj().Pkg() == nil { + return nil + } + return &StubInfo{ + Fset: fset, + Concrete: concType, + Interface: ifaceObj, + Pointer: pointer, + } +} + +// ifaceType returns the named interface type to which e refers, if any. +func ifaceType(e ast.Expr, info *types.Info) *types.TypeName { + tv, ok := info.Types[e] + if !ok { + return nil + } + return ifaceObjFromType(tv.Type) +} + +func ifaceObjFromType(t types.Type) *types.TypeName { + named, ok := aliases.Unalias(t).(*types.Named) + if !ok { + return nil + } + if !types.IsInterface(named) { + return nil + } + // Interfaces defined in the "builtin" package return nil a Pkg(). + // But they are still real interfaces that we need to make a special case for. + // Therefore, protect gopls from panicking if a new interface type was added in the future. + if named.Obj().Pkg() == nil && named.Obj().Name() != "error" { + return nil + } + return named.Obj() +} + +// concreteType tries to extract the *types.Named that defines +// the concrete type given the ast.Expr where the "missing method" +// or "conversion" errors happened. If the concrete type is something +// that cannot have methods defined on it (such as basic types), this +// method will return a nil *types.Named. The second return parameter +// is a boolean that indicates whether the concreteType was defined as a +// pointer or value. +func concreteType(e ast.Expr, info *types.Info) (*types.Named, bool) { + tv, ok := info.Types[e] + if !ok { + return nil, false + } + typ := tv.Type + ptr, isPtr := aliases.Unalias(typ).(*types.Pointer) + if isPtr { + typ = ptr.Elem() + } + named, ok := aliases.Unalias(typ).(*types.Named) + if !ok { + return nil, false + } + return named, isPtr +} + +// enclosingFunction returns the signature and type of the function +// enclosing the given position. +func enclosingFunction(path []ast.Node, info *types.Info) *ast.FuncType { + for _, node := range path { + switch t := node.(type) { + case *ast.FuncDecl: + if _, ok := info.Defs[t.Name]; ok { + return t.Type + } + case *ast.FuncLit: + if _, ok := info.Types[t]; ok { + return t.Type + } + } + } + return nil +} diff --git a/contribs/gnopls/internal/analysis/stubmethods/stubmethods_test.go b/contribs/gnopls/internal/analysis/stubmethods/stubmethods_test.go new file mode 100644 index 00000000000..9c744c9b7a3 --- /dev/null +++ b/contribs/gnopls/internal/analysis/stubmethods/stubmethods_test.go @@ -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 stubmethods_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/analysis/stubmethods" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + analysistest.Run(t, testdata, stubmethods.Analyzer, "typeparams") +} diff --git a/contribs/gnopls/internal/analysis/stubmethods/testdata/src/typeparams/implement.go b/contribs/gnopls/internal/analysis/stubmethods/testdata/src/typeparams/implement.go new file mode 100644 index 00000000000..7b6f2911ea9 --- /dev/null +++ b/contribs/gnopls/internal/analysis/stubmethods/testdata/src/typeparams/implement.go @@ -0,0 +1,15 @@ +// 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 stubmethods + +var _ I = Y{} // want "does not implement I" + +type I interface{ F() } + +type X struct{} + +func (X) F(string) {} + +type Y struct{ X } diff --git a/contribs/gnopls/internal/analysis/undeclaredname/doc.go b/contribs/gnopls/internal/analysis/undeclaredname/doc.go new file mode 100644 index 00000000000..02989c9d75b --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/doc.go @@ -0,0 +1,23 @@ +// 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 undeclaredname defines an Analyzer that applies suggested fixes +// to errors of the type "undeclared name: %s". +// +// # Analyzer undeclaredname +// +// undeclaredname: suggested fixes for "undeclared name: <>" +// +// This checker provides suggested fixes for type errors of the +// type "undeclared name: <>". It will either insert a new statement, +// such as: +// +// <> := +// +// or a new function declaration, such as: +// +// func <>(inferred parameters) { +// panic("implement me!") +// } +package undeclaredname diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/a.go new file mode 100644 index 00000000000..c5d8a2d789c --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/a.go @@ -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 undeclared + +func x() int { + var z int + z = y // want "(undeclared name|undefined): y" + + if z == m { // want "(undeclared name|undefined): m" + z = 1 + } + + if z == 1 { + z = 1 + } else if z == n+1 { // want "(undeclared name|undefined): n" + z = 1 + } + + switch z { + case 10: + z = 1 + case a: // want "(undeclared name|undefined): a" + z = 1 + } + return z +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/channels.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/channels.go new file mode 100644 index 00000000000..76c7ba685e1 --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/channels.go @@ -0,0 +1,13 @@ +// 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 undeclared + +func channels(s string) { + undefinedChannels(c()) // want "(undeclared name|undefined): undefinedChannels" +} + +func c() (<-chan string, chan string) { + return make(<-chan string), make(chan string) +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/consecutive_params.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/consecutive_params.go new file mode 100644 index 00000000000..73beace102c --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/consecutive_params.go @@ -0,0 +1,10 @@ +// 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 undeclared + +func consecutiveParams() { + var s string + undefinedConsecutiveParams(s, s) // want "(undeclared name|undefined): undefinedConsecutiveParams" +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/error_param.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/error_param.go new file mode 100644 index 00000000000..5de9254112d --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/error_param.go @@ -0,0 +1,10 @@ +// 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 undeclared + +func errorParam() { + var err error + undefinedErrorParam(err) // want "(undeclared name|undefined): undefinedErrorParam" +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/literals.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/literals.go new file mode 100644 index 00000000000..c62174ec947 --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/literals.go @@ -0,0 +1,11 @@ +// 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 undeclared + +type T struct{} + +func literals() { + undefinedLiterals("hey compiler", T{}, &T{}) // want "(undeclared name|undefined): undefinedLiterals" +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/operation.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/operation.go new file mode 100644 index 00000000000..9396da4bd9d --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/operation.go @@ -0,0 +1,11 @@ +// 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 undeclared + +import "time" + +func operation() { + undefinedOperation(10 * time.Second) // want "(undeclared name|undefined): undefinedOperation" +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/selector.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/selector.go new file mode 100644 index 00000000000..a4ed290d466 --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/selector.go @@ -0,0 +1,10 @@ +// 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 undeclared + +func selector() { + m := map[int]bool{} + undefinedSelector(m[1]) // want "(undeclared name|undefined): undefinedSelector" +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/slice.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/slice.go new file mode 100644 index 00000000000..5cde299add3 --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/slice.go @@ -0,0 +1,9 @@ +// 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 undeclared + +func slice() { + undefinedSlice([]int{1, 2}) // want "(undeclared name|undefined): undefinedSlice" +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/tuple.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/tuple.go new file mode 100644 index 00000000000..9e91c59c25e --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/tuple.go @@ -0,0 +1,13 @@ +// 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 undeclared + +func tuple() { + undefinedTuple(b()) // want "(undeclared name|undefined): undefinedTuple" +} + +func b() (string, error) { + return "", nil +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/unique_params.go b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/unique_params.go new file mode 100644 index 00000000000..5b4241425e5 --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/testdata/src/a/unique_params.go @@ -0,0 +1,11 @@ +// 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 undeclared + +func uniqueArguments() { + var s string + var i int + undefinedUniqueArguments(s, i, s) // want "(undeclared name|undefined): undefinedUniqueArguments" +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/undeclared.go b/contribs/gnopls/internal/analysis/undeclaredname/undeclared.go new file mode 100644 index 00000000000..afd9b652b97 --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/undeclared.go @@ -0,0 +1,360 @@ +// 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 undeclaredname + +import ( + "bytes" + _ "embed" + "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" +) + +//go:embed doc.go +var doc string + +var Analyzer = &analysis.Analyzer{ + Name: "undeclaredname", + Doc: analysisinternal.MustExtractDoc(doc, "undeclaredname"), + Requires: []*analysis.Analyzer{}, + Run: run, + RunDespiteErrors: true, + URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/undeclaredname", +} + +// The prefix for this error message changed in Go 1.20. +var undeclaredNamePrefixes = []string{"undeclared name: ", "undefined: "} + +func run(pass *analysis.Pass) (interface{}, error) { + for _, err := range pass.TypeErrors { + runForError(pass, err) + } + return nil, nil +} + +func runForError(pass *analysis.Pass, err types.Error) { + // Extract symbol name from error. + var name string + for _, prefix := range undeclaredNamePrefixes { + if !strings.HasPrefix(err.Msg, prefix) { + continue + } + name = strings.TrimPrefix(err.Msg, prefix) + } + if name == "" { + return + } + + // Find file enclosing error. + var file *ast.File + for _, f := range pass.Files { + if f.Pos() <= err.Pos && err.Pos < f.End() { + file = f + break + } + } + if file == nil { + return + } + + // Find path to identifier in the error. + path, _ := astutil.PathEnclosingInterval(file, err.Pos, err.Pos) + if len(path) < 2 { + return + } + ident, ok := path[0].(*ast.Ident) + if !ok || ident.Name != name { + return + } + + // Skip selector expressions because it might be too complex + // to try and provide a suggested fix for fields and methods. + if _, ok := path[1].(*ast.SelectorExpr); ok { + return + } + + // Undeclared quick fixes only work in function bodies. + inFunc := false + for i := range path { + if _, inFunc = path[i].(*ast.FuncDecl); inFunc { + if i == 0 { + return + } + if _, isBody := path[i-1].(*ast.BlockStmt); !isBody { + return + } + break + } + } + if !inFunc { + return + } + + // Offer a fix. + noun := "variable" + if isCallPosition(path) { + noun = "function" + } + pass.Report(analysis.Diagnostic{ + Pos: err.Pos, + End: err.Pos + token.Pos(len(name)), + Message: err.Msg, + Category: FixCategory, + SuggestedFixes: []analysis.SuggestedFix{{ + Message: fmt.Sprintf("Create %s %q", noun, name), + // No TextEdits => computed by a gopls command + }}, + }) +} + +const FixCategory = "undeclaredname" // recognized by gopls ApplyFix + +// SuggestedFix computes the edits for the lazy (no-edits) fix suggested by the analyzer. +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) { + pos := start // don't use the end + path, _ := astutil.PathEnclosingInterval(file, pos, pos) + if len(path) < 2 { + return nil, nil, fmt.Errorf("no expression found") + } + ident, ok := path[0].(*ast.Ident) + if !ok { + return nil, nil, fmt.Errorf("no identifier found") + } + + // Check for a possible call expression, in which case we should add a + // new function declaration. + if isCallPosition(path) { + return newFunctionDeclaration(path, file, pkg, info, fset) + } + + // Get the place to insert the new statement. + insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path) + if insertBeforeStmt == nil { + return nil, nil, fmt.Errorf("could not locate insertion point") + } + + insertBefore := safetoken.StartPosition(fset, insertBeforeStmt.Pos()).Offset + + // Get the indent to add on the line after the new statement. + // Since this will have a parse error, we can not use format.Source(). + contentBeforeStmt, indent := content[:insertBefore], "\n" + if nl := bytes.LastIndex(contentBeforeStmt, []byte("\n")); nl != -1 { + indent = string(contentBeforeStmt[nl:]) + } + + // Create the new local variable statement. + newStmt := fmt.Sprintf("%s := %s", ident.Name, indent) + return fset, &analysis.SuggestedFix{ + Message: fmt.Sprintf("Create variable %q", ident.Name), + TextEdits: []analysis.TextEdit{{ + Pos: insertBeforeStmt.Pos(), + End: insertBeforeStmt.Pos(), + NewText: []byte(newStmt), + }}, + }, nil +} + +func newFunctionDeclaration(path []ast.Node, file *ast.File, pkg *types.Package, info *types.Info, fset *token.FileSet) (*token.FileSet, *analysis.SuggestedFix, error) { + if len(path) < 3 { + return nil, nil, fmt.Errorf("unexpected set of enclosing nodes: %v", path) + } + ident, ok := path[0].(*ast.Ident) + if !ok { + return nil, nil, fmt.Errorf("no name for function declaration %v (%T)", path[0], path[0]) + } + call, ok := path[1].(*ast.CallExpr) + if !ok { + return nil, nil, fmt.Errorf("no call expression found %v (%T)", path[1], path[1]) + } + + // Find the enclosing function, so that we can add the new declaration + // below. + var enclosing *ast.FuncDecl + for _, n := range path { + if n, ok := n.(*ast.FuncDecl); ok { + enclosing = n + break + } + } + // TODO(rstambler): Support the situation when there is no enclosing + // function. + if enclosing == nil { + return nil, nil, fmt.Errorf("no enclosing function found: %v", path) + } + + pos := enclosing.End() + + var paramNames []string + var paramTypes []types.Type + // keep track of all param names to later ensure uniqueness + nameCounts := map[string]int{} + for _, arg := range call.Args { + typ := info.TypeOf(arg) + if typ == nil { + return nil, nil, fmt.Errorf("unable to determine type for %s", arg) + } + + switch t := typ.(type) { + // this is the case where another function call returning multiple + // results is used as an argument + case *types.Tuple: + n := t.Len() + for i := 0; i < n; i++ { + name := typeToArgName(t.At(i).Type()) + nameCounts[name]++ + + paramNames = append(paramNames, name) + paramTypes = append(paramTypes, types.Default(t.At(i).Type())) + } + + default: + // does the argument have a name we can reuse? + // only happens in case of a *ast.Ident + var name string + if ident, ok := arg.(*ast.Ident); ok { + name = ident.Name + } + + if name == "" { + name = typeToArgName(typ) + } + + nameCounts[name]++ + + paramNames = append(paramNames, name) + paramTypes = append(paramTypes, types.Default(typ)) + } + } + + for n, c := range nameCounts { + // Any names we saw more than once will need a unique suffix added + // on. Reset the count to 1 to act as the suffix for the first + // occurrence of that name. + if c >= 2 { + nameCounts[n] = 1 + } else { + delete(nameCounts, n) + } + } + + params := &ast.FieldList{} + + for i, name := range paramNames { + if suffix, repeats := nameCounts[name]; repeats { + nameCounts[name]++ + name = fmt.Sprintf("%s%d", name, suffix) + } + + // only worth checking after previous param in the list + if i > 0 { + // if type of parameter at hand is the same as the previous one, + // add it to the previous param list of identifiers so to have: + // (s1, s2 string) + // and not + // (s1 string, s2 string) + if paramTypes[i] == paramTypes[i-1] { + params.List[len(params.List)-1].Names = append(params.List[len(params.List)-1].Names, ast.NewIdent(name)) + continue + } + } + + params.List = append(params.List, &ast.Field{ + Names: []*ast.Ident{ + ast.NewIdent(name), + }, + Type: analysisinternal.TypeExpr(file, pkg, paramTypes[i]), + }) + } + + decl := &ast.FuncDecl{ + Name: ast.NewIdent(ident.Name), + Type: &ast.FuncType{ + Params: params, + // TODO(golang/go#47558): Also handle result + // parameters here based on context of CallExpr. + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: ast.NewIdent("panic"), + Args: []ast.Expr{ + &ast.BasicLit{ + Value: `"unimplemented"`, + }, + }, + }, + }, + }, + }, + } + + b := bytes.NewBufferString("\n\n") + if err := format.Node(b, fset, decl); err != nil { + return nil, nil, err + } + return fset, &analysis.SuggestedFix{ + Message: fmt.Sprintf("Create function %q", ident.Name), + TextEdits: []analysis.TextEdit{{ + Pos: pos, + End: pos, + NewText: b.Bytes(), + }}, + }, nil +} + +func typeToArgName(ty types.Type) string { + s := types.Default(ty).String() + + switch t := aliases.Unalias(ty).(type) { + case *types.Basic: + // use first letter in type name for basic types + return s[0:1] + case *types.Slice: + // use element type to decide var name for slices + return typeToArgName(t.Elem()) + case *types.Array: + // use element type to decide var name for arrays + return typeToArgName(t.Elem()) + case *types.Chan: + return "ch" + } + + s = strings.TrimFunc(s, func(r rune) bool { + return !unicode.IsLetter(r) + }) + + if s == "error" { + return "err" + } + + // remove package (if present) + // and make first letter lowercase + a := []rune(s[strings.LastIndexByte(s, '.')+1:]) + a[0] = unicode.ToLower(a[0]) + return string(a) +} + +// isCallPosition reports whether the path denotes the subtree in call position, f(). +func isCallPosition(path []ast.Node) bool { + return len(path) > 1 && + is[*ast.CallExpr](path[1]) && + path[1].(*ast.CallExpr).Fun == path[0] +} + +func is[T any](x any) bool { + _, ok := x.(T) + return ok +} diff --git a/contribs/gnopls/internal/analysis/undeclaredname/undeclared_test.go b/contribs/gnopls/internal/analysis/undeclaredname/undeclared_test.go new file mode 100644 index 00000000000..ea3d724515b --- /dev/null +++ b/contribs/gnopls/internal/analysis/undeclaredname/undeclared_test.go @@ -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 undeclaredname_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/analysis/undeclaredname" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + analysistest.Run(t, testdata, undeclaredname.Analyzer, "a") +} diff --git a/contribs/gnopls/internal/analysis/unusedparams/cmd/main.go b/contribs/gnopls/internal/analysis/unusedparams/cmd/main.go new file mode 100644 index 00000000000..2f35fb06083 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedparams/cmd/main.go @@ -0,0 +1,13 @@ +// 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. + +// The unusedparams command runs the unusedparams analyzer. +package main + +import ( + "golang.org/x/tools/go/analysis/singlechecker" + "golang.org/x/tools/gopls/internal/analysis/unusedparams" +) + +func main() { singlechecker.Main(unusedparams.Analyzer) } diff --git a/contribs/gnopls/internal/analysis/unusedparams/doc.go b/contribs/gnopls/internal/analysis/unusedparams/doc.go new file mode 100644 index 00000000000..07e43c0d084 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedparams/doc.go @@ -0,0 +1,34 @@ +// 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 unusedparams defines an analyzer that checks for unused +// parameters of functions. +// +// # Analyzer unusedparams +// +// unusedparams: check for unused parameters of functions +// +// The unusedparams analyzer checks functions to see if there are +// any parameters that are not being used. +// +// To ensure soundness, it ignores: +// - "address-taken" functions, that is, functions that are used as +// a value rather than being called directly; their signatures may +// be required to conform to a func type. +// - exported functions or methods, since they may be address-taken +// in another package. +// - unexported methods whose name matches an interface method +// declared in the same package, since the method's signature +// may be required to conform to the interface type. +// - functions with empty bodies, or containing just a call to panic. +// - parameters that are unnamed, or named "_", the blank identifier. +// +// The analyzer suggests a fix of replacing the parameter name by "_", +// but in such cases a deeper fix can be obtained by invoking the +// "Refactor: remove unused parameter" code action, which will +// eliminate the parameter entirely, along with all corresponding +// arguments at call sites, while taking care to preserve any side +// effects in the argument expressions; see +// https://github.com/golang/tools/releases/tag/gopls%2Fv0.14. +package unusedparams diff --git a/contribs/gnopls/internal/analysis/unusedparams/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/unusedparams/testdata/src/a/a.go new file mode 100644 index 00000000000..3661e1f3cbe --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedparams/testdata/src/a/a.go @@ -0,0 +1,87 @@ +// 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 ( + "bytes" + "fmt" + "net/http" +) + +type parent interface { + n(f bool) +} + +type yuh struct { + a int +} + +func (y *yuh) n(f bool) { + for i := 0; i < 10; i++ { + fmt.Println(i) + } +} + +func a(i1 int, i2 int, i3 int) int { // want "unused parameter: i2" + i3 += i1 + _ = func(z int) int { // want "unused parameter: z" + _ = 1 + return 1 + } + return i3 +} + +func b(c bytes.Buffer) { // want "unused parameter: c" + _ = 1 +} + +func z(h http.ResponseWriter, _ *http.Request) { // no report: func z is address-taken + fmt.Println("Before") +} + +func l(h http.Handler) http.Handler { // want "unused parameter: h" + return http.HandlerFunc(z) +} + +func mult(a, b int) int { // want "unused parameter: b" + a += 1 + return a +} + +func y(a int) { + panic("yo") +} + +var _ = func(x int) {} // empty body: no diagnostic + +var _ = func(x int) { println() } // want "unused parameter: x" + +var ( + calledGlobal = func(x int) { println() } // want "unused parameter: x" + addressTakenGlobal = func(x int) { println() } // no report: function is address-taken +) + +func _() { + calledGlobal(1) + println(addressTakenGlobal) +} + +func Exported(unused int) {} // no finding: an exported function may be address-taken + +type T int + +func (T) m(f bool) { println() } // want "unused parameter: f" +func (T) n(f bool) { println() } // no finding: n may match the interface method parent.n + +func _() { + var fib func(x, y int) int + fib = func(x, y int) int { // want "unused parameter: y" + if x < 2 { + return x + } + return fib(x-1, 123) + fib(x-2, 456) + } + fib(10, 42) +} diff --git a/contribs/gnopls/internal/analysis/unusedparams/testdata/src/a/a.go.golden b/contribs/gnopls/internal/analysis/unusedparams/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..dea8a6d44ae --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedparams/testdata/src/a/a.go.golden @@ -0,0 +1,87 @@ +// 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 ( + "bytes" + "fmt" + "net/http" +) + +type parent interface { + n(f bool) +} + +type yuh struct { + a int +} + +func (y *yuh) n(f bool) { + for i := 0; i < 10; i++ { + fmt.Println(i) + } +} + +func a(i1 int, _ int, i3 int) int { // want "unused parameter: i2" + i3 += i1 + _ = func(_ int) int { // want "unused parameter: z" + _ = 1 + return 1 + } + return i3 +} + +func b(_ bytes.Buffer) { // want "unused parameter: c" + _ = 1 +} + +func z(h http.ResponseWriter, _ *http.Request) { // no report: func z is address-taken + fmt.Println("Before") +} + +func l(_ http.Handler) http.Handler { // want "unused parameter: h" + return http.HandlerFunc(z) +} + +func mult(a, _ int) int { // want "unused parameter: b" + a += 1 + return a +} + +func y(a int) { + panic("yo") +} + +var _ = func(x int) {} // empty body: no diagnostic + +var _ = func(_ int) { println() } // want "unused parameter: x" + +var ( + calledGlobal = func(_ int) { println() } // want "unused parameter: x" + addressTakenGlobal = func(x int) { println() } // no report: function is address-taken +) + +func _() { + calledGlobal(1) + println(addressTakenGlobal) +} + +func Exported(unused int) {} // no finding: an exported function may be address-taken + +type T int + +func (T) m(_ bool) { println() } // want "unused parameter: f" +func (T) n(f bool) { println() } // no finding: n may match the interface method parent.n + +func _() { + var fib func(x, y int) int + fib = func(x, _ int) int { // want "unused parameter: y" + if x < 2 { + return x + } + return fib(x-1, 123) + fib(x-2, 456) + } + fib(10, 42) +} diff --git a/contribs/gnopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go b/contribs/gnopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go new file mode 100644 index 00000000000..d89926a7db5 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go @@ -0,0 +1,55 @@ +// 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 typeparams + +import ( + "bytes" + "fmt" + "net/http" +) + +type parent[T any] interface { + n(f T) +} + +type yuh[T any] struct { + a T +} + +func (y *yuh[int]) n(f bool) { + for i := 0; i < 10; i++ { + fmt.Println(i) + } +} + +func a[T comparable](i1 int, i2 T, i3 int) int { // want "unused parameter: i2" + i3 += i1 + _ = func(z int) int { // want "unused parameter: z" + _ = 1 + return 1 + } + return i3 +} + +func b[T any](c bytes.Buffer) { // want "unused parameter: c" + _ = 1 +} + +func z[T http.ResponseWriter](h T, _ *http.Request) { // no report: func z is address-taken + fmt.Println("Before") +} + +func l(h http.Handler) http.Handler { // want "unused parameter: h" + return http.HandlerFunc(z[http.ResponseWriter]) +} + +func mult(a, b int) int { // want "unused parameter: b" + a += 1 + return a +} + +func y[T any](a T) { + panic("yo") +} diff --git a/contribs/gnopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden b/contribs/gnopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden new file mode 100644 index 00000000000..85479bc8b50 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedparams/testdata/src/typeparams/typeparams.go.golden @@ -0,0 +1,55 @@ +// 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 typeparams + +import ( + "bytes" + "fmt" + "net/http" +) + +type parent[T any] interface { + n(f T) +} + +type yuh[T any] struct { + a T +} + +func (y *yuh[int]) n(f bool) { + for i := 0; i < 10; i++ { + fmt.Println(i) + } +} + +func a[T comparable](i1 int, _ T, i3 int) int { // want "unused parameter: i2" + i3 += i1 + _ = func(_ int) int { // want "unused parameter: z" + _ = 1 + return 1 + } + return i3 +} + +func b[T any](_ bytes.Buffer) { // want "unused parameter: c" + _ = 1 +} + +func z[T http.ResponseWriter](h T, _ *http.Request) { // no report: func z is address-taken + fmt.Println("Before") +} + +func l(_ http.Handler) http.Handler { // want "unused parameter: h" + return http.HandlerFunc(z[http.ResponseWriter]) +} + +func mult(a, _ int) int { // want "unused parameter: b" + a += 1 + return a +} + +func y[T any](a T) { + panic("yo") +} diff --git a/contribs/gnopls/internal/analysis/unusedparams/unusedparams.go b/contribs/gnopls/internal/analysis/unusedparams/unusedparams.go new file mode 100644 index 00000000000..ca808a740d3 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedparams/unusedparams.go @@ -0,0 +1,308 @@ +// 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 unusedparams + +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" + "golang.org/x/tools/gopls/internal/util/moreslices" + "golang.org/x/tools/internal/analysisinternal" +) + +//go:embed doc.go +var doc string + +var Analyzer = &analysis.Analyzer{ + Name: "unusedparams", + Doc: analysisinternal.MustExtractDoc(doc, "unusedparams"), + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, + URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams", +} + +const FixCategory = "unusedparams" // recognized by gopls ApplyFix + +func run(pass *analysis.Pass) (any, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + // First find all "address-taken" functions. + // We must conservatively assume that their parameters + // are all required to conform to some signature. + // + // A named function is address-taken if it is somewhere + // used not in call position: + // + // f(...) // not address-taken + // use(f) // address-taken + // + // A literal function is address-taken if it is not + // immediately bound to a variable, or if that variable is + // used not in call position: + // + // f := func() { ... }; f() used only in call position + // var f func(); f = func() { ...f()... }; f() ditto + // use(func() { ... }) address-taken + // + + // Note: this algorithm relies on the assumption that the + // analyzer is called only for the "widest" package for a + // given file: that is, p_test in preference to p, if both + // exist. Analyzing only package p may produce diagnostics + // that would be falsified based on declarations in p_test.go + // files. The gopls analysis driver does this, but most + // drivers to not, so running this command in, say, + // unitchecker or multichecker may produce incorrect results. + + // Gather global information: + // - uses of functions not in call position + // - unexported interface methods + // - all referenced variables + + usesOutsideCall := make(map[types.Object][]*ast.Ident) + unexportedIMethodNames := make(map[string]bool) + { + callPosn := make(map[*ast.Ident]bool) // all idents f appearing in f() calls + filter := []ast.Node{ + (*ast.CallExpr)(nil), + (*ast.InterfaceType)(nil), + } + inspect.Preorder(filter, func(n ast.Node) { + switch n := n.(type) { + case *ast.CallExpr: + // Strip off any generic instantiation. + fun := n.Fun + switch fun_ := fun.(type) { + case *ast.IndexExpr: + fun = fun_.X // f[T]() (funcs[i]() is rejected below) + case *ast.IndexListExpr: + fun = fun_.X // f[K, V]() + } + + // Find object: + // record non-exported function, method, or func-typed var. + var id *ast.Ident + switch fun := fun.(type) { + case *ast.Ident: + id = fun + case *ast.SelectorExpr: + id = fun.Sel + } + if id != nil && !id.IsExported() { + switch pass.TypesInfo.Uses[id].(type) { + case *types.Func, *types.Var: + callPosn[id] = true + } + } + + case *ast.InterfaceType: + // Record the set of names of unexported interface methods. + // (It would be more precise to record signatures but + // generics makes it tricky, and this conservative + // heuristic is close enough.) + t := pass.TypesInfo.TypeOf(n).(*types.Interface) + for i := 0; i < t.NumExplicitMethods(); i++ { + m := t.ExplicitMethod(i) + if !m.Exported() && m.Name() != "_" { + unexportedIMethodNames[m.Name()] = true + } + } + } + }) + + for id, obj := range pass.TypesInfo.Uses { + if !callPosn[id] { + // This includes "f = func() {...}", which we deal with below. + usesOutsideCall[obj] = append(usesOutsideCall[obj], id) + } + } + } + + // Find all vars (notably parameters) that are used. + usedVars := make(map[*types.Var]bool) + for _, obj := range pass.TypesInfo.Uses { + if v, ok := obj.(*types.Var); ok { + if v.IsField() { + continue // no point gathering these + } + usedVars[v] = true + } + } + + // Check each non-address-taken function's parameters are all used. + filter := []ast.Node{ + (*ast.FuncDecl)(nil), + (*ast.FuncLit)(nil), + } + inspect.WithStack(filter, func(n ast.Node, push bool, stack []ast.Node) bool { + // (We always return true so that we visit nested FuncLits.) + + if !push { + return true + } + + var ( + fn types.Object // function symbol (*Func, possibly *Var for a FuncLit) + ftype *ast.FuncType + body *ast.BlockStmt + ) + switch n := n.(type) { + case *ast.FuncDecl: + // We can't analyze non-Go functions. + if n.Body == nil { + return true + } + + // Ignore exported functions and methods: we + // must assume they may be address-taken in + // another package. + if n.Name.IsExported() { + return true + } + + // Ignore methods that match the name of any + // interface method declared in this package, + // as the method's signature may need to conform + // to the interface. + if n.Recv != nil && unexportedIMethodNames[n.Name.Name] { + return true + } + + fn = pass.TypesInfo.Defs[n.Name].(*types.Func) + ftype, body = n.Type, n.Body + + case *ast.FuncLit: + // Find the symbol for the variable (if any) + // to which the FuncLit is bound. + // (We don't bother to allow ParenExprs.) + switch parent := stack[len(stack)-2].(type) { + case *ast.AssignStmt: + // f = func() {...} + // f := func() {...} + for i, rhs := range parent.Rhs { + if rhs == n { + if id, ok := parent.Lhs[i].(*ast.Ident); ok { + fn = pass.TypesInfo.ObjectOf(id) + + // Edge case: f = func() {...} + // should not count as a use. + if pass.TypesInfo.Uses[id] != nil { + usesOutsideCall[fn] = moreslices.Remove(usesOutsideCall[fn], id) + } + + if fn == nil && id.Name == "_" { + // Edge case: _ = func() {...} + // has no var. Fake one. + fn = types.NewVar(id.Pos(), pass.Pkg, id.Name, pass.TypesInfo.TypeOf(n)) + } + } + break + } + } + + case *ast.ValueSpec: + // var f = func() { ... } + // (unless f is an exported package-level var) + for i, val := range parent.Values { + if val == n { + v := pass.TypesInfo.Defs[parent.Names[i]] + if !(v.Parent() == pass.Pkg.Scope() && v.Exported()) { + fn = v + } + break + } + } + } + + ftype, body = n.Type, n.Body + } + + // Ignore address-taken functions and methods: unused + // parameters may be needed to conform to a func type. + if fn == nil || len(usesOutsideCall[fn]) > 0 { + return true + } + + // If there are no parameters, there are no unused parameters. + if ftype.Params.NumFields() == 0 { + return true + } + + // To reduce false positives, ignore functions with an + // empty or panic body. + // + // We choose not to ignore functions whose body is a + // single return statement (as earlier versions did) + // func f() { return } + // func f() { return g(...) } + // as we suspect that was just heuristic to reduce + // false positives in the earlier unsound algorithm. + switch len(body.List) { + case 0: + // Empty body. Although the parameter is + // unnecessary, it's pretty obvious to the + // reader that that's the case, so we allow it. + return true // func f() {} + case 1: + if stmt, ok := body.List[0].(*ast.ExprStmt); ok { + // We allow a panic body, as it is often a + // placeholder for a future implementation: + // func f() { panic(...) } + if call, ok := stmt.X.(*ast.CallExpr); ok { + if fun, ok := call.Fun.(*ast.Ident); ok && fun.Name == "panic" { + return true + } + } + } + } + + // Report each unused parameter. + for _, field := range ftype.Params.List { + for _, id := range field.Names { + if id.Name == "_" { + continue + } + param := pass.TypesInfo.Defs[id].(*types.Var) + if !usedVars[param] { + start, end := field.Pos(), field.End() + if len(field.Names) > 1 { + start, end = id.Pos(), id.End() + } + // This diagnostic carries both an edit-based fix to + // rename the unused parameter, and a command-based fix + // to remove it (see golang.RemoveUnusedParameter). + pass.Report(analysis.Diagnostic{ + Pos: start, + End: end, + Message: fmt.Sprintf("unused parameter: %s", id.Name), + Category: FixCategory, + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: `Rename parameter to "_"`, + TextEdits: []analysis.TextEdit{{ + Pos: id.Pos(), + End: id.End(), + NewText: []byte("_"), + }}, + }, + { + Message: fmt.Sprintf("Remove unused parameter %q", id.Name), + // No TextEdits => computed by gopls command + }, + }, + }) + } + } + } + + return true + }) + return nil, nil +} diff --git a/contribs/gnopls/internal/analysis/unusedparams/unusedparams_test.go b/contribs/gnopls/internal/analysis/unusedparams/unusedparams_test.go new file mode 100644 index 00000000000..1e2d8851b8b --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedparams/unusedparams_test.go @@ -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 unusedparams_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/analysis/unusedparams" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + analysistest.RunWithSuggestedFixes(t, testdata, unusedparams.Analyzer, "a", "typeparams") +} diff --git a/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/assign/a.go b/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/assign/a.go new file mode 100644 index 00000000000..8421824b2d3 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/assign/a.go @@ -0,0 +1,74 @@ +// 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" + "os" +) + +type A struct { + b int +} + +func singleAssignment() { + v := "s" // want `declared (and|but) not used` + + s := []int{ // want `declared (and|but) not used` + 1, + 2, + } + + a := func(s string) bool { // want `declared (and|but) not used` + return false + } + + if 1 == 1 { + s := "v" // want `declared (and|but) not used` + } + + panic("I should survive") +} + +func noOtherStmtsInBlock() { + v := "s" // want `declared (and|but) not used` +} + +func partOfMultiAssignment() { + f, err := os.Open("file") // want `declared (and|but) not used` + panic(err) +} + +func sideEffects(cBool chan bool, cInt chan int) { + b := <-c // want `declared (and|but) not used` + s := fmt.Sprint("") // want `declared (and|but) not used` + a := A{ // want `declared (and|but) not used` + b: func() int { + return 1 + }(), + } + c := A{<-cInt} // want `declared (and|but) not used` + d := fInt() + <-cInt // want `declared (and|but) not used` + e := fBool() && <-cBool // want `declared (and|but) not used` + f := map[int]int{ // want `declared (and|but) not used` + fInt(): <-cInt, + } + g := []int{<-cInt} // want `declared (and|but) not used` + h := func(s string) {} // want `declared (and|but) not used` + i := func(s string) {}() // want `declared (and|but) not used` +} + +func commentAbove() { + // v is a variable + v := "s" // want `declared (and|but) not used` +} + +func fBool() bool { + return true +} + +func fInt() int { + return 1 +} diff --git a/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/assign/a.go.golden b/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/assign/a.go.golden new file mode 100644 index 00000000000..8f8d6128ea8 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/assign/a.go.golden @@ -0,0 +1,59 @@ +// 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" + "os" +) + +type A struct { + b int +} + +func singleAssignment() { + if 1 == 1 { + } + + panic("I should survive") +} + +func noOtherStmtsInBlock() { +} + +func partOfMultiAssignment() { + _, err := os.Open("file") // want `declared (and|but) not used` + panic(err) +} + +func sideEffects(cBool chan bool, cInt chan int) { + <-c // want `declared (and|but) not used` + fmt.Sprint("") // want `declared (and|but) not used` + A{ // want `declared (and|but) not used` + b: func() int { + return 1 + }(), + } + A{<-cInt} // want `declared (and|but) not used` + fInt() + <-cInt // want `declared (and|but) not used` + fBool() && <-cBool // want `declared (and|but) not used` + map[int]int{ // want `declared (and|but) not used` + fInt(): <-cInt, + } + []int{<-cInt} // want `declared (and|but) not used` + func(s string) {}() // want `declared (and|but) not used` +} + +func commentAbove() { + // v is a variable +} + +func fBool() bool { + return true +} + +func fInt() int { + return 1 +} diff --git a/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/decl/a.go b/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/decl/a.go new file mode 100644 index 00000000000..e01fdd8686e --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/decl/a.go @@ -0,0 +1,30 @@ +// 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 decl + +func a() { + var b, c bool // want `declared (and|but) not used` + panic(c) + + if 1 == 1 { + var s string // want `declared (and|but) not used` + } +} + +func b() { + // b is a variable + var b bool // want `declared (and|but) not used` +} + +func c() { + var ( + d string + + // some comment for c + c bool // want `declared (and|but) not used` + ) + + panic(d) +} diff --git a/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/decl/a.go.golden b/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/decl/a.go.golden new file mode 100644 index 00000000000..0594acdf7e3 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedvariable/testdata/src/decl/a.go.golden @@ -0,0 +1,24 @@ +// 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 decl + +func a() { + var c bool // want `declared (and|but) not used` + panic(c) + + if 1 == 1 { + } +} + +func b() { + // b is a variable +} + +func c() { + var ( + d string + ) + panic(d) +} diff --git a/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable.go b/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable.go new file mode 100644 index 00000000000..8019cfe9eca --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable.go @@ -0,0 +1,315 @@ +// 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 unusedvariable defines an analyzer that checks for unused variables. +package unusedvariable + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/token" + "go/types" + "regexp" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/astutil" +) + +const Doc = `check for unused variables and suggest fixes` + +var Analyzer = &analysis.Analyzer{ + Name: "unusedvariable", + Doc: Doc, + Requires: []*analysis.Analyzer{}, + Run: run, + RunDespiteErrors: true, // an unusedvariable diagnostic is a compile error + URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedvariable", +} + +// The suffix for this error message changed in Go 1.20 and Go 1.23. +var unusedVariableRegexp = []*regexp.Regexp{ + regexp.MustCompile("^(.*) declared but not used$"), + regexp.MustCompile("^(.*) declared and not used$"), // Go 1.20+ + regexp.MustCompile("^declared and not used: (.*)$"), // Go 1.23+ +} + +func run(pass *analysis.Pass) (interface{}, error) { + for _, typeErr := range pass.TypeErrors { + for _, re := range unusedVariableRegexp { + match := re.FindStringSubmatch(typeErr.Msg) + if len(match) > 0 { + varName := match[1] + // Beginning in Go 1.23, go/types began quoting vars as `v'. + varName = strings.Trim(varName, "'`'") + + err := runForError(pass, typeErr, varName) + if err != nil { + return nil, err + } + } + } + } + + return nil, nil +} + +func runForError(pass *analysis.Pass, err types.Error, name string) error { + var file *ast.File + for _, f := range pass.Files { + if f.Pos() <= err.Pos && err.Pos < f.End() { + file = f + break + } + } + if file == nil { + return nil + } + + path, _ := astutil.PathEnclosingInterval(file, err.Pos, err.Pos) + if len(path) < 2 { + return nil + } + + ident, ok := path[0].(*ast.Ident) + if !ok || ident.Name != name { + return nil + } + + diag := analysis.Diagnostic{ + Pos: ident.Pos(), + End: ident.End(), + Message: err.Msg, + } + + for i := range path { + switch stmt := path[i].(type) { + case *ast.ValueSpec: + // Find GenDecl to which offending ValueSpec belongs. + if decl, ok := path[i+1].(*ast.GenDecl); ok { + fixes := removeVariableFromSpec(pass, path, stmt, decl, ident) + // fixes may be nil + if len(fixes) > 0 { + diag.SuggestedFixes = fixes + pass.Report(diag) + } + } + + case *ast.AssignStmt: + if stmt.Tok != token.DEFINE { + continue + } + + containsIdent := false + for _, expr := range stmt.Lhs { + if expr == ident { + containsIdent = true + } + } + if !containsIdent { + continue + } + + fixes := removeVariableFromAssignment(path, stmt, ident) + // fixes may be nil + if len(fixes) > 0 { + diag.SuggestedFixes = fixes + pass.Report(diag) + } + } + } + + return nil +} + +func removeVariableFromSpec(pass *analysis.Pass, path []ast.Node, stmt *ast.ValueSpec, decl *ast.GenDecl, ident *ast.Ident) []analysis.SuggestedFix { + newDecl := new(ast.GenDecl) + *newDecl = *decl + newDecl.Specs = nil + + for _, spec := range decl.Specs { + if spec != stmt { + newDecl.Specs = append(newDecl.Specs, spec) + continue + } + + newSpec := new(ast.ValueSpec) + *newSpec = *stmt + newSpec.Names = nil + + for _, n := range stmt.Names { + if n != ident { + newSpec.Names = append(newSpec.Names, n) + } + } + + if len(newSpec.Names) > 0 { + newDecl.Specs = append(newDecl.Specs, newSpec) + } + } + + // decl.End() does not include any comments, so if a comment is present we + // need to account for it when we delete the statement + end := decl.End() + if stmt.Comment != nil && stmt.Comment.End() > end { + end = stmt.Comment.End() + } + + // There are no other specs left in the declaration, the whole statement can + // be deleted + if len(newDecl.Specs) == 0 { + // Find parent DeclStmt and delete it + for _, node := range path { + if declStmt, ok := node.(*ast.DeclStmt); ok { + edits := deleteStmtFromBlock(path, declStmt) + if len(edits) == 0 { + return nil // can this happen? + } + return []analysis.SuggestedFix{ + { + Message: suggestedFixMessage(ident.Name), + TextEdits: edits, + }, + } + } + } + } + + var b bytes.Buffer + if err := format.Node(&b, pass.Fset, newDecl); err != nil { + return nil + } + + return []analysis.SuggestedFix{ + { + Message: suggestedFixMessage(ident.Name), + TextEdits: []analysis.TextEdit{ + { + Pos: decl.Pos(), + // Avoid adding a new empty line + End: end + 1, + NewText: b.Bytes(), + }, + }, + }, + } +} + +func removeVariableFromAssignment(path []ast.Node, stmt *ast.AssignStmt, ident *ast.Ident) []analysis.SuggestedFix { + // The only variable in the assignment is unused + if len(stmt.Lhs) == 1 { + // If LHS has only one expression to be valid it has to have 1 expression + // on RHS + // + // RHS may have side effects, preserve RHS + if exprMayHaveSideEffects(stmt.Rhs[0]) { + // Delete until RHS + return []analysis.SuggestedFix{ + { + Message: suggestedFixMessage(ident.Name), + TextEdits: []analysis.TextEdit{ + { + Pos: ident.Pos(), + End: stmt.Rhs[0].Pos(), + }, + }, + }, + } + } + + // RHS does not have any side effects, delete the whole statement + edits := deleteStmtFromBlock(path, stmt) + if len(edits) == 0 { + return nil // can this happen? + } + return []analysis.SuggestedFix{ + { + Message: suggestedFixMessage(ident.Name), + TextEdits: edits, + }, + } + } + + // Otherwise replace ident with `_` + return []analysis.SuggestedFix{ + { + Message: suggestedFixMessage(ident.Name), + TextEdits: []analysis.TextEdit{ + { + Pos: ident.Pos(), + End: ident.End(), + NewText: []byte("_"), + }, + }, + }, + } +} + +func suggestedFixMessage(name string) string { + return fmt.Sprintf("Remove variable %s", name) +} + +func deleteStmtFromBlock(path []ast.Node, stmt ast.Stmt) []analysis.TextEdit { + // Find innermost enclosing BlockStmt. + var block *ast.BlockStmt + for i := range path { + if blockStmt, ok := path[i].(*ast.BlockStmt); ok { + block = blockStmt + break + } + } + + nodeIndex := -1 + for i, blockStmt := range block.List { + if blockStmt == stmt { + nodeIndex = i + break + } + } + + // The statement we need to delete was not found in BlockStmt + if nodeIndex == -1 { + return nil + } + + // Delete until the end of the block unless there is another statement after + // the one we are trying to delete + end := block.Rbrace + if nodeIndex < len(block.List)-1 { + end = block.List[nodeIndex+1].Pos() + } + + return []analysis.TextEdit{ + { + Pos: stmt.Pos(), + End: end, + }, + } +} + +// exprMayHaveSideEffects reports whether the expression may have side effects +// (because it contains a function call or channel receive). We disregard +// runtime panics as well written programs should not encounter them. +func exprMayHaveSideEffects(expr ast.Expr) bool { + var mayHaveSideEffects bool + ast.Inspect(expr, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.CallExpr: // possible function call + mayHaveSideEffects = true + return false + case *ast.UnaryExpr: + if n.Op == token.ARROW { // channel receive + mayHaveSideEffects = true + return false + } + case *ast.FuncLit: + return false // evaluating what's inside a FuncLit has no effect + } + return true + }) + + return mayHaveSideEffects +} diff --git a/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable_test.go b/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable_test.go new file mode 100644 index 00000000000..5dcca007a98 --- /dev/null +++ b/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable_test.go @@ -0,0 +1,24 @@ +// 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 unusedvariable_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/analysis/unusedvariable" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + + t.Run("decl", func(t *testing.T) { + analysistest.RunWithSuggestedFixes(t, testdata, unusedvariable.Analyzer, "decl") + }) + + t.Run("assign", func(t *testing.T) { + analysistest.RunWithSuggestedFixes(t, testdata, unusedvariable.Analyzer, "assign") + }) +} diff --git a/contribs/gnopls/internal/analysis/useany/testdata/src/a/a.go b/contribs/gnopls/internal/analysis/useany/testdata/src/a/a.go new file mode 100644 index 00000000000..22d69315070 --- /dev/null +++ b/contribs/gnopls/internal/analysis/useany/testdata/src/a/a.go @@ -0,0 +1,25 @@ +// 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 useany checker. + +package a + +type Any interface{} + +func _[T interface{}]() {} // want "could use \"any\" for this empty interface" +func _[X any, T interface{}]() {} // want "could use \"any\" for this empty interface" +func _[any interface{}]() {} // want "could use \"any\" for this empty interface" +func _[T Any]() {} // want "could use \"any\" for this empty interface" +func _[T interface{ int | interface{} }]() {} // want "could use \"any\" for this empty interface" +func _[T interface{ int | Any }]() {} // want "could use \"any\" for this empty interface" +func _[T any]() {} + +type _[T interface{}] int // want "could use \"any\" for this empty interface" +type _[X any, T interface{}] int // want "could use \"any\" for this empty interface" +type _[any interface{}] int // want "could use \"any\" for this empty interface" +type _[T Any] int // want "could use \"any\" for this empty interface" +type _[T interface{ int | interface{} }] int // want "could use \"any\" for this empty interface" +type _[T interface{ int | Any }] int // want "could use \"any\" for this empty interface" +type _[T any] int diff --git a/contribs/gnopls/internal/analysis/useany/testdata/src/a/a.go.golden b/contribs/gnopls/internal/analysis/useany/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..efd8fd640a4 --- /dev/null +++ b/contribs/gnopls/internal/analysis/useany/testdata/src/a/a.go.golden @@ -0,0 +1,25 @@ +// 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 useany checker. + +package a + +type Any interface{} + +func _[T any]() {} // want "could use \"any\" for this empty interface" +func _[X any, T any]() {} // want "could use \"any\" for this empty interface" +func _[any interface{}]() {} // want "could use \"any\" for this empty interface" +func _[T any]() {} // want "could use \"any\" for this empty interface" +func _[T any]() {} // want "could use \"any\" for this empty interface" +func _[T any]() {} // want "could use \"any\" for this empty interface" +func _[T any]() {} + +type _[T any] int // want "could use \"any\" for this empty interface" +type _[X any, T any] int // want "could use \"any\" for this empty interface" +type _[any interface{}] int // want "could use \"any\" for this empty interface" +type _[T any] int // want "could use \"any\" for this empty interface" +type _[T any] int // want "could use \"any\" for this empty interface" +type _[T any] int // want "could use \"any\" for this empty interface" +type _[T any] int diff --git a/contribs/gnopls/internal/analysis/useany/useany.go b/contribs/gnopls/internal/analysis/useany/useany.go new file mode 100644 index 00000000000..ff25e5945d3 --- /dev/null +++ b/contribs/gnopls/internal/analysis/useany/useany.go @@ -0,0 +1,98 @@ +// 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 useany defines an Analyzer that checks for usage of interface{} in +// constraints, rather than the predeclared any. +package useany + +import ( + "fmt" + "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" +) + +const Doc = `check for constraints that could be simplified to "any"` + +var Analyzer = &analysis.Analyzer{ + Name: "useany", + Doc: Doc, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, + URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/useany", +} + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + universeAny := types.Universe.Lookup("any") + + nodeFilter := []ast.Node{ + (*ast.TypeSpec)(nil), + (*ast.FuncType)(nil), + } + + inspect.Preorder(nodeFilter, func(node ast.Node) { + var tparams *ast.FieldList + switch node := node.(type) { + case *ast.TypeSpec: + tparams = node.TypeParams + case *ast.FuncType: + tparams = node.TypeParams + default: + panic(fmt.Sprintf("unexpected node type %T", node)) + } + if tparams.NumFields() == 0 { + return + } + + for _, field := range tparams.List { + typ := pass.TypesInfo.Types[field.Type].Type + if typ == nil { + continue // something is wrong, but not our concern + } + iface, ok := typ.Underlying().(*types.Interface) + if !ok { + continue // invalid constraint + } + + // If the constraint is the empty interface, offer a fix to use 'any' + // instead. + if iface.Empty() { + id, _ := field.Type.(*ast.Ident) + if id != nil && pass.TypesInfo.Uses[id] == universeAny { + continue + } + + diag := analysis.Diagnostic{ + Pos: field.Type.Pos(), + End: field.Type.End(), + Message: `could use "any" for this empty interface`, + } + + // Only suggest a fix to 'any' if we actually resolve the predeclared + // any in this scope. + if scope := pass.TypesInfo.Scopes[node]; scope != nil { + if _, any := scope.LookupParent("any", token.NoPos); any == universeAny { + diag.SuggestedFixes = []analysis.SuggestedFix{{ + Message: `use "any"`, + TextEdits: []analysis.TextEdit{{ + Pos: field.Type.Pos(), + End: field.Type.End(), + NewText: []byte("any"), + }}, + }} + } + } + + pass.Report(diag) + } + } + }) + return nil, nil +} diff --git a/contribs/gnopls/internal/analysis/useany/useany_test.go b/contribs/gnopls/internal/analysis/useany/useany_test.go new file mode 100644 index 00000000000..a8cb692f359 --- /dev/null +++ b/contribs/gnopls/internal/analysis/useany/useany_test.go @@ -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 useany_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/analysis/useany" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + analysistest.RunWithSuggestedFixes(t, testdata, useany.Analyzer, "a") +} diff --git a/contribs/gnopls/internal/cache/analysis.go b/contribs/gnopls/internal/cache/analysis.go new file mode 100644 index 00000000000..0d7fc46237d --- /dev/null +++ b/contribs/gnopls/internal/cache/analysis.go @@ -0,0 +1,1678 @@ +// 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. + +package cache + +// This file defines gopls' driver for modular static analysis (go/analysis). + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/gob" + "encoding/json" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "log" + urlpkg "net/url" + "path/filepath" + "reflect" + "runtime" + "runtime/debug" + "slices" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/filecache" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/progress" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/frob" + "golang.org/x/tools/gopls/internal/util/moremaps" + "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/facts" + "golang.org/x/tools/internal/gcimporter" + "golang.org/x/tools/internal/typesinternal" + "golang.org/x/tools/internal/versions" +) + +/* + + DESIGN + + An analysis request (Snapshot.Analyze) is for a set of Analyzers and + PackageIDs. The result is the set of diagnostics for those + packages. Each request constructs a transitively closed DAG of + nodes, each representing a package, then works bottom up in + parallel postorder calling runCached to ensure that each node's + analysis summary is up to date. The summary contains the analysis + diagnostics as well as the intermediate results required by the + recursion, such as serialized types and facts. + + The entire DAG is ephemeral. Each node in the DAG records the set + of analyzers to run: the complete set for the root packages, and + the "facty" subset for dependencies. Each package is thus analyzed + at most once. The entire DAG shares a single FileSet for parsing + and importing. + + Each node is processed by runCached. It gets the source file + content hashes for package p, and the summaries of its "vertical" + dependencies (direct imports), and from them it computes a key + representing the unit of work (parsing, type-checking, and + analysis) that it has to do. The key is a cryptographic hash of the + "recipe" for this step, including the Metadata, the file contents, + the set of analyzers, and the type and fact information from the + vertical dependencies. + + The key is sought in a machine-global persistent file-system based + cache. If this gopls process, or another gopls process on the same + machine, has already performed this analysis step, runCached will + make a cache hit and load the serialized summary of the results. If + not, it will have to proceed to run() to parse and type-check the + package and then apply a set of analyzers to it. (The set of + analyzers applied to a single package itself forms a graph of + "actions", and it too is evaluated in parallel postorder; these + dependency edges within the same package are called "horizontal".) + Finally it writes a new cache entry. The entry contains serialized + types (export data) and analysis facts. + + Each node in the DAG acts like a go/types importer mapping, + providing a consistent view of packages and their objects: the + mapping for a node is a superset of its dependencies' mappings. + Every node has an associated *types.Package, initially nil. A + package is populated during run (cache miss) by type-checking its + syntax; but for a cache hit, the package is populated lazily, i.e. + not until it later becomes necessary because it is imported + directly or referenced by export data higher up in the DAG. + + For types, we use "shallow" export data. Historically, the Go + compiler always produced a summary of the types for a given package + that included types from other packages that it indirectly + referenced: "deep" export data. This had the advantage that the + compiler (and analogous tools such as gopls) need only load one + file per direct import. However, it meant that the files tended to + get larger based on the level of the package in the import + graph. For example, higher-level packages in the kubernetes module + have over 1MB of "deep" export data, even when they have almost no + content of their own, merely because they mention a major type that + references many others. In pathological cases the export data was + 300x larger than the source for a package due to this quadratic + growth. + + "Shallow" export data means that the serialized types describe only + a single package. If those types mention types from other packages, + the type checker may need to request additional packages beyond + just the direct imports. Type information for the entire transitive + closure of imports is provided (lazily) by the DAG. + + For correct dependency analysis, the digest used as a cache key + must reflect the "deep" export data, so it is derived recursively + from the transitive closure. As an optimization, we needn't include + every package of the transitive closure in the deep hash, only the + packages that were actually requested by the type checker. This + allows changes to a package that have no effect on its export data + to be "pruned". The direct consumer will need to be re-executed, + but if its export data is unchanged as a result, then indirect + consumers may not need to be re-executed. This allows, for example, + one to insert a print statement in a function and not "rebuild" the + whole application (though export data does record line numbers and + offsets of types which may be perturbed by otherwise insignificant + changes.) + + The summary must record whether a package is transitively + error-free (whether it would compile) because many analyzers are + not safe to run on packages with inconsistent types. + + For fact encoding, we use the same fact set as the unitchecker + (vet) to record and serialize analysis facts. The fact + serialization mechanism is analogous to "deep" export data. + +*/ + +// TODO(adonovan): +// - Add a (white-box) test of pruning when a change doesn't affect export data. +// - Optimise pruning based on subset of packages mentioned in exportdata. +// - Better logging so that it is possible to deduce why an analyzer +// is not being run--often due to very indirect failures. +// Even if the ultimate consumer decides to ignore errors, +// tests and other situations want to be assured of freedom from +// errors, not just missing results. This should be recorded. +// - Split this into a subpackage, gopls/internal/cache/driver, +// consisting of this file and three helpers from errors.go. +// The (*snapshot).Analyze method would stay behind and make calls +// to the driver package. +// Steps: +// - define a narrow driver.Snapshot interface with only these methods: +// Metadata(PackageID) Metadata +// ReadFile(Context, URI) (file.Handle, error) +// View() *View // for Options +// - share cache.{goVersionRx,parseGoImpl} + +// AnalysisProgressTitle is the title of the progress report for ongoing +// analysis. It is sought by regression tests for the progress reporting +// feature. +const AnalysisProgressTitle = "Analyzing Dependencies" + +// Analyze applies a set of analyzers to the package denoted by id, +// and returns their diagnostics for that package. +// +// The analyzers list must be duplicate free; order does not matter. +// +// Notifications of progress may be sent to the optional reporter. +func (s *Snapshot) Analyze(ctx context.Context, pkgs map[PackageID]*metadata.Package, analyzers []*settings.Analyzer, reporter *progress.Tracker) ([]*Diagnostic, error) { + start := time.Now() // for progress reporting + + var tagStr string // sorted comma-separated list of PackageIDs + { + keys := make([]string, 0, len(pkgs)) + for id := range pkgs { + keys = append(keys, string(id)) + } + sort.Strings(keys) + tagStr = strings.Join(keys, ",") + } + ctx, done := event.Start(ctx, "snapshot.Analyze", label.Package.Of(tagStr)) + defer done() + + // Filter and sort enabled root analyzers. + // A disabled analyzer may still be run if required by another. + toSrc := make(map[*analysis.Analyzer]*settings.Analyzer) + var enabledAnalyzers []*analysis.Analyzer // enabled subset + transitive requirements + for _, a := range analyzers { + if enabled, ok := s.Options().Analyses[a.Analyzer().Name]; enabled || !ok && a.EnabledByDefault() { + toSrc[a.Analyzer()] = a + enabledAnalyzers = append(enabledAnalyzers, a.Analyzer()) + } + } + sort.Slice(enabledAnalyzers, func(i, j int) bool { + return enabledAnalyzers[i].Name < enabledAnalyzers[j].Name + }) + analyzers = nil // prevent accidental use + + enabledAnalyzers = requiredAnalyzers(enabledAnalyzers) + + // Perform basic sanity checks. + // (Ideally we would do this only once.) + if err := analysis.Validate(enabledAnalyzers); err != nil { + return nil, fmt.Errorf("invalid analyzer configuration: %v", err) + } + + stableNames := make(map[*analysis.Analyzer]string) + + var facty []*analysis.Analyzer // facty subset of enabled + transitive requirements + for _, a := range enabledAnalyzers { + // TODO(adonovan): reject duplicate stable names (very unlikely). + stableNames[a] = stableName(a) + + // Register fact types of all required analyzers. + if len(a.FactTypes) > 0 { + facty = append(facty, a) + for _, f := range a.FactTypes { + gob.Register(f) // <2us + } + } + } + facty = requiredAnalyzers(facty) + + // File set for this batch (entire graph) of analysis. + fset := token.NewFileSet() + + // Starting from the root packages and following DepsByPkgPath, + // build the DAG of packages we're going to analyze. + // + // Root nodes will run the enabled set of analyzers, + // whereas dependencies will run only the facty set. + // Because (by construction) enabled is a superset of facty, + // we can analyze each node with exactly one set of analyzers. + nodes := make(map[PackageID]*analysisNode) + var leaves []*analysisNode // nodes with no unfinished successors + var makeNode func(from *analysisNode, id PackageID) (*analysisNode, error) + makeNode = func(from *analysisNode, id PackageID) (*analysisNode, error) { + an, ok := nodes[id] + if !ok { + mp := s.Metadata(id) + if mp == nil { + return nil, bug.Errorf("no metadata for %s", id) + } + + // -- preorder -- + + an = &analysisNode{ + fset: fset, + fsource: struct{ file.Source }{s}, // expose only ReadFile + viewType: s.View().Type(), + mp: mp, + analyzers: facty, // all nodes run at least the facty analyzers + allDeps: make(map[PackagePath]*analysisNode), + exportDeps: make(map[PackagePath]*analysisNode), + stableNames: stableNames, + } + nodes[id] = an + + // -- recursion -- + + // Build subgraphs for dependencies. + an.succs = make(map[PackageID]*analysisNode, len(mp.DepsByPkgPath)) + for _, depID := range mp.DepsByPkgPath { + dep, err := makeNode(an, depID) + if err != nil { + return nil, err + } + an.succs[depID] = dep + + // Compute the union of all dependencies. + // (This step has quadratic complexity.) + for pkgPath, node := range dep.allDeps { + an.allDeps[pkgPath] = node + } + } + + // -- postorder -- + + an.allDeps[mp.PkgPath] = an // add self entry (reflexive transitive closure) + + // Add leaf nodes (no successors) directly to queue. + if len(an.succs) == 0 { + leaves = append(leaves, an) + } + + // Load the contents of each compiled Go file through + // the snapshot's cache. (These are all cache hits as + // files are pre-loaded following packages.Load) + an.files = make([]file.Handle, len(mp.CompiledGoFiles)) + for i, uri := range mp.CompiledGoFiles { + fh, err := s.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + an.files[i] = fh + } + } + // Add edge from predecessor. + if from != nil { + from.unfinishedSuccs.Add(+1) // incref + an.preds = append(an.preds, from) + } + an.unfinishedPreds.Add(+1) + return an, nil + } + + // For root packages, we run the enabled set of analyzers. + var roots []*analysisNode + for id := range pkgs { + root, err := makeNode(nil, id) + if err != nil { + return nil, err + } + root.analyzers = enabledAnalyzers + roots = append(roots, root) + } + + // Now that we have read all files, + // we no longer need the snapshot. + // (but options are needed for progress reporting) + options := s.Options() + s = nil + + // Progress reporting. If supported, gopls reports progress on analysis + // passes that are taking a long time. + maybeReport := func(completed int64) {} + + // Enable progress reporting if enabled by the user + // and we have a capable reporter. + if reporter != nil && reporter.SupportsWorkDoneProgress() && options.AnalysisProgressReporting { + var reportAfter = options.ReportAnalysisProgressAfter // tests may set this to 0 + const reportEvery = 1 * time.Second + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + var ( + reportMu sync.Mutex + lastReport time.Time + wd *progress.WorkDone + ) + defer func() { + reportMu.Lock() + defer reportMu.Unlock() + + if wd != nil { + wd.End(ctx, "Done.") // ensure that the progress report exits + } + }() + maybeReport = func(completed int64) { + now := time.Now() + if now.Sub(start) < reportAfter { + return + } + + reportMu.Lock() + defer reportMu.Unlock() + + if wd == nil { + wd = reporter.Start(ctx, AnalysisProgressTitle, "", nil, cancel) + } + + if now.Sub(lastReport) > reportEvery { + lastReport = now + // Trailing space is intentional: some LSP clients strip newlines. + msg := fmt.Sprintf(`Indexed %d/%d packages. (Set "analysisProgressReporting" to false to disable notifications.)`, + completed, len(nodes)) + pct := 100 * float64(completed) / float64(len(nodes)) + wd.Report(ctx, msg, pct) + } + } + } + + // Execute phase: run leaves first, adding + // new nodes to the queue as they become leaves. + var g errgroup.Group + + // Analysis is CPU-bound. + // + // Note: avoid g.SetLimit here: it makes g.Go stop accepting work, which + // prevents workers from enqeuing, and thus finishing, and thus allowing the + // group to make progress: deadlock. + limiter := make(chan unit, runtime.GOMAXPROCS(0)) + var completed atomic.Int64 + + var enqueue func(*analysisNode) + enqueue = func(an *analysisNode) { + g.Go(func() error { + limiter <- unit{} + defer func() { <-limiter }() + + summary, err := an.runCached(ctx) + if err != nil { + return err // cancelled, or failed to produce a package + } + maybeReport(completed.Add(1)) + an.summary = summary + + // Notify each waiting predecessor, + // and enqueue it when it becomes a leaf. + for _, pred := range an.preds { + if pred.unfinishedSuccs.Add(-1) == 0 { // decref + enqueue(pred) + } + } + + // Notify each successor that we no longer need + // its action summaries, which hold Result values. + // After the last one, delete it, so that we + // free up large results such as SSA. + for _, succ := range an.succs { + succ.decrefPreds() + } + return nil + }) + } + for _, leaf := range leaves { + enqueue(leaf) + } + if err := g.Wait(); err != nil { + return nil, err // cancelled, or failed to produce a package + } + + // Inv: all root nodes now have a summary (#66732). + // + // We know this is falsified empirically. This means either + // the summary was "successfully" set to nil (above), or there + // is a problem with the graph such the enqueuing leaves does + // not lead to completion of roots (or an error). + for _, root := range roots { + if root.summary == nil { + bug.Report("root analysisNode has nil summary") + } + } + + // Report diagnostics only from enabled actions that succeeded. + // Errors from creating or analyzing packages are ignored. + // Diagnostics are reported in the order of the analyzers argument. + // + // TODO(adonovan): ignoring action errors gives the caller no way + // to distinguish "there are no problems in this code" from + // "the code (or analyzers!) are so broken that we couldn't even + // begin the analysis you asked for". + // Even if current callers choose to discard the + // results, we should propagate the per-action errors. + var results []*Diagnostic + for _, root := range roots { + for _, a := range enabledAnalyzers { + // Skip analyzers that were added only to + // fulfil requirements of the original set. + srcAnalyzer, ok := toSrc[a] + if !ok { + // Although this 'skip' operation is logically sound, + // it is nonetheless surprising that its absence should + // cause #60909 since none of the analyzers currently added for + // requirements (e.g. ctrlflow, inspect, buildssa) + // is capable of reporting diagnostics. + if summary := root.summary.Actions[stableNames[a]]; summary != nil { + if n := len(summary.Diagnostics); n > 0 { + bug.Reportf("Internal error: got %d unexpected diagnostics from analyzer %s. This analyzer was added only to fulfil the requirements of the requested set of analyzers, and it is not expected that such analyzers report diagnostics. Please report this in issue #60909.", n, a) + } + } + continue + } + + // Inv: root.summary is the successful result of run (via runCached). + // TODO(adonovan): fix: root.summary is sometimes nil! (#66732). + summary, ok := root.summary.Actions[stableNames[a]] + if summary == nil { + panic(fmt.Sprintf("analyzeSummary.Actions[%q] = (nil, %t); got %v (#60551)", + stableNames[a], ok, root.summary.Actions)) + } + if summary.Err != "" { + continue // action failed + } + for _, gobDiag := range summary.Diagnostics { + results = append(results, toSourceDiagnostic(srcAnalyzer, &gobDiag)) + } + } + } + return results, nil +} + +func (an *analysisNode) decrefPreds() { + if an.unfinishedPreds.Add(-1) == 0 { + an.summary.Actions = nil + } +} + +// An analysisNode is a node in a doubly-linked DAG isomorphic to the +// import graph. Each node represents a single package, and the DAG +// represents a batch of analysis work done at once using a single +// realm of token.Pos or types.Object values. +// +// A complete DAG is created anew for each batch of analysis; +// subgraphs are not reused over time. Each node's *types.Package +// field is initially nil and is populated on demand, either from +// type-checking syntax trees (typeCheck) or from importing export +// data (_import). When this occurs, the typesOnce event becomes +// "done". +// +// Each node's allDeps map is a "view" of all its dependencies keyed by +// package path, which defines the types.Importer mapping used when +// populating the node's types.Package. Different nodes have different +// views (e.g. due to variants), but two nodes that are related by +// graph ordering have views that are consistent in their overlap. +// exportDeps is the subset actually referenced by export data; +// this is the set for which we attempt to decode facts. +// +// Each node's run method is called in parallel postorder. On success, +// its summary field is populated, either from the cache (hit), or by +// type-checking and analyzing syntax (miss). +type analysisNode struct { + fset *token.FileSet // file set shared by entire batch (DAG) + fsource file.Source // Snapshot.ReadFile, for use by Pass.ReadFile + viewType ViewType // type of view + mp *metadata.Package // metadata for this package + files []file.Handle // contents of CompiledGoFiles + analyzers []*analysis.Analyzer // set of analyzers to run + preds []*analysisNode // graph edges: + succs map[PackageID]*analysisNode // (preds -> self -> succs) + unfinishedSuccs atomic.Int32 + unfinishedPreds atomic.Int32 // effectively a summary.Actions refcount + allDeps map[PackagePath]*analysisNode // all dependencies including self + exportDeps map[PackagePath]*analysisNode // subset of allDeps ref'd by export data (+self) + summary *analyzeSummary // serializable result of analyzing this package + stableNames map[*analysis.Analyzer]string // cross-process stable names for Analyzers + + typesOnce sync.Once // guards lazy population of types and typesErr fields + types *types.Package // type information lazily imported from summary + typesErr error // an error producing type information +} + +func (an *analysisNode) String() string { return string(an.mp.ID) } + +// _import imports this node's types.Package from export data, if not already done. +// Precondition: analysis was a success. +// Postcondition: an.types and an.exportDeps are populated. +func (an *analysisNode) _import() (*types.Package, error) { + an.typesOnce.Do(func() { + if an.mp.PkgPath == "unsafe" { + an.types = types.Unsafe + return + } + + an.types = types.NewPackage(string(an.mp.PkgPath), string(an.mp.Name)) + + // getPackages recursively imports each dependency + // referenced by the export data, in parallel. + getPackages := func(items []gcimporter.GetPackagesItem) error { + var g errgroup.Group + for i, item := range items { + path := PackagePath(item.Path) + dep, ok := an.allDeps[path] + if !ok { + // This early return bypasses Wait; that's ok. + return fmt.Errorf("%s: unknown dependency %q", an.mp, path) + } + an.exportDeps[path] = dep // record, for later fact decoding + if dep == an { + if an.typesErr != nil { + return an.typesErr + } else { + items[i].Pkg = an.types + } + } else { + i := i + g.Go(func() error { + depPkg, err := dep._import() + if err == nil { + items[i].Pkg = depPkg + } + return err + }) + } + } + return g.Wait() + } + pkg, err := gcimporter.IImportShallow(an.fset, getPackages, an.summary.Export, string(an.mp.PkgPath), bug.Reportf) + if err != nil { + an.typesErr = bug.Errorf("%s: invalid export data: %v", an.mp, err) + an.types = nil + } else if pkg != an.types { + log.Fatalf("%s: inconsistent packages", an.mp) + } + }) + return an.types, an.typesErr +} + +// analyzeSummary is a gob-serializable summary of successfully +// applying a list of analyzers to a package. +type analyzeSummary struct { + Export []byte // encoded types of package + DeepExportHash file.Hash // hash of reflexive transitive closure of export data + Compiles bool // transitively free of list/parse/type errors + Actions actionMap // maps analyzer stablename to analysis results (*actionSummary) +} + +// actionMap defines a stable Gob encoding for a map. +// TODO(adonovan): generalize and move to a library when we can use generics. +type actionMap map[string]*actionSummary + +var ( + _ gob.GobEncoder = (actionMap)(nil) + _ gob.GobDecoder = (*actionMap)(nil) +) + +type actionsMapEntry struct { + K string + V *actionSummary +} + +func (m actionMap) GobEncode() ([]byte, error) { + entries := make([]actionsMapEntry, 0, len(m)) + for k, v := range m { + entries = append(entries, actionsMapEntry{k, v}) + } + sort.Slice(entries, func(i, j int) bool { + return entries[i].K < entries[j].K + }) + var buf bytes.Buffer + err := gob.NewEncoder(&buf).Encode(entries) + return buf.Bytes(), err +} + +func (m *actionMap) GobDecode(data []byte) error { + var entries []actionsMapEntry + if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&entries); err != nil { + return err + } + *m = make(actionMap, len(entries)) + for _, e := range entries { + (*m)[e.K] = e.V + } + return nil +} + +// actionSummary is a gob-serializable summary of one possibly failed analysis action. +// If Err is non-empty, the other fields are undefined. +type actionSummary struct { + Facts []byte // the encoded facts.Set + FactsHash file.Hash // hash(Facts) + Diagnostics []gobDiagnostic + Err string // "" => success +} + +// runCached applies a list of analyzers (plus any others +// transitively required by them) to a package. It succeeds as long +// as it could produce a types.Package, even if there were direct or +// indirect list/parse/type errors, and even if all the analysis +// actions failed. It usually fails only if the package was unknown, +// a file was missing, or the operation was cancelled. +// +// Postcondition: runCached must not continue to use the snapshot +// (in background goroutines) after it has returned; see memoize.RefCounted. +func (an *analysisNode) runCached(ctx context.Context) (*analyzeSummary, error) { + // At this point we have the action results (serialized + // packages and facts) of our immediate dependencies, + // and the metadata and content of this package. + // + // We now compute a hash for all our inputs, and consult a + // global cache of promised results. If nothing material + // has changed, we'll make a hit in the shared cache. + // + // The hash of our inputs is based on the serialized export + // data and facts so that immaterial changes can be pruned + // without decoding. + key := an.cacheKey() + + // Access the cache. + var summary *analyzeSummary + const cacheKind = "analysis" + if data, err := filecache.Get(cacheKind, key); err == nil { + // cache hit + analyzeSummaryCodec.Decode(data, &summary) + if summary == nil { // debugging #66732 + bug.Reportf("analyzeSummaryCodec.Decode yielded nil *analyzeSummary") + } + } else if err != filecache.ErrNotFound { + return nil, bug.Errorf("internal error reading shared cache: %v", err) + } else { + // Cache miss: do the work. + var err error + summary, err = an.run(ctx) + if err != nil { + return nil, err + } + if summary == nil { // debugging #66732 (can't happen) + bug.Reportf("analyzeNode.run returned nil *analyzeSummary") + } + + an.unfinishedPreds.Add(+1) // incref + go func() { + defer an.decrefPreds() //decref + + cacheLimit <- unit{} // acquire token + defer func() { <-cacheLimit }() // release token + + data := analyzeSummaryCodec.Encode(summary) + if false { + log.Printf("Set key=%d value=%d id=%s\n", len(key), len(data), an.mp.ID) + } + if err := filecache.Set(cacheKind, key, data); err != nil { + event.Error(ctx, "internal error updating analysis shared cache", err) + } + }() + } + + return summary, nil +} + +// cacheLimit reduces parallelism of cache updates. +// We allow more than typical GOMAXPROCS as it's a mix of CPU and I/O. +var cacheLimit = make(chan unit, 32) + +// analysisCacheKey returns a cache key that is a cryptographic digest +// of the all the values that might affect type checking and analysis: +// the analyzer names, package metadata, names and contents of +// compiled Go files, and vdeps (successor) information +// (export data and facts). +func (an *analysisNode) cacheKey() [sha256.Size]byte { + hasher := sha256.New() + + // In principle, a key must be the hash of an + // unambiguous encoding of all the relevant data. + // If it's ambiguous, we risk collisions. + + // analyzers + fmt.Fprintf(hasher, "analyzers: %d\n", len(an.analyzers)) + for _, a := range an.analyzers { + fmt.Fprintln(hasher, a.Name) + } + + // package metadata + mp := an.mp + fmt.Fprintf(hasher, "package: %s %s %s\n", mp.ID, mp.Name, mp.PkgPath) + fmt.Fprintf(hasher, "viewtype: %s\n", an.viewType) // (affects diagnostics) + + // We can ignore m.DepsBy{Pkg,Import}Path: although the logic + // uses those fields, we account for them by hashing vdeps. + + // type sizes + wordSize := an.mp.TypesSizes.Sizeof(types.Typ[types.Int]) + maxAlign := an.mp.TypesSizes.Alignof(types.NewPointer(types.Typ[types.Int64])) + fmt.Fprintf(hasher, "sizes: %d %d\n", wordSize, maxAlign) + + // metadata errors: used for 'compiles' field + fmt.Fprintf(hasher, "errors: %d", len(mp.Errors)) + + // module Go version + if mp.Module != nil && mp.Module.GoVersion != "" { + fmt.Fprintf(hasher, "go %s\n", mp.Module.GoVersion) + } + + // file names and contents + fmt.Fprintf(hasher, "files: %d\n", len(an.files)) + for _, fh := range an.files { + fmt.Fprintln(hasher, fh.Identity()) + } + + // vdeps, in PackageID order + for _, vdep := range moremaps.Sorted(an.succs) { + fmt.Fprintf(hasher, "dep: %s\n", vdep.mp.PkgPath) + fmt.Fprintf(hasher, "export: %s\n", vdep.summary.DeepExportHash) + + // action results: errors and facts + actions := vdep.summary.Actions + names := make([]string, 0, len(actions)) + for name := range actions { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + summary := actions[name] + fmt.Fprintf(hasher, "action %s\n", name) + if summary.Err != "" { + fmt.Fprintf(hasher, "error %s\n", summary.Err) + } else { + fmt.Fprintf(hasher, "facts %s\n", summary.FactsHash) + // We can safely omit summary.diagnostics + // from the key since they have no downstream effect. + } + } + } + + var hash [sha256.Size]byte + hasher.Sum(hash[:0]) + return hash +} + +// run implements the cache-miss case. +// This function does not access the snapshot. +// +// Postcondition: on success, the analyzeSummary.Actions +// key set is {a.Name for a in analyzers}. +func (an *analysisNode) run(ctx context.Context) (*analyzeSummary, error) { + // Parse only the "compiled" Go files. + // Do the computation in parallel. + parsed := make([]*parsego.File, len(an.files)) + { + var group errgroup.Group + group.SetLimit(4) // not too much: run itself is already called in parallel + for i, fh := range an.files { + i, fh := i, fh + group.Go(func() error { + // Call parseGoImpl directly, not the caching wrapper, + // as cached ASTs require the global FileSet. + // ast.Object resolution is unfortunately an implied part of the + // go/analysis contract. + pgf, err := parseGoImpl(ctx, an.fset, fh, parsego.Full&^parser.SkipObjectResolution, false) + parsed[i] = pgf + return err + }) + } + if err := group.Wait(); err != nil { + return nil, err // cancelled, or catastrophic error (e.g. missing file) + } + } + + // Type-check the package syntax. + pkg := an.typeCheck(parsed) + + // Publish the completed package. + an.typesOnce.Do(func() { an.types = pkg.types }) + if an.types != pkg.types { + log.Fatalf("typesOnce prematurely done") + } + + // Compute the union of exportDeps across our direct imports. + // This is the set that will be needed by the fact decoder. + allExportDeps := make(map[PackagePath]*analysisNode) + for _, succ := range an.succs { + for k, v := range succ.exportDeps { + allExportDeps[k] = v + } + } + + // The fact decoder needs a means to look up a Package by path. + pkg.factsDecoder = facts.NewDecoderFunc(pkg.types, func(path string) *types.Package { + // Note: Decode is called concurrently, and thus so is this function. + + // Does the fact relate to a package referenced by export data? + if dep, ok := allExportDeps[PackagePath(path)]; ok { + dep.typesOnce.Do(func() { log.Fatal("dep.types not populated") }) + if dep.typesErr == nil { + return dep.types + } + return nil + } + + // If the fact relates to a dependency not referenced + // by export data, it is safe to ignore it. + // (In that case dep.types exists but may be unpopulated + // or in the process of being populated from export data.) + if an.allDeps[PackagePath(path)] == nil { + log.Fatalf("fact package %q is not a dependency", path) + } + return nil + }) + + // Poll cancellation state. + if err := ctx.Err(); err != nil { + return nil, err + } + + // -- analysis -- + + // Build action graph for this package. + // Each graph node (action) is one unit of analysis. + actions := make(map[*analysis.Analyzer]*action) + var mkAction func(a *analysis.Analyzer) *action + mkAction = func(a *analysis.Analyzer) *action { + act, ok := actions[a] + if !ok { + var hdeps []*action + for _, req := range a.Requires { + hdeps = append(hdeps, mkAction(req)) + } + act = &action{ + a: a, + fsource: an.fsource, + stableName: an.stableNames[a], + pkg: pkg, + vdeps: an.succs, + hdeps: hdeps, + } + actions[a] = act + } + return act + } + + // Build actions for initial package. + var roots []*action + for _, a := range an.analyzers { + roots = append(roots, mkAction(a)) + } + + // Execute the graph in parallel. + execActions(ctx, roots) + // Inv: each root's summary is set (whether success or error). + + // Don't return (or cache) the result in case of cancellation. + if err := ctx.Err(); err != nil { + return nil, err // cancelled + } + + // Return summaries only for the requested actions. + summaries := make(map[string]*actionSummary) + for _, root := range roots { + if root.summary == nil { + panic("root has nil action.summary (#60551)") + } + summaries[root.stableName] = root.summary + } + + return &analyzeSummary{ + Export: pkg.export, + DeepExportHash: pkg.deepExportHash, + Compiles: pkg.compiles, + Actions: summaries, + }, nil +} + +// Postcondition: analysisPackage.types and an.exportDeps are populated. +func (an *analysisNode) typeCheck(parsed []*parsego.File) *analysisPackage { + mp := an.mp + + if false { // debugging + log.Println("typeCheck", mp.ID) + } + + pkg := &analysisPackage{ + mp: mp, + fset: an.fset, + parsed: parsed, + files: make([]*ast.File, len(parsed)), + compiles: len(mp.Errors) == 0, // false => list error + types: types.NewPackage(string(mp.PkgPath), string(mp.Name)), + typesInfo: &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Instances: make(map[*ast.Ident]types.Instance), + Implicits: make(map[ast.Node]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + Uses: make(map[*ast.Ident]types.Object), + }, + typesSizes: mp.TypesSizes, + } + versions.InitFileVersions(pkg.typesInfo) + + // Unsafe has no syntax. + if mp.PkgPath == "unsafe" { + pkg.types = types.Unsafe + return pkg + } + + for i, p := range parsed { + pkg.files[i] = p.File + if p.ParseErr != nil { + pkg.compiles = false // parse error + } + } + + for _, vdep := range an.succs { + if !vdep.summary.Compiles { + pkg.compiles = false // transitive error + } + } + + cfg := &types.Config{ + Sizes: mp.TypesSizes, + Error: func(e error) { + pkg.compiles = false // type error + + // Suppress type errors in files with parse errors + // as parser recovery can be quite lossy (#59888). + typeError := e.(types.Error) + for _, p := range parsed { + if p.ParseErr != nil && astutil.NodeContains(p.File, typeError.Pos) { + return + } + } + pkg.typeErrors = append(pkg.typeErrors, typeError) + }, + Importer: importerFunc(func(importPath string) (*types.Package, error) { + // Beware that returning an error from this function + // will cause the type checker to synthesize a fake + // package whose Path is importPath, potentially + // losing a vendor/ prefix. If type-checking errors + // are swallowed, these packages may be confusing. + + // Map ImportPath to ID. + id, ok := mp.DepsByImpPath[ImportPath(importPath)] + if !ok { + // The import syntax is inconsistent with the metadata. + // This could be because the import declaration was + // incomplete and the metadata only includes complete + // imports; or because the metadata ignores import + // edges that would lead to cycles in the graph. + return nil, fmt.Errorf("missing metadata for import of %q", importPath) + } + + // Map ID to node. (id may be "") + dep := an.succs[id] + if dep == nil { + // Analogous to (*snapshot).missingPkgError + // in the logic for regular type-checking, + // but without a snapshot we can't provide + // such detail, and anyway most analysis + // failures aren't surfaced in the UI. + return nil, fmt.Errorf("no required module provides analysis package %q (id=%q)", importPath, id) + } + + // (Duplicates logic from check.go.) + if !metadata.IsValidImport(an.mp.PkgPath, dep.mp.PkgPath, an.viewType != GoPackagesDriverView) { + return nil, fmt.Errorf("invalid use of internal package %s", importPath) + } + + return dep._import() + }), + } + + // Set Go dialect. + if mp.Module != nil && mp.Module.GoVersion != "" { + goVersion := "go" + mp.Module.GoVersion + if validGoVersion(goVersion) { + cfg.GoVersion = goVersion + } + } + + // We want to type check cgo code if go/types supports it. + // We passed typecheckCgo to go/packages when we Loaded. + // TODO(adonovan): do we actually need this?? + typesinternal.SetUsesCgo(cfg) + + check := types.NewChecker(cfg, pkg.fset, pkg.types, pkg.typesInfo) + + // Type checking errors are handled via the config, so ignore them here. + _ = check.Files(pkg.files) + + // debugging (type errors are quite normal) + if false { + if pkg.typeErrors != nil { + log.Printf("package %s has type errors: %v", pkg.types.Path(), pkg.typeErrors) + } + } + + // Emit the export data and compute the recursive hash. + export, err := gcimporter.IExportShallow(pkg.fset, pkg.types, bug.Reportf) + if err != nil { + // TODO(adonovan): in light of exporter bugs such as #57729, + // consider using bug.Report here and retrying the IExportShallow + // call here using an empty types.Package. + log.Fatalf("internal error writing shallow export data: %v", err) + } + pkg.export = export + + // Compute a recursive hash to account for the export data of + // this package and each dependency referenced by it. + // Also, populate exportDeps. + hash := sha256.New() + fmt.Fprintf(hash, "%s %d\n", mp.PkgPath, len(export)) + hash.Write(export) + paths, err := readShallowManifest(export) + if err != nil { + log.Fatalf("internal error: bad export data: %v", err) + } + for _, path := range paths { + dep, ok := an.allDeps[path] + if !ok { + log.Fatalf("%s: missing dependency: %q", an, path) + } + fmt.Fprintf(hash, "%s %s\n", dep.mp.PkgPath, dep.summary.DeepExportHash) + an.exportDeps[path] = dep + } + an.exportDeps[mp.PkgPath] = an // self + hash.Sum(pkg.deepExportHash[:0]) + + return pkg +} + +// readShallowManifest returns the manifest of packages referenced by +// a shallow export data file for a package (excluding the package itself). +// TODO(adonovan): add a test. +func readShallowManifest(export []byte) ([]PackagePath, error) { + const selfPath = "" // dummy path + var paths []PackagePath + getPackages := func(items []gcimporter.GetPackagesItem) error { + paths = []PackagePath{} // non-nil + for _, item := range items { + if item.Path != selfPath { + paths = append(paths, PackagePath(item.Path)) + } + } + return errors.New("stop") // terminate importer + } + _, err := gcimporter.IImportShallow(token.NewFileSet(), getPackages, export, selfPath, bug.Reportf) + if paths == nil { + if err != nil { + return nil, err // failed before getPackages callback + } + return nil, bug.Errorf("internal error: IImportShallow did not call getPackages") + } + return paths, nil // success +} + +// analysisPackage contains information about a package, including +// syntax trees, used transiently during its type-checking and analysis. +type analysisPackage struct { + mp *metadata.Package + fset *token.FileSet // local to this package + parsed []*parsego.File + files []*ast.File // same as parsed[i].File + types *types.Package + compiles bool // package is transitively free of list/parse/type errors + factsDecoder *facts.Decoder + export []byte // encoding of types.Package + deepExportHash file.Hash // reflexive transitive hash of export data + typesInfo *types.Info + typeErrors []types.Error + typesSizes types.Sizes +} + +// An action represents one unit of analysis work: the application of +// one analysis to one package. Actions form a DAG, both within a +// package (as different analyzers are applied, either in sequence or +// parallel), and across packages (as dependencies are analyzed). +type action struct { + once sync.Once + a *analysis.Analyzer + fsource file.Source // Snapshot.ReadFile, for Pass.ReadFile + stableName string // cross-process stable name of analyzer + pkg *analysisPackage + hdeps []*action // horizontal dependencies + vdeps map[PackageID]*analysisNode // vertical dependencies + + // results of action.exec(): + result interface{} // result of Run function, of type a.ResultType + summary *actionSummary + err error +} + +func (act *action) String() string { + return fmt.Sprintf("%s@%s", act.a.Name, act.pkg.mp.ID) +} + +// execActions executes a set of action graph nodes in parallel. +// Postcondition: each action.summary is set, even in case of error. +func execActions(ctx context.Context, actions []*action) { + var wg sync.WaitGroup + for _, act := range actions { + act := act + wg.Add(1) + go func() { + defer wg.Done() + act.once.Do(func() { + execActions(ctx, act.hdeps) // analyze "horizontal" dependencies + act.result, act.summary, act.err = act.exec(ctx) + if act.err != nil { + act.summary = &actionSummary{Err: act.err.Error()} + // TODO(adonovan): suppress logging. But + // shouldn't the root error's causal chain + // include this information? + if false { // debugging + log.Printf("act.exec(%v) failed: %v", act, act.err) + } + } + }) + if act.summary == nil { + panic("nil action.summary (#60551)") + } + }() + } + wg.Wait() +} + +// exec defines the execution of a single action. +// It returns the (ephemeral) result of the analyzer's Run function, +// along with its (serializable) facts and diagnostics. +// Or it returns an error if the analyzer did not run to +// completion and deliver a valid result. +func (act *action) exec(ctx context.Context) (any, *actionSummary, error) { + analyzer := act.a + pkg := act.pkg + + hasFacts := len(analyzer.FactTypes) > 0 + + // Report an error if any action dependency (vertical or horizontal) failed. + // To avoid long error messages describing chains of failure, + // we return the dependencies' error' unadorned. + if hasFacts { + // TODO(adonovan): use deterministic order. + for _, vdep := range act.vdeps { + if summ := vdep.summary.Actions[act.stableName]; summ.Err != "" { + return nil, nil, errors.New(summ.Err) + } + } + } + for _, dep := range act.hdeps { + if dep.err != nil { + return nil, nil, dep.err + } + } + // Inv: all action dependencies succeeded. + + // Were there list/parse/type errors that might prevent analysis? + if !pkg.compiles && !analyzer.RunDespiteErrors { + return nil, nil, fmt.Errorf("skipping analysis %q because package %q does not compile", analyzer.Name, pkg.mp.ID) + } + // Inv: package is well-formed enough to proceed with analysis. + + if false { // debugging + log.Println("action.exec", act) + } + + // Gather analysis Result values from horizontal dependencies. + inputs := make(map[*analysis.Analyzer]interface{}) + for _, dep := range act.hdeps { + inputs[dep.a] = dep.result + } + + // TODO(adonovan): opt: facts.Set works but it may be more + // efficient to fork and tailor it to our precise needs. + // + // We've already sharded the fact encoding by action + // so that it can be done in parallel. + // We could eliminate locking. + // We could also dovetail more closely with the export data + // decoder to obtain a more compact representation of + // packages and objects (e.g. its internal IDs, instead + // of PkgPaths and objectpaths.) + // More importantly, we should avoid re-export of + // facts that related to objects that are discarded + // by "deep" export data. Better still, use a "shallow" approach. + + // Read and decode analysis facts for each direct import. + factset, err := pkg.factsDecoder.Decode(func(pkgPath string) ([]byte, error) { + if !hasFacts { + return nil, nil // analyzer doesn't use facts, so no vdeps + } + + // Package.Imports() may contain a fake "C" package. Ignore it. + if pkgPath == "C" { + return nil, nil + } + + id, ok := pkg.mp.DepsByPkgPath[PackagePath(pkgPath)] + if !ok { + // This may mean imp was synthesized by the type + // checker because it failed to import it for any reason + // (e.g. bug processing export data; metadata ignoring + // a cycle-forming import). + // In that case, the fake package's imp.Path + // is set to the failed importPath (and thus + // it may lack a "vendor/" prefix). + // + // For now, silently ignore it on the assumption + // that the error is already reported elsewhere. + // return nil, fmt.Errorf("missing metadata") + return nil, nil + } + + vdep := act.vdeps[id] + if vdep == nil { + return nil, bug.Errorf("internal error in %s: missing vdep for id=%s", pkg.types.Path(), id) + } + + return vdep.summary.Actions[act.stableName].Facts, nil + }) + if err != nil { + return nil, nil, fmt.Errorf("internal error decoding analysis facts: %w", err) + } + + // TODO(adonovan): make Export*Fact panic rather than discarding + // undeclared fact types, so that we discover bugs in analyzers. + factFilter := make(map[reflect.Type]bool) + for _, f := range analyzer.FactTypes { + factFilter[reflect.TypeOf(f)] = true + } + + // posToLocation converts from token.Pos to protocol form. + posToLocation := func(start, end token.Pos) (protocol.Location, error) { + tokFile := pkg.fset.File(start) + + // Find existing mapper by file name. + // (Don't require an exact token.File match + // as the analyzer may have re-parsed the file.) + var ( + mapper *protocol.Mapper + fixed bool + ) + for _, p := range pkg.parsed { + if p.Tok.Name() == tokFile.Name() { + mapper = p.Mapper + fixed = p.Fixed() // suppress some assertions after parser recovery + break + } + } + if mapper == nil { + // The start position was not among the package's parsed + // Go files, indicating that the analyzer added new files + // to the FileSet. + // + // For example, the cgocall analyzer re-parses and + // type-checks some of the files in a special environment; + // and asmdecl and other low-level runtime analyzers call + // ReadFile to parse non-Go files. + // (This is a supported feature, documented at go/analysis.) + // + // In principle these files could be: + // + // - OtherFiles (non-Go files such as asm). + // However, we set Pass.OtherFiles=[] because + // gopls won't service "diagnose" requests + // for non-Go files, so there's no point + // reporting diagnostics in them. + // + // - IgnoredFiles (files tagged for other configs). + // However, we set Pass.IgnoredFiles=[] because, + // in most cases, zero-config gopls should create + // another view that covers these files. + // + // - Referents of //line directives, as in cgo packages. + // The file names in this case are not known a priori. + // gopls generally tries to avoid honoring line directives, + // but analyzers such as cgocall may honor them. + // + // In short, it's unclear how this can be reached + // other than due to an analyzer bug. + return protocol.Location{}, bug.Errorf("diagnostic location is not among files of package: %s", tokFile.Name()) + } + // Inv: mapper != nil + + if end == token.NoPos { + end = start + } + + // debugging #64547 + fileStart := token.Pos(tokFile.Base()) + fileEnd := fileStart + token.Pos(tokFile.Size()) + if start < fileStart { + if !fixed { + bug.Reportf("start < start of file") + } + start = fileStart + } + if end < start { + // This can happen if End is zero (#66683) + // or a small positive displacement from zero + // due to recursive Node.End() computation. + // This usually arises from poor parser recovery + // of an incomplete term at EOF. + if !fixed { + bug.Reportf("end < start of file") + } + end = fileEnd + } + if end > fileEnd+1 { + if !fixed { + bug.Reportf("end > end of file + 1") + } + end = fileEnd + } + + return mapper.PosLocation(tokFile, start, end) + } + + // Now run the (pkg, analyzer) action. + var diagnostics []gobDiagnostic + + pass := &analysis.Pass{ + Analyzer: analyzer, + Fset: pkg.fset, + Files: pkg.files, + OtherFiles: nil, // since gopls doesn't handle non-Go (e.g. asm) files + IgnoredFiles: nil, // zero-config gopls should analyze these files in another view + Pkg: pkg.types, + TypesInfo: pkg.typesInfo, + TypesSizes: pkg.typesSizes, + TypeErrors: pkg.typeErrors, + ResultOf: inputs, + Report: func(d analysis.Diagnostic) { + diagnostic, err := toGobDiagnostic(posToLocation, analyzer, d) + if err != nil { + // Don't bug.Report here: these errors all originate in + // posToLocation, and we can more accurately discriminate + // severe errors from benign ones in that function. + event.Error(ctx, fmt.Sprintf("internal error converting diagnostic from analyzer %q", analyzer.Name), err) + return + } + diagnostics = append(diagnostics, diagnostic) + }, + ImportObjectFact: factset.ImportObjectFact, + ExportObjectFact: factset.ExportObjectFact, + ImportPackageFact: factset.ImportPackageFact, + ExportPackageFact: factset.ExportPackageFact, + AllObjectFacts: func() []analysis.ObjectFact { return factset.AllObjectFacts(factFilter) }, + AllPackageFacts: func() []analysis.PackageFact { return factset.AllPackageFacts(factFilter) }, + } + + pass.ReadFile = func(filename string) ([]byte, error) { + // Read file from snapshot, to ensure reads are consistent. + // + // TODO(adonovan): make the dependency analysis sound by + // incorporating these additional files into the the analysis + // hash. This requires either (a) preemptively reading and + // hashing a potentially large number of mostly irrelevant + // files; or (b) some kind of dynamic dependency discovery + // system like used in Bazel for C++ headers. Neither entices. + if err := analysisinternal.CheckReadable(pass, filename); err != nil { + return nil, err + } + h, err := act.fsource.ReadFile(ctx, protocol.URIFromPath(filename)) + if err != nil { + return nil, err + } + content, err := h.Content() + if err != nil { + return nil, err // file doesn't exist + } + return slices.Clone(content), nil // follow ownership of os.ReadFile + } + + // Recover from panics (only) within the analyzer logic. + // (Use an anonymous function to limit the recover scope.) + var result interface{} + func() { + start := time.Now() + defer func() { + if r := recover(); r != nil { + // An Analyzer panicked, likely due to a bug. + // + // In general we want to discover and fix such panics quickly, + // so we don't suppress them, but some bugs in third-party + // analyzers cannot be quickly fixed, so we use an allowlist + // to suppress panics. + const strict = true + if strict && bug.PanicOnBugs && + analyzer.Name != "buildir" { // see https://github.com/dominikh/go-tools/issues/1343 + // Uncomment this when debugging suspected failures + // in the driver, not the analyzer. + if false { + debug.SetTraceback("all") // show all goroutines + } + panic(r) + } else { + // In production, suppress the panic and press on. + err = fmt.Errorf("analysis %s for package %s panicked: %v", analyzer.Name, pass.Pkg.Path(), r) + } + } + + // Accumulate running time for each checker. + analyzerRunTimesMu.Lock() + analyzerRunTimes[analyzer] += time.Since(start) + analyzerRunTimesMu.Unlock() + }() + + result, err = pass.Analyzer.Run(pass) + }() + if err != nil { + return nil, nil, err + } + + if got, want := reflect.TypeOf(result), pass.Analyzer.ResultType; got != want { + return nil, nil, bug.Errorf( + "internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v", + pass.Pkg.Path(), pass.Analyzer, got, want) + } + + // Disallow Export*Fact calls after Run. + // (A panic means the Analyzer is abusing concurrency.) + pass.ExportObjectFact = func(obj types.Object, fact analysis.Fact) { + panic(fmt.Sprintf("%v: Pass.ExportObjectFact(%s, %T) called after Run", act, obj, fact)) + } + pass.ExportPackageFact = func(fact analysis.Fact) { + panic(fmt.Sprintf("%v: Pass.ExportPackageFact(%T) called after Run", act, fact)) + } + + factsdata := factset.Encode() + return result, &actionSummary{ + Diagnostics: diagnostics, + Facts: factsdata, + FactsHash: file.HashOf(factsdata), + }, nil +} + +var ( + analyzerRunTimesMu sync.Mutex + analyzerRunTimes = make(map[*analysis.Analyzer]time.Duration) +) + +type LabelDuration struct { + Label string + Duration time.Duration +} + +// AnalyzerRunTimes returns the accumulated time spent in each Analyzer's +// Run function since process start, in descending order. +func AnalyzerRunTimes() []LabelDuration { + analyzerRunTimesMu.Lock() + defer analyzerRunTimesMu.Unlock() + + slice := make([]LabelDuration, 0, len(analyzerRunTimes)) + for a, t := range analyzerRunTimes { + slice = append(slice, LabelDuration{Label: a.Name, Duration: t}) + } + sort.Slice(slice, func(i, j int) bool { + return slice[i].Duration > slice[j].Duration + }) + return slice +} + +// requiredAnalyzers returns the transitive closure of required analyzers in preorder. +func requiredAnalyzers(analyzers []*analysis.Analyzer) []*analysis.Analyzer { + var result []*analysis.Analyzer + seen := make(map[*analysis.Analyzer]bool) + var visitAll func([]*analysis.Analyzer) + visitAll = func(analyzers []*analysis.Analyzer) { + for _, a := range analyzers { + if !seen[a] { + seen[a] = true + result = append(result, a) + visitAll(a.Requires) + } + } + } + visitAll(analyzers) + return result +} + +var analyzeSummaryCodec = frob.CodecFor[*analyzeSummary]() + +// -- data types for serialization of analysis.Diagnostic and golang.Diagnostic -- + +// (The name says gob but we use frob.) +var diagnosticsCodec = frob.CodecFor[[]gobDiagnostic]() + +type gobDiagnostic struct { + Location protocol.Location + Severity protocol.DiagnosticSeverity + Code string + CodeHref string + Source string + Message string + SuggestedFixes []gobSuggestedFix + Related []gobRelatedInformation + Tags []protocol.DiagnosticTag +} + +type gobRelatedInformation struct { + Location protocol.Location + Message string +} + +type gobSuggestedFix struct { + Message string + TextEdits []gobTextEdit + Command *gobCommand + ActionKind protocol.CodeActionKind +} + +type gobCommand struct { + Title string + Command string + Arguments []json.RawMessage +} + +type gobTextEdit struct { + Location protocol.Location + NewText []byte +} + +// toGobDiagnostic converts an analysis.Diagnosic to a serializable gobDiagnostic, +// which requires expanding token.Pos positions into protocol.Location form. +func toGobDiagnostic(posToLocation func(start, end token.Pos) (protocol.Location, error), a *analysis.Analyzer, diag analysis.Diagnostic) (gobDiagnostic, error) { + var fixes []gobSuggestedFix + for _, fix := range diag.SuggestedFixes { + var gobEdits []gobTextEdit + for _, textEdit := range fix.TextEdits { + loc, err := posToLocation(textEdit.Pos, textEdit.End) + if err != nil { + return gobDiagnostic{}, fmt.Errorf("in SuggestedFixes: %w", err) + } + gobEdits = append(gobEdits, gobTextEdit{ + Location: loc, + NewText: textEdit.NewText, + }) + } + fixes = append(fixes, gobSuggestedFix{ + Message: fix.Message, + TextEdits: gobEdits, + }) + } + + var related []gobRelatedInformation + for _, r := range diag.Related { + loc, err := posToLocation(r.Pos, r.End) + if err != nil { + return gobDiagnostic{}, fmt.Errorf("in Related: %w", err) + } + related = append(related, gobRelatedInformation{ + Location: loc, + Message: r.Message, + }) + } + + loc, err := posToLocation(diag.Pos, diag.End) + if err != nil { + return gobDiagnostic{}, err + } + + // The Code column of VSCode's Problems table renders this + // information as "Source(Code)" where code is a link to CodeHref. + // (The code field must be nonempty for anything to appear.) + diagURL := effectiveURL(a, diag) + code := "default" + if diag.Category != "" { + code = diag.Category + } + + return gobDiagnostic{ + Location: loc, + // Severity for analysis diagnostics is dynamic, + // based on user configuration per analyzer. + Code: code, + CodeHref: diagURL, + Source: a.Name, + Message: diag.Message, + SuggestedFixes: fixes, + Related: related, + // Analysis diagnostics do not contain tags. + }, nil +} + +// effectiveURL computes the effective URL of diag, +// using the algorithm specified at Diagnostic.URL. +func effectiveURL(a *analysis.Analyzer, diag analysis.Diagnostic) string { + u := diag.URL + if u == "" && diag.Category != "" { + u = "#" + diag.Category + } + if base, err := urlpkg.Parse(a.URL); err == nil { + if rel, err := urlpkg.Parse(u); err == nil { + u = base.ResolveReference(rel).String() + } + } + return u +} + +// stableName returns a name for the analyzer that is unique and +// stable across address spaces. +// +// Analyzer names are not unique. For example, gopls includes +// both x/tools/passes/nilness and staticcheck/nilness. +// For serialization, we must assign each analyzer a unique identifier +// that two gopls processes accessing the cache can agree on. +func stableName(a *analysis.Analyzer) string { + // Incorporate the file and line of the analyzer's Run function. + addr := reflect.ValueOf(a.Run).Pointer() + fn := runtime.FuncForPC(addr) + file, line := fn.FileLine(addr) + + // It is tempting to use just a.Name as the stable name when + // it is unique, but making them always differ helps avoid + // name/stablename confusion. + return fmt.Sprintf("%s(%s:%d)", a.Name, filepath.Base(file), line) +} diff --git a/contribs/gnopls/internal/cache/cache.go b/contribs/gnopls/internal/cache/cache.go new file mode 100644 index 00000000000..9f85846165f --- /dev/null +++ b/contribs/gnopls/internal/cache/cache.go @@ -0,0 +1,122 @@ +// 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. + +package cache + +import ( + "reflect" + "strconv" + "sync/atomic" + + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/internal/imports" + "golang.org/x/tools/internal/memoize" +) + +// ballast is a 100MB unused byte slice that exists only to reduce garbage +// collector CPU in small workspaces and at startup. +// +// The redesign of gopls described at https://go.dev/blog/gopls-scalability +// moved gopls to a model where it has a significantly smaller heap, yet still +// allocates many short-lived data structures during parsing and type checking. +// As a result, for some workspaces, particularly when opening a low-level +// package, the steady-state heap may be a small fraction of total allocation +// while rechecking the workspace, paradoxically causing the GC to consume much +// more CPU. For example, in one benchmark that analyzes the starlark +// repository, the steady-state heap was ~10MB, and the process of diagnosing +// the workspace allocated 100-200MB. +// +// The reason for this paradoxical behavior is that GC pacing +// (https://tip.golang.org/doc/gc-guide#GOGC) causes the collector to trigger +// at some multiple of the steady-state heap size, so a small steady-state heap +// causes GC to trigger sooner and more often when allocating the ephemeral +// structures. +// +// Allocating a 100MB ballast avoids this problem by ensuring a minimum heap +// size. The value of 100MB was chosen to be proportional to the in-memory +// cache in front the filecache package, and the throughput of type checking. +// Gopls already requires hundreds of megabytes of RAM to function. +// +// Note that while other use cases for a ballast were made obsolete by +// GOMEMLIMIT, ours is not. GOMEMLIMIT helps in cases where you have a +// containerized service and want to optimize its latency and throughput by +// taking advantage of available memory. However, in our case gopls is running +// on the developer's machine alongside other applications, and can have a wide +// range of memory footprints depending on the size of the user's workspace. +// Setting GOMEMLIMIT to too low a number would make gopls perform poorly on +// large repositories, and setting it to too high a number would make gopls a +// badly behaved tenant. Short of calibrating GOMEMLIMIT based on the user's +// workspace (an intractible problem), there is no way for gopls to use +// GOMEMLIMIT to solve its GC CPU problem. +// +// Because this allocation is large and occurs early, there is a good chance +// that rather than being recycled, it comes directly from the OS already +// zeroed, and since it is never accessed, the memory region may avoid being +// backed by pages of RAM. But see +// https://groups.google.com/g/golang-nuts/c/66d0cItfkjY/m/3NvgzL_sAgAJ +// +// For more details on this technique, see: +// https://blog.twitch.tv/en/2019/04/10/go-memory-ballast-how-i-learnt-to-stop-worrying-and-love-the-heap/ +var ballast = make([]byte, 100*1e6) + +// New Creates a new cache for gopls operation results, using the given file +// set, shared store, and session options. +// +// Both the fset and store may be nil, but if store is non-nil so must be fset +// (and they must always be used together), otherwise it may be possible to get +// cached data referencing token.Pos values not mapped by the FileSet. +func New(store *memoize.Store) *Cache { + index := atomic.AddInt64(&cacheIndex, 1) + + if store == nil { + store = &memoize.Store{} + } + + c := &Cache{ + id: strconv.FormatInt(index, 10), + store: store, + memoizedFS: newMemoizedFS(), + modCache: &sharedModCache{ + caches: make(map[string]*imports.DirInfoCache), + timers: make(map[string]*refreshTimer), + }, + } + return c +} + +// A Cache holds content that is shared across multiple gopls sessions. +type Cache struct { + id string + + // store holds cached calculations. + // + // TODO(rfindley): at this point, these are not important, as we've moved our + // content-addressable cache to the file system (the filecache package). It + // is unlikely that this shared cache provides any shared value. We should + // consider removing it, replacing current uses with a simpler futures cache, + // as we've done for e.g. type-checked packages. + store *memoize.Store + + // memoizedFS holds a shared file.Source that caches reads. + // + // Reads are invalidated when *any* session gets a didChangeWatchedFile + // notification. This is fine: it is the responsibility of memoizedFS to hold + // our best knowledge of the current file system state. + *memoizedFS + + // modCache holds the + modCache *sharedModCache +} + +var cacheIndex, sessionIndex, viewIndex int64 + +func (c *Cache) ID() string { return c.id } +func (c *Cache) MemStats() map[reflect.Type]int { return c.store.Stats() } + +// FileStats returns information about the set of files stored in the cache. +// It is intended for debugging only. +func (c *Cache) FileStats() (stats command.FileStats) { + stats.Total, stats.Largest, stats.Errs = c.fileStats() + return +} diff --git a/contribs/gnopls/internal/cache/check.go b/contribs/gnopls/internal/cache/check.go new file mode 100644 index 00000000000..4923c92db8d --- /dev/null +++ b/contribs/gnopls/internal/cache/check.go @@ -0,0 +1,2067 @@ +// 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. + +package cache + +import ( + "context" + "crypto/sha256" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "go/types" + "regexp" + "runtime" + "slices" + "sort" + "strings" + "sync" + "sync/atomic" + + "golang.org/x/mod/module" + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/cache/typerefs" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/filecache" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/analysisinternal" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/gcimporter" + "golang.org/x/tools/internal/packagesinternal" + "golang.org/x/tools/internal/tokeninternal" + "golang.org/x/tools/internal/typesinternal" + "golang.org/x/tools/internal/versions" +) + +// Various optimizations that should not affect correctness. +const ( + preserveImportGraph = true // hold on to the import graph for open packages +) + +type unit = struct{} + +// A typeCheckBatch holds data for a logical type-checking operation, which may +// type-check many unrelated packages. +// +// It shares state such as parsed files and imports, to optimize type-checking +// for packages with overlapping dependency graphs. +type typeCheckBatch struct { + syntaxIndex map[PackageID]int // requested ID -> index in ids + pre preTypeCheck + post postTypeCheck + handles map[PackageID]*packageHandle // (immutable) + parseCache *parseCache + fset *token.FileSet // describes all parsed or imported files + cpulimit chan unit // concurrency limiter for CPU-bound operations + + mu sync.Mutex + syntaxPackages map[PackageID]*futurePackage // results of processing a requested package; may hold (nil, nil) + importPackages map[PackageID]*futurePackage // package results to use for importing +} + +// A futurePackage is a future result of type checking or importing a package, +// to be cached in a map. +// +// The goroutine that creates the futurePackage is responsible for evaluating +// its value, and closing the done channel. +type futurePackage struct { + done chan unit + v pkgOrErr +} + +type pkgOrErr struct { + pkg *types.Package + err error +} + +// TypeCheck parses and type-checks the specified packages, +// and returns them in the same order as the ids. +// The resulting packages' types may belong to different importers, +// so types from different packages are incommensurable. +// +// The resulting packages slice always contains len(ids) entries, though some +// of them may be nil if (and only if) the resulting error is non-nil. +// +// An error is returned if any of the requested packages fail to type-check. +// This is different from having type-checking errors: a failure to type-check +// indicates context cancellation or otherwise significant failure to perform +// the type-checking operation. +// +// In general, clients should never need to type-checked syntax for an +// intermediate test variant (ITV) package. Callers should apply +// RemoveIntermediateTestVariants (or equivalent) before this method, or any +// of the potentially type-checking methods below. +func (s *Snapshot) TypeCheck(ctx context.Context, ids ...PackageID) ([]*Package, error) { + pkgs := make([]*Package, len(ids)) + + var ( + needIDs []PackageID // ids to type-check + indexes []int // original index of requested ids + ) + + // Check for existing active packages, as any package will do. + // + // This is also done inside forEachPackage, but doing it here avoids + // unnecessary set up for type checking (e.g. assembling the package handle + // graph). + for i, id := range ids { + if pkg := s.getActivePackage(id); pkg != nil { + pkgs[i] = pkg + } else { + needIDs = append(needIDs, id) + indexes = append(indexes, i) + } + } + + post := func(i int, pkg *Package) { + pkgs[indexes[i]] = pkg + } + return pkgs, s.forEachPackage(ctx, needIDs, nil, post) +} + +// getImportGraph returns a shared import graph use for this snapshot, or nil. +// +// This is purely an optimization: holding on to more imports allows trading +// memory for CPU and latency. Currently, getImportGraph returns an import +// graph containing all packages imported by open packages, since these are +// highly likely to be needed when packages change. +// +// Furthermore, since we memoize active packages, including their imports in +// the shared import graph means we don't run the risk of pinning duplicate +// copies of common imports, if active packages are computed in separate type +// checking batches. +func (s *Snapshot) getImportGraph(ctx context.Context) *importGraph { + if !preserveImportGraph { + return nil + } + s.mu.Lock() + + // Evaluate the shared import graph for the snapshot. There are three major + // codepaths here: + // + // 1. importGraphDone == nil, importGraph == nil: it is this goroutine's + // responsibility to type-check the shared import graph. + // 2. importGraphDone == nil, importGraph != nil: it is this goroutine's + // responsibility to resolve the import graph, which may result in + // type-checking only if the existing importGraph (carried over from the + // preceding snapshot) is invalid. + // 3. importGraphDone != nil: some other goroutine is doing (1) or (2), wait + // for the work to be done. + done := s.importGraphDone + if done == nil { + done = make(chan unit) + s.importGraphDone = done + release := s.Acquire() // must acquire to use the snapshot asynchronously + go func() { + defer release() + importGraph, err := s.resolveImportGraph() // may be nil + if err != nil { + // resolveImportGraph operates on the background context, because it is + // a shared resource across the snapshot. If the snapshot is cancelled, + // don't log an error. + if s.backgroundCtx.Err() == nil { + event.Error(ctx, "computing the shared import graph", err) + } + importGraph = nil + } + s.mu.Lock() + s.importGraph = importGraph + s.mu.Unlock() + close(done) + }() + } + s.mu.Unlock() + + select { + case <-done: + return s.importGraph + case <-ctx.Done(): + return nil + } +} + +// resolveImportGraph evaluates the shared import graph to use for +// type-checking in this snapshot. This may involve re-using the import graph +// of the previous snapshot (stored in s.importGraph), or computing a fresh +// import graph. +// +// resolveImportGraph should only be called from getImportGraph. +func (s *Snapshot) resolveImportGraph() (*importGraph, error) { + ctx := s.backgroundCtx + ctx, done := event.Start(event.Detach(ctx), "cache.resolveImportGraph") + defer done() + + s.mu.Lock() + lastImportGraph := s.importGraph + s.mu.Unlock() + + openPackages := make(map[PackageID]bool) + for _, fh := range s.Overlays() { + // golang/go#66145: don't call MetadataForFile here. This function, which + // builds a shared import graph, is an optimization. We don't want it to + // have the side effect of triggering a load. + // + // In the past, a call to MetadataForFile here caused a bunch of + // unnecessary loads in multi-root workspaces (and as a result, spurious + // diagnostics). + g := s.MetadataGraph() + var mps []*metadata.Package + for _, id := range g.IDs[fh.URI()] { + mps = append(mps, g.Packages[id]) + } + metadata.RemoveIntermediateTestVariants(&mps) + for _, mp := range mps { + openPackages[mp.ID] = true + } + } + + var openPackageIDs []PackageID + for id := range openPackages { + openPackageIDs = append(openPackageIDs, id) + } + + handles, err := s.getPackageHandles(ctx, openPackageIDs) + if err != nil { + return nil, err + } + + // Subtlety: we erase the upward cone of open packages from the shared import + // graph, to increase reusability. + // + // This is easiest to understand via an example: suppose A imports B, and B + // imports C. Now suppose A and B are open. If we preserve the entire set of + // shared deps by open packages, deps will be {B, C}. But this means that any + // change to the open package B will invalidate the shared import graph, + // meaning we will experience no benefit from sharing when B is edited. + // Consider that this will be a common scenario, when A is foo_test and B is + // foo. Better to just preserve the shared import C. + // + // With precise pruning, we may want to truncate this search based on + // reachability. + // + // TODO(rfindley): this logic could use a unit test. + volatileDeps := make(map[PackageID]bool) + var isVolatile func(*packageHandle) bool + isVolatile = func(ph *packageHandle) (volatile bool) { + if v, ok := volatileDeps[ph.mp.ID]; ok { + return v + } + defer func() { + volatileDeps[ph.mp.ID] = volatile + }() + if openPackages[ph.mp.ID] { + return true + } + for _, dep := range ph.mp.DepsByPkgPath { + if isVolatile(handles[dep]) { + return true + } + } + return false + } + for _, dep := range handles { + isVolatile(dep) + } + for id, volatile := range volatileDeps { + if volatile { + delete(handles, id) + } + } + + // We reuse the last import graph if and only if none of the dependencies + // have changed. Doing better would involve analyzing dependencies to find + // subgraphs that are still valid. Not worth it, especially when in the + // common case nothing has changed. + unchanged := lastImportGraph != nil && len(handles) == len(lastImportGraph.depKeys) + var ids []PackageID + depKeys := make(map[PackageID]file.Hash) + for id, ph := range handles { + ids = append(ids, id) + depKeys[id] = ph.key + if unchanged { + prevKey, ok := lastImportGraph.depKeys[id] + unchanged = ok && prevKey == ph.key + } + } + + if unchanged { + return lastImportGraph, nil + } + + b, err := s.forEachPackageInternal(ctx, nil, ids, nil, nil, nil, handles) + if err != nil { + return nil, err + } + + next := &importGraph{ + fset: b.fset, + depKeys: depKeys, + imports: make(map[PackageID]pkgOrErr), + } + for id, fut := range b.importPackages { + if fut.v.pkg == nil && fut.v.err == nil { + panic(fmt.Sprintf("internal error: import node %s is not evaluated", id)) + } + next.imports[id] = fut.v + } + return next, nil +} + +// An importGraph holds selected results of a type-checking pass, to be re-used +// by subsequent snapshots. +type importGraph struct { + fset *token.FileSet // fileset used for type checking imports + depKeys map[PackageID]file.Hash // hash of direct dependencies for this graph + imports map[PackageID]pkgOrErr // results of type checking +} + +// Package visiting functions used by forEachPackage; see the documentation of +// forEachPackage for details. +type ( + preTypeCheck = func(int, *packageHandle) bool // false => don't type check + postTypeCheck = func(int, *Package) +) + +// forEachPackage does a pre- and post- order traversal of the packages +// specified by ids using the provided pre and post functions. +// +// The pre func is optional. If set, pre is evaluated after the package +// handle has been constructed, but before type-checking. If pre returns false, +// type-checking is skipped for this package handle. +// +// post is called with a syntax package after type-checking completes +// successfully. It is only called if pre returned true. +// +// Both pre and post may be called concurrently. +func (s *Snapshot) forEachPackage(ctx context.Context, ids []PackageID, pre preTypeCheck, post postTypeCheck) error { + ctx, done := event.Start(ctx, "cache.forEachPackage", label.PackageCount.Of(len(ids))) + defer done() + + var ( + needIDs []PackageID // ids to type-check + indexes []int // original index of requested ids + ) + + // Check for existing active packages. + // + // Since gopls can't depend on package identity, any instance of the + // requested package must be ok to return. + // + // This is an optimization to avoid redundant type-checking: following + // changes to an open package many LSP clients send several successive + // requests for package information for the modified package (semantic + // tokens, code lens, inlay hints, etc.) + for i, id := range ids { + if pkg := s.getActivePackage(id); pkg != nil { + post(i, pkg) + } else { + needIDs = append(needIDs, id) + indexes = append(indexes, i) + } + } + + if len(needIDs) == 0 { + return nil // short cut: many call sites do not handle empty ids + } + + // Wrap the pre- and post- funcs to translate indices. + var pre2 preTypeCheck + if pre != nil { + pre2 = func(i int, ph *packageHandle) bool { + return pre(indexes[i], ph) + } + } + post2 := func(i int, pkg *Package) { + s.setActivePackage(pkg.metadata.ID, pkg) + post(indexes[i], pkg) + } + + handles, err := s.getPackageHandles(ctx, needIDs) + if err != nil { + return err + } + + impGraph := s.getImportGraph(ctx) + _, err = s.forEachPackageInternal(ctx, impGraph, nil, needIDs, pre2, post2, handles) + return err +} + +// forEachPackageInternal is used by both forEachPackage and loadImportGraph to +// type-check a graph of packages. +// +// If a non-nil importGraph is provided, imports in this graph will be reused. +func (s *Snapshot) forEachPackageInternal(ctx context.Context, importGraph *importGraph, importIDs, syntaxIDs []PackageID, pre preTypeCheck, post postTypeCheck, handles map[PackageID]*packageHandle) (*typeCheckBatch, error) { + b := &typeCheckBatch{ + pre: pre, + post: post, + handles: handles, + parseCache: s.view.parseCache, + fset: fileSetWithBase(reservedForParsing), + syntaxIndex: make(map[PackageID]int), + cpulimit: make(chan unit, runtime.GOMAXPROCS(0)), + syntaxPackages: make(map[PackageID]*futurePackage), + importPackages: make(map[PackageID]*futurePackage), + } + + if importGraph != nil { + // Clone the file set every time, to ensure we do not leak files. + b.fset = tokeninternal.CloneFileSet(importGraph.fset) + // Pre-populate future cache with 'done' futures. + done := make(chan unit) + close(done) + for id, res := range importGraph.imports { + b.importPackages[id] = &futurePackage{done, res} + } + } else { + b.fset = fileSetWithBase(reservedForParsing) + } + + for i, id := range syntaxIDs { + b.syntaxIndex[id] = i + } + + // Start a single goroutine for each requested package. + // + // Other packages are reached recursively, and will not be evaluated if they + // are not needed. + var g errgroup.Group + for _, id := range importIDs { + id := id + g.Go(func() error { + _, err := b.getImportPackage(ctx, id) + return err + }) + } + for i, id := range syntaxIDs { + i := i + id := id + g.Go(func() error { + _, err := b.handleSyntaxPackage(ctx, i, id) + return err + }) + } + return b, g.Wait() +} + +// TODO(rfindley): re-order the declarations below to read better from top-to-bottom. + +// getImportPackage returns the *types.Package to use for importing the +// package referenced by id. +// +// This may be the package produced by type-checking syntax (as in the case +// where id is in the set of requested IDs), a package loaded from export data, +// or a package type-checked for import only. +func (b *typeCheckBatch) getImportPackage(ctx context.Context, id PackageID) (pkg *types.Package, err error) { + b.mu.Lock() + f, ok := b.importPackages[id] + if ok { + b.mu.Unlock() + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-f.done: + return f.v.pkg, f.v.err + } + } + + f = &futurePackage{done: make(chan unit)} + b.importPackages[id] = f + b.mu.Unlock() + + defer func() { + f.v = pkgOrErr{pkg, err} + close(f.done) + }() + + if index, ok := b.syntaxIndex[id]; ok { + pkg, err := b.handleSyntaxPackage(ctx, index, id) + if err != nil { + return nil, err + } + if pkg != nil { + return pkg, nil + } + // type-checking was short-circuited by the pre- func. + } + + ph := b.handles[id] + + // "unsafe" cannot be imported or type-checked. + // + // We check PkgPath, not id, as the structure of the ID + // depends on the build system (in particular, + // Bazel+gopackagesdriver appears to use something other than + // "unsafe", though we aren't sure what; even 'go list' can + // use "p [q.test]" for testing or if PGO is enabled. + // See golang/go#60890. + if ph.mp.PkgPath == "unsafe" { + return types.Unsafe, nil + } + + data, err := filecache.Get(exportDataKind, ph.key) + if err == filecache.ErrNotFound { + // No cached export data: type-check as fast as possible. + return b.checkPackageForImport(ctx, ph) + } + if err != nil { + return nil, fmt.Errorf("failed to read cache data for %s: %v", ph.mp.ID, err) + } + return b.importPackage(ctx, ph.mp, data) +} + +// handleSyntaxPackage handles one package from the ids slice. +// +// If type checking occurred while handling the package, it returns the +// resulting types.Package so that it may be used for importing. +// +// handleSyntaxPackage returns (nil, nil) if pre returned false. +func (b *typeCheckBatch) handleSyntaxPackage(ctx context.Context, i int, id PackageID) (pkg *types.Package, err error) { + b.mu.Lock() + f, ok := b.syntaxPackages[id] + if ok { + b.mu.Unlock() + <-f.done + return f.v.pkg, f.v.err + } + + f = &futurePackage{done: make(chan unit)} + b.syntaxPackages[id] = f + b.mu.Unlock() + defer func() { + f.v = pkgOrErr{pkg, err} + close(f.done) + }() + + ph := b.handles[id] + if b.pre != nil && !b.pre(i, ph) { + return nil, nil // skip: export data only + } + + // Wait for predecessors. + { + var g errgroup.Group + for _, depID := range ph.mp.DepsByPkgPath { + depID := depID + g.Go(func() error { + _, err := b.getImportPackage(ctx, depID) + return err + }) + } + if err := g.Wait(); err != nil { + // Failure to import a package should not abort the whole operation. + // Stop only if the context was cancelled, a likely cause. + // Import errors will be reported as type diagnostics. + if ctx.Err() != nil { + return nil, ctx.Err() + } + } + } + + // Wait to acquire a CPU token. + // + // Note: it is important to acquire this token only after awaiting + // predecessors, to avoid starvation. + select { + case <-ctx.Done(): + return nil, ctx.Err() + case b.cpulimit <- unit{}: + defer func() { + <-b.cpulimit // release CPU token + }() + } + + // Compute the syntax package. + p, err := b.checkPackage(ctx, ph) + if err != nil { + return nil, err + } + + // Update caches. + go storePackageResults(ctx, ph, p) // ...and write all packages to disk + + b.post(i, p) + + return p.pkg.types, nil +} + +// storePackageResults serializes and writes information derived from p to the +// file cache. +// The context is used only for logging; cancellation does not affect the operation. +func storePackageResults(ctx context.Context, ph *packageHandle, p *Package) { + toCache := map[string][]byte{ + xrefsKind: p.pkg.xrefs(), + methodSetsKind: p.pkg.methodsets().Encode(), + testsKind: p.pkg.tests().Encode(), + diagnosticsKind: encodeDiagnostics(p.pkg.diagnostics), + } + + if p.metadata.PkgPath != "unsafe" { // unsafe cannot be exported + exportData, err := gcimporter.IExportShallow(p.pkg.fset, p.pkg.types, bug.Reportf) + if err != nil { + bug.Reportf("exporting package %v: %v", p.metadata.ID, err) + } else { + toCache[exportDataKind] = exportData + } + } + + for kind, data := range toCache { + if err := filecache.Set(kind, ph.key, data); err != nil { + event.Error(ctx, fmt.Sprintf("storing %s data for %s", kind, ph.mp.ID), err) + } + } +} + +// importPackage loads the given package from its export data in p.exportData +// (which must already be populated). +func (b *typeCheckBatch) importPackage(ctx context.Context, mp *metadata.Package, data []byte) (*types.Package, error) { + ctx, done := event.Start(ctx, "cache.typeCheckBatch.importPackage", label.Package.Of(string(mp.ID))) + defer done() + + importLookup := b.importLookup(mp) + + thisPackage := types.NewPackage(string(mp.PkgPath), string(mp.Name)) + getPackages := func(items []gcimporter.GetPackagesItem) error { + for i, item := range items { + var id PackageID + var pkg *types.Package + if item.Path == string(mp.PkgPath) { + id = mp.ID + pkg = thisPackage + + // debugging issues #60904, #64235 + if pkg.Name() != item.Name { + // This would mean that mp.Name != item.Name, so the + // manifest in the export data of mp.PkgPath is + // inconsistent with mp.Name. Or perhaps there + // are duplicate PkgPath items in the manifest? + return bug.Errorf("internal error: package name is %q, want %q (id=%q, path=%q) (see issue #60904)", + pkg.Name(), item.Name, id, item.Path) + } + } else { + id = importLookup(PackagePath(item.Path)) + var err error + pkg, err = b.getImportPackage(ctx, id) + if err != nil { + return err + } + + // We intentionally duplicate the bug.Errorf calls because + // telemetry tells us only the program counter, not the message. + + // debugging issues #60904, #64235 + if pkg.Name() != item.Name { + // This means that, while reading the manifest of the + // export data of mp.PkgPath, one of its indirect + // dependencies had a name that differs from the + // Metadata.Name + return bug.Errorf("internal error: package name is %q, want %q (id=%q, path=%q) (see issue #60904)", + pkg.Name(), item.Name, id, item.Path) + } + } + items[i].Pkg = pkg + + } + return nil + } + + // Importing is potentially expensive, and might not encounter cancellations + // via dependencies (e.g. if they have already been evaluated). + if ctx.Err() != nil { + return nil, ctx.Err() + } + + imported, err := gcimporter.IImportShallow(b.fset, getPackages, data, string(mp.PkgPath), bug.Reportf) + if err != nil { + return nil, fmt.Errorf("import failed for %q: %v", mp.ID, err) + } + return imported, nil +} + +// checkPackageForImport type checks, but skips function bodies and does not +// record syntax information. +func (b *typeCheckBatch) checkPackageForImport(ctx context.Context, ph *packageHandle) (*types.Package, error) { + ctx, done := event.Start(ctx, "cache.typeCheckBatch.checkPackageForImport", label.Package.Of(string(ph.mp.ID))) + defer done() + + onError := func(e error) { + // Ignore errors for exporting. + } + cfg := b.typesConfig(ctx, ph.localInputs, onError) + cfg.IgnoreFuncBodies = true + + // Parse the compiled go files, bypassing the parse cache as packages checked + // for import are unlikely to get cache hits. Additionally, we can optimize + // parsing slightly by not passing parser.ParseComments. + pgfs := make([]*parsego.File, len(ph.localInputs.compiledGoFiles)) + { + var group errgroup.Group + // Set an arbitrary concurrency limit; we want some parallelism but don't + // need GOMAXPROCS, as there is already a lot of concurrency among calls to + // checkPackageForImport. + // + // TODO(rfindley): is there a better way to limit parallelism here? We could + // have a global limit on the type-check batch, but would have to be very + // careful to avoid starvation. + group.SetLimit(4) + for i, fh := range ph.localInputs.compiledGoFiles { + i, fh := i, fh + group.Go(func() error { + pgf, err := parseGoImpl(ctx, b.fset, fh, parser.SkipObjectResolution, false) + pgfs[i] = pgf + return err + }) + } + if err := group.Wait(); err != nil { + return nil, err // cancelled, or catastrophic error (e.g. missing file) + } + } + pkg := types.NewPackage(string(ph.localInputs.pkgPath), string(ph.localInputs.name)) + check := types.NewChecker(cfg, b.fset, pkg, nil) + + files := make([]*ast.File, len(pgfs)) + for i, pgf := range pgfs { + files[i] = pgf.File + } + + // Type checking is expensive, and we may not have encountered cancellations + // via parsing (e.g. if we got nothing but cache hits for parsed files). + if ctx.Err() != nil { + return nil, ctx.Err() + } + + _ = check.Files(files) // ignore errors + + // If the context was cancelled, we may have returned a ton of transient + // errors to the type checker. Swallow them. + if ctx.Err() != nil { + return nil, ctx.Err() + } + + // Asynchronously record export data. + go func() { + exportData, err := gcimporter.IExportShallow(b.fset, pkg, bug.Reportf) + if err != nil { + bug.Reportf("exporting package %v: %v", ph.mp.ID, err) + return + } + if err := filecache.Set(exportDataKind, ph.key, exportData); err != nil { + event.Error(ctx, fmt.Sprintf("storing export data for %s", ph.mp.ID), err) + } + }() + return pkg, nil +} + +// importLookup returns a function that may be used to look up a package ID for +// a given package path, based on the forward transitive closure of the initial +// package (id). +// +// The resulting function is not concurrency safe. +func (b *typeCheckBatch) importLookup(mp *metadata.Package) func(PackagePath) PackageID { + // This function implements an incremental depth first scan through the + // package imports. Previous implementations of import mapping built the + // entire PackagePath->PackageID mapping eagerly, but that resulted in a + // large amount of unnecessary work: most imports are either directly + // imported, or found through a shallow scan. + + // impMap memoizes the lookup of package paths. + impMap := map[PackagePath]PackageID{ + mp.PkgPath: mp.ID, + } + // pending is a FIFO queue of package metadata that has yet to have its + // dependencies fully scanned. + // Invariant: all entries in pending are already mapped in impMap. + pending := []*metadata.Package{mp} + + // search scans children the next package in pending, looking for pkgPath. + // Invariant: whenever search is called, pkgPath is not yet mapped. + var search func(pkgPath PackagePath) (PackageID, bool) + search = func(pkgPath PackagePath) (id PackageID, found bool) { + pkg := pending[0] + pending = pending[1:] + for depPath, depID := range pkg.DepsByPkgPath { + if prevID, ok := impMap[depPath]; ok { + // debugging #63822 + if prevID != depID { + bug.Reportf("inconsistent view of dependencies") + } + continue + } + impMap[depPath] = depID + // TODO(rfindley): express this as an operation on the import graph + // itself, rather than the set of package handles. + pending = append(pending, b.handles[depID].mp) + if depPath == pkgPath { + // Don't return early; finish processing pkg's deps. + id = depID + found = true + } + } + return id, found + } + + return func(pkgPath PackagePath) PackageID { + if id, ok := impMap[pkgPath]; ok { + return id + } + for len(pending) > 0 { + if id, found := search(pkgPath); found { + return id + } + } + return "" + } +} + +// A packageHandle holds inputs required to compute a Package, including +// metadata, derived diagnostics, files, and settings. Additionally, +// packageHandles manage a key for these inputs, to use in looking up +// precomputed results. +// +// packageHandles may be invalid following an invalidation via snapshot.clone, +// but the handles returned by getPackageHandles will always be valid. +// +// packageHandles are critical for implementing "precise pruning" in gopls: +// packageHandle.key is a hash of a precise set of inputs, such as package +// files and "reachable" syntax, that may affect type checking. +// +// packageHandles also keep track of state that allows gopls to compute, and +// then quickly recompute, these keys. This state is split into two categories: +// - local state, which depends only on the package's local files and metadata +// - other state, which includes data derived from dependencies. +// +// Dividing the data in this way allows gopls to minimize invalidation when a +// package is modified. For example, any change to a package file fully +// invalidates the package handle. On the other hand, if that change was not +// metadata-affecting it may be the case that packages indirectly depending on +// the modified package are unaffected by the change. For that reason, we have +// two types of invalidation, corresponding to the two types of data above: +// - deletion of the handle, which occurs when the package itself changes +// - clearing of the validated field, which marks the package as possibly +// invalid. +// +// With the second type of invalidation, packageHandles are re-evaluated from the +// bottom up. If this process encounters a packageHandle whose deps have not +// changed (as detected by the depkeys field), then the packageHandle in +// question must also not have changed, and we need not re-evaluate its key. +type packageHandle struct { + mp *metadata.Package + + // loadDiagnostics memoizes the result of processing error messages from + // go/packages (i.e. `go list`). + // + // These are derived from metadata using a snapshot. Since they depend on + // file contents (for translating positions), they should theoretically be + // invalidated by file changes, but historically haven't been. In practice + // they are rare and indicate a fundamental error that needs to be corrected + // before development can continue, so it may not be worth significant + // engineering effort to implement accurate invalidation here. + // + // TODO(rfindley): loadDiagnostics are out of place here, as they don't + // directly relate to type checking. We should perhaps move the caching of + // load diagnostics to an entirely separate component, so that Packages need + // only be concerned with parsing and type checking. + // (Nevertheless, since the lifetime of load diagnostics matches that of the + // Metadata, it is convenient to memoize them here.) + loadDiagnostics []*Diagnostic + + // Local data: + + // localInputs holds all local type-checking localInputs, excluding + // dependencies. + localInputs typeCheckInputs + // localKey is a hash of localInputs. + localKey file.Hash + // refs is the result of syntactic dependency analysis produced by the + // typerefs package. + refs map[string][]typerefs.Symbol + + // Data derived from dependencies: + + // validated indicates whether the current packageHandle is known to have a + // valid key. Invalidated package handles are stored for packages whose + // type information may have changed. + validated bool + // depKeys records the key of each dependency that was used to calculate the + // key above. If the handle becomes invalid, we must re-check that each still + // matches. + depKeys map[PackageID]file.Hash + // key is the hashed key for the package. + // + // It includes the all bits of the transitive closure of + // dependencies's sources. + key file.Hash +} + +// clone returns a copy of the receiver with the validated bit set to the +// provided value. +func (ph *packageHandle) clone(validated bool) *packageHandle { + copy := *ph + copy.validated = validated + return © +} + +// getPackageHandles gets package handles for all given ids and their +// dependencies, recursively. +func (s *Snapshot) getPackageHandles(ctx context.Context, ids []PackageID) (map[PackageID]*packageHandle, error) { + // perform a two-pass traversal. + // + // On the first pass, build up a bidirectional graph of handle nodes, and collect leaves. + // Then build package handles from bottom up. + + s.mu.Lock() // guard s.meta and s.packages below + b := &packageHandleBuilder{ + s: s, + transitiveRefs: make(map[typerefs.IndexID]*partialRefs), + nodes: make(map[typerefs.IndexID]*handleNode), + } + + var leaves []*handleNode + var makeNode func(*handleNode, PackageID) *handleNode + makeNode = func(from *handleNode, id PackageID) *handleNode { + idxID := b.s.pkgIndex.IndexID(id) + n, ok := b.nodes[idxID] + if !ok { + mp := s.meta.Packages[id] + if mp == nil { + panic(fmt.Sprintf("nil metadata for %q", id)) + } + n = &handleNode{ + mp: mp, + idxID: idxID, + unfinishedSuccs: int32(len(mp.DepsByPkgPath)), + } + if entry, hit := b.s.packages.Get(mp.ID); hit { + n.ph = entry + } + if n.unfinishedSuccs == 0 { + leaves = append(leaves, n) + } else { + n.succs = make(map[PackageID]*handleNode, n.unfinishedSuccs) + } + b.nodes[idxID] = n + for _, depID := range mp.DepsByPkgPath { + n.succs[depID] = makeNode(n, depID) + } + } + // Add edge from predecessor. + if from != nil { + n.preds = append(n.preds, from) + } + return n + } + for _, id := range ids { + makeNode(nil, id) + } + s.mu.Unlock() + + g, ctx := errgroup.WithContext(ctx) + + // files are preloaded, so building package handles is CPU-bound. + // + // Note that we can't use g.SetLimit, as that could result in starvation: + // g.Go blocks until a slot is available, and so all existing goroutines + // could be blocked trying to enqueue a predecessor. + limiter := make(chan unit, runtime.GOMAXPROCS(0)) + + var enqueue func(*handleNode) + enqueue = func(n *handleNode) { + g.Go(func() error { + limiter <- unit{} + defer func() { <-limiter }() + + if ctx.Err() != nil { + return ctx.Err() + } + + b.buildPackageHandle(ctx, n) + + for _, pred := range n.preds { + if atomic.AddInt32(&pred.unfinishedSuccs, -1) == 0 { + enqueue(pred) + } + } + + return n.err + }) + } + for _, leaf := range leaves { + enqueue(leaf) + } + + if err := g.Wait(); err != nil { + return nil, err + } + + // Copy handles into the result map. + handles := make(map[PackageID]*packageHandle, len(b.nodes)) + for _, v := range b.nodes { + assert(v.ph != nil, "nil handle") + handles[v.mp.ID] = v.ph + } + + return handles, nil +} + +// A packageHandleBuilder computes a batch of packageHandles concurrently, +// sharing computed transitive reachability sets used to compute package keys. +type packageHandleBuilder struct { + s *Snapshot + + // nodes are assembled synchronously. + nodes map[typerefs.IndexID]*handleNode + + // transitiveRefs is incrementally evaluated as package handles are built. + transitiveRefsMu sync.Mutex + transitiveRefs map[typerefs.IndexID]*partialRefs // see getTransitiveRefs +} + +// A handleNode represents a to-be-computed packageHandle within a graph of +// predecessors and successors. +// +// It is used to implement a bottom-up construction of packageHandles. +type handleNode struct { + mp *metadata.Package + idxID typerefs.IndexID + ph *packageHandle + err error + preds []*handleNode + succs map[PackageID]*handleNode + unfinishedSuccs int32 +} + +// partialRefs maps names declared by a given package to their set of +// transitive references. +// +// If complete is set, refs is known to be complete for the package in +// question. Otherwise, it may only map a subset of all names declared by the +// package. +type partialRefs struct { + refs map[string]*typerefs.PackageSet + complete bool +} + +// getTransitiveRefs gets or computes the set of transitively reachable +// packages for each exported name in the package specified by id. +// +// The operation may fail if building a predecessor failed. If and only if this +// occurs, the result will be nil. +func (b *packageHandleBuilder) getTransitiveRefs(pkgID PackageID) map[string]*typerefs.PackageSet { + b.transitiveRefsMu.Lock() + defer b.transitiveRefsMu.Unlock() + + idxID := b.s.pkgIndex.IndexID(pkgID) + trefs, ok := b.transitiveRefs[idxID] + if !ok { + trefs = &partialRefs{ + refs: make(map[string]*typerefs.PackageSet), + } + b.transitiveRefs[idxID] = trefs + } + + if !trefs.complete { + trefs.complete = true + ph := b.nodes[idxID].ph + for name := range ph.refs { + if ('A' <= name[0] && name[0] <= 'Z') || token.IsExported(name) { + if _, ok := trefs.refs[name]; !ok { + pkgs := b.s.pkgIndex.NewSet() + for _, sym := range ph.refs[name] { + pkgs.Add(sym.Package) + otherSet := b.getOneTransitiveRefLocked(sym) + pkgs.Union(otherSet) + } + trefs.refs[name] = pkgs + } + } + } + } + + return trefs.refs +} + +// getOneTransitiveRefLocked computes the full set packages transitively +// reachable through the given sym reference. +// +// It may return nil if the reference is invalid (i.e. the referenced name does +// not exist). +func (b *packageHandleBuilder) getOneTransitiveRefLocked(sym typerefs.Symbol) *typerefs.PackageSet { + assert(token.IsExported(sym.Name), "expected exported symbol") + + trefs := b.transitiveRefs[sym.Package] + if trefs == nil { + trefs = &partialRefs{ + refs: make(map[string]*typerefs.PackageSet), + complete: false, + } + b.transitiveRefs[sym.Package] = trefs + } + + pkgs, ok := trefs.refs[sym.Name] + if ok && pkgs == nil { + // See below, where refs is set to nil before recursing. + bug.Reportf("cycle detected to %q in reference graph", sym.Name) + } + + // Note that if (!ok && trefs.complete), the name does not exist in the + // referenced package, and we should not write to trefs as that may introduce + // a race. + if !ok && !trefs.complete { + n := b.nodes[sym.Package] + if n == nil { + // We should always have IndexID in our node set, because symbol references + // should only be recorded for packages that actually exist in the import graph. + // + // However, it is not easy to prove this (typerefs are serialized and + // deserialized), so make this code temporarily defensive while we are on a + // point release. + // + // TODO(rfindley): in the future, we should turn this into an assertion. + bug.Reportf("missing reference to package %s", b.s.pkgIndex.PackageID(sym.Package)) + return nil + } + + // Break cycles. This is perhaps overly defensive as cycles should not + // exist at this point: metadata cycles should have been broken at load + // time, and intra-package reference cycles should have been contracted by + // the typerefs algorithm. + // + // See the "cycle detected" bug report above. + trefs.refs[sym.Name] = nil + + pkgs := b.s.pkgIndex.NewSet() + for _, sym2 := range n.ph.refs[sym.Name] { + pkgs.Add(sym2.Package) + otherSet := b.getOneTransitiveRefLocked(sym2) + pkgs.Union(otherSet) + } + trefs.refs[sym.Name] = pkgs + } + + return pkgs +} + +// buildPackageHandle gets or builds a package handle for the given id, storing +// its result in the snapshot.packages map. +// +// buildPackageHandle must only be called from getPackageHandles. +func (b *packageHandleBuilder) buildPackageHandle(ctx context.Context, n *handleNode) { + var prevPH *packageHandle + if n.ph != nil { + // Existing package handle: if it is valid, return it. Otherwise, create a + // copy to update. + if n.ph.validated { + return + } + prevPH = n.ph + // Either prevPH is still valid, or we will update the key and depKeys of + // this copy. In either case, the result will be valid. + n.ph = prevPH.clone(true) + } else { + // No package handle: read and analyze the package syntax. + inputs, err := b.s.typeCheckInputs(ctx, n.mp) + if err != nil { + n.err = err + return + } + refs, err := b.s.typerefs(ctx, n.mp, inputs.compiledGoFiles) + if err != nil { + n.err = err + return + } + n.ph = &packageHandle{ + mp: n.mp, + loadDiagnostics: computeLoadDiagnostics(ctx, b.s, n.mp), + localInputs: inputs, + localKey: localPackageKey(inputs), + refs: refs, + validated: true, + } + } + + // ph either did not exist, or was invalid. We must re-evaluate deps and key. + if err := b.evaluatePackageHandle(prevPH, n); err != nil { + n.err = err + return + } + + assert(n.ph.validated, "unvalidated handle") + + // Ensure the result (or an equivalent) is recorded in the snapshot. + b.s.mu.Lock() + defer b.s.mu.Unlock() + + // Check that the metadata has not changed + // (which should invalidate this handle). + // + // TODO(rfindley): eventually promote this to an assert. + // TODO(rfindley): move this to after building the package handle graph? + if b.s.meta.Packages[n.mp.ID] != n.mp { + bug.Reportf("stale metadata for %s", n.mp.ID) + } + + // Check the packages map again in case another goroutine got there first. + if alt, ok := b.s.packages.Get(n.mp.ID); ok && alt.validated { + if alt.mp != n.mp { + bug.Reportf("existing package handle does not match for %s", n.mp.ID) + } + n.ph = alt + } else { + b.s.packages.Set(n.mp.ID, n.ph, nil) + } +} + +// evaluatePackageHandle validates and/or computes the key of ph, setting key, +// depKeys, and the validated flag on ph. +// +// It uses prevPH to avoid recomputing keys that can't have changed, since +// their depKeys did not change. +// +// See the documentation for packageHandle for more details about packageHandle +// state, and see the documentation for the typerefs package for more details +// about precise reachability analysis. +func (b *packageHandleBuilder) evaluatePackageHandle(prevPH *packageHandle, n *handleNode) error { + // Opt: if no dep keys have changed, we need not re-evaluate the key. + if prevPH != nil { + depsChanged := false + assert(len(prevPH.depKeys) == len(n.succs), "mismatching dep count") + for id, succ := range n.succs { + oldKey, ok := prevPH.depKeys[id] + assert(ok, "missing dep") + if oldKey != succ.ph.key { + depsChanged = true + break + } + } + if !depsChanged { + return nil // key cannot have changed + } + } + + // Deps have changed, so we must re-evaluate the key. + n.ph.depKeys = make(map[PackageID]file.Hash) + + // See the typerefs package: the reachable set of packages is defined to be + // the set of packages containing syntax that is reachable through the + // exported symbols in the dependencies of n.ph. + reachable := b.s.pkgIndex.NewSet() + for depID, succ := range n.succs { + n.ph.depKeys[depID] = succ.ph.key + reachable.Add(succ.idxID) + trefs := b.getTransitiveRefs(succ.mp.ID) + if trefs == nil { + // A predecessor failed to build due to e.g. context cancellation. + return fmt.Errorf("missing transitive refs for %s", succ.mp.ID) + } + for _, set := range trefs { + reachable.Union(set) + } + } + + // Collect reachable handles. + var reachableHandles []*packageHandle + // In the presence of context cancellation, any package may be missing. + // We need all dependencies to produce a valid key. + missingReachablePackage := false + reachable.Elems(func(id typerefs.IndexID) { + dh := b.nodes[id] + if dh == nil { + missingReachablePackage = true + } else { + assert(dh.ph.validated, "unvalidated dependency") + reachableHandles = append(reachableHandles, dh.ph) + } + }) + if missingReachablePackage { + return fmt.Errorf("missing reachable package") + } + // Sort for stability. + sort.Slice(reachableHandles, func(i, j int) bool { + return reachableHandles[i].mp.ID < reachableHandles[j].mp.ID + }) + + // Key is the hash of the local key, and the local key of all reachable + // packages. + depHasher := sha256.New() + depHasher.Write(n.ph.localKey[:]) + for _, rph := range reachableHandles { + depHasher.Write(rph.localKey[:]) + } + depHasher.Sum(n.ph.key[:0]) + + return nil +} + +// typerefs returns typerefs for the package described by m and cgfs, after +// either computing it or loading it from the file cache. +func (s *Snapshot) typerefs(ctx context.Context, mp *metadata.Package, cgfs []file.Handle) (map[string][]typerefs.Symbol, error) { + imports := make(map[ImportPath]*metadata.Package) + for impPath, id := range mp.DepsByImpPath { + if id != "" { + imports[impPath] = s.Metadata(id) + } + } + + data, err := s.typerefData(ctx, mp.ID, imports, cgfs) + if err != nil { + return nil, err + } + classes := typerefs.Decode(s.pkgIndex, data) + refs := make(map[string][]typerefs.Symbol) + for _, class := range classes { + for _, decl := range class.Decls { + refs[decl] = class.Refs + } + } + return refs, nil +} + +// typerefData retrieves encoded typeref data from the filecache, or computes it on +// a cache miss. +func (s *Snapshot) typerefData(ctx context.Context, id PackageID, imports map[ImportPath]*metadata.Package, cgfs []file.Handle) ([]byte, error) { + key := typerefsKey(id, imports, cgfs) + if data, err := filecache.Get(typerefsKind, key); err == nil { + return data, nil + } else if err != filecache.ErrNotFound { + bug.Reportf("internal error reading typerefs data: %v", err) + } + + pgfs, err := s.view.parseCache.parseFiles(ctx, token.NewFileSet(), parsego.Full&^parser.ParseComments, true, cgfs...) + if err != nil { + return nil, err + } + data := typerefs.Encode(pgfs, imports) + + // Store the resulting data in the cache. + go func() { + if err := filecache.Set(typerefsKind, key, data); err != nil { + event.Error(ctx, fmt.Sprintf("storing typerefs data for %s", id), err) + } + }() + + return data, nil +} + +// typerefsKey produces a key for the reference information produced by the +// typerefs package. +func typerefsKey(id PackageID, imports map[ImportPath]*metadata.Package, compiledGoFiles []file.Handle) file.Hash { + hasher := sha256.New() + + fmt.Fprintf(hasher, "typerefs: %s\n", id) + + importPaths := make([]string, 0, len(imports)) + for impPath := range imports { + importPaths = append(importPaths, string(impPath)) + } + sort.Strings(importPaths) + for _, importPath := range importPaths { + imp := imports[ImportPath(importPath)] + // TODO(rfindley): strength reduce the typerefs.Export API to guarantee + // that it only depends on these attributes of dependencies. + fmt.Fprintf(hasher, "import %s %s %s", importPath, imp.ID, imp.Name) + } + + fmt.Fprintf(hasher, "compiledGoFiles: %d\n", len(compiledGoFiles)) + for _, fh := range compiledGoFiles { + fmt.Fprintln(hasher, fh.Identity()) + } + + var hash [sha256.Size]byte + hasher.Sum(hash[:0]) + return hash +} + +// typeCheckInputs contains the inputs of a call to typeCheckImpl, which +// type-checks a package. +// +// Part of the purpose of this type is to keep type checking in-sync with the +// package handle key, by explicitly identifying the inputs to type checking. +type typeCheckInputs struct { + id PackageID + + // Used for type checking: + pkgPath PackagePath + name PackageName + goFiles, compiledGoFiles []file.Handle + sizes types.Sizes + depsByImpPath map[ImportPath]PackageID + goVersion string // packages.Module.GoVersion, e.g. "1.18" + + // Used for type check diagnostics: + // TODO(rfindley): consider storing less data in gobDiagnostics, and + // interpreting each diagnostic in the context of a fixed set of options. + // Then these fields need not be part of the type checking inputs. + supportsRelatedInformation bool + linkTarget string + viewType ViewType +} + +func (s *Snapshot) typeCheckInputs(ctx context.Context, mp *metadata.Package) (typeCheckInputs, error) { + // Read both lists of files of this package. + // + // Parallelism is not necessary here as the files will have already been + // pre-read at load time. + // + // goFiles aren't presented to the type checker--nor + // are they included in the key, unsoundly--but their + // syntax trees are available from (*pkg).File(URI). + // TODO(adonovan): consider parsing them on demand? + // The need should be rare. + goFiles, err := readFiles(ctx, s, mp.GoFiles) + if err != nil { + return typeCheckInputs{}, err + } + compiledGoFiles, err := readFiles(ctx, s, mp.CompiledGoFiles) + if err != nil { + return typeCheckInputs{}, err + } + + goVersion := "" + if mp.Module != nil && mp.Module.GoVersion != "" { + goVersion = mp.Module.GoVersion + } + + return typeCheckInputs{ + id: mp.ID, + pkgPath: mp.PkgPath, + name: mp.Name, + goFiles: goFiles, + compiledGoFiles: compiledGoFiles, + sizes: mp.TypesSizes, + depsByImpPath: mp.DepsByImpPath, + goVersion: goVersion, + + supportsRelatedInformation: s.Options().RelatedInformationSupported, + linkTarget: s.Options().LinkTarget, + viewType: s.view.typ, + }, nil +} + +// readFiles reads the content of each file URL from the source +// (e.g. snapshot or cache). +func readFiles(ctx context.Context, fs file.Source, uris []protocol.DocumentURI) (_ []file.Handle, err error) { + fhs := make([]file.Handle, len(uris)) + for i, uri := range uris { + fhs[i], err = fs.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + } + return fhs, nil +} + +// localPackageKey returns a key for local inputs into type-checking, excluding +// dependency information: files, metadata, and configuration. +func localPackageKey(inputs typeCheckInputs) file.Hash { + hasher := sha256.New() + + // In principle, a key must be the hash of an + // unambiguous encoding of all the relevant data. + // If it's ambiguous, we risk collisions. + + // package identifiers + fmt.Fprintf(hasher, "package: %s %s %s\n", inputs.id, inputs.name, inputs.pkgPath) + + // module Go version + fmt.Fprintf(hasher, "go %s\n", inputs.goVersion) + + // import map + importPaths := make([]string, 0, len(inputs.depsByImpPath)) + for impPath := range inputs.depsByImpPath { + importPaths = append(importPaths, string(impPath)) + } + sort.Strings(importPaths) + for _, impPath := range importPaths { + fmt.Fprintf(hasher, "import %s %s", impPath, string(inputs.depsByImpPath[ImportPath(impPath)])) + } + + // file names and contents + fmt.Fprintf(hasher, "compiledGoFiles: %d\n", len(inputs.compiledGoFiles)) + for _, fh := range inputs.compiledGoFiles { + fmt.Fprintln(hasher, fh.Identity()) + } + fmt.Fprintf(hasher, "goFiles: %d\n", len(inputs.goFiles)) + for _, fh := range inputs.goFiles { + fmt.Fprintln(hasher, fh.Identity()) + } + + // types sizes + wordSize := inputs.sizes.Sizeof(types.Typ[types.Int]) + maxAlign := inputs.sizes.Alignof(types.NewPointer(types.Typ[types.Int64])) + fmt.Fprintf(hasher, "sizes: %d %d\n", wordSize, maxAlign) + + fmt.Fprintf(hasher, "relatedInformation: %t\n", inputs.supportsRelatedInformation) + fmt.Fprintf(hasher, "linkTarget: %s\n", inputs.linkTarget) + fmt.Fprintf(hasher, "viewType: %d\n", inputs.viewType) + + var hash [sha256.Size]byte + hasher.Sum(hash[:0]) + return hash +} + +// checkPackage type checks the parsed source files in compiledGoFiles. +// (The resulting pkg also holds the parsed but not type-checked goFiles.) +// deps holds the future results of type-checking the direct dependencies. +func (b *typeCheckBatch) checkPackage(ctx context.Context, ph *packageHandle) (*Package, error) { + inputs := ph.localInputs + ctx, done := event.Start(ctx, "cache.typeCheckBatch.checkPackage", label.Package.Of(string(inputs.id))) + defer done() + + pkg := &syntaxPackage{ + id: inputs.id, + fset: b.fset, // must match parse call below + types: types.NewPackage(string(inputs.pkgPath), string(inputs.name)), + typesSizes: inputs.sizes, + typesInfo: &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Instances: make(map[*ast.Ident]types.Instance), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + }, + } + versions.InitFileVersions(pkg.typesInfo) + + // Collect parsed files from the type check pass, capturing parse errors from + // compiled files. + var err error + pkg.goFiles, err = b.parseCache.parseFiles(ctx, b.fset, parsego.Full, false, inputs.goFiles...) + if err != nil { + return nil, err + } + pkg.compiledGoFiles, err = b.parseCache.parseFiles(ctx, b.fset, parsego.Full, false, inputs.compiledGoFiles...) + if err != nil { + return nil, err + } + for _, pgf := range pkg.compiledGoFiles { + if pgf.ParseErr != nil { + pkg.parseErrors = append(pkg.parseErrors, pgf.ParseErr) + } + } + + // Use the default type information for the unsafe package. + if inputs.pkgPath == "unsafe" { + // Don't type check Unsafe: it's unnecessary, and doing so exposes a data + // race to Unsafe.completed. + pkg.types = types.Unsafe + } else { + + if len(pkg.compiledGoFiles) == 0 { + // No files most likely means go/packages failed. + // + // TODO(rfindley): in the past, we would capture go list errors in this + // case, to present go list errors to the user. However we had no tests for + // this behavior. It is unclear if anything better can be done here. + return nil, fmt.Errorf("no parsed files for package %s", inputs.pkgPath) + } + + onError := func(e error) { + pkg.typeErrors = append(pkg.typeErrors, e.(types.Error)) + } + cfg := b.typesConfig(ctx, inputs, onError) + check := types.NewChecker(cfg, pkg.fset, pkg.types, pkg.typesInfo) + + var files []*ast.File + for _, cgf := range pkg.compiledGoFiles { + files = append(files, cgf.File) + } + + // Type checking is expensive, and we may not have encountered cancellations + // via parsing (e.g. if we got nothing but cache hits for parsed files). + if ctx.Err() != nil { + return nil, ctx.Err() + } + + // Type checking errors are handled via the config, so ignore them here. + _ = check.Files(files) // 50us-15ms, depending on size of package + + // If the context was cancelled, we may have returned a ton of transient + // errors to the type checker. Swallow them. + if ctx.Err() != nil { + return nil, ctx.Err() + } + + // Collect imports by package path for the DependencyTypes API. + pkg.importMap = make(map[PackagePath]*types.Package) + var collectDeps func(*types.Package) + collectDeps = func(p *types.Package) { + pkgPath := PackagePath(p.Path()) + if _, ok := pkg.importMap[pkgPath]; ok { + return + } + pkg.importMap[pkgPath] = p + for _, imp := range p.Imports() { + collectDeps(imp) + } + } + collectDeps(pkg.types) + + // Work around golang/go#61561: interface instances aren't concurrency-safe + // as they are not completed by the type checker. + for _, inst := range pkg.typesInfo.Instances { + if iface, _ := inst.Type.Underlying().(*types.Interface); iface != nil { + iface.Complete() + } + } + } + + // Our heuristic for whether to show type checking errors is: + // + If there is a parse error _in the current file_, suppress type + // errors in that file. + // + Otherwise, show type errors even in the presence of parse errors in + // other package files. go/types attempts to suppress follow-on errors + // due to bad syntax, so on balance type checking errors still provide + // a decent signal/noise ratio as long as the file in question parses. + + // Track URIs with parse errors so that we can suppress type errors for these + // files. + unparseable := map[protocol.DocumentURI]bool{} + for _, e := range pkg.parseErrors { + diags, err := parseErrorDiagnostics(pkg, e) + if err != nil { + event.Error(ctx, "unable to compute positions for parse errors", err, label.Package.Of(string(inputs.id))) + continue + } + for _, diag := range diags { + unparseable[diag.URI] = true + pkg.diagnostics = append(pkg.diagnostics, diag) + } + } + + diags := typeErrorsToDiagnostics(pkg, inputs, pkg.typeErrors) + for _, diag := range diags { + // If the file didn't parse cleanly, it is highly likely that type + // checking errors will be confusing or redundant. But otherwise, type + // checking usually provides a good enough signal to include. + if !unparseable[diag.URI] { + pkg.diagnostics = append(pkg.diagnostics, diag) + } + } + + return &Package{ph.mp, ph.loadDiagnostics, pkg}, nil +} + +// e.g. "go1" or "go1.2" or "go1.2.3" +var goVersionRx = regexp.MustCompile(`^go[1-9][0-9]*(?:\.(0|[1-9][0-9]*)){0,2}$`) + +func (b *typeCheckBatch) typesConfig(ctx context.Context, inputs typeCheckInputs, onError func(e error)) *types.Config { + cfg := &types.Config{ + Sizes: inputs.sizes, + Error: onError, + Importer: importerFunc(func(path string) (*types.Package, error) { + // While all of the import errors could be reported + // based on the metadata before we start type checking, + // reporting them via types.Importer places the errors + // at the correct source location. + id, ok := inputs.depsByImpPath[ImportPath(path)] + if !ok { + // If the import declaration is broken, + // go list may fail to report metadata about it. + // See TestFixImportDecl for an example. + return nil, fmt.Errorf("missing metadata for import of %q", path) + } + depPH := b.handles[id] + if depPH == nil { + // e.g. missing metadata for dependencies in buildPackageHandle + return nil, missingPkgError(inputs.id, path, inputs.viewType) + } + if !metadata.IsValidImport(inputs.pkgPath, depPH.mp.PkgPath, inputs.viewType != GoPackagesDriverView) { + return nil, fmt.Errorf("invalid use of internal package %q", path) + } + return b.getImportPackage(ctx, id) + }), + } + + if inputs.goVersion != "" { + goVersion := "go" + inputs.goVersion + if validGoVersion(goVersion) { + cfg.GoVersion = goVersion + } + } + + // We want to type check cgo code if go/types supports it. + // We passed typecheckCgo to go/packages when we Loaded. + typesinternal.SetUsesCgo(cfg) + return cfg +} + +// validGoVersion reports whether goVersion is a valid Go version for go/types. +// types.NewChecker panics if GoVersion is invalid. +// +// Note that, prior to go1.21, go/types required exactly two components to the +// version number. For example, go types would panic with the Go version +// go1.21.1. validGoVersion handles this case when built with go1.20 or earlier. +func validGoVersion(goVersion string) bool { + if !goVersionRx.MatchString(goVersion) { + return false // malformed version string + } + + if relVer := releaseVersion(); relVer != "" && versions.Before(versions.Lang(relVer), versions.Lang(goVersion)) { + return false // 'go list' is too new for go/types + } + + // TODO(rfindley): remove once we no longer support building gopls with Go + // 1.20 or earlier. + if !slices.Contains(build.Default.ReleaseTags, "go1.21") && strings.Count(goVersion, ".") >= 2 { + return false // unsupported patch version + } + + return true +} + +// releaseVersion reports the Go language version used to compile gopls, or "" +// if it cannot be determined. +func releaseVersion() string { + if len(build.Default.ReleaseTags) > 0 { + v := build.Default.ReleaseTags[len(build.Default.ReleaseTags)-1] + var dummy int + if _, err := fmt.Sscanf(v, "go1.%d", &dummy); err == nil { + return v + } + } + return "" +} + +// depsErrors creates diagnostics for each metadata error (e.g. import cycle). +// These may be attached to import declarations in the transitive source files +// of pkg, or to 'requires' declarations in the package's go.mod file. +// +// TODO(rfindley): move this to load.go +func depsErrors(ctx context.Context, snapshot *Snapshot, mp *metadata.Package) ([]*Diagnostic, error) { + // Select packages that can't be found, and were imported in non-workspace packages. + // Workspace packages already show their own errors. + var relevantErrors []*packagesinternal.PackageError + for _, depsError := range mp.DepsErrors { + // Up to Go 1.15, the missing package was included in the stack, which + // was presumably a bug. We want the next one up. + directImporterIdx := len(depsError.ImportStack) - 1 + if directImporterIdx < 0 { + continue + } + + directImporter := depsError.ImportStack[directImporterIdx] + if snapshot.isWorkspacePackage(PackageID(directImporter)) { + continue + } + relevantErrors = append(relevantErrors, depsError) + } + + // Don't build the import index for nothing. + if len(relevantErrors) == 0 { + return nil, nil + } + + // Subsequent checks require Go files. + if len(mp.CompiledGoFiles) == 0 { + return nil, nil + } + + // Build an index of all imports in the package. + type fileImport struct { + cgf *parsego.File + imp *ast.ImportSpec + } + allImports := map[string][]fileImport{} + for _, uri := range mp.CompiledGoFiles { + pgf, err := parseGoURI(ctx, snapshot, uri, parsego.Header) + if err != nil { + return nil, err + } + fset := tokeninternal.FileSetFor(pgf.Tok) + // TODO(adonovan): modify Imports() to accept a single token.File (cgf.Tok). + for _, group := range astutil.Imports(fset, pgf.File) { + for _, imp := range group { + if imp.Path == nil { + continue + } + path := strings.Trim(imp.Path.Value, `"`) + allImports[path] = append(allImports[path], fileImport{pgf, imp}) + } + } + } + + // Apply a diagnostic to any import involved in the error, stopping once + // we reach the workspace. + var errors []*Diagnostic + for _, depErr := range relevantErrors { + for i := len(depErr.ImportStack) - 1; i >= 0; i-- { + item := depErr.ImportStack[i] + if snapshot.isWorkspacePackage(PackageID(item)) { + break + } + + for _, imp := range allImports[item] { + rng, err := imp.cgf.NodeRange(imp.imp) + if err != nil { + return nil, err + } + diag := &Diagnostic{ + URI: imp.cgf.URI, + Range: rng, + Severity: protocol.SeverityError, + Source: TypeError, + Message: fmt.Sprintf("error while importing %v: %v", item, depErr.Err), + SuggestedFixes: goGetQuickFixes(mp.Module != nil, imp.cgf.URI, item), + } + if !bundleLazyFixes(diag) { + bug.Reportf("failed to bundle fixes for diagnostic %q", diag.Message) + } + errors = append(errors, diag) + } + } + } + + modFile, err := nearestModFile(ctx, mp.CompiledGoFiles[0], snapshot) + if err != nil { + return nil, err + } + pm, err := parseModURI(ctx, snapshot, modFile) + if err != nil { + return nil, err + } + + // Add a diagnostic to the module that contained the lowest-level import of + // the missing package. + for _, depErr := range relevantErrors { + for i := len(depErr.ImportStack) - 1; i >= 0; i-- { + item := depErr.ImportStack[i] + mp := snapshot.Metadata(PackageID(item)) + if mp == nil || mp.Module == nil { + continue + } + modVer := module.Version{Path: mp.Module.Path, Version: mp.Module.Version} + reference := findModuleReference(pm.File, modVer) + if reference == nil { + continue + } + rng, err := pm.Mapper.OffsetRange(reference.Start.Byte, reference.End.Byte) + if err != nil { + return nil, err + } + diag := &Diagnostic{ + URI: pm.URI, + Range: rng, + Severity: protocol.SeverityError, + Source: TypeError, + Message: fmt.Sprintf("error while importing %v: %v", item, depErr.Err), + SuggestedFixes: goGetQuickFixes(true, pm.URI, item), + } + if !bundleLazyFixes(diag) { + bug.Reportf("failed to bundle fixes for diagnostic %q", diag.Message) + } + errors = append(errors, diag) + break + } + } + return errors, nil +} + +// missingPkgError returns an error message for a missing package that varies +// based on the user's workspace mode. +func missingPkgError(from PackageID, pkgPath string, viewType ViewType) error { + switch viewType { + case GoModView, GoWorkView: + if metadata.IsCommandLineArguments(from) { + return fmt.Errorf("current file is not included in a workspace module") + } else { + // Previously, we would present the initialization error here. + return fmt.Errorf("no required module provides package %q", pkgPath) + } + case AdHocView: + return fmt.Errorf("cannot find package %q in GOROOT", pkgPath) + case GoPackagesDriverView: + return fmt.Errorf("go/packages driver could not load %q", pkgPath) + case GOPATHView: + return fmt.Errorf("cannot find package %q in GOROOT or GOPATH", pkgPath) + default: + return fmt.Errorf("unable to load package") + } +} + +// typeErrorsToDiagnostics translates a slice of types.Errors into a slice of +// Diagnostics. +// +// In addition to simply mapping data such as position information and error +// codes, this function interprets related go/types "continuation" errors as +// protocol.DiagnosticRelatedInformation. Continuation errors are go/types +// errors whose messages starts with "\t". By convention, these errors relate +// to the previous error in the errs slice (such as if they were printed in +// sequence to a terminal). +// +// Fields in typeCheckInputs may affect the resulting diagnostics. +func typeErrorsToDiagnostics(pkg *syntaxPackage, inputs typeCheckInputs, errs []types.Error) []*Diagnostic { + var result []*Diagnostic + + // batch records diagnostics for a set of related types.Errors. + // (related[0] is the primary error.) + batch := func(related []types.Error) { + var diags []*Diagnostic + for i, e := range related { + code, start, end, ok := typesinternal.ReadGo116ErrorData(e) + if !ok || !start.IsValid() || !end.IsValid() { + start, end = e.Pos, e.Pos + code = 0 + } + if !start.IsValid() { + // Type checker errors may be missing position information if they + // relate to synthetic syntax, such as if the file were fixed. In that + // case, we should have a parse error anyway, so skipping the type + // checker error is likely benign. + // + // TODO(golang/go#64335): we should eventually verify that all type + // checked syntax has valid positions, and promote this skip to a bug + // report. + continue + } + + // Invariant: both start and end are IsValid. + if !end.IsValid() { + panic("end is invalid") + } + + posn := safetoken.StartPosition(e.Fset, start) + if !posn.IsValid() { + // All valid positions produced by the type checker should described by + // its fileset. + // + // Note: in golang/go#64488, we observed an error that was positioned + // over fixed syntax, which overflowed its file. So it's definitely + // possible that we get here (it's hard to reason about fixing up the + // AST). Nevertheless, it's a bug. + bug.Reportf("internal error: type checker error %q outside its Fset", e) + continue + } + pgf, err := pkg.File(protocol.URIFromPath(posn.Filename)) + if err != nil { + // Sometimes type-checker errors refer to positions in other packages, + // such as when a declaration duplicates a dot-imported name. + // + // In these cases, we don't want to report an error in the other + // package (the message would be rather confusing), but we do want to + // report an error in the current package (golang/go#59005). + if i == 0 { + bug.Reportf("internal error: could not locate file for primary type checker error %v: %v", e, err) + } + continue + } + + // debugging #65960 + // + // At this point, we know 'start' IsValid, and + // StartPosition(start) worked (with e.Fset). + // + // If the asserted condition is true, 'start' + // is also in range for pgf.Tok, which means + // the PosRange failure must be caused by 'end'. + if pgf.Tok != e.Fset.File(start) { + bug.Reportf("internal error: inconsistent token.Files for pos") + } + + if end == start { + // Expand the end position to a more meaningful span. + end = analysisinternal.TypeErrorEndPos(e.Fset, pgf.Src, start) + + // debugging #65960 + if _, err := safetoken.Offset(pgf.Tok, end); err != nil { + bug.Reportf("TypeErrorEndPos returned invalid end: %v", err) + } + } else { + // debugging #65960 + if _, err := safetoken.Offset(pgf.Tok, end); err != nil { + bug.Reportf("ReadGo116ErrorData returned invalid end: %v", err) + } + } + + rng, err := pgf.Mapper.PosRange(pgf.Tok, start, end) + if err != nil { + bug.Reportf("internal error: could not compute pos to range for %v: %v", e, err) + continue + } + msg := related[0].Msg // primary + if i > 0 { + if inputs.supportsRelatedInformation { + msg += " (see details)" + } else { + msg += fmt.Sprintf(" (this error: %v)", e.Msg) + } + } + diag := &Diagnostic{ + URI: pgf.URI, + Range: rng, + Severity: protocol.SeverityError, + Source: TypeError, + Message: msg, + } + if code != 0 { + diag.Code = code.String() + diag.CodeHref = typesCodeHref(inputs.linkTarget, code) + } + if code == typesinternal.UnusedVar || code == typesinternal.UnusedImport { + diag.Tags = append(diag.Tags, protocol.Unnecessary) + } + if match := importErrorRe.FindStringSubmatch(e.Msg); match != nil { + diag.SuggestedFixes = append(diag.SuggestedFixes, goGetQuickFixes(inputs.viewType.usesModules(), pgf.URI, match[1])...) + } + if match := unsupportedFeatureRe.FindStringSubmatch(e.Msg); match != nil { + diag.SuggestedFixes = append(diag.SuggestedFixes, editGoDirectiveQuickFix(inputs.viewType.usesModules(), pgf.URI, match[1])...) + } + + // Link up related information. For the primary error, all related errors + // are treated as related information. For secondary errors, only the + // primary is related. + // + // This is because go/types assumes that errors are read top-down, such as + // in the cycle error "A refers to...". The structure of the secondary + // error set likely only makes sense for the primary error. + // + // NOTE: len(diags) == 0 if the primary diagnostic has invalid positions. + // See also golang/go#66731. + if i > 0 && len(diags) > 0 { + primary := diags[0] + primary.Related = append(primary.Related, protocol.DiagnosticRelatedInformation{ + Location: protocol.Location{URI: diag.URI, Range: diag.Range}, + Message: related[i].Msg, // use the unmodified secondary error for related errors. + }) + diag.Related = []protocol.DiagnosticRelatedInformation{{ + Location: protocol.Location{URI: primary.URI, Range: primary.Range}, + }} + } + diags = append(diags, diag) + } + result = append(result, diags...) + } + + // Process batches of related errors. + for len(errs) > 0 { + related := []types.Error{errs[0]} + for i := 1; i < len(errs); i++ { + spl := errs[i] + if len(spl.Msg) == 0 || spl.Msg[0] != '\t' { + break + } + spl.Msg = spl.Msg[len("\t"):] + related = append(related, spl) + } + batch(related) + errs = errs[len(related):] + } + + return result +} + +// An importFunc is an implementation of the single-method +// types.Importer interface based on a function value. +type importerFunc func(path string) (*types.Package, error) + +func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } diff --git a/contribs/gnopls/internal/cache/constraints.go b/contribs/gnopls/internal/cache/constraints.go new file mode 100644 index 00000000000..9503abc1ebd --- /dev/null +++ b/contribs/gnopls/internal/cache/constraints.go @@ -0,0 +1,61 @@ +// 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 cache + +import ( + "go/ast" + "go/build/constraint" + "go/parser" + "go/token" +) + +// isStandaloneFile reports whether a file with the given contents should be +// considered a 'standalone main file', meaning a package that consists of only +// a single file. +func isStandaloneFile(src []byte, standaloneTags []string) bool { + f, err := parser.ParseFile(token.NewFileSet(), "", src, parser.PackageClauseOnly|parser.ParseComments) + if err != nil { + return false + } + + if f.Name == nil || f.Name.Name != "main" { + return false + } + + found := false + walkConstraints(f, func(c constraint.Expr) bool { + if tag, ok := c.(*constraint.TagExpr); ok { + for _, t := range standaloneTags { + if t == tag.Tag { + found = true + return false + } + } + } + return true + }) + + return found +} + +// walkConstraints calls f for each constraint expression in the file, until +// all constraints are exhausted or f returns false. +func walkConstraints(file *ast.File, f func(constraint.Expr) bool) { + for _, cg := range file.Comments { + // Even with PackageClauseOnly the parser consumes the semicolon following + // the package clause, so we must guard against comments that come after + // the package name. + if cg.Pos() > file.Name.Pos() { + continue + } + for _, comment := range cg.List { + if c, err := constraint.Parse(comment.Text); err == nil { + if !f(c) { + return + } + } + } + } +} diff --git a/contribs/gnopls/internal/cache/constraints_test.go b/contribs/gnopls/internal/cache/constraints_test.go new file mode 100644 index 00000000000..23c9f39cb19 --- /dev/null +++ b/contribs/gnopls/internal/cache/constraints_test.go @@ -0,0 +1,126 @@ +// 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.16 +// +build go1.16 + +package cache + +import ( + "testing" +) + +func TestIsStandaloneFile(t *testing.T) { + tests := []struct { + desc string + contents string + standaloneTags []string + want bool + }{ + { + "new syntax", + "//go:build ignore\n\npackage main\n", + []string{"ignore"}, + true, + }, + { + "legacy syntax", + "// +build ignore\n\npackage main\n", + []string{"ignore"}, + true, + }, + { + "multiple tags", + "//go:build ignore\n\npackage main\n", + []string{"exclude", "ignore"}, + true, + }, + { + "invalid tag", + "// +build ignore\n\npackage main\n", + []string{"script"}, + false, + }, + { + "non-main package", + "//go:build ignore\n\npackage p\n", + []string{"ignore"}, + false, + }, + { + "alternate tag", + "// +build script\n\npackage main\n", + []string{"script"}, + true, + }, + { + "both syntax", + "//go:build ignore\n// +build ignore\n\npackage main\n", + []string{"ignore"}, + true, + }, + { + "after comments", + "// A non-directive comment\n//go:build ignore\n\npackage main\n", + []string{"ignore"}, + true, + }, + { + "after package decl", + "package main //go:build ignore\n", + []string{"ignore"}, + false, + }, + { + "on line after package decl", + "package main\n\n//go:build ignore\n", + []string{"ignore"}, + false, + }, + { + "combined with other expressions", + "\n\n//go:build ignore || darwin\n\npackage main\n", + []string{"ignore"}, + false, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + if got := isStandaloneFile([]byte(test.contents), test.standaloneTags); got != test.want { + t.Errorf("isStandaloneFile(%q, %v) = %t, want %t", test.contents, test.standaloneTags, got, test.want) + } + }) + } +} + +func TestVersionRegexp(t *testing.T) { + // good + for _, s := range []string{ + "go1", + "go1.2", + "go1.2.3", + "go1.0.33", + } { + if !goVersionRx.MatchString(s) { + t.Errorf("Valid Go version %q does not match the regexp", s) + } + } + + // bad + for _, s := range []string{ + "go", // missing numbers + "go0", // Go starts at 1 + "go01", // leading zero + "go1.π", // non-decimal + "go1.-1", // negative + "go1.02.3", // leading zero + "go1.2.3.4", // too many segments + "go1.2.3-pre", // textual suffix + } { + if goVersionRx.MatchString(s) { + t.Errorf("Invalid Go version %q unexpectedly matches the regexp", s) + } + } +} diff --git a/contribs/gnopls/internal/cache/debug.go b/contribs/gnopls/internal/cache/debug.go new file mode 100644 index 00000000000..1eb7e16850b --- /dev/null +++ b/contribs/gnopls/internal/cache/debug.go @@ -0,0 +1,12 @@ +// 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 cache + +// assert panics with the given msg if cond is not true. +func assert(cond bool, msg string) { + if !cond { + panic(msg) + } +} diff --git a/contribs/gnopls/internal/cache/diagnostics.go b/contribs/gnopls/internal/cache/diagnostics.go new file mode 100644 index 00000000000..797ce961cd8 --- /dev/null +++ b/contribs/gnopls/internal/cache/diagnostics.go @@ -0,0 +1,192 @@ +// 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 cache + +import ( + "encoding/json" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" +) + +// A InitializationError is an error that causes snapshot initialization to fail. +// It is either the error returned from go/packages.Load, or an error parsing a +// workspace go.work or go.mod file. +// +// Such an error generally indicates that the View is malformed, and will never +// be usable. +type InitializationError struct { + // MainError is the primary error. Must be non-nil. + MainError error + + // Diagnostics contains any supplemental (structured) diagnostics extracted + // from the load error. + Diagnostics map[protocol.DocumentURI][]*Diagnostic +} + +func byURI(d *Diagnostic) protocol.DocumentURI { return d.URI } // For use in maps.Group. + +// An Diagnostic corresponds to an LSP Diagnostic. +// https://microsoft.github.io/language-server-protocol/specification#diagnostic +// +// It is (effectively) gob-serializable; see {encode,decode}Diagnostics. +type Diagnostic struct { + URI protocol.DocumentURI // of diagnosed file (not diagnostic documentation) + Range protocol.Range + Severity protocol.DiagnosticSeverity + Code string // analysis.Diagnostic.Category (or "default" if empty) or hidden go/types error code + CodeHref string + + // Source is a human-readable description of the source of the error. + // Diagnostics generated by an analysis.Analyzer set it to Analyzer.Name. + Source DiagnosticSource + + Message string + + Tags []protocol.DiagnosticTag + Related []protocol.DiagnosticRelatedInformation + + // Fields below are used internally to generate lazy fixes. They aren't + // part of the LSP spec and historically didn't leave the server. + // + // Update(2023-05): version 3.16 of the LSP spec included support for the + // Diagnostic.data field, which holds arbitrary data preserved in the + // diagnostic for codeAction requests. This field allows bundling additional + // information for lazy fixes, and gopls can (and should) use this + // information to avoid re-evaluating diagnostics in code-action handlers. + // + // In order to stage this transition incrementally, the 'BundledFixes' field + // may store a 'bundled' (=json-serialized) form of the associated + // SuggestedFixes. Not all diagnostics have their fixes bundled. + BundledFixes *json.RawMessage + SuggestedFixes []SuggestedFix +} + +func (d *Diagnostic) String() string { + return fmt.Sprintf("%v: %s", d.Range, d.Message) +} + +type DiagnosticSource string + +const ( + UnknownError DiagnosticSource = "" + ListError DiagnosticSource = "go list" + ParseError DiagnosticSource = "syntax" + TypeError DiagnosticSource = "compiler" + ModTidyError DiagnosticSource = "go mod tidy" + OptimizationDetailsError DiagnosticSource = "optimizer details" + UpgradeNotification DiagnosticSource = "upgrade available" + Vulncheck DiagnosticSource = "vulncheck imports" + Govulncheck DiagnosticSource = "govulncheck" + TemplateError DiagnosticSource = "template" + WorkFileError DiagnosticSource = "go.work file" + ConsistencyInfo DiagnosticSource = "consistency" +) + +// A SuggestedFix represents a suggested fix (for a diagnostic) +// produced by analysis, in protocol form. +// +// The fixes are reported to the client as a set of code actions in +// response to a CodeAction query for a set of diagnostics. Multiple +// SuggestedFixes may be produced for the same logical fix, varying +// only in ActionKind. For example, a fix may be both a Refactor +// (which should appear on the refactoring menu) and a SourceFixAll (a +// clear fix that can be safely applied without explicit consent). +type SuggestedFix struct { + Title string + Edits map[protocol.DocumentURI][]protocol.TextEdit + Command *protocol.Command + ActionKind protocol.CodeActionKind +} + +// SuggestedFixFromCommand returns a suggested fix to run the given command. +func SuggestedFixFromCommand(cmd *protocol.Command, kind protocol.CodeActionKind) SuggestedFix { + return SuggestedFix{ + Title: cmd.Title, + Command: cmd, + ActionKind: kind, + } +} + +// lazyFixesJSON is a JSON-serializable list of code actions (arising +// from "lazy" SuggestedFixes with no Edits) to be saved in the +// protocol.Diagnostic.Data field. Computation of the edits is thus +// deferred until the action's command is invoked. +type lazyFixesJSON struct { + // TODO(rfindley): pack some sort of identifier here for later + // lookup/validation? + Actions []protocol.CodeAction +} + +// bundleLazyFixes attempts to bundle sd.SuggestedFixes into the +// sd.BundledFixes field, so that it can be round-tripped through the client. +// It returns false if the fixes cannot be bundled. +func bundleLazyFixes(sd *Diagnostic) bool { + if len(sd.SuggestedFixes) == 0 { + return true + } + var actions []protocol.CodeAction + for _, fix := range sd.SuggestedFixes { + if fix.Edits != nil { + // For now, we only support bundled code actions that execute commands. + // + // In order to cleanly support bundled edits, we'd have to guarantee that + // the edits were generated on the current snapshot. But this naively + // implies that every fix would have to include a snapshot ID, which + // would require us to republish all diagnostics on each new snapshot. + // + // TODO(rfindley): in order to avoid this additional chatter, we'd need + // to build some sort of registry or other mechanism on the snapshot to + // check whether a diagnostic is still valid. + return false + } + action := protocol.CodeAction{ + Title: fix.Title, + Kind: fix.ActionKind, + Command: fix.Command, + } + actions = append(actions, action) + } + fixes := lazyFixesJSON{ + Actions: actions, + } + data, err := json.Marshal(fixes) + if err != nil { + bug.Reportf("marshalling lazy fixes: %v", err) + return false + } + msg := json.RawMessage(data) + sd.BundledFixes = &msg + return true +} + +// BundledLazyFixes extracts any bundled codeActions from the +// diag.Data field. +func BundledLazyFixes(diag protocol.Diagnostic) []protocol.CodeAction { + var fix lazyFixesJSON + if diag.Data != nil { + err := protocol.UnmarshalJSON(*diag.Data, &fix) + if err != nil { + bug.Reportf("unmarshalling lazy fix: %v", err) + return nil + } + } + + var actions []protocol.CodeAction + for _, action := range fix.Actions { + // See bundleLazyFixes: for now we only support bundling commands. + if action.Edit != nil { + bug.Reportf("bundled fix %q includes workspace edits", action.Title) + continue + } + // associate the action with the incoming diagnostic + // (Note that this does not mutate the fix.Fixes slice). + action.Diagnostics = []protocol.Diagnostic{diag} + actions = append(actions, action) + } + + return actions +} diff --git a/contribs/gnopls/internal/cache/errors.go b/contribs/gnopls/internal/cache/errors.go new file mode 100644 index 00000000000..4423fb69e3e --- /dev/null +++ b/contribs/gnopls/internal/cache/errors.go @@ -0,0 +1,506 @@ +// 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. + +package cache + +// This file defines routines to convert diagnostics from go list, go +// get, go/packages, parsing, type checking, and analysis into +// golang.Diagnostic form, and suggesting quick fixes. + +import ( + "context" + "fmt" + "go/parser" + "go/scanner" + "go/token" + "path/filepath" + "regexp" + "strconv" + "strings" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/typesinternal" +) + +// goPackagesErrorDiagnostics translates the given go/packages Error into a +// diagnostic, using the provided metadata and filesource. +// +// The slice of diagnostics may be empty. +func goPackagesErrorDiagnostics(ctx context.Context, e packages.Error, mp *metadata.Package, fs file.Source) ([]*Diagnostic, error) { + if diag, err := parseGoListImportCycleError(ctx, e, mp, fs); err != nil { + return nil, err + } else if diag != nil { + return []*Diagnostic{diag}, nil + } + + // Parse error location and attempt to convert to protocol form. + loc, err := func() (protocol.Location, error) { + filename, line, col8 := parseGoListError(e, mp.LoadDir) + uri := protocol.URIFromPath(filename) + + fh, err := fs.ReadFile(ctx, uri) + if err != nil { + return protocol.Location{}, err + } + content, err := fh.Content() + if err != nil { + return protocol.Location{}, err + } + mapper := protocol.NewMapper(uri, content) + posn, err := mapper.LineCol8Position(line, col8) + if err != nil { + return protocol.Location{}, err + } + return protocol.Location{ + URI: uri, + Range: protocol.Range{ + Start: posn, + End: posn, + }, + }, nil + }() + + // TODO(rfindley): in some cases the go command outputs invalid spans, for + // example (from TestGoListErrors): + // + // package a + // import + // + // In this case, the go command will complain about a.go:2:8, which is after + // the trailing newline but still considered to be on the second line, most + // likely because *token.File lacks information about newline termination. + // + // We could do better here by handling that case. + if err != nil { + // Unable to parse a valid position. + // Apply the error to all files to be safe. + var diags []*Diagnostic + for _, uri := range mp.CompiledGoFiles { + diags = append(diags, &Diagnostic{ + URI: uri, + Severity: protocol.SeverityError, + Source: ListError, + Message: e.Msg, + }) + } + return diags, nil + } + return []*Diagnostic{{ + URI: loc.URI, + Range: loc.Range, + Severity: protocol.SeverityError, + Source: ListError, + Message: e.Msg, + }}, nil +} + +func parseErrorDiagnostics(pkg *syntaxPackage, errList scanner.ErrorList) ([]*Diagnostic, error) { + // The first parser error is likely the root cause of the problem. + if errList.Len() <= 0 { + return nil, fmt.Errorf("no errors in %v", errList) + } + e := errList[0] + pgf, err := pkg.File(protocol.URIFromPath(e.Pos.Filename)) + if err != nil { + return nil, err + } + rng, err := pgf.Mapper.OffsetRange(e.Pos.Offset, e.Pos.Offset) + if err != nil { + return nil, err + } + return []*Diagnostic{{ + URI: pgf.URI, + Range: rng, + Severity: protocol.SeverityError, + Source: ParseError, + Message: e.Msg, + }}, nil +} + +var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`) +var unsupportedFeatureRe = regexp.MustCompile(`.*require.* go(\d+\.\d+) or later`) + +func goGetQuickFixes(haveModule bool, uri protocol.DocumentURI, pkg string) []SuggestedFix { + // Go get only supports module mode for now. + if !haveModule { + return nil + } + title := fmt.Sprintf("go get package %v", pkg) + cmd := command.NewGoGetPackageCommand(title, command.GoGetPackageArgs{ + URI: uri, + AddRequire: true, + Pkg: pkg, + }) + return []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)} +} + +func editGoDirectiveQuickFix(haveModule bool, uri protocol.DocumentURI, version string) []SuggestedFix { + // Go mod edit only supports module mode. + if !haveModule { + return nil + } + title := fmt.Sprintf("go mod edit -go=%s", version) + cmd := command.NewEditGoDirectiveCommand(title, command.EditGoDirectiveArgs{ + URI: uri, + Version: version, + }) + return []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)} +} + +// encodeDiagnostics gob-encodes the given diagnostics. +func encodeDiagnostics(srcDiags []*Diagnostic) []byte { + var gobDiags []gobDiagnostic + for _, srcDiag := range srcDiags { + var gobFixes []gobSuggestedFix + for _, srcFix := range srcDiag.SuggestedFixes { + gobFix := gobSuggestedFix{ + Message: srcFix.Title, + ActionKind: srcFix.ActionKind, + } + for uri, srcEdits := range srcFix.Edits { + for _, srcEdit := range srcEdits { + gobFix.TextEdits = append(gobFix.TextEdits, gobTextEdit{ + Location: protocol.Location{ + URI: uri, + Range: srcEdit.Range, + }, + NewText: []byte(srcEdit.NewText), + }) + } + } + if srcCmd := srcFix.Command; srcCmd != nil { + gobFix.Command = &gobCommand{ + Title: srcCmd.Title, + Command: srcCmd.Command, + Arguments: srcCmd.Arguments, + } + } + gobFixes = append(gobFixes, gobFix) + } + var gobRelated []gobRelatedInformation + for _, srcRel := range srcDiag.Related { + gobRel := gobRelatedInformation(srcRel) + gobRelated = append(gobRelated, gobRel) + } + gobDiag := gobDiagnostic{ + Location: protocol.Location{ + URI: srcDiag.URI, + Range: srcDiag.Range, + }, + Severity: srcDiag.Severity, + Code: srcDiag.Code, + CodeHref: srcDiag.CodeHref, + Source: string(srcDiag.Source), + Message: srcDiag.Message, + SuggestedFixes: gobFixes, + Related: gobRelated, + Tags: srcDiag.Tags, + } + gobDiags = append(gobDiags, gobDiag) + } + return diagnosticsCodec.Encode(gobDiags) +} + +// decodeDiagnostics decodes the given gob-encoded diagnostics. +func decodeDiagnostics(data []byte) []*Diagnostic { + var gobDiags []gobDiagnostic + diagnosticsCodec.Decode(data, &gobDiags) + var srcDiags []*Diagnostic + for _, gobDiag := range gobDiags { + var srcFixes []SuggestedFix + for _, gobFix := range gobDiag.SuggestedFixes { + srcFix := SuggestedFix{ + Title: gobFix.Message, + ActionKind: gobFix.ActionKind, + } + for _, gobEdit := range gobFix.TextEdits { + if srcFix.Edits == nil { + srcFix.Edits = make(map[protocol.DocumentURI][]protocol.TextEdit) + } + srcEdit := protocol.TextEdit{ + Range: gobEdit.Location.Range, + NewText: string(gobEdit.NewText), + } + uri := gobEdit.Location.URI + srcFix.Edits[uri] = append(srcFix.Edits[uri], srcEdit) + } + if gobCmd := gobFix.Command; gobCmd != nil { + srcFix.Command = &protocol.Command{ + Title: gobCmd.Title, + Command: gobCmd.Command, + Arguments: gobCmd.Arguments, + } + } + srcFixes = append(srcFixes, srcFix) + } + var srcRelated []protocol.DiagnosticRelatedInformation + for _, gobRel := range gobDiag.Related { + srcRel := protocol.DiagnosticRelatedInformation(gobRel) + srcRelated = append(srcRelated, srcRel) + } + srcDiag := &Diagnostic{ + URI: gobDiag.Location.URI, + Range: gobDiag.Location.Range, + Severity: gobDiag.Severity, + Code: gobDiag.Code, + CodeHref: gobDiag.CodeHref, + Source: DiagnosticSource(gobDiag.Source), + Message: gobDiag.Message, + Tags: gobDiag.Tags, + Related: srcRelated, + SuggestedFixes: srcFixes, + } + srcDiags = append(srcDiags, srcDiag) + } + return srcDiags +} + +// toSourceDiagnostic converts a gobDiagnostic to "source" form. +func toSourceDiagnostic(srcAnalyzer *settings.Analyzer, gobDiag *gobDiagnostic) *Diagnostic { + var related []protocol.DiagnosticRelatedInformation + for _, gobRelated := range gobDiag.Related { + related = append(related, protocol.DiagnosticRelatedInformation(gobRelated)) + } + + severity := srcAnalyzer.Severity() + if severity == 0 { + severity = protocol.SeverityWarning + } + + diag := &Diagnostic{ + URI: gobDiag.Location.URI, + Range: gobDiag.Location.Range, + Severity: severity, + Code: gobDiag.Code, + CodeHref: gobDiag.CodeHref, + Source: DiagnosticSource(gobDiag.Source), + Message: gobDiag.Message, + Related: related, + Tags: srcAnalyzer.Tags(), + } + + // We cross the set of fixes (whether edit- or command-based) + // with the set of kinds, as a single fix may represent more + // than one kind of action (e.g. refactor, quickfix, fixall), + // each corresponding to a distinct client UI element + // or operation. + kinds := srcAnalyzer.ActionKinds() + if len(kinds) == 0 { + kinds = []protocol.CodeActionKind{protocol.QuickFix} + } + + var fixes []SuggestedFix + for _, fix := range gobDiag.SuggestedFixes { + if len(fix.TextEdits) > 0 { + // Accumulate edit-based fixes supplied by the diagnostic itself. + edits := make(map[protocol.DocumentURI][]protocol.TextEdit) + for _, e := range fix.TextEdits { + uri := e.Location.URI + edits[uri] = append(edits[uri], protocol.TextEdit{ + Range: e.Location.Range, + NewText: string(e.NewText), + }) + } + for _, kind := range kinds { + fixes = append(fixes, SuggestedFix{ + Title: fix.Message, + Edits: edits, + ActionKind: kind, + }) + } + + } else { + // Accumulate command-based fixes, whose edits + // are not provided by the analyzer but are computed on demand + // by logic "adjacent to" the analyzer. + // + // The analysis.Diagnostic.Category is used as the fix name. + cmd := command.NewApplyFixCommand(fix.Message, command.ApplyFixArgs{ + Fix: diag.Code, + URI: gobDiag.Location.URI, + Range: gobDiag.Location.Range, + }) + for _, kind := range kinds { + fixes = append(fixes, SuggestedFixFromCommand(cmd, kind)) + } + + // Ensure that the analyzer specifies a category for all its no-edit fixes. + // This is asserted by analysistest.RunWithSuggestedFixes, but there + // may be gaps in test coverage. + if diag.Code == "" || diag.Code == "default" { + bug.Reportf("missing Diagnostic.Code: %#v", *diag) + } + } + } + diag.SuggestedFixes = fixes + + // If the fixes only delete code, assume that the diagnostic is reporting dead code. + if onlyDeletions(diag.SuggestedFixes) { + diag.Tags = append(diag.Tags, protocol.Unnecessary) + } + return diag +} + +// onlyDeletions returns true if fixes is non-empty and all of the suggested +// fixes are deletions. +func onlyDeletions(fixes []SuggestedFix) bool { + for _, fix := range fixes { + if fix.Command != nil { + return false + } + for _, edits := range fix.Edits { + for _, edit := range edits { + if edit.NewText != "" { + return false + } + if protocol.ComparePosition(edit.Range.Start, edit.Range.End) == 0 { + return false + } + } + } + } + return len(fixes) > 0 +} + +func typesCodeHref(linkTarget string, code typesinternal.ErrorCode) string { + return BuildLink(linkTarget, "golang.org/x/tools/internal/typesinternal", code.String()) +} + +// BuildLink constructs a URL with the given target, path, and anchor. +func BuildLink(target, path, anchor string) protocol.URI { + link := fmt.Sprintf("https://%s/%s", target, path) + if anchor == "" { + return link + } + return link + "#" + anchor +} + +func parseGoListError(e packages.Error, dir string) (filename string, line, col8 int) { + input := e.Pos + if input == "" { + // No position. Attempt to parse one out of a + // go list error of the form "file:line:col: + // message" by stripping off the message. + input = strings.TrimSpace(e.Msg) + if i := strings.Index(input, ": "); i >= 0 { + input = input[:i] + } + } + + filename, line, col8 = splitFileLineCol(input) + if !filepath.IsAbs(filename) { + filename = filepath.Join(dir, filename) + } + return filename, line, col8 +} + +// splitFileLineCol splits s into "filename:line:col", +// where line and col consist of decimal digits. +func splitFileLineCol(s string) (file string, line, col8 int) { + // Beware that the filename may contain colon on Windows. + + // stripColonDigits removes a ":%d" suffix, if any. + stripColonDigits := func(s string) (rest string, num int) { + if i := strings.LastIndex(s, ":"); i >= 0 { + if v, err := strconv.ParseInt(s[i+1:], 10, 32); err == nil { + return s[:i], int(v) + } + } + return s, -1 + } + + // strip col ":%d" + s, n1 := stripColonDigits(s) + if n1 < 0 { + return s, 1, 1 // "filename" + } + + // strip line ":%d" + s, n2 := stripColonDigits(s) + if n2 < 0 { + return s, n1, 1 // "filename:line" + } + + return s, n2, n1 // "filename:line:col" +} + +// parseGoListImportCycleError attempts to parse the given go/packages error as +// an import cycle, returning a diagnostic if successful. +// +// If the error is not detected as an import cycle error, it returns nil, nil. +func parseGoListImportCycleError(ctx context.Context, e packages.Error, mp *metadata.Package, fs file.Source) (*Diagnostic, error) { + re := regexp.MustCompile(`(.*): import stack: \[(.+)\]`) + matches := re.FindStringSubmatch(strings.TrimSpace(e.Msg)) + if len(matches) < 3 { + return nil, nil + } + msg := matches[1] + importList := strings.Split(matches[2], " ") + // Since the error is relative to the current package. The import that is causing + // the import cycle error is the second one in the list. + if len(importList) < 2 { + return nil, nil + } + // Imports have quotation marks around them. + circImp := strconv.Quote(importList[1]) + for _, uri := range mp.CompiledGoFiles { + pgf, err := parseGoURI(ctx, fs, uri, parsego.Header) + if err != nil { + return nil, err + } + // Search file imports for the import that is causing the import cycle. + for _, imp := range pgf.File.Imports { + if imp.Path.Value == circImp { + rng, err := pgf.NodeMappedRange(imp) + if err != nil { + return nil, nil + } + + return &Diagnostic{ + URI: pgf.URI, + Range: rng.Range(), + Severity: protocol.SeverityError, + Source: ListError, + Message: msg, + }, nil + } + } + } + return nil, nil +} + +// parseGoURI is a helper to parse the Go file at the given URI from the file +// source fs. The resulting syntax and token.File belong to an ephemeral, +// encapsulated FileSet, so this file stands only on its own: it's not suitable +// to use in a list of file of a package, for example. +// +// It returns an error if the file could not be read. +// +// TODO(rfindley): eliminate this helper. +func parseGoURI(ctx context.Context, fs file.Source, uri protocol.DocumentURI, mode parser.Mode) (*parsego.File, error) { + fh, err := fs.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + return parseGoImpl(ctx, token.NewFileSet(), fh, mode, false) +} + +// parseModURI is a helper to parse the Mod file at the given URI from the file +// source fs. +// +// It returns an error if the file could not be read. +func parseModURI(ctx context.Context, fs file.Source, uri protocol.DocumentURI) (*ParsedModule, error) { + fh, err := fs.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + return parseModImpl(ctx, fh) +} diff --git a/contribs/gnopls/internal/cache/errors_test.go b/contribs/gnopls/internal/cache/errors_test.go new file mode 100644 index 00000000000..664135a8826 --- /dev/null +++ b/contribs/gnopls/internal/cache/errors_test.go @@ -0,0 +1,128 @@ +// 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. + +package cache + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/protocol" +) + +func TestParseErrorMessage(t *testing.T) { + tests := []struct { + name string + in string + expectedFileName string + expectedLine int // (missing => 1) + expectedColumn int // (missing => 1) + }{ + { + name: "from go list output", + in: "\nattributes.go:13:1: expected 'package', found 'type'", + expectedFileName: "attributes.go", + expectedLine: 13, + expectedColumn: 1, + }, + { + name: "windows driver letter", + in: "C:\\foo\\bar.go:13: message", + expectedFileName: "bar.go", + expectedLine: 13, + expectedColumn: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fn, line, col8 := parseGoListError(packages.Error{Msg: tt.in}, ".") + + if !strings.HasSuffix(fn, tt.expectedFileName) { + t.Errorf("expected filename with suffix %v but got %v", tt.expectedFileName, fn) + } + if line != tt.expectedLine { + t.Errorf("expected line %v but got %v", tt.expectedLine, line) + } + if col8 != tt.expectedColumn { + t.Errorf("expected col %v but got %v", tt.expectedLine, col8) + } + }) + } +} + +func TestDiagnosticEncoding(t *testing.T) { + diags := []*Diagnostic{ + {}, // empty + { + URI: "file///foo", + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 2}, + End: protocol.Position{Line: 6, Character: 7}, + }, + Severity: protocol.SeverityWarning, + Code: "red", + CodeHref: "https://go.dev", + Source: "test", + Message: "something bad happened", + Tags: []protocol.DiagnosticTag{81}, + Related: []protocol.DiagnosticRelatedInformation{ + { + Location: protocol.Location{ + URI: "file:///other", + Range: protocol.Range{ + Start: protocol.Position{Line: 3, Character: 6}, + End: protocol.Position{Line: 4, Character: 9}, + }, + }, + Message: "psst, over here", + }, + }, + + // Fields below are used internally to generate quick fixes. They aren't + // part of the LSP spec and don't leave the server. + SuggestedFixes: []SuggestedFix{ + { + Title: "fix it!", + Edits: map[protocol.DocumentURI][]protocol.TextEdit{ + "file:///foo": {{ + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 2}, + End: protocol.Position{Line: 6, Character: 7}, + }, + NewText: "abc", + }}, + "file:///other": {{ + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 2}, + End: protocol.Position{Line: 6, Character: 7}, + }, + NewText: "!@#!", + }}, + }, + Command: &protocol.Command{ + Title: "run a command", + Command: "gopls.fix", + Arguments: []json.RawMessage{json.RawMessage(`{"a":1}`)}, + }, + ActionKind: protocol.QuickFix, + }, + }, + }, + { + URI: "file//bar", + // other fields tested above + }, + } + + data := encodeDiagnostics(diags) + diags2 := decodeDiagnostics(data) + + if diff := cmp.Diff(diags, diags2); diff != "" { + t.Errorf("decoded diagnostics do not match (-original +decoded):\n%s", diff) + } +} diff --git a/contribs/gnopls/internal/cache/filemap.go b/contribs/gnopls/internal/cache/filemap.go new file mode 100644 index 00000000000..ee64d7c32c3 --- /dev/null +++ b/contribs/gnopls/internal/cache/filemap.go @@ -0,0 +1,151 @@ +// 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 cache + +import ( + "path/filepath" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/persistent" +) + +// A fileMap maps files in the snapshot, with some additional bookkeeping: +// It keeps track of overlays as well as directories containing any observed +// file. +type fileMap struct { + files *persistent.Map[protocol.DocumentURI, file.Handle] + overlays *persistent.Map[protocol.DocumentURI, *overlay] // the subset of files that are overlays + dirs *persistent.Set[string] // all dirs containing files; if nil, dirs have not been initialized +} + +func newFileMap() *fileMap { + return &fileMap{ + files: new(persistent.Map[protocol.DocumentURI, file.Handle]), + overlays: new(persistent.Map[protocol.DocumentURI, *overlay]), + dirs: new(persistent.Set[string]), + } +} + +// clone creates a copy of the fileMap, incorporating the changes specified by +// the changes map. +func (m *fileMap) clone(changes map[protocol.DocumentURI]file.Handle) *fileMap { + m2 := &fileMap{ + files: m.files.Clone(), + overlays: m.overlays.Clone(), + } + if m.dirs != nil { + m2.dirs = m.dirs.Clone() + } + + // Handle file changes. + // + // Note, we can't simply delete the file unconditionally and let it be + // re-read by the snapshot, as (1) the snapshot must always observe all + // overlays, and (2) deleting a file forces directories to be reevaluated, as + // it may be the last file in a directory. We want to avoid that work in the + // common case where a file has simply changed. + // + // For that reason, we also do this in two passes, processing deletions + // first, as a set before a deletion would result in pointless work. + for uri, fh := range changes { + if !fileExists(fh) { + m2.delete(uri) + } + } + for uri, fh := range changes { + if fileExists(fh) { + m2.set(uri, fh) + } + } + return m2 +} + +func (m *fileMap) destroy() { + m.files.Destroy() + m.overlays.Destroy() + if m.dirs != nil { + m.dirs.Destroy() + } +} + +// get returns the file handle mapped by the given key, or (nil, false) if the +// key is not present. +func (m *fileMap) get(key protocol.DocumentURI) (file.Handle, bool) { + return m.files.Get(key) +} + +// foreach calls f for each (uri, fh) in the map. +func (m *fileMap) foreach(f func(uri protocol.DocumentURI, fh file.Handle)) { + m.files.Range(f) +} + +// set stores the given file handle for key, updating overlays and directories +// accordingly. +func (m *fileMap) set(key protocol.DocumentURI, fh file.Handle) { + m.files.Set(key, fh, nil) + + // update overlays + if o, ok := fh.(*overlay); ok { + m.overlays.Set(key, o, nil) + } else { + // Setting a non-overlay must delete the corresponding overlay, to preserve + // the accuracy of the overlay set. + m.overlays.Delete(key) + } + + // update dirs, if they have been computed + if m.dirs != nil { + m.addDirs(key) + } +} + +// addDirs adds all directories containing u to the dirs set. +func (m *fileMap) addDirs(u protocol.DocumentURI) { + dir := filepath.Dir(u.Path()) + for dir != "" && !m.dirs.Contains(dir) { + m.dirs.Add(dir) + dir = filepath.Dir(dir) + } +} + +// delete removes a file from the map, and updates overlays and dirs +// accordingly. +func (m *fileMap) delete(key protocol.DocumentURI) { + m.files.Delete(key) + m.overlays.Delete(key) + + // Deleting a file may cause the set of dirs to shrink; therefore we must + // re-evaluate the dir set. + // + // Do this lazily, to avoid work if there are multiple deletions in a row. + if m.dirs != nil { + m.dirs.Destroy() + m.dirs = nil + } +} + +// getOverlays returns a new unordered array of overlay files. +func (m *fileMap) getOverlays() []*overlay { + var overlays []*overlay + m.overlays.Range(func(_ protocol.DocumentURI, o *overlay) { + overlays = append(overlays, o) + }) + return overlays +} + +// getDirs reports returns the set of dirs observed by the fileMap. +// +// This operation mutates the fileMap. +// The result must not be mutated by the caller. +func (m *fileMap) getDirs() *persistent.Set[string] { + if m.dirs == nil { + m.dirs = new(persistent.Set[string]) + m.files.Range(func(u protocol.DocumentURI, _ file.Handle) { + m.addDirs(u) + }) + } + return m.dirs +} diff --git a/contribs/gnopls/internal/cache/filemap_test.go b/contribs/gnopls/internal/cache/filemap_test.go new file mode 100644 index 00000000000..13f2c1a9ccd --- /dev/null +++ b/contribs/gnopls/internal/cache/filemap_test.go @@ -0,0 +1,112 @@ +// 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 cache + +import ( + "path/filepath" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" +) + +func TestFileMap(t *testing.T) { + const ( + set = iota + del + ) + type op struct { + op int // set or remove + path string + overlay bool + } + tests := []struct { + label string + ops []op + wantFiles []string + wantOverlays []string + wantDirs []string + }{ + {"empty", nil, nil, nil, nil}, + {"singleton", []op{ + {set, "/a/b", false}, + }, []string{"/a/b"}, nil, []string{"/", "/a"}}, + {"overlay", []op{ + {set, "/a/b", true}, + }, []string{"/a/b"}, []string{"/a/b"}, []string{"/", "/a"}}, + {"replace overlay", []op{ + {set, "/a/b", true}, + {set, "/a/b", false}, + }, []string{"/a/b"}, nil, []string{"/", "/a"}}, + {"multi dir", []op{ + {set, "/a/b", false}, + {set, "/c/d", false}, + }, []string{"/a/b", "/c/d"}, nil, []string{"/", "/a", "/c"}}, + {"empty dir", []op{ + {set, "/a/b", false}, + {set, "/c/d", false}, + {del, "/a/b", false}, + }, []string{"/c/d"}, nil, []string{"/", "/c"}}, + } + + // Normalize paths for windows compatibility. + normalize := func(path string) string { + y := filepath.ToSlash(path) + // Windows paths may start with a drive letter + if len(y) > 2 && y[1] == ':' && y[0] >= 'A' && y[0] <= 'Z' { + y = y[2:] + } + return y + } + + for _, test := range tests { + t.Run(test.label, func(t *testing.T) { + m := newFileMap() + for _, op := range test.ops { + uri := protocol.URIFromPath(filepath.FromSlash(op.path)) + switch op.op { + case set: + var fh file.Handle + if op.overlay { + fh = &overlay{uri: uri} + } else { + fh = &diskFile{uri: uri} + } + m.set(uri, fh) + case del: + m.delete(uri) + } + } + + var gotFiles []string + m.foreach(func(uri protocol.DocumentURI, _ file.Handle) { + gotFiles = append(gotFiles, normalize(uri.Path())) + }) + sort.Strings(gotFiles) + if diff := cmp.Diff(test.wantFiles, gotFiles); diff != "" { + t.Errorf("Files mismatch (-want +got):\n%s", diff) + } + + var gotOverlays []string + for _, o := range m.getOverlays() { + gotOverlays = append(gotOverlays, normalize(o.URI().Path())) + } + if diff := cmp.Diff(test.wantOverlays, gotOverlays); diff != "" { + t.Errorf("Overlays mismatch (-want +got):\n%s", diff) + } + + var gotDirs []string + m.getDirs().Range(func(dir string) { + gotDirs = append(gotDirs, normalize(dir)) + }) + sort.Strings(gotDirs) + if diff := cmp.Diff(test.wantDirs, gotDirs); diff != "" { + t.Errorf("Dirs mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/contribs/gnopls/internal/cache/filterer.go b/contribs/gnopls/internal/cache/filterer.go new file mode 100644 index 00000000000..0ec18369bdf --- /dev/null +++ b/contribs/gnopls/internal/cache/filterer.go @@ -0,0 +1,83 @@ +// 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 cache + +import ( + "path" + "path/filepath" + "regexp" + "strings" +) + +type Filterer struct { + // Whether a filter is excluded depends on the operator (first char of the raw filter). + // Slices filters and excluded then should have the same length. + filters []*regexp.Regexp + excluded []bool +} + +// NewFilterer computes regular expression form of all raw filters +func NewFilterer(rawFilters []string) *Filterer { + var f Filterer + for _, filter := range rawFilters { + filter = path.Clean(filepath.ToSlash(filter)) + // TODO(dungtuanle): fix: validate [+-] prefix. + op, prefix := filter[0], filter[1:] + // convertFilterToRegexp adds "/" at the end of prefix to handle cases where a filter is a prefix of another filter. + // For example, it prevents [+foobar, -foo] from excluding "foobar". + f.filters = append(f.filters, convertFilterToRegexp(filepath.ToSlash(prefix))) + f.excluded = append(f.excluded, op == '-') + } + + return &f +} + +// Disallow return true if the path is excluded from the filterer's filters. +func (f *Filterer) Disallow(path string) bool { + // Ensure trailing but not leading slash. + path = strings.TrimPrefix(path, "/") + if !strings.HasSuffix(path, "/") { + path += "/" + } + + // TODO(adonovan): opt: iterate in reverse and break at first match. + excluded := false + for i, filter := range f.filters { + if filter.MatchString(path) { + excluded = f.excluded[i] // last match wins + } + } + return excluded +} + +// convertFilterToRegexp replaces glob-like operator substrings in a string file path to their equivalent regex forms. +// Supporting glob-like operators: +// - **: match zero or more complete path segments +func convertFilterToRegexp(filter string) *regexp.Regexp { + if filter == "" { + return regexp.MustCompile(".*") + } + var ret strings.Builder + ret.WriteString("^") + segs := strings.Split(filter, "/") + for _, seg := range segs { + // Inv: seg != "" since path is clean. + if seg == "**" { + ret.WriteString(".*") + } else { + ret.WriteString(regexp.QuoteMeta(seg)) + } + ret.WriteString("/") + } + pattern := ret.String() + + // Remove unnecessary "^.*" prefix, which increased + // BenchmarkWorkspaceSymbols time by ~20% (even though + // filter CPU time increased by only by ~2.5%) when the + // default filter was changed to "**/node_modules". + pattern = strings.TrimPrefix(pattern, "^.*") + + return regexp.MustCompile(pattern) +} diff --git a/contribs/gnopls/internal/cache/fs_memoized.go b/contribs/gnopls/internal/cache/fs_memoized.go new file mode 100644 index 00000000000..9f156e3e153 --- /dev/null +++ b/contribs/gnopls/internal/cache/fs_memoized.go @@ -0,0 +1,171 @@ +// 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 cache + +import ( + "context" + "os" + "sync" + "time" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/robustio" +) + +// A memoizedFS is a file source that memoizes reads, to reduce IO. +type memoizedFS struct { + mu sync.Mutex + + // filesByID maps existing file inodes to the result of a read. + // (The read may have failed, e.g. due to EACCES or a delete between stat+read.) + // Each slice is a non-empty list of aliases: different URIs. + filesByID map[robustio.FileID][]*diskFile +} + +func newMemoizedFS() *memoizedFS { + return &memoizedFS{filesByID: make(map[robustio.FileID][]*diskFile)} +} + +// A diskFile is a file in the filesystem, or a failure to read one. +// It implements the file.Source interface. +type diskFile struct { + uri protocol.DocumentURI + modTime time.Time + content []byte + hash file.Hash + err error +} + +func (h *diskFile) URI() protocol.DocumentURI { return h.uri } + +func (h *diskFile) Identity() file.Identity { + return file.Identity{ + URI: h.uri, + Hash: h.hash, + } +} + +func (h *diskFile) SameContentsOnDisk() bool { return true } +func (h *diskFile) Version() int32 { return 0 } +func (h *diskFile) Content() ([]byte, error) { return h.content, h.err } + +// ReadFile stats and (maybe) reads the file, updates the cache, and returns it. +func (fs *memoizedFS) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) { + id, mtime, err := robustio.GetFileID(uri.Path()) + if err != nil { + // file does not exist + return &diskFile{ + err: err, + uri: uri, + }, nil + } + + // We check if the file has changed by comparing modification times. Notably, + // this is an imperfect heuristic as various systems have low resolution + // mtimes (as much as 1s on WSL or s390x builders), so we only cache + // filehandles if mtime is old enough to be reliable, meaning that we don't + // expect a subsequent write to have the same mtime. + // + // The coarsest mtime precision we've seen in practice is 1s, so consider + // mtime to be unreliable if it is less than 2s old. Capture this before + // doing anything else. + recentlyModified := time.Since(mtime) < 2*time.Second + + fs.mu.Lock() + fhs, ok := fs.filesByID[id] + if ok && fhs[0].modTime.Equal(mtime) { + var fh *diskFile + // We have already seen this file and it has not changed. + for _, h := range fhs { + if h.uri == uri { + fh = h + break + } + } + // No file handle for this exact URI. Create an alias, but share content. + if fh == nil { + newFH := *fhs[0] + newFH.uri = uri + fh = &newFH + fhs = append(fhs, fh) + fs.filesByID[id] = fhs + } + fs.mu.Unlock() + return fh, nil + } + fs.mu.Unlock() + + // Unknown file, or file has changed. Read (or re-read) it. + fh, err := readFile(ctx, uri, mtime) // ~25us + if err != nil { + return nil, err // e.g. cancelled (not: read failed) + } + + fs.mu.Lock() + if !recentlyModified { + fs.filesByID[id] = []*diskFile{fh} + } else { + delete(fs.filesByID, id) + } + fs.mu.Unlock() + return fh, nil +} + +// fileStats returns information about the set of files stored in fs. It is +// intended for debugging only. +func (fs *memoizedFS) fileStats() (files, largest, errs int) { + fs.mu.Lock() + defer fs.mu.Unlock() + + files = len(fs.filesByID) + largest = 0 + errs = 0 + + for _, files := range fs.filesByID { + rep := files[0] + if len(rep.content) > largest { + largest = len(rep.content) + } + if rep.err != nil { + errs++ + } + } + return files, largest, errs +} + +// ioLimit limits the number of parallel file reads per process. +var ioLimit = make(chan struct{}, 128) + +func readFile(ctx context.Context, uri protocol.DocumentURI, mtime time.Time) (*diskFile, error) { + select { + case ioLimit <- struct{}{}: + case <-ctx.Done(): + return nil, ctx.Err() + } + defer func() { <-ioLimit }() + + ctx, done := event.Start(ctx, "cache.readFile", label.File.Of(uri.Path())) + _ = ctx + defer done() + + // It is possible that a race causes us to read a file with different file + // ID, or whose mtime differs from the given mtime. However, in these cases + // we expect the client to notify of a subsequent file change, and the file + // content should be eventually consistent. + content, err := os.ReadFile(uri.Path()) // ~20us + if err != nil { + content = nil // just in case + } + return &diskFile{ + modTime: mtime, + uri: uri, + content: content, + hash: file.HashOf(content), + err: err, + }, nil +} diff --git a/contribs/gnopls/internal/cache/fs_overlay.go b/contribs/gnopls/internal/cache/fs_overlay.go new file mode 100644 index 00000000000..265598bb967 --- /dev/null +++ b/contribs/gnopls/internal/cache/fs_overlay.go @@ -0,0 +1,79 @@ +// 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 cache + +import ( + "context" + "sync" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" +) + +// An overlayFS is a file.Source that keeps track of overlays on top of a +// delegate FileSource. +type overlayFS struct { + delegate file.Source + + mu sync.Mutex + overlays map[protocol.DocumentURI]*overlay +} + +func newOverlayFS(delegate file.Source) *overlayFS { + return &overlayFS{ + delegate: delegate, + overlays: make(map[protocol.DocumentURI]*overlay), + } +} + +// Overlays returns a new unordered array of overlays. +func (fs *overlayFS) Overlays() []*overlay { + fs.mu.Lock() + defer fs.mu.Unlock() + overlays := make([]*overlay, 0, len(fs.overlays)) + for _, overlay := range fs.overlays { + overlays = append(overlays, overlay) + } + return overlays +} + +func (fs *overlayFS) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) { + fs.mu.Lock() + overlay, ok := fs.overlays[uri] + fs.mu.Unlock() + if ok { + return overlay, nil + } + return fs.delegate.ReadFile(ctx, uri) +} + +// An overlay is a file open in the editor. It may have unsaved edits. +// It implements the file.Handle interface, and the implicit contract +// of the debug.FileTmpl template. +type overlay struct { + uri protocol.DocumentURI + content []byte + hash file.Hash + version int32 + kind file.Kind + + // saved is true if a file matches the state on disk, + // and therefore does not need to be part of the overlay sent to go/packages. + saved bool +} + +func (o *overlay) URI() protocol.DocumentURI { return o.uri } + +func (o *overlay) Identity() file.Identity { + return file.Identity{ + URI: o.uri, + Hash: o.hash, + } +} + +func (o *overlay) Content() ([]byte, error) { return o.content, nil } +func (o *overlay) Version() int32 { return o.version } +func (o *overlay) SameContentsOnDisk() bool { return o.saved } +func (o *overlay) Kind() file.Kind { return o.kind } diff --git a/contribs/gnopls/internal/cache/imports.go b/contribs/gnopls/internal/cache/imports.go new file mode 100644 index 00000000000..c467a851f8f --- /dev/null +++ b/contribs/gnopls/internal/cache/imports.go @@ -0,0 +1,254 @@ +// 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 cache + +import ( + "context" + "fmt" + "sync" + "time" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/keys" + "golang.org/x/tools/internal/imports" +) + +// refreshTimer implements delayed asynchronous refreshing of state. +// +// See the [refreshTimer.schedule] documentation for more details. +type refreshTimer struct { + mu sync.Mutex + duration time.Duration + timer *time.Timer + refreshFn func() +} + +// newRefreshTimer constructs a new refresh timer which schedules refreshes +// using the given function. +func newRefreshTimer(refresh func()) *refreshTimer { + return &refreshTimer{ + refreshFn: refresh, + } +} + +// stop stops any future scheduled refresh. +func (t *refreshTimer) stop() { + t.mu.Lock() + defer t.mu.Unlock() + + if t.timer != nil { + t.timer.Stop() + t.timer = nil + t.refreshFn = nil // release resources + } +} + +// schedule schedules the refresh function to run at some point in the future, +// if no existing refresh is already scheduled. +// +// At a minimum, scheduled refreshes are delayed by 30s, but they may be +// delayed longer to keep their expected execution time under 2% of wall clock +// time. +func (t *refreshTimer) schedule() { + t.mu.Lock() + defer t.mu.Unlock() + + if t.timer == nil { + // Don't refresh more than twice per minute. + delay := 30 * time.Second + // Don't spend more than ~2% of the time refreshing. + if adaptive := 50 * t.duration; adaptive > delay { + delay = adaptive + } + t.timer = time.AfterFunc(delay, func() { + start := time.Now() + t.mu.Lock() + refreshFn := t.refreshFn + t.mu.Unlock() + if refreshFn != nil { // timer may be stopped. + refreshFn() + t.mu.Lock() + t.duration = time.Since(start) + t.timer = nil + t.mu.Unlock() + } + }) + } +} + +// A sharedModCache tracks goimports state for GOMODCACHE directories +// (each session may have its own GOMODCACHE). +// +// This state is refreshed independently of view-specific imports state. +type sharedModCache struct { + mu sync.Mutex + caches map[string]*imports.DirInfoCache // GOMODCACHE -> cache content; never invalidated + // TODO(rfindley): consider stopping these timers when the session shuts down. + timers map[string]*refreshTimer // GOMODCACHE -> timer +} + +func (c *sharedModCache) dirCache(dir string) *imports.DirInfoCache { + c.mu.Lock() + defer c.mu.Unlock() + + cache, ok := c.caches[dir] + if !ok { + cache = imports.NewDirInfoCache() + c.caches[dir] = cache + } + return cache +} + +// refreshDir schedules a refresh of the given directory, which must be a +// module cache. +func (c *sharedModCache) refreshDir(ctx context.Context, dir string, logf func(string, ...any)) { + cache := c.dirCache(dir) + + c.mu.Lock() + defer c.mu.Unlock() + timer, ok := c.timers[dir] + if !ok { + timer = newRefreshTimer(func() { + _, done := event.Start(ctx, "cache.sharedModCache.refreshDir", label.Directory.Of(dir)) + defer done() + imports.ScanModuleCache(dir, cache, logf) + }) + c.timers[dir] = timer + } + + timer.schedule() +} + +// importsState tracks view-specific imports state. +type importsState struct { + ctx context.Context + modCache *sharedModCache + refreshTimer *refreshTimer + + mu sync.Mutex + processEnv *imports.ProcessEnv + cachedModFileHash file.Hash +} + +// newImportsState constructs a new imports state for running goimports +// functions via [runProcessEnvFunc]. +// +// The returned state will automatically refresh itself following a delay. +func newImportsState(backgroundCtx context.Context, modCache *sharedModCache, env *imports.ProcessEnv) *importsState { + s := &importsState{ + ctx: backgroundCtx, + modCache: modCache, + processEnv: env, + } + s.refreshTimer = newRefreshTimer(s.refreshProcessEnv) + s.refreshTimer.schedule() + return s +} + +// stopTimer stops scheduled refreshes of this imports state. +func (s *importsState) stopTimer() { + s.refreshTimer.stop() +} + +// runProcessEnvFunc runs goimports. +// +// Any call to runProcessEnvFunc will schedule a refresh of the imports state +// at some point in the future, if such a refresh is not already scheduled. See +// [refreshTimer] for more details. +func (s *importsState) runProcessEnvFunc(ctx context.Context, snapshot *Snapshot, fn func(context.Context, *imports.Options) error) error { + ctx, done := event.Start(ctx, "cache.importsState.runProcessEnvFunc") + defer done() + + s.mu.Lock() + defer s.mu.Unlock() + + // Find the hash of active mod files, if any. Using the unsaved content + // is slightly wasteful, since we'll drop caches a little too often, but + // the mod file shouldn't be changing while people are autocompleting. + // + // TODO(rfindley): consider instead hashing on-disk modfiles here. + var modFileHash file.Hash + for m := range snapshot.view.workspaceModFiles { + fh, err := snapshot.ReadFile(ctx, m) + if err != nil { + return err + } + modFileHash.XORWith(fh.Identity().Hash) + } + + // If anything relevant to imports has changed, clear caches and + // update the processEnv. Clearing caches blocks on any background + // scans. + if modFileHash != s.cachedModFileHash { + s.processEnv.ClearModuleInfo() + s.cachedModFileHash = modFileHash + } + + // Run the user function. + opts := &imports.Options{ + // Defaults. + AllErrors: true, + Comments: true, + Fragment: true, + FormatOnly: false, + TabIndent: true, + TabWidth: 8, + Env: s.processEnv, + LocalPrefix: snapshot.Options().Local, + } + + if err := fn(ctx, opts); err != nil { + return err + } + + // Refresh the imports resolver after usage. This may seem counterintuitive, + // since it means the first ProcessEnvFunc after a long period of inactivity + // may be stale, but in practice we run ProcessEnvFuncs frequently during + // active development (e.g. during completion), and so this mechanism will be + // active while gopls is in use, and inactive when gopls is idle. + s.refreshTimer.schedule() + + // TODO(rfindley): the GOMODCACHE value used here isn't directly tied to the + // ProcessEnv.Env["GOMODCACHE"], though they should theoretically always + // agree. It would be better if we guaranteed this, possibly by setting all + // required environment variables in ProcessEnv.Env, to avoid the redundant + // Go command invocation. + gomodcache := snapshot.view.folder.Env.GOMODCACHE + s.modCache.refreshDir(s.ctx, gomodcache, s.processEnv.Logf) + + return nil +} + +func (s *importsState) refreshProcessEnv() { + ctx, done := event.Start(s.ctx, "cache.importsState.refreshProcessEnv") + defer done() + + start := time.Now() + + s.mu.Lock() + resolver, err := s.processEnv.GetResolver() + s.mu.Unlock() + if err != nil { + event.Error(s.ctx, "failed to get import resolver", err) + return + } + + event.Log(s.ctx, "background imports cache refresh starting") + resolver2 := resolver.ClearForNewScan() + + // Prime the new resolver before updating the processEnv, so that gopls + // doesn't wait on an unprimed cache. + if err := imports.PrimeCache(context.Background(), resolver2); err == nil { + event.Log(ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start))) + } else { + event.Log(ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err)) + } + + s.mu.Lock() + s.processEnv.UpdateResolver(resolver2) + s.mu.Unlock() +} diff --git a/contribs/gnopls/internal/cache/keys.go b/contribs/gnopls/internal/cache/keys.go new file mode 100644 index 00000000000..664e539edbc --- /dev/null +++ b/contribs/gnopls/internal/cache/keys.go @@ -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 cache + +// session event tracing + +import ( + "io" + + "golang.org/x/tools/internal/event/label" +) + +var ( + KeyCreateSession = NewSessionKey("create_session", "A new session was added") + KeyUpdateSession = NewSessionKey("update_session", "Updated information about a session") + KeyShutdownSession = NewSessionKey("shutdown_session", "A session was shut down") +) + +// SessionKey represents an event label key that has a *Session value. +type SessionKey struct { + name string + description string +} + +// NewSessionKey creates a new Key for *Session values. +func NewSessionKey(name, description string) *SessionKey { + return &SessionKey{name: name, description: description} +} + +func (k *SessionKey) Name() string { return k.name } +func (k *SessionKey) Description() string { return k.description } + +func (k *SessionKey) Format(w io.Writer, buf []byte, l label.Label) { + io.WriteString(w, k.From(l).ID()) +} + +// Of creates a new Label with this key and the supplied session. +func (k *SessionKey) Of(v *Session) label.Label { return label.OfValue(k, v) } + +// Get can be used to get the session for the key from a label.Map. +func (k *SessionKey) Get(lm label.Map) *Session { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return nil +} + +// From can be used to get the session value from a Label. +func (k *SessionKey) From(t label.Label) *Session { + err, _ := t.UnpackValue().(*Session) + return err +} diff --git a/contribs/gnopls/internal/cache/load.go b/contribs/gnopls/internal/cache/load.go new file mode 100644 index 00000000000..9373766b413 --- /dev/null +++ b/contribs/gnopls/internal/cache/load.go @@ -0,0 +1,801 @@ +// 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. + +package cache + +import ( + "bytes" + "context" + "errors" + "fmt" + "path/filepath" + "slices" + "sort" + "strings" + "sync/atomic" + "time" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/immutable" + "golang.org/x/tools/gopls/internal/util/pathutil" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/packagesinternal" + "golang.org/x/tools/internal/xcontext" +) + +var loadID uint64 // atomic identifier for loads + +// errNoPackages indicates that a load query matched no packages. +var errNoPackages = errors.New("no packages returned") + +// load calls packages.Load for the given scopes, updating package metadata, +// import graph, and mapped files with the result. +// +// The resulting error may wrap the moduleErrorMap error type, representing +// errors associated with specific modules. +// +// If scopes contains a file scope there must be exactly one scope. +func (s *Snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadScope) (err error) { + if ctx.Err() != nil { + // Check context cancellation before incrementing id below: a load on a + // cancelled context should be a no-op. + return ctx.Err() + } + id := atomic.AddUint64(&loadID, 1) + eventName := fmt.Sprintf("go/packages.Load #%d", id) // unique name for logging + + var query []string + var standalone bool // whether this is a load of a standalone file + + // Keep track of module query -> module path so that we can later correlate query + // errors with errors. + moduleQueries := make(map[string]string) + for _, scope := range scopes { + switch scope := scope.(type) { + case packageLoadScope: + // The only time we pass package paths is when we're doing a + // partial workspace load. In those cases, the paths came back from + // go list and should already be GOPATH-vendorized when appropriate. + query = append(query, string(scope)) + + case fileLoadScope: + // Given multiple scopes, the resulting load might contain inaccurate + // information. For example go/packages returns at most one command-line + // arguments package, and does not handle a combination of standalone + // files and packages. + uri := protocol.DocumentURI(scope) + if len(scopes) > 1 { + panic(fmt.Sprintf("internal error: load called with multiple scopes when a file scope is present (file: %s)", uri)) + } + fh := s.FindFile(uri) + if fh == nil || s.FileKind(fh) != file.Go { + // Don't try to load a file that doesn't exist, or isn't a go file. + continue + } + contents, err := fh.Content() + if err != nil { + continue + } + if isStandaloneFile(contents, s.Options().StandaloneTags) { + standalone = true + query = append(query, uri.Path()) + } else { + query = append(query, fmt.Sprintf("file=%s", uri.Path())) + } + + case moduleLoadScope: + modQuery := fmt.Sprintf("%s%c...", scope.dir, filepath.Separator) + query = append(query, modQuery) + moduleQueries[modQuery] = scope.modulePath + + case viewLoadScope: + // If we are outside of GOPATH, a module, or some other known + // build system, don't load subdirectories. + if s.view.typ == AdHocView { + query = append(query, "./") + } else { + query = append(query, "./...") + } + + default: + panic(fmt.Sprintf("unknown scope type %T", scope)) + } + } + if len(query) == 0 { + return nil + } + sort.Strings(query) // for determinism + + ctx, done := event.Start(ctx, "cache.snapshot.load", label.Query.Of(query)) + defer done() + + startTime := time.Now() + + inv, cleanupInvocation, err := s.GoCommandInvocation(allowNetwork, &gocommand.Invocation{ + WorkingDir: s.view.root.Path(), + }) + if err != nil { + return err + } + defer cleanupInvocation() + + // Set a last resort deadline on packages.Load since it calls the go + // command, which may hang indefinitely if it has a bug. golang/go#42132 + // and golang/go#42255 have more context. + ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) + defer cancel() + + cfg := s.config(ctx, inv) + pkgs, err := packages.Load(cfg, query...) + + // If the context was canceled, return early. Otherwise, we might be + // type-checking an incomplete result. Check the context directly, + // because go/packages adds extra information to the error. + if ctx.Err() != nil { + return ctx.Err() + } + + // This log message is sought for by TestReloadOnlyOnce. + { + lbls := append(s.Labels(), + label.Query.Of(query), + label.PackageCount.Of(len(pkgs)), + label.Duration.Of(time.Since(startTime)), + ) + if err != nil { + event.Error(ctx, eventName, err, lbls...) + } else { + event.Log(ctx, eventName, lbls...) + } + } + + if standalone { + // Handle standalone package result. + // + // In general, this should just be a single "command-line-arguments" + // package containing the requested file. However, if the file is a test + // file, go/packages may return test variants of the command-line-arguments + // package. We don't support this; theoretically we could, but it seems + // unnecessarily complicated. + // + // Prior to golang/go#64233 we just assumed that we'd get exactly one + // package here. The categorization of bug reports below may be a bit + // verbose, but anticipates that perhaps we don't fully understand + // possible failure modes. + errorf := bug.Errorf + if s.view.typ == GoPackagesDriverView { + errorf = fmt.Errorf // all bets are off + } + + var standalonePkg *packages.Package + for _, pkg := range pkgs { + if pkg.ID == "command-line-arguments" { + if standalonePkg != nil { + return errorf("internal error: go/packages returned multiple standalone packages") + } + standalonePkg = pkg + } else if packagesinternal.GetForTest(pkg) == "" && !strings.HasSuffix(pkg.ID, ".test") { + return errorf("internal error: go/packages returned unexpected package %q for standalone file", pkg.ID) + } + } + if standalonePkg == nil { + return errorf("internal error: go/packages failed to return non-test standalone package") + } + if len(standalonePkg.CompiledGoFiles) > 0 { + pkgs = []*packages.Package{standalonePkg} + } else { + pkgs = nil + } + } + + if len(pkgs) == 0 { + if err == nil { + err = errNoPackages + } + return fmt.Errorf("packages.Load error: %w", err) + } + + moduleErrs := make(map[string][]packages.Error) // module path -> errors + filterFunc := s.view.filterFunc() + newMetadata := make(map[PackageID]*metadata.Package) + for _, pkg := range pkgs { + if pkg.Module != nil && strings.Contains(pkg.Module.Path, "command-line-arguments") { + // golang/go#61543: modules containing "command-line-arguments" cause + // gopls to get all sorts of confused, because anything containing the + // string "command-line-arguments" is treated as a script. And yes, this + // happened in practice! (https://xkcd.com/327). Rather than try to work + // around this very rare edge case, just fail loudly. + return fmt.Errorf(`load failed: module name in %s contains "command-line-arguments", which is disallowed`, pkg.Module.GoMod) + } + // The Go command returns synthetic list results for module queries that + // encountered module errors. + // + // For example, given a module path a.mod, we'll query for "a.mod/..." and + // the go command will return a package named "a.mod/..." holding this + // error. Save it for later interpretation. + // + // See golang/go#50862 for more details. + if mod := moduleQueries[pkg.PkgPath]; mod != "" { // a synthetic result for the unloadable module + if len(pkg.Errors) > 0 { + moduleErrs[mod] = pkg.Errors + } + continue + } + + if s.Options().VerboseOutput { + event.Log(ctx, eventName, append( + s.Labels(), + label.Package.Of(pkg.ID), + label.Files.Of(pkg.CompiledGoFiles))...) + } + + // Ignore packages with no sources, since we will never be able to + // correctly invalidate that metadata. + if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 { + continue + } + // Special case for the builtin package, as it has no dependencies. + if pkg.PkgPath == "builtin" { + if len(pkg.GoFiles) != 1 { + return fmt.Errorf("only expected 1 file for builtin, got %v", len(pkg.GoFiles)) + } + s.setBuiltin(pkg.GoFiles[0]) + continue + } + // Skip test main packages. + if isTestMain(pkg, s.view.folder.Env.GOCACHE) { + continue + } + // Skip filtered packages. They may be added anyway if they're + // dependencies of non-filtered packages. + // + // TODO(rfindley): why exclude metadata arbitrarily here? It should be safe + // to capture all metadata. + // TODO(rfindley): what about compiled go files? + if allFilesExcluded(pkg.GoFiles, filterFunc) { + continue + } + buildMetadata(newMetadata, pkg, cfg.Dir, standalone, s.view.typ != GoPackagesDriverView) + } + + s.mu.Lock() + + // Assert the invariant s.packages.Get(id).m == s.meta.metadata[id]. + s.packages.Range(func(id PackageID, ph *packageHandle) { + if s.meta.Packages[id] != ph.mp { + panic("inconsistent metadata") + } + }) + + // Compute the minimal metadata updates (for Clone) + // required to preserve the above invariant. + var files []protocol.DocumentURI // files to preload + seenFiles := make(map[protocol.DocumentURI]bool) + updates := make(map[PackageID]*metadata.Package) + for _, mp := range newMetadata { + if existing := s.meta.Packages[mp.ID]; existing == nil { + // Record any new files we should pre-load. + for _, uri := range mp.CompiledGoFiles { + if !seenFiles[uri] { + seenFiles[uri] = true + files = append(files, uri) + } + } + updates[mp.ID] = mp + s.shouldLoad.Delete(mp.ID) + } + } + + if s.Options().VerboseOutput { + event.Log(ctx, fmt.Sprintf("%s: updating metadata for %d packages", eventName, len(updates))) + } + + meta := s.meta.Update(updates) + workspacePackages := computeWorkspacePackagesLocked(ctx, s, meta) + s.meta = meta + s.workspacePackages = workspacePackages + s.resetActivePackagesLocked() + + s.mu.Unlock() + + // Opt: preLoad files in parallel. + // + // Requesting files in batch optimizes the underlying filesystem reads. + // However, this is also currently necessary for correctness: populating all + // files in the snapshot is necessary for certain operations that rely on the + // completeness of the file map, e.g. computing the set of directories to + // watch. + // + // TODO(rfindley, golang/go#57558): determine the set of directories based on + // loaded packages, so that reading files here is not necessary for + // correctness. + s.preloadFiles(ctx, files) + + if len(moduleErrs) > 0 { + return &moduleErrorMap{moduleErrs} + } + + return nil +} + +type moduleErrorMap struct { + errs map[string][]packages.Error // module path -> errors +} + +func (m *moduleErrorMap) Error() string { + var paths []string // sort for stability + for path, errs := range m.errs { + if len(errs) > 0 { // should always be true, but be cautious + paths = append(paths, path) + } + } + sort.Strings(paths) + + var buf bytes.Buffer + fmt.Fprintf(&buf, "%d modules have errors:\n", len(paths)) + for _, path := range paths { + fmt.Fprintf(&buf, "\t%s:%s\n", path, m.errs[path][0].Msg) + } + + return buf.String() +} + +// buildMetadata populates the updates map with metadata updates to +// apply, based on the given pkg. It recurs through pkg.Imports to ensure that +// metadata exists for all dependencies. +// +// Returns the metadata.Package that was built (or which was already present in +// updates), or nil if the package could not be built. Notably, the resulting +// metadata.Package may have an ID that differs from pkg.ID. +func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Package, loadDir string, standalone, goListView bool) *metadata.Package { + // Allow for multiple ad-hoc packages in the workspace (see #47584). + pkgPath := PackagePath(pkg.PkgPath) + id := PackageID(pkg.ID) + + if metadata.IsCommandLineArguments(id) { + var f string // file to use as disambiguating suffix + if len(pkg.CompiledGoFiles) > 0 { + f = pkg.CompiledGoFiles[0] + + // If there are multiple files, + // we can't use only the first. + // (Can this happen? #64557) + if len(pkg.CompiledGoFiles) > 1 { + bug.Reportf("unexpected files in command-line-arguments package: %v", pkg.CompiledGoFiles) + return nil + } + } else if len(pkg.IgnoredFiles) > 0 { + // A file=empty.go query results in IgnoredFiles=[empty.go]. + f = pkg.IgnoredFiles[0] + } else { + bug.Reportf("command-line-arguments package has neither CompiledGoFiles nor IgnoredFiles") + return nil + } + id = PackageID(pkg.ID + f) + pkgPath = PackagePath(pkg.PkgPath + f) + } + + // Duplicate? + if existing, ok := updates[id]; ok { + // A package was encountered twice due to shared + // subgraphs (common) or cycles (rare). Although "go + // list" usually breaks cycles, we don't rely on it. + // breakImportCycles in metadataGraph.Clone takes care + // of it later. + return existing + } + + if pkg.TypesSizes == nil { + panic(id + ".TypeSizes is nil") + } + + // Recreate the metadata rather than reusing it to avoid locking. + mp := &metadata.Package{ + ID: id, + PkgPath: pkgPath, + Name: PackageName(pkg.Name), + ForTest: PackagePath(packagesinternal.GetForTest(pkg)), + TypesSizes: pkg.TypesSizes, + LoadDir: loadDir, + Module: pkg.Module, + Errors: pkg.Errors, + DepsErrors: packagesinternal.GetDepsErrors(pkg), + Standalone: standalone, + } + + updates[id] = mp + + copyURIs := func(dst *[]protocol.DocumentURI, src []string) { + for _, filename := range src { + *dst = append(*dst, protocol.URIFromPath(filename)) + } + } + copyURIs(&mp.CompiledGoFiles, pkg.CompiledGoFiles) + copyURIs(&mp.GoFiles, pkg.GoFiles) + copyURIs(&mp.IgnoredFiles, pkg.IgnoredFiles) + copyURIs(&mp.OtherFiles, pkg.OtherFiles) + + depsByImpPath := make(map[ImportPath]PackageID) + depsByPkgPath := make(map[PackagePath]PackageID) + for importPath, imported := range pkg.Imports { + importPath := ImportPath(importPath) + + // It is not an invariant that importPath == imported.PkgPath. + // For example, package "net" imports "golang.org/x/net/dns/dnsmessage" + // which refers to the package whose ID and PkgPath are both + // "vendor/golang.org/x/net/dns/dnsmessage". Notice the ImportMap, + // which maps ImportPaths to PackagePaths: + // + // $ go list -json net vendor/golang.org/x/net/dns/dnsmessage + // { + // "ImportPath": "net", + // "Name": "net", + // "Imports": [ + // "C", + // "vendor/golang.org/x/net/dns/dnsmessage", + // "vendor/golang.org/x/net/route", + // ... + // ], + // "ImportMap": { + // "golang.org/x/net/dns/dnsmessage": "vendor/golang.org/x/net/dns/dnsmessage", + // "golang.org/x/net/route": "vendor/golang.org/x/net/route" + // }, + // ... + // } + // { + // "ImportPath": "vendor/golang.org/x/net/dns/dnsmessage", + // "Name": "dnsmessage", + // ... + // } + // + // (Beware that, for historical reasons, go list uses + // the JSON field "ImportPath" for the package's + // path--effectively the linker symbol prefix.) + // + // The example above is slightly special to go list + // because it's in the std module. Otherwise, + // vendored modules are simply modules whose directory + // is vendor/ instead of GOMODCACHE, and the + // import path equals the package path. + // + // But in GOPATH (non-module) mode, it's possible for + // package vendoring to cause a non-identity ImportMap, + // as in this example: + // + // $ cd $HOME/src + // $ find . -type f + // ./b/b.go + // ./vendor/example.com/a/a.go + // $ cat ./b/b.go + // package b + // import _ "example.com/a" + // $ cat ./vendor/example.com/a/a.go + // package a + // $ GOPATH=$HOME GO111MODULE=off go list -json ./b | grep -A2 ImportMap + // "ImportMap": { + // "example.com/a": "vendor/example.com/a" + // }, + + // Don't remember any imports with significant errors. + // + // The len=0 condition is a heuristic check for imports of + // non-existent packages (for which go/packages will create + // an edge to a synthesized node). The heuristic is unsound + // because some valid packages have zero files, for example, + // a directory containing only the file p_test.go defines an + // empty package p. + // TODO(adonovan): clarify this. Perhaps go/packages should + // report which nodes were synthesized. + if importPath != "unsafe" && len(imported.CompiledGoFiles) == 0 { + depsByImpPath[importPath] = "" // missing + continue + } + + // Don't record self-import edges. + // (This simplifies metadataGraph's cycle check.) + if PackageID(imported.ID) == id { + if len(pkg.Errors) == 0 { + bug.Reportf("self-import without error in package %s", id) + } + continue + } + + dep := buildMetadata(updates, imported, loadDir, false, goListView) // only top level packages can be standalone + + // Don't record edges to packages with no name, as they cause trouble for + // the importer (golang/go#60952). + // + // Also don't record edges to packages whose ID was modified (i.e. + // command-line-arguments packages), as encountered in golang/go#66109. In + // this case, we could theoretically keep the edge through dep.ID, but + // since this import doesn't make any sense in the first place, we instead + // choose to consider it invalid. + // + // However, we do want to insert these packages into the update map + // (buildMetadata above), so that we get type-checking diagnostics for the + // invalid packages. + if dep == nil || dep.ID != PackageID(imported.ID) || imported.Name == "" { + depsByImpPath[importPath] = "" // missing + continue + } + + depsByImpPath[importPath] = PackageID(imported.ID) + depsByPkgPath[PackagePath(imported.PkgPath)] = PackageID(imported.ID) + } + mp.DepsByImpPath = depsByImpPath + mp.DepsByPkgPath = depsByPkgPath + return mp + + // m.Diagnostics is set later in the loading pass, using + // computeLoadDiagnostics. +} + +// computeLoadDiagnostics computes and sets m.Diagnostics for the given metadata m. +// +// It should only be called during package handle construction in buildPackageHandle. +func computeLoadDiagnostics(ctx context.Context, snapshot *Snapshot, mp *metadata.Package) []*Diagnostic { + var diags []*Diagnostic + for _, packagesErr := range mp.Errors { + // Filter out parse errors from go list. We'll get them when we + // actually parse, and buggy overlay support may generate spurious + // errors. (See TestNewModule_Issue38207.) + if strings.Contains(packagesErr.Msg, "expected '") { + continue + } + pkgDiags, err := goPackagesErrorDiagnostics(ctx, packagesErr, mp, snapshot) + if err != nil { + // There are certain cases where the go command returns invalid + // positions, so we cannot panic or even bug.Reportf here. + event.Error(ctx, "unable to compute positions for list errors", err, label.Package.Of(string(mp.ID))) + continue + } + diags = append(diags, pkgDiags...) + } + + // TODO(rfindley): this is buggy: an insignificant change to a modfile + // (or an unsaved modfile) could affect the position of deps errors, + // without invalidating the package. + depsDiags, err := depsErrors(ctx, snapshot, mp) + if err != nil { + if ctx.Err() == nil { + // TODO(rfindley): consider making this a bug.Reportf. depsErrors should + // not normally fail. + event.Error(ctx, "unable to compute deps errors", err, label.Package.Of(string(mp.ID))) + } + } else { + diags = append(diags, depsDiags...) + } + return diags +} + +// IsWorkspacePackage reports whether id points to a workspace package in s. +// +// Currently, the result depends on the current set of loaded packages, and so +// is not guaranteed to be stable. +func (s *Snapshot) IsWorkspacePackage(ctx context.Context, id PackageID) bool { + s.mu.Lock() + defer s.mu.Unlock() + + mg := s.meta + m := mg.Packages[id] + if m == nil { + return false + } + return isWorkspacePackageLocked(ctx, s, mg, m) +} + +// isWorkspacePackageLocked reports whether p is a workspace package for the +// snapshot s. +// +// Workspace packages are packages that we consider the user to be actively +// working on. As such, they are re-diagnosed on every keystroke, and searched +// for various workspace-wide queries such as references or workspace symbols. +// +// See the commentary inline for a description of the workspace package +// heuristics. +// +// s.mu must be held while calling this function. +// +// TODO(rfindley): remove 'meta' from this function signature. Whether or not a +// package is a workspace package should depend only on the package, view +// definition, and snapshot file source. While useful, the heuristic +// "allFilesHaveRealPackages" does not add that much value and is path +// dependent as it depends on the timing of loads. +func isWorkspacePackageLocked(ctx context.Context, s *Snapshot, meta *metadata.Graph, pkg *metadata.Package) bool { + if metadata.IsCommandLineArguments(pkg.ID) { + // Ad-hoc command-line-arguments packages aren't workspace packages. + // With zero-config gopls (golang/go#57979) they should be very rare, as + // they should only arise when the user opens a file outside the workspace + // which isn't present in the import graph of a workspace package. + // + // Considering them as workspace packages tends to be racy, as they don't + // deterministically belong to any view. + if !pkg.Standalone { + return false + } + + // If all the files contained in pkg have a real package, we don't need to + // keep pkg as a workspace package. + if allFilesHaveRealPackages(meta, pkg) { + return false + } + + // For now, allow open standalone packages (i.e. go:build ignore) to be + // workspace packages, but this means they could belong to multiple views. + return containsOpenFileLocked(s, pkg) + } + + // If a real package is open, consider it to be part of the workspace. + // + // TODO(rfindley): reconsider this. In golang/go#66145, we saw that even if a + // View sees a real package for a file, it doesn't mean that View is able to + // cleanly diagnose the package. Yet, we do want to show diagnostics for open + // packages outside the workspace. Is there a better way to ensure that only + // the 'best' View gets a workspace package for the open file? + if containsOpenFileLocked(s, pkg) { + return true + } + + // Apply filtering logic. + // + // Workspace packages must contain at least one non-filtered file. + filterFunc := s.view.filterFunc() + uris := make(map[protocol.DocumentURI]unit) // filtered package URIs + for _, uri := range slices.Concat(pkg.CompiledGoFiles, pkg.GoFiles) { + if !strings.Contains(string(uri), "/vendor/") && !filterFunc(uri) { + uris[uri] = struct{}{} + } + } + if len(uris) == 0 { + return false // no non-filtered files + } + + // For non-module views (of type GOPATH or AdHoc), or if + // expandWorkspaceToModule is unset, workspace packages must be contained in + // the workspace folder. + // + // For module views (of type GoMod or GoWork), packages must in any case be + // in a workspace module (enforced below). + if !s.view.typ.usesModules() || !s.Options().ExpandWorkspaceToModule { + folder := s.view.folder.Dir.Path() + inFolder := false + for uri := range uris { + if pathutil.InDir(folder, uri.Path()) { + inFolder = true + break + } + } + if !inFolder { + return false + } + } + + // In module mode, a workspace package must be contained in a workspace + // module. + if s.view.typ.usesModules() { + var modURI protocol.DocumentURI + if pkg.Module != nil { + modURI = protocol.URIFromPath(pkg.Module.GoMod) + } else { + // golang/go#65816: for std and cmd, Module is nil. + // Fall back to an inferior heuristic. + if len(pkg.CompiledGoFiles) == 0 { + return false // need at least one file to guess the go.mod file + } + dir := pkg.CompiledGoFiles[0].Dir() + var err error + modURI, err = findRootPattern(ctx, dir, "go.mod", lockedSnapshot{s}) + if err != nil || modURI == "" { + // err != nil implies context cancellation, in which case the result of + // this query does not matter. + return false + } + } + _, ok := s.view.workspaceModFiles[modURI] + return ok + } + + return true // an ad-hoc package or GOPATH package +} + +// containsOpenFileLocked reports whether any file referenced by m is open in +// the snapshot s. +// +// s.mu must be held while calling this function. +func containsOpenFileLocked(s *Snapshot, mp *metadata.Package) bool { + uris := map[protocol.DocumentURI]struct{}{} + for _, uri := range mp.CompiledGoFiles { + uris[uri] = struct{}{} + } + for _, uri := range mp.GoFiles { + uris[uri] = struct{}{} + } + + for uri := range uris { + fh, _ := s.files.get(uri) + if _, open := fh.(*overlay); open { + return true + } + } + return false +} + +// computeWorkspacePackagesLocked computes workspace packages in the +// snapshot s for the given metadata graph. The result does not +// contain intermediate test variants. +// +// s.mu must be held while calling this function. +func computeWorkspacePackagesLocked(ctx context.Context, s *Snapshot, meta *metadata.Graph) immutable.Map[PackageID, PackagePath] { + // The provided context is used for reading snapshot files, which can only + // fail due to context cancellation. Don't let this happen as it could lead + // to inconsistent results. + ctx = xcontext.Detach(ctx) + workspacePackages := make(map[PackageID]PackagePath) + for _, mp := range meta.Packages { + if !isWorkspacePackageLocked(ctx, s, meta, mp) { + continue + } + + switch { + case mp.ForTest == "": + // A normal package. + workspacePackages[mp.ID] = mp.PkgPath + case mp.ForTest == mp.PkgPath, mp.ForTest+"_test" == mp.PkgPath: + // The test variant of some workspace package or its x_test. + // To load it, we need to load the non-test variant with -test. + // + // Notably, this excludes intermediate test variants from workspace + // packages. + assert(!mp.IsIntermediateTestVariant(), "unexpected ITV") + workspacePackages[mp.ID] = mp.ForTest + } + } + return immutable.MapOf(workspacePackages) +} + +// allFilesHaveRealPackages reports whether all files referenced by m are +// contained in a "real" package (not command-line-arguments). +// +// If m is valid but all "real" packages containing any file are invalid, this +// function returns false. +// +// If m is not a command-line-arguments package, this is trivially true. +func allFilesHaveRealPackages(g *metadata.Graph, mp *metadata.Package) bool { + n := len(mp.CompiledGoFiles) +checkURIs: + for _, uri := range append(mp.CompiledGoFiles[0:n:n], mp.GoFiles...) { + for _, id := range g.IDs[uri] { + if !metadata.IsCommandLineArguments(id) { + continue checkURIs + } + } + return false + } + return true +} + +func isTestMain(pkg *packages.Package, gocache string) bool { + // Test mains must have an import path that ends with ".test". + if !strings.HasSuffix(pkg.PkgPath, ".test") { + return false + } + // Test main packages are always named "main". + if pkg.Name != "main" { + return false + } + // Test mains always have exactly one GoFile that is in the build cache. + if len(pkg.GoFiles) > 1 { + return false + } + if !pathutil.InDir(gocache, pkg.GoFiles[0]) { + return false + } + return true +} diff --git a/contribs/gnopls/internal/cache/metadata/cycle_test.go b/contribs/gnopls/internal/cache/metadata/cycle_test.go new file mode 100644 index 00000000000..09628d881e9 --- /dev/null +++ b/contribs/gnopls/internal/cache/metadata/cycle_test.go @@ -0,0 +1,146 @@ +// 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 metadata + +import ( + "sort" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/util/bug" +) + +func init() { + bug.PanicOnBugs = true +} + +// This is an internal test of the breakImportCycles logic. +func TestBreakImportCycles(t *testing.T) { + + // parse parses an import dependency graph. + // The input is a semicolon-separated list of node descriptions. + // Each node description is a package ID, optionally followed by + // "->" and a comma-separated list of successor IDs. + // Thus "a->b;b->c,d;e" represents the set of nodes {a,b,e} + // and the set of edges {a->b, b->c, b->d}. + parse := func(s string) map[PackageID]*Package { + m := make(map[PackageID]*Package) + makeNode := func(name string) *Package { + id := PackageID(name) + n, ok := m[id] + if !ok { + n = &Package{ + ID: id, + DepsByPkgPath: make(map[PackagePath]PackageID), + } + m[id] = n + } + return n + } + if s != "" { + for _, item := range strings.Split(s, ";") { + nodeID, succIDs, ok := strings.Cut(item, "->") + node := makeNode(nodeID) + if ok { + for _, succID := range strings.Split(succIDs, ",") { + node.DepsByPkgPath[PackagePath(succID)] = PackageID(succID) + } + } + } + } + return m + } + + // Sanity check of cycle detector. + { + got := cyclic(parse("a->b;b->c;c->a,d")) + has := func(s string) bool { return strings.Contains(got, s) } + if !(has("a->b") && has("b->c") && has("c->a") && !has("d")) { + t.Fatalf("cyclic: got %q, want a->b->c->a or equivalent", got) + } + } + + // format formats an import graph, in lexicographic order, + // in the notation of parse, but with a "!" after the name + // of each node that has errors. + format := func(graph map[PackageID]*Package) string { + var items []string + for _, mp := range graph { + item := string(mp.ID) + if len(mp.Errors) > 0 { + item += "!" + } + var succs []string + for _, depID := range mp.DepsByPkgPath { + succs = append(succs, string(depID)) + } + if succs != nil { + sort.Strings(succs) + item += "->" + strings.Join(succs, ",") + } + items = append(items, item) + } + sort.Strings(items) + return strings.Join(items, ";") + } + + // We needn't test self-cycles as they are eliminated at Metadata construction. + for _, test := range []struct { + metadata, updates, want string + }{ + // Simple 2-cycle. + {"a->b", "b->a", + "a->b;b!"}, // broke b->a + + {"a->b;b->c;c", "b->a,c", + "a->b;b!->c;c"}, // broke b->a + + // Reversing direction of p->s edge creates pqrs cycle. + {"a->p,q,r,s;p->q,s,z;q->r,z;r->s,z;s->z", "p->q,z;s->p,z", + "a->p,q,r,s;p!->z;q->r,z;r->s,z;s!->z"}, // broke p->q, s->p + + // We break all intra-SCC edges from updated nodes, + // which may be more than necessary (e.g. a->b). + {"a->b;b->c;c;d->a", "a->b,e;c->d", + "a!->e;b->c;c!;d->a"}, // broke a->b, c->d + } { + metadata := parse(test.metadata) + updates := parse(test.updates) + + if cycle := cyclic(metadata); cycle != "" { + t.Errorf("initial metadata %s has cycle %s: ", format(metadata), cycle) + continue + } + + t.Log("initial", format(metadata)) + + // Apply updates. + // (parse doesn't have a way to express node deletions, + // but they aren't very interesting.) + for id, mp := range updates { + metadata[id] = mp + } + + t.Log("updated", format(metadata)) + + // breakImportCycles accesses only these fields of Metadata: + // DepsByImpPath, ID - read + // DepsByPkgPath - read, updated + // Errors - updated + breakImportCycles(metadata, updates) + + t.Log("acyclic", format(metadata)) + + if cycle := cyclic(metadata); cycle != "" { + t.Errorf("resulting metadata %s has cycle %s: ", format(metadata), cycle) + } + + got := format(metadata) + if got != test.want { + t.Errorf("test.metadata=%s test.updates=%s: got=%s want=%s", + test.metadata, test.updates, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/cache/metadata/graph.go b/contribs/gnopls/internal/cache/metadata/graph.go new file mode 100644 index 00000000000..f09822d3575 --- /dev/null +++ b/contribs/gnopls/internal/cache/metadata/graph.go @@ -0,0 +1,413 @@ +// 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 metadata + +import ( + "sort" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" +) + +// A Graph is an immutable and transitively closed graph of [Package] data. +type Graph struct { + // Packages maps package IDs to their associated Packages. + Packages map[PackageID]*Package + + // ImportedBy maps package IDs to the list of packages that import them. + ImportedBy map[PackageID][]PackageID + + // IDs maps file URIs to package IDs, sorted by (!valid, cli, packageID). + // A single file may belong to multiple packages due to tests packages. + // + // Invariant: all IDs present in the IDs map exist in the metadata map. + IDs map[protocol.DocumentURI][]PackageID +} + +// Update creates a new Graph containing the result of applying the given +// updates to the receiver, though the receiver is not itself mutated. As a +// special case, if updates is empty, Update just returns the receiver. +// +// A nil map value is used to indicate a deletion. +func (g *Graph) Update(updates map[PackageID]*Package) *Graph { + if len(updates) == 0 { + // Optimization: since the graph is immutable, we can return the receiver. + return g + } + + // Debugging golang/go#64227, golang/vscode-go#3126: + // Assert that the existing metadata graph is acyclic. + if cycle := cyclic(g.Packages); cycle != "" { + bug.Reportf("metadata is cyclic even before updates: %s", cycle) + } + // Assert that the updates contain no self-cycles. + for id, mp := range updates { + if mp != nil { + for _, depID := range mp.DepsByPkgPath { + if depID == id { + bug.Reportf("self-cycle in metadata update: %s", id) + } + } + } + } + + // Copy pkgs map then apply updates. + pkgs := make(map[PackageID]*Package, len(g.Packages)) + for id, mp := range g.Packages { + pkgs[id] = mp + } + for id, mp := range updates { + if mp == nil { + delete(pkgs, id) + } else { + pkgs[id] = mp + } + } + + // Break import cycles involving updated nodes. + breakImportCycles(pkgs, updates) + + return newGraph(pkgs) +} + +// newGraph returns a new metadataGraph, +// deriving relations from the specified metadata. +func newGraph(pkgs map[PackageID]*Package) *Graph { + // Build the import graph. + importedBy := make(map[PackageID][]PackageID) + for id, mp := range pkgs { + for _, depID := range mp.DepsByPkgPath { + importedBy[depID] = append(importedBy[depID], id) + } + } + + // Collect file associations. + uriIDs := make(map[protocol.DocumentURI][]PackageID) + for id, mp := range pkgs { + uris := map[protocol.DocumentURI]struct{}{} + for _, uri := range mp.CompiledGoFiles { + uris[uri] = struct{}{} + } + for _, uri := range mp.GoFiles { + uris[uri] = struct{}{} + } + for uri := range uris { + uriIDs[uri] = append(uriIDs[uri], id) + } + } + + // Sort and filter file associations. + for uri, ids := range uriIDs { + sort.Slice(ids, func(i, j int) bool { + cli := IsCommandLineArguments(ids[i]) + clj := IsCommandLineArguments(ids[j]) + if cli != clj { + return clj + } + + // 2. packages appear in name order. + return ids[i] < ids[j] + }) + + // Choose the best IDs for each URI, according to the following rules: + // - If there are any valid real packages, choose them. + // - Else, choose the first valid command-line-argument package, if it exists. + // + // TODO(rfindley): it might be better to track all IDs here, and exclude + // them later when type checking, but this is the existing behavior. + for i, id := range ids { + // If we've seen *anything* prior to command-line arguments package, take + // it. Note that ids[0] may itself be command-line-arguments. + if i > 0 && IsCommandLineArguments(id) { + uriIDs[uri] = ids[:i] + break + } + } + } + + return &Graph{ + Packages: pkgs, + ImportedBy: importedBy, + IDs: uriIDs, + } +} + +// ReverseReflexiveTransitiveClosure returns a new mapping containing the +// metadata for the specified packages along with any package that +// transitively imports one of them, keyed by ID, including all the initial packages. +func (g *Graph) ReverseReflexiveTransitiveClosure(ids ...PackageID) map[PackageID]*Package { + seen := make(map[PackageID]*Package) + var visitAll func([]PackageID) + visitAll = func(ids []PackageID) { + for _, id := range ids { + if seen[id] == nil { + if mp := g.Packages[id]; mp != nil { + seen[id] = mp + visitAll(g.ImportedBy[id]) + } + } + } + } + visitAll(ids) + return seen +} + +// breakImportCycles breaks import cycles in the metadata by deleting +// Deps* edges. It modifies only metadata present in the 'updates' +// subset. This function has an internal test. +func breakImportCycles(metadata, updates map[PackageID]*Package) { + // 'go list' should never report a cycle without flagging it + // as such, but we're extra cautious since we're combining + // information from multiple runs of 'go list'. Also, Bazel + // may silently report cycles. + cycles := detectImportCycles(metadata, updates) + if len(cycles) > 0 { + // There were cycles (uncommon). Break them. + // + // The naive way to break cycles would be to perform a + // depth-first traversal and to detect and delete + // cycle-forming edges as we encounter them. + // However, we're not allowed to modify the existing + // Metadata records, so we can only break edges out of + // the 'updates' subset. + // + // Another possibility would be to delete not the + // cycle forming edge but the topmost edge on the + // stack whose tail is an updated node. + // However, this would require that we retroactively + // undo all the effects of the traversals that + // occurred since that edge was pushed on the stack. + // + // We use a simpler scheme: we compute the set of cycles. + // All cyclic paths necessarily involve at least one + // updated node, so it is sufficient to break all + // edges from each updated node to other members of + // the strong component. + // + // This may result in the deletion of dominating + // edges, causing some dependencies to appear + // spuriously unreachable. Consider A <-> B -> C + // where updates={A,B}. The cycle is {A,B} so the + // algorithm will break both A->B and B->A, causing + // A to no longer depend on B or C. + // + // But that's ok: any error in Metadata.Errors is + // conservatively assumed by snapshot.clone to be a + // potential import cycle error, and causes special + // invalidation so that if B later drops its + // cycle-forming import of A, both A and B will be + // invalidated. + for _, cycle := range cycles { + cyclic := make(map[PackageID]bool) + for _, mp := range cycle { + cyclic[mp.ID] = true + } + for id := range cyclic { + if mp := updates[id]; mp != nil { + for path, depID := range mp.DepsByImpPath { + if cyclic[depID] { + delete(mp.DepsByImpPath, path) + } + } + for path, depID := range mp.DepsByPkgPath { + if cyclic[depID] { + delete(mp.DepsByPkgPath, path) + } + } + + // Set m.Errors to enable special + // invalidation logic in snapshot.clone. + if len(mp.Errors) == 0 { + mp.Errors = []packages.Error{{ + Msg: "detected import cycle", + Kind: packages.ListError, + }} + } + } + } + } + + // double-check when debugging + if false { + if cycles := detectImportCycles(metadata, updates); len(cycles) > 0 { + bug.Reportf("unbroken cycle: %v", cycles) + } + } + } +} + +// cyclic returns a description of a cycle, +// if the graph is cyclic, otherwise "". +func cyclic(graph map[PackageID]*Package) string { + const ( + unvisited = 0 + visited = 1 + onstack = 2 + ) + color := make(map[PackageID]int) + var visit func(id PackageID) string + visit = func(id PackageID) string { + switch color[id] { + case unvisited: + color[id] = onstack + case onstack: + return string(id) // cycle! + case visited: + return "" + } + if mp := graph[id]; mp != nil { + for _, depID := range mp.DepsByPkgPath { + if cycle := visit(depID); cycle != "" { + return string(id) + "->" + cycle + } + } + } + color[id] = visited + return "" + } + for id := range graph { + if cycle := visit(id); cycle != "" { + return cycle + } + } + return "" +} + +// detectImportCycles reports cycles in the metadata graph. It returns a new +// unordered array of all cycles (nontrivial strong components) in the +// metadata graph reachable from a non-nil 'updates' value. +func detectImportCycles(metadata, updates map[PackageID]*Package) [][]*Package { + // We use the depth-first algorithm of Tarjan. + // https://doi.org/10.1137/0201010 + // + // TODO(adonovan): when we can use generics, consider factoring + // in common with the other implementation of Tarjan (in typerefs), + // abstracting over the node and edge representation. + + // A node wraps a Metadata with its working state. + // (Unfortunately we can't intrude on shared Metadata.) + type node struct { + rep *node + mp *Package + index, lowlink int32 + scc int8 // TODO(adonovan): opt: cram these 1.5 bits into previous word + } + nodes := make(map[PackageID]*node, len(metadata)) + nodeOf := func(id PackageID) *node { + n, ok := nodes[id] + if !ok { + mp := metadata[id] + if mp == nil { + // Dangling import edge. + // Not sure whether a go/packages driver ever + // emits this, but create a dummy node in case. + // Obviously it won't be part of any cycle. + mp = &Package{ID: id} + } + n = &node{mp: mp} + n.rep = n + nodes[id] = n + } + return n + } + + // find returns the canonical node decl. + // (The nodes form a disjoint set forest.) + var find func(*node) *node + find = func(n *node) *node { + rep := n.rep + if rep != n { + rep = find(rep) + n.rep = rep // simple path compression (no union-by-rank) + } + return rep + } + + // global state + var ( + index int32 = 1 + stack []*node + sccs [][]*Package // set of nontrivial strongly connected components + ) + + // visit implements the depth-first search of Tarjan's SCC algorithm + // Precondition: x is canonical. + var visit func(*node) + visit = func(x *node) { + x.index = index + x.lowlink = index + index++ + + stack = append(stack, x) // push + x.scc = -1 + + for _, yid := range x.mp.DepsByPkgPath { + y := nodeOf(yid) + // Loop invariant: x is canonical. + y = find(y) + if x == y { + continue // nodes already combined (self-edges are impossible) + } + + switch { + case y.scc > 0: + // y is already a collapsed SCC + + case y.scc < 0: + // y is on the stack, and thus in the current SCC. + if y.index < x.lowlink { + x.lowlink = y.index + } + + default: + // y is unvisited; visit it now. + visit(y) + // Note: x and y are now non-canonical. + x = find(x) + if y.lowlink < x.lowlink { + x.lowlink = y.lowlink + } + } + } + + // Is x the root of an SCC? + if x.lowlink == x.index { + // Gather all metadata in the SCC (if nontrivial). + var scc []*Package + for { + // Pop y from stack. + i := len(stack) - 1 + y := stack[i] + stack = stack[:i] + if x != y || scc != nil { + scc = append(scc, y.mp) + } + if x == y { + break // complete + } + // x becomes y's canonical representative. + y.rep = x + } + if scc != nil { + sccs = append(sccs, scc) + } + x.scc = 1 + } + } + + // Visit only the updated nodes: + // the existing metadata graph has no cycles, + // so any new cycle must involve an updated node. + for id, mp := range updates { + if mp != nil { + if n := nodeOf(id); n.index == 0 { // unvisited + visit(n) + } + } + } + + return sccs +} diff --git a/contribs/gnopls/internal/cache/metadata/metadata.go b/contribs/gnopls/internal/cache/metadata/metadata.go new file mode 100644 index 00000000000..e42aac304f6 --- /dev/null +++ b/contribs/gnopls/internal/cache/metadata/metadata.go @@ -0,0 +1,259 @@ +// 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. + +// The metadata package defines types and functions for working with package +// metadata, which describes Go packages and their relationships. +// +// Package metadata is loaded by gopls using go/packages, and the [Package] +// type is itself a projection and translation of data from +// go/packages.Package. +// +// Packages are assembled into an immutable [Graph] +package metadata + +import ( + "go/ast" + "go/types" + "sort" + "strconv" + "strings" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/packagesinternal" +) + +// Declare explicit types for package paths, names, and IDs to ensure that we +// never use an ID where a path belongs, and vice versa. If we confused these, +// it would result in confusing errors because package IDs often look like +// package paths. +type ( + PackageID string // go list's unique identifier for a package (e.g. "vendor/example.com/foo [vendor/example.com/bar.test]") + PackagePath string // name used to prefix linker symbols (e.g. "vendor/example.com/foo") + PackageName string // identifier in 'package' declaration (e.g. "foo") + ImportPath string // path that appears in an import declaration (e.g. "example.com/foo") +) + +// Package represents package metadata retrieved from go/packages. +// The DepsBy{Imp,Pkg}Path maps do not contain self-import edges. +// +// An ad-hoc package (without go.mod or GOPATH) has its ID, PkgPath, +// and LoadDir equal to the absolute path of its directory. +type Package struct { + ID PackageID + PkgPath PackagePath + Name PackageName + + // These fields are as defined by go/packages.Package + GoFiles []protocol.DocumentURI + CompiledGoFiles []protocol.DocumentURI + IgnoredFiles []protocol.DocumentURI + OtherFiles []protocol.DocumentURI + + ForTest PackagePath // q in a "p [q.test]" package, else "" + TypesSizes types.Sizes + Errors []packages.Error // must be set for packages in import cycles + DepsByImpPath map[ImportPath]PackageID // may contain dups; empty ID => missing + DepsByPkgPath map[PackagePath]PackageID // values are unique and non-empty + Module *packages.Module + DepsErrors []*packagesinternal.PackageError + LoadDir string // directory from which go/packages was run + Standalone bool // package synthesized for a standalone file (e.g. ignore-tagged) +} + +func (mp *Package) String() string { return string(mp.ID) } + +// IsIntermediateTestVariant reports whether the given package is an +// intermediate test variant (ITV), e.g. "net/http [net/url.test]". +// +// An ITV has identical syntax to the regular variant, but different +// import metadata (DepsBy{Imp,Pkg}Path). +// +// Such test variants arise when an x_test package (in this case net/url_test) +// imports a package (in this case net/http) that itself imports the +// non-x_test package (in this case net/url). +// +// This is done so that the forward transitive closure of net/url_test has +// only one package for the "net/url" import. +// The ITV exists to hold the test variant import: +// +// net/url_test [net/url.test] +// +// | "net/http" -> net/http [net/url.test] +// | "net/url" -> net/url [net/url.test] +// | ... +// +// net/http [net/url.test] +// +// | "net/url" -> net/url [net/url.test] +// | ... +// +// This restriction propagates throughout the import graph of net/http: for +// every package imported by net/http that imports net/url, there must be an +// intermediate test variant that instead imports "net/url [net/url.test]". +// +// As one can see from the example of net/url and net/http, intermediate test +// variants can result in many additional packages that are essentially (but +// not quite) identical. For this reason, we filter these variants wherever +// possible. +// +// # Why we mostly ignore intermediate test variants +// +// In projects with complicated tests, there may be a very large +// number of ITVs--asymptotically more than the number of ordinary +// variants. Since they have identical syntax, it is fine in most +// cases to ignore them since the results of analyzing the ordinary +// variant suffice. However, this is not entirely sound. +// +// Consider this package: +// +// // p/p.go -- in all variants of p +// package p +// type T struct { io.Closer } +// +// // p/p_test.go -- in test variant of p +// package p +// func (T) Close() error { ... } +// +// The ordinary variant "p" defines T with a Close method promoted +// from io.Closer. But its test variant "p [p.test]" defines a type T +// with a Close method from p_test.go. +// +// Now consider a package q that imports p, perhaps indirectly. Within +// it, T.Close will resolve to the first Close method: +// +// // q/q.go -- in all variants of q +// package q +// import "p" +// var _ = new(p.T).Close +// +// Let's assume p also contains this file defining an external test (xtest): +// +// // p/p_x_test.go -- external test of p +// package p_test +// import ( "q"; "testing" ) +// func Test(t *testing.T) { ... } +// +// Note that q imports p, but p's xtest imports q. Now, in "q +// [p.test]", the intermediate test variant of q built for p's +// external test, T.Close resolves not to the io.Closer.Close +// interface method, but to the concrete method of T.Close +// declared in p_test.go. +// +// If we now request all references to the T.Close declaration in +// p_test.go, the result should include the reference from q's ITV. +// (It's not just methods that can be affected; fields can too, though +// it requires bizarre code to achieve.) +// +// As a matter of policy, gopls mostly ignores this subtlety, +// because to account for it would require that we type-check every +// intermediate test variant of p, of which there could be many. +// Good code doesn't rely on such trickery. +// +// Most callers of MetadataForFile call RemoveIntermediateTestVariants +// to discard them before requesting type checking, or the products of +// type-checking such as the cross-reference index or method set index. +// +// MetadataForFile doesn't do this filtering itself because in some +// cases we need to make a reverse dependency query on the metadata +// graph, and it's important to include the rdeps of ITVs in that +// query. But the filtering of ITVs should be applied after that step, +// before type checking. +// +// In general, we should never type check an ITV. +func (mp *Package) IsIntermediateTestVariant() bool { + return mp.ForTest != "" && mp.ForTest != mp.PkgPath && mp.ForTest+"_test" != mp.PkgPath +} + +// A Source maps package IDs to metadata for the packages. +// +// TODO(rfindley): replace this with a concrete metadata graph, once it is +// exposed from the snapshot. +type Source interface { + // Metadata returns the [Package] for the given package ID, or nil if it does + // not exist. + // TODO(rfindley): consider returning (*Metadata, bool) + // TODO(rfindley): consider renaming this method. + Metadata(PackageID) *Package +} + +// TODO(rfindley): move the utility functions below to a util.go file. + +// IsCommandLineArguments reports whether a given value denotes +// "command-line-arguments" package, which is a package with an unknown ID +// created by the go command. It can have a test variant, which is why callers +// should not check that a value equals "command-line-arguments" directly. +func IsCommandLineArguments(id PackageID) bool { + return strings.Contains(string(id), "command-line-arguments") +} + +// SortPostOrder sorts the IDs so that if x depends on y, then y appears before x. +func SortPostOrder(meta Source, ids []PackageID) { + postorder := make(map[PackageID]int) + order := 0 + var visit func(PackageID) + visit = func(id PackageID) { + if _, ok := postorder[id]; !ok { + postorder[id] = -1 // break recursion + if mp := meta.Metadata(id); mp != nil { + for _, depID := range mp.DepsByPkgPath { + visit(depID) + } + } + order++ + postorder[id] = order + } + } + for _, id := range ids { + visit(id) + } + sort.Slice(ids, func(i, j int) bool { + return postorder[ids[i]] < postorder[ids[j]] + }) +} + +// UnquoteImportPath returns the unquoted import path of s, +// or "" if the path is not properly quoted. +func UnquoteImportPath(spec *ast.ImportSpec) ImportPath { + path, err := strconv.Unquote(spec.Path.Value) + if err != nil { + return "" + } + return ImportPath(path) +} + +// RemoveIntermediateTestVariants removes intermediate test variants, modifying +// the array. We use a pointer to a slice make it impossible to forget to use +// the result. +func RemoveIntermediateTestVariants(pmetas *[]*Package) { + metas := *pmetas + res := metas[:0] + for _, mp := range metas { + if !mp.IsIntermediateTestVariant() { + res = append(res, mp) + } + } + *pmetas = res +} + +// IsValidImport returns whether from may import to. +func IsValidImport(from, to PackagePath, goList bool) bool { + // If the metadata came from a build system other than go list + // (e.g. bazel) it is beyond our means to compute visibility. + if !goList { + return true + } + i := strings.LastIndex(string(to), "/internal/") + if i == -1 { + return true + } + // TODO(rfindley): this looks wrong: IsCommandLineArguments is meant to + // operate on package IDs, not package paths. + if IsCommandLineArguments(PackageID(from)) { + return true + } + // TODO(rfindley): this is wrong. mod.testx/p should not be able to + // import mod.test/internal: https://go.dev/play/p/-Ca6P-E4V4q + return strings.HasPrefix(string(from), string(to[:i])) +} diff --git a/contribs/gnopls/internal/cache/methodsets/methodsets.go b/contribs/gnopls/internal/cache/methodsets/methodsets.go new file mode 100644 index 00000000000..f6a2ba96b33 --- /dev/null +++ b/contribs/gnopls/internal/cache/methodsets/methodsets.go @@ -0,0 +1,493 @@ +// 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 methodsets defines an incremental, serializable index of +// method-set information that allows efficient 'implements' queries +// across packages of the workspace without using the type checker. +// +// This package provides only the "global" (all workspace) search; the +// "local" search within a given package uses a different +// implementation based on type-checker data structures for a single +// package plus variants; see ../implementation.go. +// The local algorithm is more precise as it tests function-local types too. +// +// A global index of function-local types is challenging since they +// may reference other local types, for which we would need to invent +// stable names, an unsolved problem described in passing in Go issue +// 57497. The global algorithm also does not index anonymous interface +// types, even outside function bodies. +// +// Consequently, global results are not symmetric: applying the +// operation twice may not get you back where you started. +package methodsets + +// DESIGN +// +// See https://go.dev/cl/452060 for a minimal exposition of the algorithm. +// +// For each method, we compute a fingerprint: a string representing +// the method name and type such that equal fingerprint strings mean +// identical method types. +// +// For efficiency, the fingerprint is reduced to a single bit +// of a uint64, so that the method set can be represented as +// the union of those method bits (a uint64 bitmask). +// Assignability thus reduces to a subset check on bitmasks +// followed by equality checks on fingerprints. +// +// In earlier experiments, using 128-bit masks instead of 64 reduced +// the number of candidates by about 2x. Using (like a Bloom filter) a +// different hash function to compute a second 64-bit mask and +// performing a second mask test reduced it by about 4x. +// Neither had much effect on the running time, presumably because a +// single 64-bit mask is quite effective. See CL 452060 for details. + +import ( + "fmt" + "go/token" + "go/types" + "hash/crc32" + "strconv" + "strings" + + "golang.org/x/tools/go/types/objectpath" + "golang.org/x/tools/gopls/internal/util/frob" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/aliases" +) + +// An Index records the non-empty method sets of all package-level +// types in a package in a form that permits assignability queries +// without the type checker. +type Index struct { + pkg gobPackage +} + +// Decode decodes the given gob-encoded data as an Index. +func Decode(data []byte) *Index { + var pkg gobPackage + packageCodec.Decode(data, &pkg) + return &Index{pkg} +} + +// Encode encodes the receiver as gob-encoded data. +func (index *Index) Encode() []byte { + return packageCodec.Encode(index.pkg) +} + +// NewIndex returns a new index of method-set information for all +// package-level types in the specified package. +func NewIndex(fset *token.FileSet, pkg *types.Package) *Index { + return new(indexBuilder).build(fset, pkg) +} + +// A Location records the extent of an identifier in byte-offset form. +// +// Conversion to protocol (UTF-16) form is done by the caller after a +// search, not during index construction. +type Location struct { + Filename string + Start, End int // byte offsets +} + +// A Key represents the method set of a given type in a form suitable +// to pass to the (*Index).Search method of many different Indexes. +type Key struct { + mset gobMethodSet // note: lacks position information +} + +// KeyOf returns the search key for the method sets of a given type. +// It returns false if the type has no methods. +func KeyOf(t types.Type) (Key, bool) { + mset := methodSetInfo(t, nil) + if mset.Mask == 0 { + return Key{}, false // no methods + } + return Key{mset}, true +} + +// A Result reports a matching type or method in a method-set search. +type Result struct { + Location Location // location of the type or method + + // methods only: + PkgPath string // path of declaring package (may differ due to embedding) + ObjectPath objectpath.Path // path of method within declaring package +} + +// Search reports each type that implements (or is implemented by) the +// type that produced the search key. If methodID is nonempty, only +// that method of each type is reported. +// +// The result does not include the error.Error method. +// TODO(adonovan): give this special case a more systematic treatment. +func (index *Index) Search(key Key, methodID string) []Result { + var results []Result + for _, candidate := range index.pkg.MethodSets { + // Traditionally this feature doesn't report + // interface/interface elements of the relation. + // I think that's a mistake. + // TODO(adonovan): UX: change it, here and in the local implementation. + if candidate.IsInterface && key.mset.IsInterface { + continue + } + if !satisfies(candidate, key.mset) && !satisfies(key.mset, candidate) { + continue + } + + if candidate.Tricky { + // If any interface method is tricky then extra + // checking may be needed to eliminate a false positive. + // TODO(adonovan): implement it. + } + + if methodID == "" { + results = append(results, Result{Location: index.location(candidate.Posn)}) + } else { + for _, m := range candidate.Methods { + // Here we exploit knowledge of the shape of the fingerprint string. + if strings.HasPrefix(m.Fingerprint, methodID) && + m.Fingerprint[len(methodID)] == '(' { + + // Don't report error.Error among the results: + // it has no true source location, no package, + // and is excluded from the xrefs index. + if m.PkgPath == 0 || m.ObjectPath == 0 { + if methodID != "Error" { + panic("missing info for" + methodID) + } + continue + } + + results = append(results, Result{ + Location: index.location(m.Posn), + PkgPath: index.pkg.Strings[m.PkgPath], + ObjectPath: objectpath.Path(index.pkg.Strings[m.ObjectPath]), + }) + break + } + } + } + } + return results +} + +// satisfies does a fast check for whether x satisfies y. +func satisfies(x, y gobMethodSet) bool { + return y.IsInterface && x.Mask&y.Mask == y.Mask && subset(y, x) +} + +// subset reports whether method set x is a subset of y. +func subset(x, y gobMethodSet) bool { +outer: + for _, mx := range x.Methods { + for _, my := range y.Methods { + if mx.Sum == my.Sum && mx.Fingerprint == my.Fingerprint { + continue outer // found; try next x method + } + } + return false // method of x not found in y + } + return true // all methods of x found in y +} + +func (index *Index) location(posn gobPosition) Location { + return Location{ + Filename: index.pkg.Strings[posn.File], + Start: posn.Offset, + End: posn.Offset + posn.Len, + } +} + +// An indexBuilder builds an index for a single package. +type indexBuilder struct { + gobPackage + stringIndex map[string]int +} + +// build adds to the index all package-level named types of the specified package. +func (b *indexBuilder) build(fset *token.FileSet, pkg *types.Package) *Index { + _ = b.string("") // 0 => "" + + objectPos := func(obj types.Object) gobPosition { + posn := safetoken.StartPosition(fset, obj.Pos()) + return gobPosition{b.string(posn.Filename), posn.Offset, len(obj.Name())} + } + + objectpathFor := new(objectpath.Encoder).For + + // setindexInfo sets the (Posn, PkgPath, ObjectPath) fields for each method declaration. + setIndexInfo := func(m *gobMethod, method *types.Func) { + // error.Error has empty Position, PkgPath, and ObjectPath. + if method.Pkg() == nil { + return + } + + m.Posn = objectPos(method) + m.PkgPath = b.string(method.Pkg().Path()) + + // Instantiations of generic methods don't have an + // object path, so we use the generic. + if p, err := objectpathFor(method.Origin()); err != nil { + panic(err) // can't happen for a method of a package-level type + } else { + m.ObjectPath = b.string(string(p)) + } + } + + // We ignore aliases, though in principle they could define a + // struct{...} or interface{...} type, or an instantiation of + // a generic, that has a novel method set. + scope := pkg.Scope() + for _, name := range scope.Names() { + if tname, ok := scope.Lookup(name).(*types.TypeName); ok && !tname.IsAlias() { + if mset := methodSetInfo(tname.Type(), setIndexInfo); mset.Mask != 0 { + mset.Posn = objectPos(tname) + // Only record types with non-trivial method sets. + b.MethodSets = append(b.MethodSets, mset) + } + } + } + + return &Index{pkg: b.gobPackage} +} + +// string returns a small integer that encodes the string. +func (b *indexBuilder) string(s string) int { + i, ok := b.stringIndex[s] + if !ok { + i = len(b.Strings) + if b.stringIndex == nil { + b.stringIndex = make(map[string]int) + } + b.stringIndex[s] = i + b.Strings = append(b.Strings, s) + } + return i +} + +// methodSetInfo returns the method-set fingerprint of a type. +// It calls the optional setIndexInfo function for each gobMethod. +// This is used during index construction, but not search (KeyOf), +// to store extra information. +func methodSetInfo(t types.Type, setIndexInfo func(*gobMethod, *types.Func)) gobMethodSet { + // For non-interface types, use *T + // (if T is not already a pointer) + // since it may have more methods. + mset := types.NewMethodSet(EnsurePointer(t)) + + // Convert the method set into a compact summary. + var mask uint64 + tricky := false + methods := make([]gobMethod, mset.Len()) + for i := 0; i < mset.Len(); i++ { + m := mset.At(i).Obj().(*types.Func) + fp, isTricky := fingerprint(m) + if isTricky { + tricky = true + } + sum := crc32.ChecksumIEEE([]byte(fp)) + methods[i] = gobMethod{Fingerprint: fp, Sum: sum} + if setIndexInfo != nil { + setIndexInfo(&methods[i], m) // set Position, PkgPath, ObjectPath + } + mask |= 1 << uint64(((sum>>24)^(sum>>16)^(sum>>8)^sum)&0x3f) + } + return gobMethodSet{ + IsInterface: types.IsInterface(t), + Tricky: tricky, + Mask: mask, + Methods: methods, + } +} + +// EnsurePointer wraps T in a types.Pointer if T is a named, non-interface type. +// This is useful to make sure you consider a named type's full method set. +func EnsurePointer(T types.Type) types.Type { + if _, ok := aliases.Unalias(T).(*types.Named); ok && !types.IsInterface(T) { + return types.NewPointer(T) + } + + return T +} + +// fingerprint returns an encoding of a method signature such that two +// methods with equal encodings have identical types, except for a few +// tricky types whose encodings may spuriously match and whose exact +// identity computation requires the type checker to eliminate false +// positives (which are rare). The boolean result indicates whether +// the result was one of these tricky types. +// +// In the standard library, 99.8% of package-level types have a +// non-tricky method-set. The most common exceptions are due to type +// parameters. +// +// The fingerprint string starts with method.Id() + "(". +func fingerprint(method *types.Func) (string, bool) { + var buf strings.Builder + tricky := false + var fprint func(t types.Type) + fprint = func(t types.Type) { + switch t := t.(type) { + case *aliases.Alias: + fprint(aliases.Unalias(t)) + + case *types.Named: + tname := t.Obj() + if tname.Pkg() != nil { + buf.WriteString(strconv.Quote(tname.Pkg().Path())) + buf.WriteByte('.') + } else if tname.Name() != "error" && tname.Name() != "comparable" { + panic(tname) // error and comparable the only named types with no package + } + buf.WriteString(tname.Name()) + + case *types.Array: + fmt.Fprintf(&buf, "[%d]", t.Len()) + fprint(t.Elem()) + + case *types.Slice: + buf.WriteString("[]") + fprint(t.Elem()) + + case *types.Pointer: + buf.WriteByte('*') + fprint(t.Elem()) + + case *types.Map: + buf.WriteString("map[") + fprint(t.Key()) + buf.WriteByte(']') + fprint(t.Elem()) + + case *types.Chan: + switch t.Dir() { + case types.SendRecv: + buf.WriteString("chan ") + case types.SendOnly: + buf.WriteString("<-chan ") + case types.RecvOnly: + buf.WriteString("chan<- ") + } + fprint(t.Elem()) + + case *types.Tuple: + buf.WriteByte('(') + for i := 0; i < t.Len(); i++ { + if i > 0 { + buf.WriteByte(',') + } + fprint(t.At(i).Type()) + } + buf.WriteByte(')') + + case *types.Basic: + // Use canonical names for uint8 and int32 aliases. + switch t.Kind() { + case types.Byte: + buf.WriteString("byte") + case types.Rune: + buf.WriteString("rune") + default: + buf.WriteString(t.String()) + } + + case *types.Signature: + buf.WriteString("func") + fprint(t.Params()) + if t.Variadic() { + buf.WriteString("...") // not quite Go syntax + } + fprint(t.Results()) + + case *types.Struct: + // Non-empty unnamed struct types in method + // signatures are vanishingly rare. + buf.WriteString("struct{") + for i := 0; i < t.NumFields(); i++ { + if i > 0 { + buf.WriteByte(';') + } + f := t.Field(i) + // This isn't quite right for embedded type aliases. + // (See types.TypeString(StructType) and #44410 for context.) + // But this is vanishingly rare. + if !f.Embedded() { + buf.WriteString(f.Id()) + buf.WriteByte(' ') + } + fprint(f.Type()) + if tag := t.Tag(i); tag != "" { + buf.WriteByte(' ') + buf.WriteString(strconv.Quote(tag)) + } + } + buf.WriteString("}") + + case *types.Interface: + if t.NumMethods() == 0 { + buf.WriteString("any") // common case + } else { + // Interface assignability is particularly + // tricky due to the possibility of recursion. + tricky = true + // We could still give more disambiguating precision + // than "..." if we wanted to. + buf.WriteString("interface{...}") + } + + case *types.TypeParam: + tricky = true + // TODO(adonovan): refine this by adding a numeric suffix + // indicating the index among the receiver type's parameters. + buf.WriteByte('?') + + default: // incl. *types.Union + panic(t) + } + } + + buf.WriteString(method.Id()) // e.g. "pkg.Type" + sig := method.Signature() + fprint(sig.Params()) + fprint(sig.Results()) + return buf.String(), tricky +} + +// -- serial format of index -- + +// (The name says gob but in fact we use frob.) +var packageCodec = frob.CodecFor[gobPackage]() + +// A gobPackage records the method set of each package-level type for a single package. +type gobPackage struct { + Strings []string // index of strings used by gobPosition.File, gobMethod.{Pkg,Object}Path + MethodSets []gobMethodSet +} + +// A gobMethodSet records the method set of a single type. +type gobMethodSet struct { + Posn gobPosition + IsInterface bool + Tricky bool // at least one method is tricky; assignability requires go/types + Mask uint64 // mask with 1 bit from each of methods[*].sum + Methods []gobMethod +} + +// A gobMethod records the name, type, and position of a single method. +type gobMethod struct { + Fingerprint string // string of form "methodID(params...)(results)" + Sum uint32 // checksum of fingerprint + + // index records only (zero in KeyOf; also for index of error.Error). + Posn gobPosition // location of method declaration + PkgPath int // path of package containing method declaration + ObjectPath int // object path of method relative to PkgPath +} + +// A gobPosition records the file, offset, and length of an identifier. +type gobPosition struct { + File int // index into gobPackage.Strings + Offset, Len int // in bytes +} diff --git a/contribs/gnopls/internal/cache/mod.go b/contribs/gnopls/internal/cache/mod.go new file mode 100644 index 00000000000..6837ec3257c --- /dev/null +++ b/contribs/gnopls/internal/cache/mod.go @@ -0,0 +1,495 @@ +// 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. + +package cache + +import ( + "context" + "errors" + "fmt" + "path/filepath" + "regexp" + "strings" + + "golang.org/x/mod/modfile" + "golang.org/x/mod/module" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/memoize" +) + +// A ParsedModule contains the results of parsing a go.mod file. +type ParsedModule struct { + URI protocol.DocumentURI + File *modfile.File + Mapper *protocol.Mapper + ParseErrors []*Diagnostic +} + +// ParseMod parses a go.mod file, using a cache. It may return partial results and an error. +func (s *Snapshot) ParseMod(ctx context.Context, fh file.Handle) (*ParsedModule, error) { + uri := fh.URI() + + s.mu.Lock() + entry, hit := s.parseModHandles.Get(uri) + s.mu.Unlock() + + type parseModKey file.Identity + type parseModResult struct { + parsed *ParsedModule + err error + } + + // cache miss? + if !hit { + promise, release := s.store.Promise(parseModKey(fh.Identity()), func(ctx context.Context, _ interface{}) interface{} { + parsed, err := parseModImpl(ctx, fh) + return parseModResult{parsed, err} + }) + + entry = promise + s.mu.Lock() + s.parseModHandles.Set(uri, entry, func(_, _ interface{}) { release() }) + s.mu.Unlock() + } + + // Await result. + v, err := s.awaitPromise(ctx, entry) + if err != nil { + return nil, err + } + res := v.(parseModResult) + return res.parsed, res.err +} + +// parseModImpl parses the go.mod file whose name and contents are in fh. +// It may return partial results and an error. +func parseModImpl(ctx context.Context, fh file.Handle) (*ParsedModule, error) { + _, done := event.Start(ctx, "cache.ParseMod", label.URI.Of(fh.URI())) + defer done() + + contents, err := fh.Content() + if err != nil { + return nil, err + } + m := protocol.NewMapper(fh.URI(), contents) + file, parseErr := modfile.Parse(fh.URI().Path(), contents, nil) + // Attempt to convert the error to a standardized parse error. + var parseErrors []*Diagnostic + if parseErr != nil { + mfErrList, ok := parseErr.(modfile.ErrorList) + if !ok { + return nil, fmt.Errorf("unexpected parse error type %v", parseErr) + } + for _, mfErr := range mfErrList { + rng, err := m.OffsetRange(mfErr.Pos.Byte, mfErr.Pos.Byte) + if err != nil { + return nil, err + } + parseErrors = append(parseErrors, &Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityError, + Source: ParseError, + Message: mfErr.Err.Error(), + }) + } + } + return &ParsedModule{ + URI: fh.URI(), + Mapper: m, + File: file, + ParseErrors: parseErrors, + }, parseErr +} + +// A ParsedWorkFile contains the results of parsing a go.work file. +type ParsedWorkFile struct { + URI protocol.DocumentURI + File *modfile.WorkFile + Mapper *protocol.Mapper + ParseErrors []*Diagnostic +} + +// ParseWork parses a go.work file, using a cache. It may return partial results and an error. +// TODO(adonovan): move to new work.go file. +func (s *Snapshot) ParseWork(ctx context.Context, fh file.Handle) (*ParsedWorkFile, error) { + uri := fh.URI() + + s.mu.Lock() + entry, hit := s.parseWorkHandles.Get(uri) + s.mu.Unlock() + + type parseWorkKey file.Identity + type parseWorkResult struct { + parsed *ParsedWorkFile + err error + } + + // cache miss? + if !hit { + handle, release := s.store.Promise(parseWorkKey(fh.Identity()), func(ctx context.Context, _ interface{}) interface{} { + parsed, err := parseWorkImpl(ctx, fh) + return parseWorkResult{parsed, err} + }) + + entry = handle + s.mu.Lock() + s.parseWorkHandles.Set(uri, entry, func(_, _ interface{}) { release() }) + s.mu.Unlock() + } + + // Await result. + v, err := s.awaitPromise(ctx, entry) + if err != nil { + return nil, err + } + res := v.(parseWorkResult) + return res.parsed, res.err +} + +// parseWorkImpl parses a go.work file. It may return partial results and an error. +func parseWorkImpl(ctx context.Context, fh file.Handle) (*ParsedWorkFile, error) { + _, done := event.Start(ctx, "cache.ParseWork", label.URI.Of(fh.URI())) + defer done() + + content, err := fh.Content() + if err != nil { + return nil, err + } + m := protocol.NewMapper(fh.URI(), content) + file, parseErr := modfile.ParseWork(fh.URI().Path(), content, nil) + // Attempt to convert the error to a standardized parse error. + var parseErrors []*Diagnostic + if parseErr != nil { + mfErrList, ok := parseErr.(modfile.ErrorList) + if !ok { + return nil, fmt.Errorf("unexpected parse error type %v", parseErr) + } + for _, mfErr := range mfErrList { + rng, err := m.OffsetRange(mfErr.Pos.Byte, mfErr.Pos.Byte) + if err != nil { + return nil, err + } + parseErrors = append(parseErrors, &Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityError, + Source: ParseError, + Message: mfErr.Err.Error(), + }) + } + } + return &ParsedWorkFile{ + URI: fh.URI(), + Mapper: m, + File: file, + ParseErrors: parseErrors, + }, parseErr +} + +// ModWhy returns the "go mod why" result for each module named in a +// require statement in the go.mod file. +// TODO(adonovan): move to new mod_why.go file. +func (s *Snapshot) ModWhy(ctx context.Context, fh file.Handle) (map[string]string, error) { + uri := fh.URI() + + if s.FileKind(fh) != file.Mod { + return nil, fmt.Errorf("%s is not a go.mod file", uri) + } + + s.mu.Lock() + entry, hit := s.modWhyHandles.Get(uri) + s.mu.Unlock() + + type modWhyResult struct { + why map[string]string + err error + } + + // cache miss? + if !hit { + handle := memoize.NewPromise("modWhy", func(ctx context.Context, arg interface{}) interface{} { + why, err := modWhyImpl(ctx, arg.(*Snapshot), fh) + return modWhyResult{why, err} + }) + + entry = handle + s.mu.Lock() + s.modWhyHandles.Set(uri, entry, nil) + s.mu.Unlock() + } + + // Await result. + v, err := s.awaitPromise(ctx, entry) + if err != nil { + return nil, err + } + res := v.(modWhyResult) + return res.why, res.err +} + +// modWhyImpl returns the result of "go mod why -m" on the specified go.mod file. +func modWhyImpl(ctx context.Context, snapshot *Snapshot, fh file.Handle) (map[string]string, error) { + ctx, done := event.Start(ctx, "cache.ModWhy", label.URI.Of(fh.URI())) + defer done() + + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil { + return nil, err + } + // No requires to explain. + if len(pm.File.Require) == 0 { + return nil, nil // empty result + } + // Run `go mod why` on all the dependencies. + args := []string{"why", "-m"} + for _, req := range pm.File.Require { + args = append(args, req.Mod.Path) + } + inv, cleanupInvocation, err := snapshot.GoCommandInvocation(false, &gocommand.Invocation{ + Verb: "mod", + Args: args, + WorkingDir: filepath.Dir(fh.URI().Path()), + }) + if err != nil { + return nil, err + } + defer cleanupInvocation() + stdout, err := snapshot.View().GoCommandRunner().Run(ctx, *inv) + if err != nil { + return nil, err + } + whyList := strings.Split(stdout.String(), "\n\n") + if len(whyList) != len(pm.File.Require) { + return nil, fmt.Errorf("mismatched number of results: got %v, want %v", len(whyList), len(pm.File.Require)) + } + why := make(map[string]string, len(pm.File.Require)) + for i, req := range pm.File.Require { + why[req.Mod.Path] = whyList[i] + } + return why, nil +} + +// extractGoCommandErrors tries to parse errors that come from the go command +// and shape them into go.mod diagnostics. +// TODO: rename this to 'load errors' +func (s *Snapshot) extractGoCommandErrors(ctx context.Context, goCmdError error) []*Diagnostic { + if goCmdError == nil { + return nil + } + + type locatedErr struct { + loc protocol.Location + msg string + } + diagLocations := map[*ParsedModule]locatedErr{} + backupDiagLocations := map[*ParsedModule]locatedErr{} + + // If moduleErrs is non-nil, go command errors are scoped to specific + // modules. + var moduleErrs *moduleErrorMap + _ = errors.As(goCmdError, &moduleErrs) + + // Match the error against all the mod files in the workspace. + for _, uri := range s.View().ModFiles() { + fh, err := s.ReadFile(ctx, uri) + if err != nil { + event.Error(ctx, "getting modfile for Go command error", err) + continue + } + pm, err := s.ParseMod(ctx, fh) + if err != nil { + // Parsing errors are reported elsewhere + return nil + } + var msgs []string // error messages to consider + if moduleErrs != nil { + if pm.File.Module != nil { + for _, mes := range moduleErrs.errs[pm.File.Module.Mod.Path] { + msgs = append(msgs, mes.Error()) + } + } + } else { + msgs = append(msgs, goCmdError.Error()) + } + for _, msg := range msgs { + if strings.Contains(goCmdError.Error(), "errors parsing go.mod") { + // The go command emits parse errors for completely invalid go.mod files. + // Those are reported by our own diagnostics and can be ignored here. + // As of writing, we are not aware of any other errors that include + // file/position information, so don't even try to find it. + continue + } + loc, found, err := s.matchErrorToModule(pm, msg) + if err != nil { + event.Error(ctx, "matching error to module", err) + continue + } + le := locatedErr{ + loc: loc, + msg: msg, + } + if found { + diagLocations[pm] = le + } else { + backupDiagLocations[pm] = le + } + } + } + + // If we didn't find any good matches, assign diagnostics to all go.mod files. + if len(diagLocations) == 0 { + diagLocations = backupDiagLocations + } + + var srcErrs []*Diagnostic + for pm, le := range diagLocations { + diag, err := s.goCommandDiagnostic(pm, le.loc, le.msg) + if err != nil { + event.Error(ctx, "building go command diagnostic", err) + continue + } + srcErrs = append(srcErrs, diag) + } + return srcErrs +} + +var moduleVersionInErrorRe = regexp.MustCompile(`[:\s]([+-._~0-9A-Za-z]+)@([+-._~0-9A-Za-z]+)[:\s]`) + +// matchErrorToModule matches a go command error message to a go.mod file. +// Some examples: +// +// example.com@v1.2.2: reading example.com/@v/v1.2.2.mod: no such file or directory +// go: github.com/cockroachdb/apd/v2@v2.0.72: reading github.com/cockroachdb/apd/go.mod at revision v2.0.72: unknown revision v2.0.72 +// go: example.com@v1.2.3 requires\n\trandom.org@v1.2.3: parsing go.mod:\n\tmodule declares its path as: bob.org\n\tbut was required as: random.org +// +// It returns the location of a reference to the one of the modules and true +// if one exists. If none is found it returns a fallback location and false. +func (s *Snapshot) matchErrorToModule(pm *ParsedModule, goCmdError string) (protocol.Location, bool, error) { + var reference *modfile.Line + matches := moduleVersionInErrorRe.FindAllStringSubmatch(goCmdError, -1) + + for i := len(matches) - 1; i >= 0; i-- { + ver := module.Version{Path: matches[i][1], Version: matches[i][2]} + if err := module.Check(ver.Path, ver.Version); err != nil { + continue + } + reference = findModuleReference(pm.File, ver) + if reference != nil { + break + } + } + + if reference == nil { + // No match for the module path was found in the go.mod file. + // Show the error on the module declaration, if one exists, or + // just the first line of the file. + var start, end int + if pm.File.Module != nil && pm.File.Module.Syntax != nil { + syntax := pm.File.Module.Syntax + start, end = syntax.Start.Byte, syntax.End.Byte + } + loc, err := pm.Mapper.OffsetLocation(start, end) + return loc, false, err + } + + loc, err := pm.Mapper.OffsetLocation(reference.Start.Byte, reference.End.Byte) + return loc, true, err +} + +// goCommandDiagnostic creates a diagnostic for a given go command error. +func (s *Snapshot) goCommandDiagnostic(pm *ParsedModule, loc protocol.Location, goCmdError string) (*Diagnostic, error) { + matches := moduleVersionInErrorRe.FindAllStringSubmatch(goCmdError, -1) + var innermost *module.Version + for i := len(matches) - 1; i >= 0; i-- { + ver := module.Version{Path: matches[i][1], Version: matches[i][2]} + if err := module.Check(ver.Path, ver.Version); err != nil { + continue + } + innermost = &ver + break + } + + switch { + case strings.Contains(goCmdError, "inconsistent vendoring"): + cmd := command.NewVendorCommand("Run go mod vendor", command.URIArg{URI: pm.URI}) + return &Diagnostic{ + URI: pm.URI, + Range: loc.Range, + Severity: protocol.SeverityError, + Source: ListError, + Message: `Inconsistent vendoring detected. Please re-run "go mod vendor". +See https://github.com/golang/go/issues/39164 for more detail on this issue.`, + SuggestedFixes: []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)}, + }, nil + + case strings.Contains(goCmdError, "updates to go.sum needed"), strings.Contains(goCmdError, "missing go.sum entry"): + var args []protocol.DocumentURI + args = append(args, s.View().ModFiles()...) + tidyCmd := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: args}) + updateCmd := command.NewUpdateGoSumCommand("Update go.sum", command.URIArgs{URIs: args}) + msg := "go.sum is out of sync with go.mod. Please update it by applying the quick fix." + if innermost != nil { + msg = fmt.Sprintf("go.sum is out of sync with go.mod: entry for %v is missing. Please updating it by applying the quick fix.", innermost) + } + return &Diagnostic{ + URI: pm.URI, + Range: loc.Range, + Severity: protocol.SeverityError, + Source: ListError, + Message: msg, + SuggestedFixes: []SuggestedFix{ + SuggestedFixFromCommand(tidyCmd, protocol.QuickFix), + SuggestedFixFromCommand(updateCmd, protocol.QuickFix), + }, + }, nil + case strings.Contains(goCmdError, "disabled by GOPROXY=off") && innermost != nil: + title := fmt.Sprintf("Download %v@%v", innermost.Path, innermost.Version) + cmd := command.NewAddDependencyCommand(title, command.DependencyArgs{ + URI: pm.URI, + AddRequire: false, + GoCmdArgs: []string{fmt.Sprintf("%v@%v", innermost.Path, innermost.Version)}, + }) + return &Diagnostic{ + URI: pm.URI, + Range: loc.Range, + Severity: protocol.SeverityError, + Message: fmt.Sprintf("%v@%v has not been downloaded", innermost.Path, innermost.Version), + Source: ListError, + SuggestedFixes: []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)}, + }, nil + default: + return &Diagnostic{ + URI: pm.URI, + Range: loc.Range, + Severity: protocol.SeverityError, + Source: ListError, + Message: goCmdError, + }, nil + } +} + +func findModuleReference(mf *modfile.File, ver module.Version) *modfile.Line { + for _, req := range mf.Require { + if req.Mod == ver { + return req.Syntax + } + } + for _, ex := range mf.Exclude { + if ex.Mod == ver { + return ex.Syntax + } + } + for _, rep := range mf.Replace { + if rep.New == ver || rep.Old == ver { + return rep.Syntax + } + } + return nil +} diff --git a/contribs/gnopls/internal/cache/mod_tidy.go b/contribs/gnopls/internal/cache/mod_tidy.go new file mode 100644 index 00000000000..8532d1c7497 --- /dev/null +++ b/contribs/gnopls/internal/cache/mod_tidy.go @@ -0,0 +1,503 @@ +// 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 cache + +import ( + "context" + "errors" + "fmt" + "go/ast" + "go/token" + "os" + "path/filepath" + "strconv" + "strings" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/memoize" +) + +// This error is sought by mod diagnostics. +var ErrNoModOnDisk = errors.New("go.mod file is not on disk") + +// A TidiedModule contains the results of running `go mod tidy` on a module. +type TidiedModule struct { + // Diagnostics representing changes made by `go mod tidy`. + Diagnostics []*Diagnostic + // The bytes of the go.mod file after it was tidied. + TidiedContent []byte +} + +// ModTidy returns the go.mod file that would be obtained by running +// "go mod tidy". Concurrent requests are combined into a single command. +func (s *Snapshot) ModTidy(ctx context.Context, pm *ParsedModule) (*TidiedModule, error) { + ctx, done := event.Start(ctx, "cache.snapshot.ModTidy") + defer done() + + uri := pm.URI + if pm.File == nil { + return nil, fmt.Errorf("cannot tidy unparseable go.mod file: %v", uri) + } + + s.mu.Lock() + entry, hit := s.modTidyHandles.Get(uri) + s.mu.Unlock() + + type modTidyResult struct { + tidied *TidiedModule + err error + } + + // Cache miss? + if !hit { + // If the file handle is an overlay, it may not be written to disk. + // The go.mod file has to be on disk for `go mod tidy` to work. + // TODO(rfindley): is this still true with Go 1.16 overlay support? + fh, err := s.ReadFile(ctx, pm.URI) + if err != nil { + return nil, err + } + if _, ok := fh.(*overlay); ok { + if info, _ := os.Stat(uri.Path()); info == nil { + return nil, ErrNoModOnDisk + } + } + + if err := s.awaitLoaded(ctx); err != nil { + return nil, err + } + + handle := memoize.NewPromise("modTidy", func(ctx context.Context, arg interface{}) interface{} { + tidied, err := modTidyImpl(ctx, arg.(*Snapshot), pm) + return modTidyResult{tidied, err} + }) + + entry = handle + s.mu.Lock() + s.modTidyHandles.Set(uri, entry, nil) + s.mu.Unlock() + } + + // Await result. + v, err := s.awaitPromise(ctx, entry) + if err != nil { + return nil, err + } + res := v.(modTidyResult) + return res.tidied, res.err +} + +// modTidyImpl runs "go mod tidy" on a go.mod file. +func modTidyImpl(ctx context.Context, snapshot *Snapshot, pm *ParsedModule) (*TidiedModule, error) { + ctx, done := event.Start(ctx, "cache.ModTidy", label.URI.Of(pm.URI)) + defer done() + + tempDir, cleanup, err := TempModDir(ctx, snapshot, pm.URI) + if err != nil { + return nil, err + } + defer cleanup() + + inv, cleanupInvocation, err := snapshot.GoCommandInvocation(false, &gocommand.Invocation{ + Verb: "mod", + Args: []string{"tidy", "-modfile=" + filepath.Join(tempDir, "go.mod")}, + Env: []string{"GOWORK=off"}, + WorkingDir: pm.URI.Dir().Path(), + }) + if err != nil { + return nil, err + } + defer cleanupInvocation() + if _, err := snapshot.view.gocmdRunner.Run(ctx, *inv); err != nil { + return nil, err + } + + // Go directly to disk to get the temporary mod file, + // since it is always on disk. + tempMod := filepath.Join(tempDir, "go.mod") + tempContents, err := os.ReadFile(tempMod) + if err != nil { + return nil, err + } + ideal, err := modfile.Parse(tempMod, tempContents, nil) + if err != nil { + // We do not need to worry about the temporary file's parse errors + // since it has been "tidied". + return nil, err + } + + // Compare the original and tidied go.mod files to compute errors and + // suggested fixes. + diagnostics, err := modTidyDiagnostics(ctx, snapshot, pm, ideal) + if err != nil { + return nil, err + } + + return &TidiedModule{ + Diagnostics: diagnostics, + TidiedContent: tempContents, + }, nil +} + +// modTidyDiagnostics computes the differences between the original and tidied +// go.mod files to produce diagnostic and suggested fixes. Some diagnostics +// may appear on the Go files that import packages from missing modules. +func modTidyDiagnostics(ctx context.Context, snapshot *Snapshot, pm *ParsedModule, ideal *modfile.File) (diagnostics []*Diagnostic, err error) { + // First, determine which modules are unused and which are missing from the + // original go.mod file. + var ( + unused = make(map[string]*modfile.Require, len(pm.File.Require)) + missing = make(map[string]*modfile.Require, len(ideal.Require)) + wrongDirectness = make(map[string]*modfile.Require, len(pm.File.Require)) + ) + for _, req := range pm.File.Require { + unused[req.Mod.Path] = req + } + for _, req := range ideal.Require { + origReq := unused[req.Mod.Path] + if origReq == nil { + missing[req.Mod.Path] = req + continue + } else if origReq.Indirect != req.Indirect { + wrongDirectness[req.Mod.Path] = origReq + } + delete(unused, req.Mod.Path) + } + for _, req := range wrongDirectness { + // Handle dependencies that are incorrectly labeled indirect and + // vice versa. + srcDiag, err := directnessDiagnostic(pm.Mapper, req) + if err != nil { + // We're probably in a bad state if we can't compute a + // directnessDiagnostic, but try to keep going so as to not suppress + // other, valid diagnostics. + event.Error(ctx, "computing directness diagnostic", err) + continue + } + diagnostics = append(diagnostics, srcDiag) + } + // Next, compute any diagnostics for modules that are missing from the + // go.mod file. The fixes will be for the go.mod file, but the + // diagnostics should also appear in both the go.mod file and the import + // statements in the Go files in which the dependencies are used. + // Finally, add errors for any unused dependencies. + if len(missing) > 0 { + missingModuleDiagnostics, err := missingModuleDiagnostics(ctx, snapshot, pm, ideal, missing) + if err != nil { + return nil, err + } + diagnostics = append(diagnostics, missingModuleDiagnostics...) + } + + // Opt: if this is the only diagnostic, we can avoid textual edits and just + // run the Go command. + // + // See also the documentation for command.RemoveDependencyArgs.OnlyDiagnostic. + onlyDiagnostic := len(diagnostics) == 0 && len(unused) == 1 + for _, req := range unused { + srcErr, err := unusedDiagnostic(pm.Mapper, req, onlyDiagnostic) + if err != nil { + return nil, err + } + diagnostics = append(diagnostics, srcErr) + } + return diagnostics, nil +} + +func missingModuleDiagnostics(ctx context.Context, snapshot *Snapshot, pm *ParsedModule, ideal *modfile.File, missing map[string]*modfile.Require) ([]*Diagnostic, error) { + missingModuleFixes := map[*modfile.Require][]SuggestedFix{} + var diagnostics []*Diagnostic + for _, req := range missing { + srcDiag, err := missingModuleDiagnostic(pm, req) + if err != nil { + return nil, err + } + missingModuleFixes[req] = srcDiag.SuggestedFixes + diagnostics = append(diagnostics, srcDiag) + } + + // Add diagnostics for missing modules anywhere they are imported in the + // workspace. + metas, err := snapshot.WorkspaceMetadata(ctx) + if err != nil { + return nil, err + } + // TODO(adonovan): opt: opportunities for parallelism abound. + for _, mp := range metas { + // Read both lists of files of this package. + // + // Parallelism is not necessary here as the files will have already been + // pre-read at load time. + goFiles, err := readFiles(ctx, snapshot, mp.GoFiles) + if err != nil { + return nil, err + } + compiledGoFiles, err := readFiles(ctx, snapshot, mp.CompiledGoFiles) + if err != nil { + return nil, err + } + + missingImports := map[string]*modfile.Require{} + + // If -mod=readonly is not set we may have successfully imported + // packages from missing modules. Otherwise they'll be in + // MissingDependencies. Combine both. + imps, err := parseImports(ctx, snapshot, goFiles) + if err != nil { + return nil, err + } + for imp := range imps { + if req, ok := missing[imp]; ok { + missingImports[imp] = req + break + } + // If the import is a package of the dependency, then add the + // package to the map, this will eliminate the need to do this + // prefix package search on each import for each file. + // Example: + // + // import ( + // "golang.org/x/tools/go/expect" + // "golang.org/x/tools/go/packages" + // ) + // They both are related to the same module: "golang.org/x/tools". + var match string + for _, req := range ideal.Require { + if strings.HasPrefix(imp, req.Mod.Path) && len(req.Mod.Path) > len(match) { + match = req.Mod.Path + } + } + if req, ok := missing[match]; ok { + missingImports[imp] = req + } + } + // None of this package's imports are from missing modules. + if len(missingImports) == 0 { + continue + } + for _, goFile := range compiledGoFiles { + pgf, err := snapshot.ParseGo(ctx, goFile, parsego.Header) + if err != nil { + continue + } + file, m := pgf.File, pgf.Mapper + if file == nil || m == nil { + continue + } + imports := make(map[string]*ast.ImportSpec) + for _, imp := range file.Imports { + if imp.Path == nil { + continue + } + if target, err := strconv.Unquote(imp.Path.Value); err == nil { + imports[target] = imp + } + } + if len(imports) == 0 { + continue + } + for importPath, req := range missingImports { + imp, ok := imports[importPath] + if !ok { + continue + } + fixes, ok := missingModuleFixes[req] + if !ok { + return nil, fmt.Errorf("no missing module fix for %q (%q)", importPath, req.Mod.Path) + } + srcErr, err := missingModuleForImport(pgf, imp, req, fixes) + if err != nil { + return nil, err + } + diagnostics = append(diagnostics, srcErr) + } + } + } + return diagnostics, nil +} + +// unusedDiagnostic returns a Diagnostic for an unused require. +func unusedDiagnostic(m *protocol.Mapper, req *modfile.Require, onlyDiagnostic bool) (*Diagnostic, error) { + rng, err := m.OffsetRange(req.Syntax.Start.Byte, req.Syntax.End.Byte) + if err != nil { + return nil, err + } + title := fmt.Sprintf("Remove dependency: %s", req.Mod.Path) + cmd := command.NewRemoveDependencyCommand(title, command.RemoveDependencyArgs{ + URI: m.URI, + OnlyDiagnostic: onlyDiagnostic, + ModulePath: req.Mod.Path, + }) + return &Diagnostic{ + URI: m.URI, + Range: rng, + Severity: protocol.SeverityWarning, + Source: ModTidyError, + Message: fmt.Sprintf("%s is not used in this module", req.Mod.Path), + SuggestedFixes: []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)}, + }, nil +} + +// directnessDiagnostic extracts errors when a dependency is labeled indirect when +// it should be direct and vice versa. +func directnessDiagnostic(m *protocol.Mapper, req *modfile.Require) (*Diagnostic, error) { + rng, err := m.OffsetRange(req.Syntax.Start.Byte, req.Syntax.End.Byte) + if err != nil { + return nil, err + } + direction := "indirect" + if req.Indirect { + direction = "direct" + + // If the dependency should be direct, just highlight the // indirect. + if comments := req.Syntax.Comment(); comments != nil && len(comments.Suffix) > 0 { + end := comments.Suffix[0].Start + end.LineRune += len(comments.Suffix[0].Token) + end.Byte += len(comments.Suffix[0].Token) + rng, err = m.OffsetRange(comments.Suffix[0].Start.Byte, end.Byte) + if err != nil { + return nil, err + } + } + } + // If the dependency should be indirect, add the // indirect. + edits, err := switchDirectness(req, m) + if err != nil { + return nil, err + } + return &Diagnostic{ + URI: m.URI, + Range: rng, + Severity: protocol.SeverityWarning, + Source: ModTidyError, + Message: fmt.Sprintf("%s should be %s", req.Mod.Path, direction), + SuggestedFixes: []SuggestedFix{{ + Title: fmt.Sprintf("Change %s to %s", req.Mod.Path, direction), + Edits: map[protocol.DocumentURI][]protocol.TextEdit{ + m.URI: edits, + }, + ActionKind: protocol.QuickFix, + }}, + }, nil +} + +func missingModuleDiagnostic(pm *ParsedModule, req *modfile.Require) (*Diagnostic, error) { + var rng protocol.Range + // Default to the start of the file if there is no module declaration. + if pm.File != nil && pm.File.Module != nil && pm.File.Module.Syntax != nil { + start, end := pm.File.Module.Syntax.Span() + var err error + rng, err = pm.Mapper.OffsetRange(start.Byte, end.Byte) + if err != nil { + return nil, err + } + } + title := fmt.Sprintf("Add %s to your go.mod file", req.Mod.Path) + cmd := command.NewAddDependencyCommand(title, command.DependencyArgs{ + URI: pm.Mapper.URI, + AddRequire: !req.Indirect, + GoCmdArgs: []string{req.Mod.Path + "@" + req.Mod.Version}, + }) + return &Diagnostic{ + URI: pm.Mapper.URI, + Range: rng, + Severity: protocol.SeverityError, + Source: ModTidyError, + Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path), + SuggestedFixes: []SuggestedFix{SuggestedFixFromCommand(cmd, protocol.QuickFix)}, + }, nil +} + +// switchDirectness gets the edits needed to change an indirect dependency to +// direct and vice versa. +func switchDirectness(req *modfile.Require, m *protocol.Mapper) ([]protocol.TextEdit, error) { + // We need a private copy of the parsed go.mod file, since we're going to + // modify it. + copied, err := modfile.Parse("", m.Content, nil) + if err != nil { + return nil, err + } + // Change the directness in the matching require statement. To avoid + // reordering the require statements, rewrite all of them. + var requires []*modfile.Require + seenVersions := make(map[string]string) + for _, r := range copied.Require { + if seen := seenVersions[r.Mod.Path]; seen != "" && seen != r.Mod.Version { + // Avoid a panic in SetRequire below, which panics on conflicting + // versions. + return nil, fmt.Errorf("%q has conflicting versions: %q and %q", r.Mod.Path, seen, r.Mod.Version) + } + seenVersions[r.Mod.Path] = r.Mod.Version + if r.Mod.Path == req.Mod.Path { + requires = append(requires, &modfile.Require{ + Mod: r.Mod, + Syntax: r.Syntax, + Indirect: !r.Indirect, + }) + continue + } + requires = append(requires, r) + } + copied.SetRequire(requires) + newContent, err := copied.Format() + if err != nil { + return nil, err + } + // Calculate the edits to be made due to the change. + edits := diff.Bytes(m.Content, newContent) + return protocol.EditsFromDiffEdits(m, edits) +} + +// missingModuleForImport creates an error for a given import path that comes +// from a missing module. +func missingModuleForImport(pgf *parsego.File, imp *ast.ImportSpec, req *modfile.Require, fixes []SuggestedFix) (*Diagnostic, error) { + if req.Syntax == nil { + return nil, fmt.Errorf("no syntax for %v", req) + } + rng, err := pgf.NodeRange(imp.Path) + if err != nil { + return nil, err + } + return &Diagnostic{ + URI: pgf.URI, + Range: rng, + Severity: protocol.SeverityError, + Source: ModTidyError, + Message: fmt.Sprintf("%s is not in your go.mod file", req.Mod.Path), + SuggestedFixes: fixes, + }, nil +} + +// parseImports parses the headers of the specified files and returns +// the set of strings that appear in import declarations within +// GoFiles. Errors are ignored. +// +// (We can't simply use Metadata.Imports because it is based on +// CompiledGoFiles, after cgo processing.) +// +// TODO(rfindley): this should key off ImportPath. +func parseImports(ctx context.Context, s *Snapshot, files []file.Handle) (map[string]bool, error) { + pgfs, err := s.view.parseCache.parseFiles(ctx, token.NewFileSet(), parsego.Header, false, files...) + if err != nil { // e.g. context cancellation + return nil, err + } + + seen := make(map[string]bool) + for _, pgf := range pgfs { + for _, spec := range pgf.File.Imports { + path, _ := strconv.Unquote(spec.Path.Value) + seen[path] = true + } + } + return seen, nil +} diff --git a/contribs/gnopls/internal/cache/mod_vuln.go b/contribs/gnopls/internal/cache/mod_vuln.go new file mode 100644 index 00000000000..a92f5b5abe1 --- /dev/null +++ b/contribs/gnopls/internal/cache/mod_vuln.go @@ -0,0 +1,389 @@ +// 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 cache + +import ( + "context" + "fmt" + "io" + "os" + "sort" + "strings" + "sync" + + "golang.org/x/mod/semver" + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/vulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/osv" + isem "golang.org/x/tools/gopls/internal/vulncheck/semver" + "golang.org/x/tools/internal/memoize" + "golang.org/x/vuln/scan" +) + +// ModVuln returns import vulnerability analysis for the given go.mod URI. +// Concurrent requests are combined into a single command. +func (s *Snapshot) ModVuln(ctx context.Context, modURI protocol.DocumentURI) (*vulncheck.Result, error) { + s.mu.Lock() + entry, hit := s.modVulnHandles.Get(modURI) + s.mu.Unlock() + + type modVuln struct { + result *vulncheck.Result + err error + } + + // Cache miss? + if !hit { + handle := memoize.NewPromise("modVuln", func(ctx context.Context, arg interface{}) interface{} { + result, err := modVulnImpl(ctx, arg.(*Snapshot)) + return modVuln{result, err} + }) + + entry = handle + s.mu.Lock() + s.modVulnHandles.Set(modURI, entry, nil) + s.mu.Unlock() + } + + // Await result. + v, err := s.awaitPromise(ctx, entry) + if err != nil { + return nil, err + } + res := v.(modVuln) + return res.result, res.err +} + +// GoVersionForVulnTest is an internal environment variable used in gopls +// testing to examine govulncheck behavior with a go version different +// than what `go version` returns in the system. +const GoVersionForVulnTest = "_GOPLS_TEST_VULNCHECK_GOVERSION" + +// modVulnImpl queries the vulndb and reports which vulnerabilities +// apply to this snapshot. The result contains a set of packages, +// grouped by vuln ID and by module. This implements the "import-based" +// vulnerability report on go.mod files. +func modVulnImpl(ctx context.Context, snapshot *Snapshot) (*vulncheck.Result, error) { + // TODO(hyangah): can we let 'govulncheck' take a package list + // used in the workspace and implement this function? + + // We want to report the intersection of vulnerable packages in the vulndb + // and packages transitively imported by this module ('go list -deps all'). + // We use snapshot.AllMetadata to retrieve the list of packages + // as an approximation. + // + // TODO(hyangah): snapshot.AllMetadata is a superset of + // `go list all` - e.g. when the workspace has multiple main modules + // (multiple go.mod files), that can include packages that are not + // used by this module. Vulncheck behavior with go.work is not well + // defined. Figure out the meaning, and if we decide to present + // the result as if each module is analyzed independently, make + // gopls track a separate build list for each module and use that + // information instead of snapshot.AllMetadata. + allMeta, err := snapshot.AllMetadata(ctx) + if err != nil { + return nil, err + } + + // TODO(hyangah): handle vulnerabilities in the standard library. + + // Group packages by modules since vuln db is keyed by module. + packagesByModule := map[metadata.PackagePath][]*metadata.Package{} + for _, mp := range allMeta { + modulePath := metadata.PackagePath(osv.GoStdModulePath) + if mi := mp.Module; mi != nil { + modulePath = metadata.PackagePath(mi.Path) + } + packagesByModule[modulePath] = append(packagesByModule[modulePath], mp) + } + + var ( + mu sync.Mutex + // Keys are osv.Entry.ID + osvs = map[string]*osv.Entry{} + findings []*govulncheck.Finding + ) + + goVersion := snapshot.Options().Env[GoVersionForVulnTest] + if goVersion == "" { + goVersion = snapshot.GoVersionString() + } + + stdlibModule := &packages.Module{ + Path: osv.GoStdModulePath, + Version: goVersion, + } + + // GOVULNDB may point the test db URI. + db := GetEnv(snapshot, "GOVULNDB") + + var group errgroup.Group + group.SetLimit(10) // limit govulncheck api runs + for _, mps := range packagesByModule { + mps := mps + group.Go(func() error { + effectiveModule := stdlibModule + if m := mps[0].Module; m != nil { + effectiveModule = m + } + for effectiveModule.Replace != nil { + effectiveModule = effectiveModule.Replace + } + ver := effectiveModule.Version + if ver == "" || !isem.Valid(ver) { + // skip invalid version strings. the underlying scan api is strict. + return nil + } + + // TODO(hyangah): batch these requests and add in-memory cache for efficiency. + vulns, err := osvsByModule(ctx, db, effectiveModule.Path+"@"+ver) + if err != nil { + return err + } + if len(vulns) == 0 { // No known vulnerability. + return nil + } + + // set of packages in this module known to gopls. + // This will be lazily initialized when we need it. + var knownPkgs map[metadata.PackagePath]bool + + // Report vulnerabilities that affect packages of this module. + for _, entry := range vulns { + var vulnerablePkgs []*govulncheck.Finding + fixed := fixedVersion(effectiveModule.Path, entry.Affected) + + for _, a := range entry.Affected { + if a.Module.Ecosystem != osv.GoEcosystem || a.Module.Path != effectiveModule.Path { + continue + } + for _, imp := range a.EcosystemSpecific.Packages { + if knownPkgs == nil { + knownPkgs = toPackagePathSet(mps) + } + if knownPkgs[metadata.PackagePath(imp.Path)] { + vulnerablePkgs = append(vulnerablePkgs, &govulncheck.Finding{ + OSV: entry.ID, + FixedVersion: fixed, + Trace: []*govulncheck.Frame{ + { + Module: effectiveModule.Path, + Version: effectiveModule.Version, + Package: imp.Path, + }, + }, + }) + } + } + } + if len(vulnerablePkgs) == 0 { + continue + } + mu.Lock() + osvs[entry.ID] = entry + findings = append(findings, vulnerablePkgs...) + mu.Unlock() + } + return nil + }) + } + if err := group.Wait(); err != nil { + return nil, err + } + + // Sort so the results are deterministic. + sort.Slice(findings, func(i, j int) bool { + x, y := findings[i], findings[j] + if x.OSV != y.OSV { + return x.OSV < y.OSV + } + return x.Trace[0].Package < y.Trace[0].Package + }) + ret := &vulncheck.Result{ + Entries: osvs, + Findings: findings, + Mode: vulncheck.ModeImports, + } + return ret, nil +} + +// TODO(rfindley): this function was exposed during refactoring. Reconsider it. +func GetEnv(snapshot *Snapshot, key string) string { + val, ok := snapshot.Options().Env[key] + if ok { + return val + } + return os.Getenv(key) +} + +// toPackagePathSet transforms the metadata to a set of package paths. +func toPackagePathSet(mds []*metadata.Package) map[metadata.PackagePath]bool { + pkgPaths := make(map[metadata.PackagePath]bool, len(mds)) + for _, md := range mds { + pkgPaths[md.PkgPath] = true + } + return pkgPaths +} + +func fixedVersion(modulePath string, affected []osv.Affected) string { + fixed := latestFixed(modulePath, affected) + if fixed != "" { + fixed = versionString(modulePath, fixed) + } + return fixed +} + +// latestFixed returns the latest fixed version in the list of affected ranges, +// or the empty string if there are no fixed versions. +func latestFixed(modulePath string, as []osv.Affected) string { + v := "" + for _, a := range as { + if a.Module.Path != modulePath { + continue + } + for _, r := range a.Ranges { + if r.Type == osv.RangeTypeSemver { + for _, e := range r.Events { + if e.Fixed != "" && (v == "" || + semver.Compare(isem.CanonicalizeSemverPrefix(e.Fixed), isem.CanonicalizeSemverPrefix(v)) > 0) { + v = e.Fixed + } + } + } + } + } + return v +} + +// versionString prepends a version string prefix (`v` or `go` +// depending on the modulePath) to the given semver-style version string. +func versionString(modulePath, version string) string { + if version == "" { + return "" + } + v := "v" + version + // These are internal Go module paths used by the vuln DB + // when listing vulns in standard library and the go command. + if modulePath == "stdlib" || modulePath == "toolchain" { + return semverToGoTag(v) + } + return v +} + +// semverToGoTag returns the Go standard library repository tag corresponding +// to semver, a version string without the initial "v". +// Go tags differ from standard semantic versions in a few ways, +// such as beginning with "go" instead of "v". +func semverToGoTag(v string) string { + if strings.HasPrefix(v, "v0.0.0") { + return "master" + } + // Special case: v1.0.0 => go1. + if v == "v1.0.0" { + return "go1" + } + if !semver.IsValid(v) { + return fmt.Sprintf("", v) + } + goVersion := semver.Canonical(v) + prerelease := semver.Prerelease(goVersion) + versionWithoutPrerelease := strings.TrimSuffix(goVersion, prerelease) + patch := strings.TrimPrefix(versionWithoutPrerelease, semver.MajorMinor(goVersion)+".") + if patch == "0" { + versionWithoutPrerelease = strings.TrimSuffix(versionWithoutPrerelease, ".0") + } + goVersion = fmt.Sprintf("go%s", strings.TrimPrefix(versionWithoutPrerelease, "v")) + if prerelease != "" { + // Go prereleases look like "beta1" instead of "beta.1". + // "beta1" is bad for sorting (since beta10 comes before beta9), so + // require the dot form. + i := finalDigitsIndex(prerelease) + if i >= 1 { + if prerelease[i-1] != '.' { + return fmt.Sprintf("", v) + } + // Remove the dot. + prerelease = prerelease[:i-1] + prerelease[i:] + } + goVersion += strings.TrimPrefix(prerelease, "-") + } + return goVersion +} + +// finalDigitsIndex returns the index of the first digit in the sequence of digits ending s. +// If s doesn't end in digits, it returns -1. +func finalDigitsIndex(s string) int { + // Assume ASCII (since the semver package does anyway). + var i int + for i = len(s) - 1; i >= 0; i-- { + if s[i] < '0' || s[i] > '9' { + break + } + } + if i == len(s)-1 { + return -1 + } + return i + 1 +} + +// osvsByModule runs a govulncheck database query. +func osvsByModule(ctx context.Context, db, moduleVersion string) ([]*osv.Entry, error) { + var args []string + args = append(args, "-mode=query", "-json") + if db != "" { + args = append(args, "-db="+db) + } + args = append(args, moduleVersion) + + ir, iw := io.Pipe() + handler := &osvReader{} + + var g errgroup.Group + g.Go(func() error { + defer iw.Close() // scan API doesn't close cmd.Stderr/cmd.Stdout. + cmd := scan.Command(ctx, args...) + cmd.Stdout = iw + // TODO(hakim): Do we need to set cmd.Env = getEnvSlices(), + // or is the process environment good enough? + if err := cmd.Start(); err != nil { + return err + } + return cmd.Wait() + }) + g.Go(func() error { + return govulncheck.HandleJSON(ir, handler) + }) + + if err := g.Wait(); err != nil { + return nil, err + } + return handler.entry, nil +} + +// osvReader implements govulncheck.Handler. +type osvReader struct { + entry []*osv.Entry +} + +func (h *osvReader) OSV(entry *osv.Entry) error { + h.entry = append(h.entry, entry) + return nil +} + +func (h *osvReader) Config(config *govulncheck.Config) error { + return nil +} + +func (h *osvReader) Finding(finding *govulncheck.Finding) error { + return nil +} + +func (h *osvReader) Progress(progress *govulncheck.Progress) error { + return nil +} diff --git a/contribs/gnopls/internal/cache/os_darwin.go b/contribs/gnopls/internal/cache/os_darwin.go new file mode 100644 index 00000000000..4c2a7236dcc --- /dev/null +++ b/contribs/gnopls/internal/cache/os_darwin.go @@ -0,0 +1,59 @@ +// 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 cache + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + "unsafe" +) + +func init() { + checkPathValid = darwinCheckPathValid +} + +func darwinCheckPathValid(path string) error { + // Darwin provides fcntl(F_GETPATH) to get a path for an arbitrary FD. + // Conveniently for our purposes, it gives the canonical case back. But + // there's no guarantee that it will follow the same route through the + // filesystem that the original path did. + + path, err := filepath.Abs(path) + if err != nil { + return err + } + fd, err := syscall.Open(path, os.O_RDONLY, 0) + if err != nil { + return err + } + defer syscall.Close(fd) + buf := make([]byte, 4096) // No MAXPATHLEN in syscall, I think it's 1024, this is bigger. + + // Wheeee! syscall doesn't expose a way to call Fcntl except FcntlFlock. + // As of writing, it just passes the pointer through, so we can just lie. + if err := syscall.FcntlFlock(uintptr(fd), syscall.F_GETPATH, (*syscall.Flock_t)(unsafe.Pointer(&buf[0]))); err != nil { + return err + } + buf = buf[:bytes.IndexByte(buf, 0)] + + isRoot := func(p string) bool { + return p[len(p)-1] == filepath.Separator + } + // Darwin seems to like having multiple names for the same folder. Match as much of the suffix as we can. + for got, want := path, string(buf); !isRoot(got) && !isRoot(want); got, want = filepath.Dir(got), filepath.Dir(want) { + g, w := filepath.Base(got), filepath.Base(want) + if !strings.EqualFold(g, w) { + break + } + if g != w { + return fmt.Errorf("invalid path %q: component %q is listed by macOS as %q", path, g, w) + } + } + return nil +} diff --git a/contribs/gnopls/internal/cache/os_windows.go b/contribs/gnopls/internal/cache/os_windows.go new file mode 100644 index 00000000000..32fb1f40f49 --- /dev/null +++ b/contribs/gnopls/internal/cache/os_windows.go @@ -0,0 +1,56 @@ +// 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 cache + +import ( + "fmt" + "path/filepath" + "syscall" +) + +func init() { + checkPathValid = windowsCheckPathValid +} + +func windowsCheckPathValid(path string) error { + // Back in the day, Windows used to have short and long filenames, and + // it still supports those APIs. GetLongPathName gets the real case for a + // path, so we can use it here. Inspired by + // http://stackoverflow.com/q/2113822. + + // Short paths can be longer than long paths, and unicode, so be generous. + buflen := 4 * len(path) + namep, err := syscall.UTF16PtrFromString(path) + if err != nil { + return err + } + short := make([]uint16, buflen) + n, err := syscall.GetShortPathName(namep, &short[0], uint32(len(short)*2)) // buflen is in bytes. + if err != nil { + return err + } + if int(n) > len(short)*2 { + return fmt.Errorf("short buffer too short: %v vs %v*2", n, len(short)) + } + long := make([]uint16, buflen) + n, err = syscall.GetLongPathName(&short[0], &long[0], uint32(len(long)*2)) + if err != nil { + return err + } + if int(n) > len(long)*2 { + return fmt.Errorf("long buffer too short: %v vs %v*2", n, len(long)) + } + longstr := syscall.UTF16ToString(long) + + isRoot := func(p string) bool { + return p[len(p)-1] == filepath.Separator + } + for got, want := path, longstr; !isRoot(got) && !isRoot(want); got, want = filepath.Dir(got), filepath.Dir(want) { + if g, w := filepath.Base(got), filepath.Base(want); g != w { + return fmt.Errorf("invalid path %q: component %q is listed by Windows as %q", path, g, w) + } + } + return nil +} diff --git a/contribs/gnopls/internal/cache/package.go b/contribs/gnopls/internal/cache/package.go new file mode 100644 index 00000000000..5c0da7e6af0 --- /dev/null +++ b/contribs/gnopls/internal/cache/package.go @@ -0,0 +1,193 @@ +// 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. + +package cache + +import ( + "fmt" + "go/ast" + "go/scanner" + "go/token" + "go/types" + "sync" + + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/methodsets" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/cache/testfuncs" + "golang.org/x/tools/gopls/internal/cache/xrefs" + "golang.org/x/tools/gopls/internal/protocol" +) + +// Convenient aliases for very heavily used types. +type ( + PackageID = metadata.PackageID + PackagePath = metadata.PackagePath + PackageName = metadata.PackageName + ImportPath = metadata.ImportPath +) + +// A Package is the union of package metadata and type checking results. +// +// TODO(rfindley): for now, we do not persist the post-processing of +// loadDiagnostics, because the value of the snapshot.packages map is just the +// package handle. Fix this. +type Package struct { + metadata *metadata.Package + loadDiagnostics []*Diagnostic + pkg *syntaxPackage +} + +// syntaxPackage contains parse trees and type information for a package. +type syntaxPackage struct { + // -- identifiers -- + id PackageID + + // -- outputs -- + fset *token.FileSet // for now, same as the snapshot's FileSet + goFiles []*parsego.File + compiledGoFiles []*parsego.File + diagnostics []*Diagnostic + parseErrors []scanner.ErrorList + typeErrors []types.Error + types *types.Package + typesInfo *types.Info + typesSizes types.Sizes + importMap map[PackagePath]*types.Package + + xrefsOnce sync.Once + _xrefs []byte // only used by the xrefs method + + methodsetsOnce sync.Once + _methodsets *methodsets.Index // only used by the methodsets method + + testsOnce sync.Once + _tests *testfuncs.Index // only used by the tests method +} + +func (p *syntaxPackage) xrefs() []byte { + p.xrefsOnce.Do(func() { + p._xrefs = xrefs.Index(p.compiledGoFiles, p.types, p.typesInfo) + }) + return p._xrefs +} + +func (p *syntaxPackage) methodsets() *methodsets.Index { + p.methodsetsOnce.Do(func() { + p._methodsets = methodsets.NewIndex(p.fset, p.types) + }) + return p._methodsets +} + +func (p *syntaxPackage) tests() *testfuncs.Index { + p.testsOnce.Do(func() { + p._tests = testfuncs.NewIndex(p.compiledGoFiles, p.typesInfo) + }) + return p._tests +} + +func (p *Package) String() string { return string(p.metadata.ID) } + +func (p *Package) Metadata() *metadata.Package { return p.metadata } + +// A loadScope defines a package loading scope for use with go/packages. +// +// TODO(rfindley): move this to load.go. +type loadScope interface { + aScope() +} + +// TODO(rfindley): move to load.go +type ( + fileLoadScope protocol.DocumentURI // load packages containing a file (including command-line-arguments) + packageLoadScope string // load a specific package (the value is its PackageID) + moduleLoadScope struct { + dir string // dir containing the go.mod file + modulePath string // parsed module path + } + viewLoadScope struct{} // load the workspace +) + +// Implement the loadScope interface. +func (fileLoadScope) aScope() {} +func (packageLoadScope) aScope() {} +func (moduleLoadScope) aScope() {} +func (viewLoadScope) aScope() {} + +func (p *Package) CompiledGoFiles() []*parsego.File { + return p.pkg.compiledGoFiles +} + +func (p *Package) File(uri protocol.DocumentURI) (*parsego.File, error) { + return p.pkg.File(uri) +} + +func (pkg *syntaxPackage) File(uri protocol.DocumentURI) (*parsego.File, error) { + for _, cgf := range pkg.compiledGoFiles { + if cgf.URI == uri { + return cgf, nil + } + } + for _, gf := range pkg.goFiles { + if gf.URI == uri { + return gf, nil + } + } + return nil, fmt.Errorf("no parsed file for %s in %v", uri, pkg.id) +} + +// Syntax returns parsed compiled Go files contained in this package. +func (p *Package) Syntax() []*ast.File { + var syntax []*ast.File + for _, pgf := range p.pkg.compiledGoFiles { + syntax = append(syntax, pgf.File) + } + return syntax +} + +// FileSet returns the FileSet describing this package's positions. +// +// The returned FileSet is guaranteed to describe all Syntax, but may also +// describe additional files. +func (p *Package) FileSet() *token.FileSet { + return p.pkg.fset +} + +// Types returns the type checked go/types.Package. +func (p *Package) Types() *types.Package { + return p.pkg.types +} + +// TypesInfo returns the go/types.Info annotating the Syntax of this package +// with type information. +// +// All fields in the resulting Info are populated. +func (p *Package) TypesInfo() *types.Info { + return p.pkg.typesInfo +} + +// TypesSizes returns the sizing function used for types in this package. +func (p *Package) TypesSizes() types.Sizes { + return p.pkg.typesSizes +} + +// DependencyTypes returns the type checker's symbol for the specified +// package. It returns nil if path is not among the transitive +// dependencies of p, or if no symbols from that package were +// referenced during the type-checking of p. +func (p *Package) DependencyTypes(path PackagePath) *types.Package { + return p.pkg.importMap[path] +} + +// ParseErrors returns a slice containing all non-empty parse errors produces +// while parsing p.Syntax, or nil if the package contains no parse errors. +func (p *Package) ParseErrors() []scanner.ErrorList { + return p.pkg.parseErrors +} + +// TypeErrors returns the go/types.Errors produced during type checking this +// package, if any. +func (p *Package) TypeErrors() []types.Error { + return p.pkg.typeErrors +} diff --git a/contribs/gnopls/internal/cache/parse.go b/contribs/gnopls/internal/cache/parse.go new file mode 100644 index 00000000000..56130c6e1fb --- /dev/null +++ b/contribs/gnopls/internal/cache/parse.go @@ -0,0 +1,45 @@ +// 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. + +package cache + +import ( + "context" + "fmt" + "go/parser" + "go/token" + "path/filepath" + + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" +) + +// ParseGo parses the file whose contents are provided by fh. +// The resulting tree may have been fixed up. +// If the file is not available, returns nil and an error. +func (s *Snapshot) ParseGo(ctx context.Context, fh file.Handle, mode parser.Mode) (*parsego.File, error) { + pgfs, err := s.view.parseCache.parseFiles(ctx, token.NewFileSet(), mode, false, fh) + if err != nil { + return nil, err + } + return pgfs[0], nil +} + +// parseGoImpl parses the Go source file whose content is provided by fh. +func parseGoImpl(ctx context.Context, fset *token.FileSet, fh file.Handle, mode parser.Mode, purgeFuncBodies bool) (*parsego.File, error) { + ext := filepath.Ext(fh.URI().Path()) + if ext != ".go" && ext != "" { // files generated by cgo have no extension + return nil, fmt.Errorf("cannot parse non-Go file %s", fh.URI()) + } + content, err := fh.Content() + if err != nil { + return nil, err + } + // Check for context cancellation before actually doing the parse. + if ctx.Err() != nil { + return nil, ctx.Err() + } + pgf, _ := parsego.Parse(ctx, fset, fh.URI(), content, mode, purgeFuncBodies) + return pgf, nil +} diff --git a/contribs/gnopls/internal/cache/parse_cache.go b/contribs/gnopls/internal/cache/parse_cache.go new file mode 100644 index 00000000000..8586f655d28 --- /dev/null +++ b/contribs/gnopls/internal/cache/parse_cache.go @@ -0,0 +1,419 @@ +// 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 cache + +import ( + "bytes" + "container/heap" + "context" + "fmt" + "go/parser" + "go/token" + "math/bits" + "runtime" + "sync" + "time" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/memoize" + "golang.org/x/tools/internal/tokeninternal" +) + +// This file contains an implementation of an LRU parse cache, that offsets the +// base token.Pos value of each cached file so that they may be later described +// by a single dedicated FileSet. +// +// This is achieved by tracking a monotonic offset in the token.Pos space, that +// is incremented before parsing allow room for the resulting parsed file. + +// reservedForParsing defines the room in the token.Pos space reserved for +// cached parsed files. +// +// Files parsed through the parseCache are guaranteed not to have overlapping +// spans: the parseCache tracks a monotonic base for newly parsed files. +// +// By offsetting the initial base of a FileSet, we can allow other operations +// accepting the FileSet (such as the gcimporter) to add new files using the +// normal FileSet APIs without overlapping with cached parsed files. +// +// Note that 1<<60 represents an exabyte of parsed data, more than any gopls +// process can ever parse. +// +// On 32-bit systems we don't cache parse results (see parseFiles). +const reservedForParsing = 1 << (bits.UintSize - 4) + +// fileSetWithBase returns a new token.FileSet with Base() equal to the +// requested base. +// +// If base < 1, fileSetWithBase panics. +// (1 is the smallest permitted FileSet base). +func fileSetWithBase(base int) *token.FileSet { + fset := token.NewFileSet() + if base > 1 { + // Add a dummy file to set the base of fset. We won't ever use the + // resulting FileSet, so it doesn't matter how we achieve this. + // + // FileSets leave a 1-byte padding between files, so we set the base by + // adding a zero-length file at base-1. + fset.AddFile("", base-1, 0) + } + if fset.Base() != base { + panic("unexpected FileSet.Base") + } + return fset +} + +const ( + // Always keep 100 recent files, independent of their wall-clock age, to + // optimize the case where the user resumes editing after a delay. + parseCacheMinFiles = 100 +) + +// parsePadding is additional padding allocated to allow for increases in +// length (such as appending missing braces) caused by fixAST. +// +// This is used to mitigate a chicken and egg problem: we must know the base +// offset of the file we're about to parse, before we start parsing, and yet +// src fixups may affect the actual size of the parsed content (and therefore +// the offsets of subsequent files). +// +// When we encounter a file that no longer fits in its allocated space in the +// fileset, we have no choice but to re-parse it. Leaving a generous padding +// reduces the likelihood of this "slow path". +// +// This value is mutable for testing, so that we can exercise the slow path. +var parsePadding = 1000 // mutable for testing + +// A parseCache holds recently accessed parsed Go files. After new files are +// stored, older files may be evicted from the cache via garbage collection. +// +// The parseCache.parseFiles method exposes a batch API for parsing (and +// caching) multiple files. This is necessary for type-checking, where files +// must be parsed in a common fileset. +type parseCache struct { + expireAfter time.Duration // interval at which to collect expired cache entries + done chan struct{} // closed when GC is stopped + + mu sync.Mutex + m map[parseKey]*parseCacheEntry + lru queue // min-atime priority queue of *parseCacheEntry + clock uint64 // clock time, incremented when the cache is updated + nextBase int // base offset for the next parsed file +} + +// newParseCache creates a new parse cache and starts a goroutine to garbage +// collect entries whose age is at least expireAfter. +// +// Callers must call parseCache.stop when the parse cache is no longer in use. +func newParseCache(expireAfter time.Duration) *parseCache { + c := &parseCache{ + expireAfter: expireAfter, + m: make(map[parseKey]*parseCacheEntry), + done: make(chan struct{}), + } + go c.gc() + return c +} + +// stop causes the GC goroutine to exit. +func (c *parseCache) stop() { + close(c.done) +} + +// parseKey uniquely identifies a parsed Go file. +type parseKey struct { + uri protocol.DocumentURI + mode parser.Mode + purgeFuncBodies bool +} + +type parseCacheEntry struct { + key parseKey + hash file.Hash + promise *memoize.Promise // memoize.Promise[*parsego.File] + atime uint64 // clock time of last access, for use in LRU sorting + walltime time.Time // actual time of last access, for use in time-based eviction; too coarse for LRU on some systems + lruIndex int // owned by the queue implementation +} + +// startParse prepares a parsing pass, creating new promises in the cache for +// any cache misses. +// +// The resulting slice has an entry for every given file handle, though some +// entries may be nil if there was an error reading the file (in which case the +// resulting error will be non-nil). +func (c *parseCache) startParse(mode parser.Mode, purgeFuncBodies bool, fhs ...file.Handle) ([]*memoize.Promise, error) { + c.mu.Lock() + defer c.mu.Unlock() + + // Any parsing pass increments the clock, as we'll update access times. + // (technically, if fhs is empty this isn't necessary, but that's a degenerate case). + // + // All entries parsed from a single call get the same access time. + c.clock++ + walltime := time.Now() + + // Read file data and collect cacheable files. + var ( + data = make([][]byte, len(fhs)) // file content for each readable file + promises = make([]*memoize.Promise, len(fhs)) + firstReadError error // first error from fh.Read, or nil + ) + for i, fh := range fhs { + content, err := fh.Content() + if err != nil { + if firstReadError == nil { + firstReadError = err + } + continue + } + data[i] = content + + key := parseKey{ + uri: fh.URI(), + mode: mode, + purgeFuncBodies: purgeFuncBodies, + } + + if e, ok := c.m[key]; ok { + if e.hash == fh.Identity().Hash { // cache hit + e.atime = c.clock + e.walltime = walltime + heap.Fix(&c.lru, e.lruIndex) + promises[i] = e.promise + continue + } else { + // A cache hit, for a different version. Delete it. + delete(c.m, e.key) + heap.Remove(&c.lru, e.lruIndex) + } + } + + uri := fh.URI() + promise := memoize.NewPromise("parseCache.parse", func(ctx context.Context, _ interface{}) interface{} { + // Allocate 2*len(content)+parsePadding to allow for re-parsing once + // inside of parseGoSrc without exceeding the allocated space. + base, nextBase := c.allocateSpace(2*len(content) + parsePadding) + + pgf, fixes1 := parsego.Parse(ctx, fileSetWithBase(base), uri, content, mode, purgeFuncBodies) + file := pgf.Tok + if file.Base()+file.Size()+1 > nextBase { + // The parsed file exceeds its allocated space, likely due to multiple + // passes of src fixing. In this case, we have no choice but to re-do + // the operation with the correct size. + // + // Even though the final successful parse requires only file.Size() + // bytes of Pos space, we need to accommodate all the missteps to get + // there, as parseGoSrc will repeat them. + actual := file.Base() + file.Size() - base // actual size consumed, after re-parsing + base2, nextBase2 := c.allocateSpace(actual) + pgf2, fixes2 := parsego.Parse(ctx, fileSetWithBase(base2), uri, content, mode, purgeFuncBodies) + + // In golang/go#59097 we observed that this panic condition was hit. + // One bug was found and fixed, but record more information here in + // case there is still a bug here. + if end := pgf2.Tok.Base() + pgf2.Tok.Size(); end != nextBase2-1 { + var errBuf bytes.Buffer + fmt.Fprintf(&errBuf, "internal error: non-deterministic parsing result:\n") + fmt.Fprintf(&errBuf, "\t%q (%d-%d) does not span %d-%d\n", uri, pgf2.Tok.Base(), base2, end, nextBase2-1) + fmt.Fprintf(&errBuf, "\tfirst %q (%d-%d)\n", pgf.URI, pgf.Tok.Base(), pgf.Tok.Base()+pgf.Tok.Size()) + fmt.Fprintf(&errBuf, "\tfirst space: (%d-%d), second space: (%d-%d)\n", base, nextBase, base2, nextBase2) + fmt.Fprintf(&errBuf, "\tfirst mode: %v, second mode: %v", pgf.Mode, pgf2.Mode) + fmt.Fprintf(&errBuf, "\tfirst err: %v, second err: %v", pgf.ParseErr, pgf2.ParseErr) + fmt.Fprintf(&errBuf, "\tfirst fixes: %v, second fixes: %v", fixes1, fixes2) + panic(errBuf.String()) + } + pgf = pgf2 + } + return pgf + }) + promises[i] = promise + + // add new entry; entries are gc'ed asynchronously + e := &parseCacheEntry{ + key: key, + hash: fh.Identity().Hash, + promise: promise, + atime: c.clock, + walltime: walltime, + } + c.m[e.key] = e + heap.Push(&c.lru, e) + } + + if len(c.m) != len(c.lru) { + panic("map and LRU are inconsistent") + } + + return promises, firstReadError +} + +func (c *parseCache) gc() { + const period = 10 * time.Second // gc period + timer := time.NewTicker(period) + defer timer.Stop() + + for { + select { + case <-c.done: + return + case <-timer.C: + } + + c.gcOnce() + } +} + +func (c *parseCache) gcOnce() { + now := time.Now() + c.mu.Lock() + defer c.mu.Unlock() + + for len(c.m) > parseCacheMinFiles { + e := heap.Pop(&c.lru).(*parseCacheEntry) + if now.Sub(e.walltime) >= c.expireAfter { + delete(c.m, e.key) + } else { + heap.Push(&c.lru, e) + break + } + } +} + +// allocateSpace reserves the next n bytes of token.Pos space in the +// cache. +// +// It returns the resulting file base, next base, and an offset FileSet to use +// for parsing. +func (c *parseCache) allocateSpace(size int) (int, int) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.nextBase == 0 { + // FileSet base values must be at least 1. + c.nextBase = 1 + } + base := c.nextBase + c.nextBase += size + 1 + return base, c.nextBase +} + +// parseFiles returns a parsego.File for each file handle in fhs, in the +// requested parse mode. +// +// For parsed files that already exists in the cache, access time will be +// updated. For others, parseFiles will parse and store as many results in the +// cache as space allows. +// +// The token.File for each resulting parsed file will be added to the provided +// FileSet, using the tokeninternal.AddExistingFiles API. Consequently, the +// given fset should only be used in other APIs if its base is >= +// reservedForParsing. +// +// If parseFiles returns an error, it still returns a slice, +// but with a nil entry for each file that could not be parsed. +func (c *parseCache) parseFiles(ctx context.Context, fset *token.FileSet, mode parser.Mode, purgeFuncBodies bool, fhs ...file.Handle) ([]*parsego.File, error) { + pgfs := make([]*parsego.File, len(fhs)) + + // Temporary fall-back for 32-bit systems, where reservedForParsing is too + // small to be viable. We don't actually support 32-bit systems, so this + // workaround is only for tests and can be removed when we stop running + // 32-bit TryBots for gopls. + if bits.UintSize == 32 { + for i, fh := range fhs { + var err error + pgfs[i], err = parseGoImpl(ctx, fset, fh, mode, purgeFuncBodies) + if err != nil { + return pgfs, err + } + } + return pgfs, nil + } + + promises, firstErr := c.startParse(mode, purgeFuncBodies, fhs...) + + // Await all parsing. + var g errgroup.Group + g.SetLimit(runtime.GOMAXPROCS(-1)) // parsing is CPU-bound. + for i, promise := range promises { + if promise == nil { + continue + } + i := i + promise := promise + g.Go(func() error { + result, err := promise.Get(ctx, nil) + if err != nil { + return err + } + pgfs[i] = result.(*parsego.File) + return nil + }) + } + + if err := g.Wait(); err != nil && firstErr == nil { + firstErr = err + } + + // Augment the FileSet to map all parsed files. + var tokenFiles []*token.File + for _, pgf := range pgfs { + if pgf == nil { + continue + } + tokenFiles = append(tokenFiles, pgf.Tok) + } + tokeninternal.AddExistingFiles(fset, tokenFiles) + + const debugIssue59080 = true + if debugIssue59080 { + for _, f := range tokenFiles { + pos := token.Pos(f.Base()) + f2 := fset.File(pos) + if f2 != f { + panic(fmt.Sprintf("internal error: File(%d (start)) = %v, not %v", pos, f2, f)) + } + pos = token.Pos(f.Base() + f.Size()) + f2 = fset.File(pos) + if f2 != f { + panic(fmt.Sprintf("internal error: File(%d (end)) = %v, not %v", pos, f2, f)) + } + } + } + + return pgfs, firstErr +} + +// -- priority queue boilerplate -- + +// queue is a min-atime prority queue of cache entries. +type queue []*parseCacheEntry + +func (q queue) Len() int { return len(q) } + +func (q queue) Less(i, j int) bool { return q[i].atime < q[j].atime } + +func (q queue) Swap(i, j int) { + q[i], q[j] = q[j], q[i] + q[i].lruIndex = i + q[j].lruIndex = j +} + +func (q *queue) Push(x interface{}) { + e := x.(*parseCacheEntry) + e.lruIndex = len(*q) + *q = append(*q, e) +} + +func (q *queue) Pop() interface{} { + last := len(*q) - 1 + e := (*q)[last] + (*q)[last] = nil // aid GC + *q = (*q)[:last] + return e +} diff --git a/contribs/gnopls/internal/cache/parse_cache_test.go b/contribs/gnopls/internal/cache/parse_cache_test.go new file mode 100644 index 00000000000..7aefac77c38 --- /dev/null +++ b/contribs/gnopls/internal/cache/parse_cache_test.go @@ -0,0 +1,234 @@ +// 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 cache + +import ( + "context" + "fmt" + "go/token" + "math/bits" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" +) + +func skipIfNoParseCache(t *testing.T) { + if bits.UintSize == 32 { + t.Skip("the parse cache is not supported on 32-bit systems") + } +} + +func TestParseCache(t *testing.T) { + skipIfNoParseCache(t) + + ctx := context.Background() + uri := protocol.DocumentURI("file:///myfile") + fh := makeFakeFileHandle(uri, []byte("package p\n\nconst _ = \"foo\"")) + fset := token.NewFileSet() + + cache := newParseCache(0) + pgfs1, err := cache.parseFiles(ctx, fset, parsego.Full, false, fh) + if err != nil { + t.Fatal(err) + } + pgf1 := pgfs1[0] + pgfs2, err := cache.parseFiles(ctx, fset, parsego.Full, false, fh) + pgf2 := pgfs2[0] + if err != nil { + t.Fatal(err) + } + if pgf1 != pgf2 { + t.Errorf("parseFiles(%q): unexpected cache miss on repeated call", uri) + } + + // Fill up the cache with other files, but don't evict the file above. + cache.gcOnce() + files := []file.Handle{fh} + files = append(files, dummyFileHandles(parseCacheMinFiles-1)...) + + pgfs3, err := cache.parseFiles(ctx, fset, parsego.Full, false, files...) + if err != nil { + t.Fatal(err) + } + pgf3 := pgfs3[0] + if pgf3 != pgf1 { + t.Errorf("parseFiles(%q, ...): unexpected cache miss", uri) + } + if pgf3.Tok.Base() != pgf1.Tok.Base() || pgf3.Tok.Size() != pgf1.Tok.Size() { + t.Errorf("parseFiles(%q, ...): result.Tok has base: %d, size: %d, want (%d, %d)", uri, pgf3.Tok.Base(), pgf3.Tok.Size(), pgf1.Tok.Base(), pgf1.Tok.Size()) + } + if tok := fset.File(token.Pos(pgf3.Tok.Base())); tok != pgf3.Tok { + t.Errorf("parseFiles(%q, ...): result.Tok not contained in FileSet", uri) + } + + // Now overwrite the cache, after which we should get new results. + cache.gcOnce() + files = dummyFileHandles(parseCacheMinFiles) + _, err = cache.parseFiles(ctx, fset, parsego.Full, false, files...) + if err != nil { + t.Fatal(err) + } + // force a GC, which should collect the recently parsed files + cache.gcOnce() + pgfs4, err := cache.parseFiles(ctx, fset, parsego.Full, false, fh) + if err != nil { + t.Fatal(err) + } + if pgfs4[0] == pgf1 { + t.Errorf("parseFiles(%q): unexpected cache hit after overwriting cache", uri) + } +} + +func TestParseCache_Reparsing(t *testing.T) { + skipIfNoParseCache(t) + + defer func(padding int) { + parsePadding = padding + }(parsePadding) + parsePadding = 0 + + files := dummyFileHandles(parseCacheMinFiles) + danglingSelector := []byte("package p\nfunc _() {\n\tx.\n}") + files = append(files, makeFakeFileHandle("file:///bad1", danglingSelector)) + files = append(files, makeFakeFileHandle("file:///bad2", danglingSelector)) + + // Parsing should succeed even though we overflow the padding. + cache := newParseCache(0) + _, err := cache.parseFiles(context.Background(), token.NewFileSet(), parsego.Full, false, files...) + if err != nil { + t.Fatal(err) + } +} + +// Re-parsing the first file should not panic. +func TestParseCache_Issue59097(t *testing.T) { + skipIfNoParseCache(t) + + defer func(padding int) { + parsePadding = padding + }(parsePadding) + parsePadding = 0 + + danglingSelector := []byte("package p\nfunc _() {\n\tx.\n}") + files := []file.Handle{makeFakeFileHandle("file:///bad", danglingSelector)} + + // Parsing should succeed even though we overflow the padding. + cache := newParseCache(0) + _, err := cache.parseFiles(context.Background(), token.NewFileSet(), parsego.Full, false, files...) + if err != nil { + t.Fatal(err) + } +} + +func TestParseCache_TimeEviction(t *testing.T) { + skipIfNoParseCache(t) + + ctx := context.Background() + fset := token.NewFileSet() + uri := protocol.DocumentURI("file:///myfile") + fh := makeFakeFileHandle(uri, []byte("package p\n\nconst _ = \"foo\"")) + + const gcDuration = 10 * time.Millisecond + cache := newParseCache(gcDuration) + cache.stop() // we'll manage GC manually, for testing. + + pgfs0, err := cache.parseFiles(ctx, fset, parsego.Full, false, fh, fh) + if err != nil { + t.Fatal(err) + } + + files := dummyFileHandles(parseCacheMinFiles) + _, err = cache.parseFiles(ctx, fset, parsego.Full, false, files...) + if err != nil { + t.Fatal(err) + } + + // Even after filling up the 'min' files, we get a cache hit for our original file. + pgfs1, err := cache.parseFiles(ctx, fset, parsego.Full, false, fh, fh) + if err != nil { + t.Fatal(err) + } + + if pgfs0[0] != pgfs1[0] { + t.Errorf("before GC, got unexpected cache miss") + } + + // But after GC, we get a cache miss. + _, err = cache.parseFiles(ctx, fset, parsego.Full, false, files...) // mark dummy files as newer + if err != nil { + t.Fatal(err) + } + time.Sleep(gcDuration) + cache.gcOnce() + + pgfs2, err := cache.parseFiles(ctx, fset, parsego.Full, false, fh, fh) + if err != nil { + t.Fatal(err) + } + + if pgfs0[0] == pgfs2[0] { + t.Errorf("after GC, got unexpected cache hit for %s", pgfs0[0].URI) + } +} + +func TestParseCache_Duplicates(t *testing.T) { + skipIfNoParseCache(t) + + ctx := context.Background() + uri := protocol.DocumentURI("file:///myfile") + fh := makeFakeFileHandle(uri, []byte("package p\n\nconst _ = \"foo\"")) + + cache := newParseCache(0) + pgfs, err := cache.parseFiles(ctx, token.NewFileSet(), parsego.Full, false, fh, fh) + if err != nil { + t.Fatal(err) + } + if pgfs[0] != pgfs[1] { + t.Errorf("parseFiles(fh, fh): = [%p, %p], want duplicate files", pgfs[0].File, pgfs[1].File) + } +} + +func dummyFileHandles(n int) []file.Handle { + var fhs []file.Handle + for i := 0; i < n; i++ { + uri := protocol.DocumentURI(fmt.Sprintf("file:///_%d", i)) + src := []byte(fmt.Sprintf("package p\nvar _ = %d", i)) + fhs = append(fhs, makeFakeFileHandle(uri, src)) + } + return fhs +} + +func makeFakeFileHandle(uri protocol.DocumentURI, src []byte) fakeFileHandle { + return fakeFileHandle{ + uri: uri, + data: src, + hash: file.HashOf(src), + } +} + +type fakeFileHandle struct { + file.Handle + uri protocol.DocumentURI + data []byte + hash file.Hash +} + +func (h fakeFileHandle) URI() protocol.DocumentURI { + return h.uri +} + +func (h fakeFileHandle) Content() ([]byte, error) { + return h.data, nil +} + +func (h fakeFileHandle) Identity() file.Identity { + return file.Identity{ + URI: h.uri, + Hash: h.hash, + } +} diff --git a/contribs/gnopls/internal/cache/parsego/file.go b/contribs/gnopls/internal/cache/parsego/file.go new file mode 100644 index 00000000000..b03929e6c86 --- /dev/null +++ b/contribs/gnopls/internal/cache/parsego/file.go @@ -0,0 +1,102 @@ +// 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 parsego + +import ( + "go/ast" + "go/parser" + "go/scanner" + "go/token" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/safetoken" +) + +// A File contains the results of parsing a Go file. +type File struct { + URI protocol.DocumentURI + Mode parser.Mode + File *ast.File + Tok *token.File + // Source code used to build the AST. It may be different from the + // actual content of the file if we have fixed the AST. + Src []byte + + // fixedSrc and fixedAST report on "fixing" that occurred during parsing of + // this file. + // + // fixedSrc means Src holds file content that was modified to improve parsing. + // fixedAST means File was modified after parsing, so AST positions may not + // reflect the content of Src. + // + // TODO(rfindley): there are many places where we haphazardly use the Src or + // positions without checking these fields. Audit these places and guard + // accordingly. After doing so, we may find that we don't need to + // differentiate fixedSrc and fixedAST. + fixedSrc bool + fixedAST bool + Mapper *protocol.Mapper // may map fixed Src, not file content + ParseErr scanner.ErrorList +} + +func (pgf File) String() string { return string(pgf.URI) } + +// Fixed reports whether p was "Fixed", meaning that its source or positions +// may not correlate with the original file. +func (pgf File) Fixed() bool { + return pgf.fixedSrc || pgf.fixedAST +} + +// -- go/token domain convenience helpers -- + +// PositionPos returns the token.Pos of protocol position p within the file. +func (pgf *File) PositionPos(p protocol.Position) (token.Pos, error) { + offset, err := pgf.Mapper.PositionOffset(p) + if err != nil { + return token.NoPos, err + } + return safetoken.Pos(pgf.Tok, offset) +} + +// PosRange returns a protocol Range for the token.Pos interval in this file. +func (pgf *File) PosRange(start, end token.Pos) (protocol.Range, error) { + return pgf.Mapper.PosRange(pgf.Tok, start, end) +} + +// PosMappedRange returns a MappedRange for the token.Pos interval in this file. +// A MappedRange can be converted to any other form. +func (pgf *File) PosMappedRange(start, end token.Pos) (protocol.MappedRange, error) { + return pgf.Mapper.PosMappedRange(pgf.Tok, start, end) +} + +// PosLocation returns a protocol Location for the token.Pos interval in this file. +func (pgf *File) PosLocation(start, end token.Pos) (protocol.Location, error) { + return pgf.Mapper.PosLocation(pgf.Tok, start, end) +} + +// NodeRange returns a protocol Range for the ast.Node interval in this file. +func (pgf *File) NodeRange(node ast.Node) (protocol.Range, error) { + return pgf.Mapper.NodeRange(pgf.Tok, node) +} + +// NodeMappedRange returns a MappedRange for the ast.Node interval in this file. +// A MappedRange can be converted to any other form. +func (pgf *File) NodeMappedRange(node ast.Node) (protocol.MappedRange, error) { + return pgf.Mapper.NodeMappedRange(pgf.Tok, node) +} + +// NodeLocation returns a protocol Location for the ast.Node interval in this file. +func (pgf *File) NodeLocation(node ast.Node) (protocol.Location, error) { + return pgf.Mapper.PosLocation(pgf.Tok, node.Pos(), node.End()) +} + +// RangePos parses a protocol Range back into the go/token domain. +func (pgf *File) RangePos(r protocol.Range) (token.Pos, token.Pos, error) { + start, end, err := pgf.Mapper.RangeOffsets(r) + if err != nil { + return token.NoPos, token.NoPos, err + } + return pgf.Tok.Pos(start), pgf.Tok.Pos(end), nil +} diff --git a/contribs/gnopls/internal/cache/parsego/parse.go b/contribs/gnopls/internal/cache/parsego/parse.go new file mode 100644 index 00000000000..0143a36ab71 --- /dev/null +++ b/contribs/gnopls/internal/cache/parsego/parse.go @@ -0,0 +1,967 @@ +// 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 parsego + +import ( + "bytes" + "context" + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "reflect" + + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/event" +) + +// Common parse modes; these should be reused wherever possible to increase +// cache hits. +const ( + // Header specifies that the main package declaration and imports are needed. + // This is the mode used when attempting to examine the package graph structure. + Header = parser.AllErrors | parser.ParseComments | parser.ImportsOnly | parser.SkipObjectResolution + + // Full specifies the full AST is needed. + // This is used for files of direct interest where the entire contents must + // be considered. + Full = parser.AllErrors | parser.ParseComments | parser.SkipObjectResolution +) + +// Parse parses a buffer of Go source, repairing the tree if necessary. +// +// The provided ctx is used only for logging. +func Parse(ctx context.Context, fset *token.FileSet, uri protocol.DocumentURI, src []byte, mode parser.Mode, purgeFuncBodies bool) (res *File, fixes []fixType) { + if purgeFuncBodies { + src = astutil.PurgeFuncBodies(src) + } + ctx, done := event.Start(ctx, "cache.ParseGoSrc", label.File.Of(uri.Path())) + defer done() + + file, err := parser.ParseFile(fset, uri.Path(), src, mode) + var parseErr scanner.ErrorList + if err != nil { + // We passed a byte slice, so the only possible error is a parse error. + parseErr = err.(scanner.ErrorList) + } + + tok := fset.File(file.Pos()) + if tok == nil { + // file.Pos is the location of the package declaration (issue #53202). If there was + // none, we can't find the token.File that ParseFile created, and we + // have no choice but to recreate it. + tok = fset.AddFile(uri.Path(), -1, len(src)) + tok.SetLinesForContent(src) + } + + fixedSrc := false + fixedAST := false + // If there were parse errors, attempt to fix them up. + if parseErr != nil { + // Fix any badly parsed parts of the AST. + astFixes := fixAST(file, tok, src) + fixedAST = len(astFixes) > 0 + if fixedAST { + fixes = append(fixes, astFixes...) + } + + for i := 0; i < 10; i++ { + // Fix certain syntax errors that render the file unparseable. + newSrc, srcFix := fixSrc(file, tok, src) + if newSrc == nil { + break + } + + // If we thought there was something to fix 10 times in a row, + // it is likely we got stuck in a loop somehow. Log out a diff + // of the last changes we made to aid in debugging. + if i == 9 { + unified := diff.Unified("before", "after", string(src), string(newSrc)) + event.Log(ctx, fmt.Sprintf("fixSrc loop - last diff:\n%v", unified), label.File.Of(tok.Name())) + } + + newFile, newErr := parser.ParseFile(fset, uri.Path(), newSrc, mode) + if newFile == nil { + break // no progress + } + + // Maintain the original parseError so we don't try formatting the + // doctored file. + file = newFile + src = newSrc + tok = fset.File(file.Pos()) + + // Only now that we accept the fix do we record the src fix from above. + fixes = append(fixes, srcFix) + fixedSrc = true + + if newErr == nil { + break // nothing to fix + } + + // Note that fixedAST is reset after we fix src. + astFixes = fixAST(file, tok, src) + fixedAST = len(astFixes) > 0 + if fixedAST { + fixes = append(fixes, astFixes...) + } + } + } + + return &File{ + URI: uri, + Mode: mode, + Src: src, + fixedSrc: fixedSrc, + fixedAST: fixedAST, + File: file, + Tok: tok, + Mapper: protocol.NewMapper(uri, src), + ParseErr: parseErr, + }, fixes +} + +// fixAST inspects the AST and potentially modifies any *ast.BadStmts so that it can be +// type-checked more effectively. +// +// If fixAST returns true, the resulting AST is considered "fixed", meaning +// positions have been mangled, and type checker errors may not make sense. +func fixAST(n ast.Node, tok *token.File, src []byte) (fixes []fixType) { + var err error + walkASTWithParent(n, func(n, parent ast.Node) bool { + switch n := n.(type) { + case *ast.BadStmt: + if fixDeferOrGoStmt(n, parent, tok, src) { + fixes = append(fixes, fixedDeferOrGo) + // Recursively fix in our fixed node. + moreFixes := fixAST(parent, tok, src) + fixes = append(fixes, moreFixes...) + } else { + err = fmt.Errorf("unable to parse defer or go from *ast.BadStmt: %v", err) + } + return false + case *ast.BadExpr: + if fixArrayType(n, parent, tok, src) { + fixes = append(fixes, fixedArrayType) + // Recursively fix in our fixed node. + moreFixes := fixAST(parent, tok, src) + fixes = append(fixes, moreFixes...) + return false + } + + // Fix cases where parser interprets if/for/switch "init" + // statement as "cond" expression, e.g.: + // + // // "i := foo" is init statement, not condition. + // for i := foo + // + if fixInitStmt(n, parent, tok, src) { + fixes = append(fixes, fixedInit) + } + return false + case *ast.SelectorExpr: + // Fix cases where a keyword prefix results in a phantom "_" selector, e.g.: + // + // foo.var<> // want to complete to "foo.variance" + // + if fixPhantomSelector(n, tok, src) { + fixes = append(fixes, fixedPhantomSelector) + } + return true + + case *ast.BlockStmt: + switch parent.(type) { + case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt: + // Adjust closing curly brace of empty switch/select + // statements so we can complete inside them. + if fixEmptySwitch(n, tok, src) { + fixes = append(fixes, fixedEmptySwitch) + } + } + + return true + default: + return true + } + }) + return fixes +} + +// walkASTWithParent walks the AST rooted at n. The semantics are +// similar to ast.Inspect except it does not call f(nil). +func walkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) { + var ancestors []ast.Node + ast.Inspect(n, func(n ast.Node) (recurse bool) { + defer func() { + if recurse { + ancestors = append(ancestors, n) + } + }() + + if n == nil { + ancestors = ancestors[:len(ancestors)-1] + return false + } + + var parent ast.Node + if len(ancestors) > 0 { + parent = ancestors[len(ancestors)-1] + } + + return f(n, parent) + }) +} + +// TODO(rfindley): revert this intrumentation once we're certain the crash in +// #59097 is fixed. +type fixType int + +const ( + noFix fixType = iota + fixedCurlies + fixedDanglingSelector + fixedDeferOrGo + fixedArrayType + fixedInit + fixedPhantomSelector + fixedEmptySwitch +) + +// fixSrc attempts to modify the file's source code to fix certain +// syntax errors that leave the rest of the file unparsed. +// +// fixSrc returns a non-nil result if and only if a fix was applied. +func fixSrc(f *ast.File, tf *token.File, src []byte) (newSrc []byte, fix fixType) { + walkASTWithParent(f, func(n, parent ast.Node) bool { + if newSrc != nil { + return false + } + + switch n := n.(type) { + case *ast.BlockStmt: + newSrc = fixMissingCurlies(f, n, parent, tf, src) + if newSrc != nil { + fix = fixedCurlies + } + case *ast.SelectorExpr: + newSrc = fixDanglingSelector(n, tf, src) + if newSrc != nil { + fix = fixedDanglingSelector + } + } + + return newSrc == nil + }) + + return newSrc, fix +} + +// fixMissingCurlies adds in curly braces for block statements that +// are missing curly braces. For example: +// +// if foo +// +// becomes +// +// if foo {} +func fixMissingCurlies(f *ast.File, b *ast.BlockStmt, parent ast.Node, tok *token.File, src []byte) []byte { + // If the "{" is already in the source code, there isn't anything to + // fix since we aren't missing curlies. + if b.Lbrace.IsValid() { + braceOffset, err := safetoken.Offset(tok, b.Lbrace) + if err != nil { + return nil + } + if braceOffset < len(src) && src[braceOffset] == '{' { + return nil + } + } + + parentLine := safetoken.Line(tok, parent.Pos()) + + if parentLine >= tok.LineCount() { + // If we are the last line in the file, no need to fix anything. + return nil + } + + // Insert curlies at the end of parent's starting line. The parent + // is the statement that contains the block, e.g. *ast.IfStmt. The + // block's Pos()/End() can't be relied upon because they are based + // on the (missing) curly braces. We assume the statement is a + // single line for now and try sticking the curly braces at the end. + insertPos := tok.LineStart(parentLine+1) - 1 + + // Scootch position backwards until it's not in a comment. For example: + // + // if foo<> // some amazing comment | + // someOtherCode() + // + // insertPos will be located at "|", so we back it out of the comment. + didSomething := true + for didSomething { + didSomething = false + for _, c := range f.Comments { + if c.Pos() < insertPos && insertPos <= c.End() { + insertPos = c.Pos() + didSomething = true + } + } + } + + // Bail out if line doesn't end in an ident or ".". This is to avoid + // cases like below where we end up making things worse by adding + // curlies: + // + // if foo && + // bar<> + switch precedingToken(insertPos, tok, src) { + case token.IDENT, token.PERIOD: + // ok + default: + return nil + } + + var buf bytes.Buffer + buf.Grow(len(src) + 3) + offset, err := safetoken.Offset(tok, insertPos) + if err != nil { + return nil + } + buf.Write(src[:offset]) + + // Detect if we need to insert a semicolon to fix "for" loop situations like: + // + // for i := foo(); foo<> + // + // Just adding curlies is not sufficient to make things parse well. + if fs, ok := parent.(*ast.ForStmt); ok { + if _, ok := fs.Cond.(*ast.BadExpr); !ok { + if xs, ok := fs.Post.(*ast.ExprStmt); ok { + if _, ok := xs.X.(*ast.BadExpr); ok { + buf.WriteByte(';') + } + } + } + } + + // Insert "{}" at insertPos. + buf.WriteByte('{') + buf.WriteByte('}') + buf.Write(src[offset:]) + return buf.Bytes() +} + +// fixEmptySwitch moves empty switch/select statements' closing curly +// brace down one line. This allows us to properly detect incomplete +// "case" and "default" keywords as inside the switch statement. For +// example: +// +// switch { +// def<> +// } +// +// gets parsed like: +// +// switch { +// } +// +// Later we manually pull out the "def" token, but we need to detect +// that our "<>" position is inside the switch block. To do that we +// move the curly brace so it looks like: +// +// switch { +// +// } +// +// The resulting bool reports whether any fixing occurred. +func fixEmptySwitch(body *ast.BlockStmt, tok *token.File, src []byte) bool { + // We only care about empty switch statements. + if len(body.List) > 0 || !body.Rbrace.IsValid() { + return false + } + + // If the right brace is actually in the source code at the + // specified position, don't mess with it. + braceOffset, err := safetoken.Offset(tok, body.Rbrace) + if err != nil { + return false + } + if braceOffset < len(src) && src[braceOffset] == '}' { + return false + } + + braceLine := safetoken.Line(tok, body.Rbrace) + if braceLine >= tok.LineCount() { + // If we are the last line in the file, no need to fix anything. + return false + } + + // Move the right brace down one line. + body.Rbrace = tok.LineStart(braceLine + 1) + return true +} + +// fixDanglingSelector inserts real "_" selector expressions in place +// of phantom "_" selectors. For example: +// +// func _() { +// x.<> +// } +// +// var x struct { i int } +// +// To fix completion at "<>", we insert a real "_" after the "." so the +// following declaration of "x" can be parsed and type checked +// normally. +func fixDanglingSelector(s *ast.SelectorExpr, tf *token.File, src []byte) []byte { + if !isPhantomUnderscore(s.Sel, tf, src) { + return nil + } + + if !s.X.End().IsValid() { + return nil + } + + insertOffset, err := safetoken.Offset(tf, s.X.End()) + if err != nil { + return nil + } + // Insert directly after the selector's ".". + insertOffset++ + if src[insertOffset-1] != '.' { + return nil + } + + var buf bytes.Buffer + buf.Grow(len(src) + 1) + buf.Write(src[:insertOffset]) + buf.WriteByte('_') + buf.Write(src[insertOffset:]) + return buf.Bytes() +} + +// fixPhantomSelector tries to fix selector expressions with phantom +// "_" selectors. In particular, we check if the selector is a +// keyword, and if so we swap in an *ast.Ident with the keyword text. For example: +// +// foo.var +// +// yields a "_" selector instead of "var" since "var" is a keyword. +// +// TODO(rfindley): should this constitute an ast 'fix'? +// +// The resulting bool reports whether any fixing occurred. +func fixPhantomSelector(sel *ast.SelectorExpr, tf *token.File, src []byte) bool { + if !isPhantomUnderscore(sel.Sel, tf, src) { + return false + } + + // Only consider selectors directly abutting the selector ".". This + // avoids false positives in cases like: + // + // foo. // don't think "var" is our selector + // var bar = 123 + // + if sel.Sel.Pos() != sel.X.End()+1 { + return false + } + + maybeKeyword := readKeyword(sel.Sel.Pos(), tf, src) + if maybeKeyword == "" { + return false + } + + return replaceNode(sel, sel.Sel, &ast.Ident{ + Name: maybeKeyword, + NamePos: sel.Sel.Pos(), + }) +} + +// isPhantomUnderscore reports whether the given ident is a phantom +// underscore. The parser sometimes inserts phantom underscores when +// it encounters otherwise unparseable situations. +func isPhantomUnderscore(id *ast.Ident, tok *token.File, src []byte) bool { + if id == nil || id.Name != "_" { + return false + } + + // Phantom underscore means the underscore is not actually in the + // program text. + offset, err := safetoken.Offset(tok, id.Pos()) + if err != nil { + return false + } + return len(src) <= offset || src[offset] != '_' +} + +// fixInitStmt fixes cases where the parser misinterprets an +// if/for/switch "init" statement as the "cond" conditional. In cases +// like "if i := 0" the user hasn't typed the semicolon yet so the +// parser is looking for the conditional expression. However, "i := 0" +// are not valid expressions, so we get a BadExpr. +// +// The resulting bool reports whether any fixing occurred. +func fixInitStmt(bad *ast.BadExpr, parent ast.Node, tok *token.File, src []byte) bool { + if !bad.Pos().IsValid() || !bad.End().IsValid() { + return false + } + + // Try to extract a statement from the BadExpr. + start, end, err := safetoken.Offsets(tok, bad.Pos(), bad.End()-1) + if err != nil { + return false + } + stmtBytes := src[start : end+1] + stmt, err := parseStmt(tok, bad.Pos(), stmtBytes) + if err != nil { + return false + } + + // If the parent statement doesn't already have an "init" statement, + // move the extracted statement into the "init" field and insert a + // dummy expression into the required "cond" field. + switch p := parent.(type) { + case *ast.IfStmt: + if p.Init != nil { + return false + } + p.Init = stmt + p.Cond = &ast.Ident{ + Name: "_", + NamePos: stmt.End(), + } + return true + case *ast.ForStmt: + if p.Init != nil { + return false + } + p.Init = stmt + p.Cond = &ast.Ident{ + Name: "_", + NamePos: stmt.End(), + } + return true + case *ast.SwitchStmt: + if p.Init != nil { + return false + } + p.Init = stmt + p.Tag = nil + return true + } + return false +} + +// readKeyword reads the keyword starting at pos, if any. +func readKeyword(pos token.Pos, tok *token.File, src []byte) string { + var kwBytes []byte + offset, err := safetoken.Offset(tok, pos) + if err != nil { + return "" + } + for i := offset; i < len(src); i++ { + // Use a simplified identifier check since keywords are always lowercase ASCII. + if src[i] < 'a' || src[i] > 'z' { + break + } + kwBytes = append(kwBytes, src[i]) + + // Stop search at arbitrarily chosen too-long-for-a-keyword length. + if len(kwBytes) > 15 { + return "" + } + } + + if kw := string(kwBytes); token.Lookup(kw).IsKeyword() { + return kw + } + + return "" +} + +// fixArrayType tries to parse an *ast.BadExpr into an *ast.ArrayType. +// go/parser often turns lone array types like "[]int" into BadExprs +// if it isn't expecting a type. +func fixArrayType(bad *ast.BadExpr, parent ast.Node, tok *token.File, src []byte) bool { + // Our expected input is a bad expression that looks like "[]someExpr". + + from := bad.Pos() + to := bad.End() + + if !from.IsValid() || !to.IsValid() { + return false + } + + exprBytes := make([]byte, 0, int(to-from)+3) + // Avoid doing tok.Offset(to) since that panics if badExpr ends at EOF. + // It also panics if the position is not in the range of the file, and + // badExprs may not necessarily have good positions, so check first. + fromOffset, toOffset, err := safetoken.Offsets(tok, from, to-1) + if err != nil { + return false + } + exprBytes = append(exprBytes, src[fromOffset:toOffset+1]...) + exprBytes = bytes.TrimSpace(exprBytes) + + // If our expression ends in "]" (e.g. "[]"), add a phantom selector + // so we can complete directly after the "[]". + if len(exprBytes) > 0 && exprBytes[len(exprBytes)-1] == ']' { + exprBytes = append(exprBytes, '_') + } + + // Add "{}" to turn our ArrayType into a CompositeLit. This is to + // handle the case of "[...]int" where we must make it a composite + // literal to be parseable. + exprBytes = append(exprBytes, '{', '}') + + expr, err := parseExpr(tok, from, exprBytes) + if err != nil { + return false + } + + cl, _ := expr.(*ast.CompositeLit) + if cl == nil { + return false + } + + at, _ := cl.Type.(*ast.ArrayType) + if at == nil { + return false + } + + return replaceNode(parent, bad, at) +} + +// precedingToken scans src to find the token preceding pos. +func precedingToken(pos token.Pos, tok *token.File, src []byte) token.Token { + s := &scanner.Scanner{} + s.Init(tok, src, nil, 0) + + var lastTok token.Token + for { + p, t, _ := s.Scan() + if t == token.EOF || p >= pos { + break + } + + lastTok = t + } + return lastTok +} + +// fixDeferOrGoStmt tries to parse an *ast.BadStmt into a defer or a go statement. +// +// go/parser packages a statement of the form "defer x." as an *ast.BadStmt because +// it does not include a call expression. This means that go/types skips type-checking +// this statement entirely, and we can't use the type information when completing. +// Here, we try to generate a fake *ast.DeferStmt or *ast.GoStmt to put into the AST, +// instead of the *ast.BadStmt. +func fixDeferOrGoStmt(bad *ast.BadStmt, parent ast.Node, tok *token.File, src []byte) bool { + // Check if we have a bad statement containing either a "go" or "defer". + s := &scanner.Scanner{} + s.Init(tok, src, nil, 0) + + var ( + pos token.Pos + tkn token.Token + ) + for { + if tkn == token.EOF { + return false + } + if pos >= bad.From { + break + } + pos, tkn, _ = s.Scan() + } + + var stmt ast.Stmt + switch tkn { + case token.DEFER: + stmt = &ast.DeferStmt{ + Defer: pos, + } + case token.GO: + stmt = &ast.GoStmt{ + Go: pos, + } + default: + return false + } + + var ( + from, to, last token.Pos + lastToken token.Token + braceDepth int + phantomSelectors []token.Pos + ) +FindTo: + for { + to, tkn, _ = s.Scan() + + if from == token.NoPos { + from = to + } + + switch tkn { + case token.EOF: + break FindTo + case token.SEMICOLON: + // If we aren't in nested braces, end of statement means + // end of expression. + if braceDepth == 0 { + break FindTo + } + case token.LBRACE: + braceDepth++ + } + + // This handles the common dangling selector case. For example in + // + // defer fmt. + // y := 1 + // + // we notice the dangling period and end our expression. + // + // If the previous token was a "." and we are looking at a "}", + // the period is likely a dangling selector and needs a phantom + // "_". Likewise if the current token is on a different line than + // the period, the period is likely a dangling selector. + if lastToken == token.PERIOD && (tkn == token.RBRACE || safetoken.Line(tok, to) > safetoken.Line(tok, last)) { + // Insert phantom "_" selector after the dangling ".". + phantomSelectors = append(phantomSelectors, last+1) + // If we aren't in a block then end the expression after the ".". + if braceDepth == 0 { + to = last + 1 + break + } + } + + lastToken = tkn + last = to + + switch tkn { + case token.RBRACE: + braceDepth-- + if braceDepth <= 0 { + if braceDepth == 0 { + // +1 to include the "}" itself. + to += 1 + } + break FindTo + } + } + } + + fromOffset, toOffset, err := safetoken.Offsets(tok, from, to) + if err != nil { + return false + } + if !from.IsValid() || fromOffset >= len(src) { + return false + } + if !to.IsValid() || toOffset >= len(src) { + return false + } + + // Insert any phantom selectors needed to prevent dangling "." from messing + // up the AST. + exprBytes := make([]byte, 0, int(to-from)+len(phantomSelectors)) + for i, b := range src[fromOffset:toOffset] { + if len(phantomSelectors) > 0 && from+token.Pos(i) == phantomSelectors[0] { + exprBytes = append(exprBytes, '_') + phantomSelectors = phantomSelectors[1:] + } + exprBytes = append(exprBytes, b) + } + + if len(phantomSelectors) > 0 { + exprBytes = append(exprBytes, '_') + } + + expr, err := parseExpr(tok, from, exprBytes) + if err != nil { + return false + } + + // Package the expression into a fake *ast.CallExpr and re-insert + // into the function. + call := &ast.CallExpr{ + Fun: expr, + Lparen: to, + Rparen: to, + } + + switch stmt := stmt.(type) { + case *ast.DeferStmt: + stmt.Call = call + case *ast.GoStmt: + stmt.Call = call + } + + return replaceNode(parent, bad, stmt) +} + +// parseStmt parses the statement in src and updates its position to +// start at pos. +// +// tok is the original file containing pos. Used to ensure that all adjusted +// positions are valid. +func parseStmt(tok *token.File, pos token.Pos, src []byte) (ast.Stmt, error) { + // Wrap our expression to make it a valid Go file we can pass to ParseFile. + fileSrc := bytes.Join([][]byte{ + []byte("package fake;func _(){"), + src, + []byte("}"), + }, nil) + + // Use ParseFile instead of ParseExpr because ParseFile has + // best-effort behavior, whereas ParseExpr fails hard on any error. + fakeFile, err := parser.ParseFile(token.NewFileSet(), "", fileSrc, 0) + if fakeFile == nil { + return nil, fmt.Errorf("error reading fake file source: %v", err) + } + + // Extract our expression node from inside the fake file. + if len(fakeFile.Decls) == 0 { + return nil, fmt.Errorf("error parsing fake file: %v", err) + } + + fakeDecl, _ := fakeFile.Decls[0].(*ast.FuncDecl) + if fakeDecl == nil || len(fakeDecl.Body.List) == 0 { + return nil, fmt.Errorf("no statement in %s: %v", src, err) + } + + stmt := fakeDecl.Body.List[0] + + // parser.ParseFile returns undefined positions. + // Adjust them for the current file. + offsetPositions(tok, stmt, pos-1-(stmt.Pos()-1)) + + return stmt, nil +} + +// parseExpr parses the expression in src and updates its position to +// start at pos. +func parseExpr(tok *token.File, pos token.Pos, src []byte) (ast.Expr, error) { + stmt, err := parseStmt(tok, pos, src) + if err != nil { + return nil, err + } + + exprStmt, ok := stmt.(*ast.ExprStmt) + if !ok { + return nil, fmt.Errorf("no expr in %s: %v", src, err) + } + + return exprStmt.X, nil +} + +var tokenPosType = reflect.TypeOf(token.NoPos) + +// offsetPositions applies an offset to the positions in an ast.Node. +func offsetPositions(tok *token.File, n ast.Node, offset token.Pos) { + fileBase := int64(tok.Base()) + fileEnd := fileBase + int64(tok.Size()) + ast.Inspect(n, func(n ast.Node) bool { + if n == nil { + return false + } + + v := reflect.ValueOf(n).Elem() + + switch v.Kind() { + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + if f.Type() != tokenPosType { + continue + } + + if !f.CanSet() { + continue + } + + // Don't offset invalid positions: they should stay invalid. + if !token.Pos(f.Int()).IsValid() { + continue + } + + // Clamp value to valid range; see #64335. + // + // TODO(golang/go#64335): this is a hack, because our fixes should not + // produce positions that overflow (but they do: golang/go#64488). + pos := f.Int() + int64(offset) + if pos < fileBase { + pos = fileBase + } + if pos > fileEnd { + pos = fileEnd + } + f.SetInt(pos) + } + } + + return true + }) +} + +// replaceNode updates parent's child oldChild to be newChild. It +// returns whether it replaced successfully. +func replaceNode(parent, oldChild, newChild ast.Node) bool { + if parent == nil || oldChild == nil || newChild == nil { + return false + } + + parentVal := reflect.ValueOf(parent).Elem() + if parentVal.Kind() != reflect.Struct { + return false + } + + newChildVal := reflect.ValueOf(newChild) + + tryReplace := func(v reflect.Value) bool { + if !v.CanSet() || !v.CanInterface() { + return false + } + + // If the existing value is oldChild, we found our child. Make + // sure our newChild is assignable and then make the swap. + if v.Interface() == oldChild && newChildVal.Type().AssignableTo(v.Type()) { + v.Set(newChildVal) + return true + } + + return false + } + + // Loop over parent's struct fields. + for i := 0; i < parentVal.NumField(); i++ { + f := parentVal.Field(i) + + switch f.Kind() { + // Check interface and pointer fields. + case reflect.Interface, reflect.Ptr: + if tryReplace(f) { + return true + } + + // Search through any slice fields. + case reflect.Slice: + for i := 0; i < f.Len(); i++ { + if tryReplace(f.Index(i)) { + return true + } + } + } + } + + return false +} diff --git a/contribs/gnopls/internal/cache/parsego/parse_test.go b/contribs/gnopls/internal/cache/parsego/parse_test.go new file mode 100644 index 00000000000..c64125427b1 --- /dev/null +++ b/contribs/gnopls/internal/cache/parsego/parse_test.go @@ -0,0 +1,46 @@ +// 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 parsego_test + +import ( + "context" + "go/ast" + "go/token" + "testing" + + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/tokeninternal" +) + +// TODO(golang/go#64335): we should have many more tests for fixed syntax. + +func TestFixPosition_Issue64488(t *testing.T) { + // This test reproduces the conditions of golang/go#64488, where a type error + // on fixed syntax overflows the token.File. + const src = ` +package foo + +func _() { + type myThing struct{} + var foo []myThing + for ${1:}, ${2:} := range foo { + $0 +} +} +` + + pgf, _ := parsego.Parse(context.Background(), token.NewFileSet(), "file://foo.go", []byte(src), parsego.Full, false) + fset := tokeninternal.FileSetFor(pgf.Tok) + ast.Inspect(pgf.File, func(n ast.Node) bool { + if n != nil { + posn := safetoken.StartPosition(fset, n.Pos()) + if !posn.IsValid() { + t.Fatalf("invalid position for %T (%v): %v not in [%d, %d]", n, n, n.Pos(), pgf.Tok.Base(), pgf.Tok.Base()+pgf.Tok.Size()) + } + } + return true + }) +} diff --git a/contribs/gnopls/internal/cache/port.go b/contribs/gnopls/internal/cache/port.go new file mode 100644 index 00000000000..e62ebe29903 --- /dev/null +++ b/contribs/gnopls/internal/cache/port.go @@ -0,0 +1,204 @@ +// 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 cache + +import ( + "bytes" + "go/build" + "go/parser" + "go/token" + "io" + "path/filepath" + "strings" + + "golang.org/x/tools/gopls/internal/util/bug" +) + +type port struct{ GOOS, GOARCH string } + +var ( + // preferredPorts holds GOOS/GOARCH combinations for which we dynamically + // create new Views, by setting GOOS=... and GOARCH=... on top of + // user-provided configuration when we detect that the default build + // configuration does not match an open file. Ports are matched in the order + // defined below, so that when multiple ports match a file we use the port + // occurring at a lower index in the slice. For that reason, we sort first + // class ports ahead of secondary ports, and (among first class ports) 64-bit + // ports ahead of the less common 32-bit ports. + preferredPorts = []port{ + // First class ports, from https://go.dev/wiki/PortingPolicy. + {"darwin", "amd64"}, + {"darwin", "arm64"}, + {"linux", "amd64"}, + {"linux", "arm64"}, + {"windows", "amd64"}, + {"linux", "arm"}, + {"linux", "386"}, + {"windows", "386"}, + + // Secondary ports, from GOROOT/src/internal/platform/zosarch.go. + // (First class ports are commented out.) + {"aix", "ppc64"}, + {"dragonfly", "amd64"}, + {"freebsd", "386"}, + {"freebsd", "amd64"}, + {"freebsd", "arm"}, + {"freebsd", "arm64"}, + {"illumos", "amd64"}, + {"linux", "ppc64"}, + {"linux", "ppc64le"}, + {"linux", "mips"}, + {"linux", "mipsle"}, + {"linux", "mips64"}, + {"linux", "mips64le"}, + {"linux", "riscv64"}, + {"linux", "s390x"}, + {"android", "386"}, + {"android", "amd64"}, + {"android", "arm"}, + {"android", "arm64"}, + {"ios", "arm64"}, + {"ios", "amd64"}, + {"js", "wasm"}, + {"netbsd", "386"}, + {"netbsd", "amd64"}, + {"netbsd", "arm"}, + {"netbsd", "arm64"}, + {"openbsd", "386"}, + {"openbsd", "amd64"}, + {"openbsd", "arm"}, + {"openbsd", "arm64"}, + {"openbsd", "mips64"}, + {"plan9", "386"}, + {"plan9", "amd64"}, + {"plan9", "arm"}, + {"solaris", "amd64"}, + {"windows", "arm"}, + {"windows", "arm64"}, + + {"aix", "ppc64"}, + {"android", "386"}, + {"android", "amd64"}, + {"android", "arm"}, + {"android", "arm64"}, + // {"darwin", "amd64"}, + // {"darwin", "arm64"}, + {"dragonfly", "amd64"}, + {"freebsd", "386"}, + {"freebsd", "amd64"}, + {"freebsd", "arm"}, + {"freebsd", "arm64"}, + {"freebsd", "riscv64"}, + {"illumos", "amd64"}, + {"ios", "amd64"}, + {"ios", "arm64"}, + {"js", "wasm"}, + // {"linux", "386"}, + // {"linux", "amd64"}, + // {"linux", "arm"}, + // {"linux", "arm64"}, + {"linux", "loong64"}, + {"linux", "mips"}, + {"linux", "mips64"}, + {"linux", "mips64le"}, + {"linux", "mipsle"}, + {"linux", "ppc64"}, + {"linux", "ppc64le"}, + {"linux", "riscv64"}, + {"linux", "s390x"}, + {"linux", "sparc64"}, + {"netbsd", "386"}, + {"netbsd", "amd64"}, + {"netbsd", "arm"}, + {"netbsd", "arm64"}, + {"openbsd", "386"}, + {"openbsd", "amd64"}, + {"openbsd", "arm"}, + {"openbsd", "arm64"}, + {"openbsd", "mips64"}, + {"openbsd", "ppc64"}, + {"openbsd", "riscv64"}, + {"plan9", "386"}, + {"plan9", "amd64"}, + {"plan9", "arm"}, + {"solaris", "amd64"}, + {"wasip1", "wasm"}, + // {"windows", "386"}, + // {"windows", "amd64"}, + {"windows", "arm"}, + {"windows", "arm64"}, + } +) + +// matches reports whether the port matches a file with the given absolute path +// and content. +// +// Note that this function accepts content rather than e.g. a file.Handle, +// because we trim content before matching for performance reasons, and +// therefore need to do this outside of matches when considering multiple ports. +func (p port) matches(path string, content []byte) bool { + ctxt := build.Default // make a copy + ctxt.UseAllFiles = false + dir, name := filepath.Split(path) + + // The only virtualized operation called by MatchFile is OpenFile. + ctxt.OpenFile = func(p string) (io.ReadCloser, error) { + if p != path { + return nil, bug.Errorf("unexpected file %q", p) + } + return io.NopCloser(bytes.NewReader(content)), nil + } + + ctxt.GOOS = p.GOOS + ctxt.GOARCH = p.GOARCH + ok, err := ctxt.MatchFile(dir, name) + return err == nil && ok +} + +// trimContentForPortMatch trims the given Go file content to a minimal file +// containing the same build constraints, if any. +// +// This is an unfortunate but necessary optimization, as matching build +// constraints using go/build has significant overhead, and involves parsing +// more than just the build constraint. +// +// TestMatchingPortsConsistency enforces consistency by comparing results +// without trimming content. +func trimContentForPortMatch(content []byte) []byte { + buildComment := buildComment(content) + return []byte(buildComment + "\npackage p") // package name does not matter +} + +// buildComment returns the first matching //go:build comment in the given +// content, or "" if none exists. +func buildComment(content []byte) string { + f, err := parser.ParseFile(token.NewFileSet(), "", content, parser.PackageClauseOnly|parser.ParseComments) + if err != nil { + return "" + } + + for _, cg := range f.Comments { + for _, c := range cg.List { + if isGoBuildComment(c.Text) { + return c.Text + } + } + } + return "" +} + +// Adapted from go/build/build.go. +// +// TODO(rfindley): use constraint.IsGoBuild once we are on 1.19+. +func isGoBuildComment(line string) bool { + const goBuildComment = "//go:build" + if !strings.HasPrefix(line, goBuildComment) { + return false + } + // Report whether //go:build is followed by a word boundary. + line = strings.TrimSpace(line) + rest := line[len(goBuildComment):] + return len(rest) == 0 || len(strings.TrimSpace(rest)) < len(rest) +} diff --git a/contribs/gnopls/internal/cache/port_test.go b/contribs/gnopls/internal/cache/port_test.go new file mode 100644 index 00000000000..a92056a9c22 --- /dev/null +++ b/contribs/gnopls/internal/cache/port_test.go @@ -0,0 +1,124 @@ +// 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 cache + +import ( + "os" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/testenv" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(m.Run()) +} + +func TestMatchingPortsStdlib(t *testing.T) { + // This test checks that we don't encounter a bug when matching ports, and + // sanity checks that the optimization to use trimmed/fake file content + // before delegating to go/build.Context.MatchFile does not affect + // correctness. + if testing.Short() { + t.Skip("skipping in short mode: takes to long on slow file systems") + } + + testenv.NeedsTool(t, "go") + + // Load, parse and type-check the program. + cfg := &packages.Config{ + Mode: packages.LoadFiles, + Tests: true, + } + pkgs, err := packages.Load(cfg, "std", "cmd") + if err != nil { + t.Fatal(err) + } + + var g errgroup.Group + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + for _, f := range pkg.CompiledGoFiles { + f := f + g.Go(func() error { + content, err := os.ReadFile(f) + // We report errors via t.Error, not by returning, + // so that a single test can report multiple test failures. + if err != nil { + t.Errorf("failed to read %s: %v", f, err) + return nil + } + fh := makeFakeFileHandle(protocol.URIFromPath(f), content) + fastPorts := matchingPreferredPorts(t, fh, true) + slowPorts := matchingPreferredPorts(t, fh, false) + if diff := cmp.Diff(fastPorts, slowPorts); diff != "" { + t.Errorf("%s: ports do not match (-trimmed +untrimmed):\n%s", f, diff) + return nil + } + return nil + }) + } + }) + g.Wait() +} + +func matchingPreferredPorts(tb testing.TB, fh file.Handle, trimContent bool) map[port]unit { + content, err := fh.Content() + if err != nil { + tb.Fatal(err) + } + if trimContent { + content = trimContentForPortMatch(content) + } + path := fh.URI().Path() + matching := make(map[port]unit) + for _, port := range preferredPorts { + if port.matches(path, content) { + matching[port] = unit{} + } + } + return matching +} + +func BenchmarkMatchingPreferredPorts(b *testing.B) { + // Copy of robustio_posix.go + const src = ` +// 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 unix +// +build unix + +package robustio + +import ( + "os" + "syscall" + "time" +) + +func getFileID(filename string) (FileID, time.Time, error) { + fi, err := os.Stat(filename) + if err != nil { + return FileID{}, time.Time{}, err + } + stat := fi.Sys().(*syscall.Stat_t) + return FileID{ + device: uint64(stat.Dev), // (int32 on darwin, uint64 on linux) + inode: stat.Ino, + }, fi.ModTime(), nil +} +` + fh := makeFakeFileHandle("file:///path/to/test/file.go", []byte(src)) + for i := 0; i < b.N; i++ { + _ = matchingPreferredPorts(b, fh, true) + } +} diff --git a/contribs/gnopls/internal/cache/session.go b/contribs/gnopls/internal/cache/session.go new file mode 100644 index 00000000000..c5e9aab98a5 --- /dev/null +++ b/contribs/gnopls/internal/cache/session.go @@ -0,0 +1,1207 @@ +// 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. + +package cache + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/typerefs" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/persistent" + "golang.org/x/tools/gopls/internal/vulncheck" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/keys" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/imports" + "golang.org/x/tools/internal/memoize" + "golang.org/x/tools/internal/xcontext" +) + +// NewSession creates a new gopls session with the given cache. +func NewSession(ctx context.Context, c *Cache) *Session { + index := atomic.AddInt64(&sessionIndex, 1) + s := &Session{ + id: strconv.FormatInt(index, 10), + cache: c, + gocmdRunner: &gocommand.Runner{}, + overlayFS: newOverlayFS(c), + parseCache: newParseCache(1 * time.Minute), // keep recently parsed files for a minute, to optimize typing CPU + viewMap: make(map[protocol.DocumentURI]*View), + } + event.Log(ctx, "New session", KeyCreateSession.Of(s)) + return s +} + +// A Session holds the state (views, file contents, parse cache, +// memoized computations) of a gopls server process. +// +// It implements the file.Source interface. +type Session struct { + // Unique identifier for this session. + id string + + // Immutable attributes shared across views. + cache *Cache // shared cache + gocmdRunner *gocommand.Runner // limits go command concurrency + + viewMu sync.Mutex + views []*View + viewMap map[protocol.DocumentURI]*View // file->best view or nil; nil after shutdown + + // snapshots is a counting semaphore that records the number + // of unreleased snapshots associated with this session. + // Shutdown waits for it to fall to zero. + snapshotWG sync.WaitGroup + + parseCache *parseCache + + *overlayFS +} + +// ID returns the unique identifier for this session on this server. +func (s *Session) ID() string { return s.id } +func (s *Session) String() string { return s.id } + +// GoCommandRunner returns the gocommand Runner for this session. +func (s *Session) GoCommandRunner() *gocommand.Runner { + return s.gocmdRunner +} + +// Shutdown the session and all views it has created. +func (s *Session) Shutdown(ctx context.Context) { + var views []*View + s.viewMu.Lock() + views = append(views, s.views...) + s.views = nil + s.viewMap = nil + s.viewMu.Unlock() + for _, view := range views { + view.shutdown() + } + s.parseCache.stop() + s.snapshotWG.Wait() // wait for all work on associated snapshots to finish + event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s)) +} + +// Cache returns the cache that created this session, for debugging only. +func (s *Session) Cache() *Cache { + return s.cache +} + +// TODO(rfindley): is the logic surrounding this error actually necessary? +var ErrViewExists = errors.New("view already exists for session") + +// NewView creates a new View, returning it and its first snapshot. If a +// non-empty tempWorkspace directory is provided, the View will record a copy +// of its gopls workspace module in that directory, so that client tooling +// can execute in the same main module. On success it also returns a release +// function that must be called when the Snapshot is no longer needed. +func (s *Session) NewView(ctx context.Context, folder *Folder) (*View, *Snapshot, func(), error) { + s.viewMu.Lock() + defer s.viewMu.Unlock() + + if s.viewMap == nil { + return nil, nil, nil, fmt.Errorf("session is shut down") + } + + // Querying the file system to check whether + // two folders denote the same existing directory. + if inode1, err := os.Stat(filepath.FromSlash(folder.Dir.Path())); err == nil { + for _, view := range s.views { + inode2, err := os.Stat(filepath.FromSlash(view.folder.Dir.Path())) + if err == nil && os.SameFile(inode1, inode2) { + return nil, nil, nil, ErrViewExists + } + } + } + + def, err := defineView(ctx, s, folder, nil) + if err != nil { + return nil, nil, nil, err + } + view, snapshot, release := s.createView(ctx, def) + s.views = append(s.views, view) + // we always need to drop the view map + s.viewMap = make(map[protocol.DocumentURI]*View) + return view, snapshot, release, nil +} + +// createView creates a new view, with an initial snapshot that retains the +// supplied context, detached from events and cancelation. +// +// The caller is responsible for calling the release function once. +func (s *Session) createView(ctx context.Context, def *viewDefinition) (*View, *Snapshot, func()) { + index := atomic.AddInt64(&viewIndex, 1) + + // We want a true background context and not a detached context here + // the spans need to be unrelated and no tag values should pollute it. + baseCtx := event.Detach(xcontext.Detach(ctx)) + backgroundCtx, cancel := context.WithCancel(baseCtx) + + // Compute a skip function to use for module cache scanning. + // + // Note that unlike other filtering operations, we definitely don't want to + // exclude the gomodcache here, even if it is contained in the workspace + // folder. + // + // TODO(rfindley): consolidate with relPathExcludedByFilter(Func), Filterer, + // View.filterFunc. + var skipPath func(string) bool + { + // Compute a prefix match, respecting segment boundaries, by ensuring + // the pattern (dir) has a trailing slash. + dirPrefix := strings.TrimSuffix(string(def.folder.Dir), "/") + "/" + filterer := NewFilterer(def.folder.Options.DirectoryFilters) + skipPath = func(dir string) bool { + uri := strings.TrimSuffix(string(protocol.URIFromPath(dir)), "/") + // Note that the logic below doesn't handle the case where uri == + // v.folder.Dir, because there is no point in excluding the entire + // workspace folder! + if rel := strings.TrimPrefix(uri, dirPrefix); rel != uri { + return filterer.Disallow(rel) + } + return false + } + } + + var ignoreFilter *ignoreFilter + { + var dirs []string + if len(def.workspaceModFiles) == 0 { + for _, entry := range filepath.SplitList(def.folder.Env.GOPATH) { + dirs = append(dirs, filepath.Join(entry, "src")) + } + } else { + dirs = append(dirs, def.folder.Env.GOMODCACHE) + for m := range def.workspaceModFiles { + dirs = append(dirs, filepath.Dir(m.Path())) + } + } + ignoreFilter = newIgnoreFilter(dirs) + } + + var pe *imports.ProcessEnv + { + env := make(map[string]string) + envSlice := slices.Concat(os.Environ(), def.folder.Options.EnvSlice(), []string{"GO111MODULE=" + def.adjustedGO111MODULE()}) + for _, kv := range envSlice { + if k, v, ok := strings.Cut(kv, "="); ok { + env[k] = v + } + } + pe = &imports.ProcessEnv{ + GocmdRunner: s.gocmdRunner, + BuildFlags: slices.Clone(def.folder.Options.BuildFlags), + // TODO(rfindley): an old comment said "processEnv operations should not mutate the modfile" + // But shouldn't we honor the default behavior of mod vendoring? + ModFlag: "readonly", + SkipPathInScan: skipPath, + Env: env, + WorkingDir: def.root.Path(), + ModCache: s.cache.modCache.dirCache(def.folder.Env.GOMODCACHE), + } + if def.folder.Options.VerboseOutput { + pe.Logf = func(format string, args ...interface{}) { + event.Log(ctx, fmt.Sprintf(format, args...)) + } + } + } + + v := &View{ + id: strconv.FormatInt(index, 10), + gocmdRunner: s.gocmdRunner, + initialWorkspaceLoad: make(chan struct{}), + initializationSema: make(chan struct{}, 1), + baseCtx: baseCtx, + parseCache: s.parseCache, + ignoreFilter: ignoreFilter, + fs: s.overlayFS, + viewDefinition: def, + importsState: newImportsState(backgroundCtx, s.cache.modCache, pe), + } + + s.snapshotWG.Add(1) + v.snapshot = &Snapshot{ + view: v, + backgroundCtx: backgroundCtx, + cancel: cancel, + store: s.cache.store, + refcount: 1, // Snapshots are born referenced. + done: s.snapshotWG.Done, + packages: new(persistent.Map[PackageID, *packageHandle]), + meta: new(metadata.Graph), + files: newFileMap(), + activePackages: new(persistent.Map[PackageID, *Package]), + symbolizeHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), + shouldLoad: new(persistent.Map[PackageID, []PackagePath]), + unloadableFiles: new(persistent.Set[protocol.DocumentURI]), + parseModHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), + parseWorkHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), + modTidyHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), + modVulnHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), + modWhyHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), + pkgIndex: typerefs.NewPackageIndex(), + moduleUpgrades: new(persistent.Map[protocol.DocumentURI, map[string]string]), + vulns: new(persistent.Map[protocol.DocumentURI, *vulncheck.Result]), + } + + // Snapshots must observe all open files, as there are some caching + // heuristics that change behavior depending on open files. + for _, o := range s.overlayFS.Overlays() { + _, _ = v.snapshot.ReadFile(ctx, o.URI()) + } + + // Record the environment of the newly created view in the log. + event.Log(ctx, fmt.Sprintf("Created View (#%s)", v.id), + label.Directory.Of(v.folder.Dir.Path()), + viewTypeKey.Of(v.typ.String()), + rootDirKey.Of(string(v.root)), + goVersionKey.Of(strings.TrimRight(v.folder.Env.GoVersionOutput, "\n")), + buildFlagsKey.Of(fmt.Sprint(v.folder.Options.BuildFlags)), + envKey.Of(fmt.Sprintf("%+v", v.folder.Env)), + envOverlayKey.Of(v.EnvOverlay()), + ) + + // Initialize the view without blocking. + initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx)) + v.cancelInitialWorkspaceLoad = initCancel + snapshot := v.snapshot + + // Pass a second reference to the background goroutine. + bgRelease := snapshot.Acquire() + go func() { + defer bgRelease() + snapshot.initialize(initCtx, true) + }() + + // Return a third reference to the caller. + return v, snapshot, snapshot.Acquire() +} + +// These keys are used to log view metadata in createView. +var ( + viewTypeKey = keys.NewString("view_type", "") + rootDirKey = keys.NewString("root_dir", "") + goVersionKey = keys.NewString("go_version", "") + buildFlagsKey = keys.New("build_flags", "") + envKey = keys.New("env", "") + envOverlayKey = keys.New("env_overlay", "") +) + +// RemoveView removes from the session the view rooted at the specified directory. +// It reports whether a view of that directory was removed. +func (s *Session) RemoveView(ctx context.Context, dir protocol.DocumentURI) bool { + s.viewMu.Lock() + defer s.viewMu.Unlock() + + if s.viewMap == nil { + return false // Session is shutdown. + } + s.viewMap = make(map[protocol.DocumentURI]*View) // reset view associations + + var newViews []*View + for _, view := range s.views { + if view.folder.Dir == dir { + view.shutdown() + } else { + newViews = append(newViews, view) + } + } + removed := len(s.views) - len(newViews) + if removed != 1 { + // This isn't a bug report, because it could be a client-side bug. + event.Error(ctx, "removing view", fmt.Errorf("removed %d views, want exactly 1", removed)) + } + s.views = newViews + return removed > 0 +} + +// View returns the view with a matching id, if present. +func (s *Session) View(id string) (*View, error) { + s.viewMu.Lock() + defer s.viewMu.Unlock() + for _, view := range s.views { + if view.ID() == id { + return view, nil + } + } + return nil, fmt.Errorf("no view with ID %q", id) +} + +// SnapshotOf returns a Snapshot corresponding to the given URI. +// +// In the case where the file can be can be associated with a View by +// bestViewForURI (based on directory information alone, without package +// metadata), SnapshotOf returns the current Snapshot for that View. Otherwise, +// it awaits loading package metadata and returns a Snapshot for the first View +// containing a real (=not command-line-arguments) package for the file. +// +// If that also fails to find a View, SnapshotOf returns a Snapshot for the +// first view in s.views that is not shut down (i.e. s.views[0] unless we lose +// a race), for determinism in tests and so that we tend to aggregate the +// resulting command-line-arguments packages into a single view. +// +// SnapshotOf returns an error if a failure occurs along the way (most likely due +// to context cancellation), or if there are no Views in the Session. +// +// On success, the caller must call the returned function to release the snapshot. +func (s *Session) SnapshotOf(ctx context.Context, uri protocol.DocumentURI) (*Snapshot, func(), error) { + // Fast path: if the uri has a static association with a view, return it. + s.viewMu.Lock() + v, err := s.viewOfLocked(ctx, uri) + s.viewMu.Unlock() + + if err != nil { + return nil, nil, err + } + + if v != nil { + snapshot, release, err := v.Snapshot() + if err == nil { + return snapshot, release, nil + } + // View is shut down. Forget this association. + s.viewMu.Lock() + if s.viewMap[uri] == v { + delete(s.viewMap, uri) + } + s.viewMu.Unlock() + } + + // Fall-back: none of the views could be associated with uri based on + // directory information alone. + // + // Don't memoize the view association in viewMap, as it is not static: Views + // may change as metadata changes. + // + // TODO(rfindley): we could perhaps optimize this case by peeking at existing + // metadata before awaiting the load (after all, a load only adds metadata). + // But that seems potentially tricky, when in the common case no loading + // should be required. + views := s.Views() + for _, v := range views { + snapshot, release, err := v.Snapshot() + if err != nil { + continue // view was shut down + } + // We don't check the error from awaitLoaded, because a load failure (that + // doesn't result from context cancelation) should not prevent us from + // continuing to search for the best view. + _ = snapshot.awaitLoaded(ctx) + g := snapshot.MetadataGraph() + if ctx.Err() != nil { + release() + return nil, nil, ctx.Err() + } + // Special handling for the builtin file, since it doesn't have packages. + if snapshot.IsBuiltin(uri) { + return snapshot, release, nil + } + // Only match this view if it loaded a real package for the file. + // + // Any view can load a command-line-arguments package; aggregate those into + // views[0] below. + for _, id := range g.IDs[uri] { + if !metadata.IsCommandLineArguments(id) || g.Packages[id].Standalone { + return snapshot, release, nil + } + } + release() + } + + for _, v := range views { + snapshot, release, err := v.Snapshot() + if err == nil { + return snapshot, release, nil // first valid snapshot + } + } + return nil, nil, errNoViews +} + +// errNoViews is sought by orphaned file diagnostics, to detect the case where +// we have no view containing a file. +var errNoViews = errors.New("no views") + +// viewOfLocked evaluates the best view for uri, memoizing its result in +// s.viewMap. +// +// Precondition: caller holds s.viewMu lock. +// +// May return (nil, nil) if no best view can be determined. +func (s *Session) viewOfLocked(ctx context.Context, uri protocol.DocumentURI) (*View, error) { + if s.viewMap == nil { + return nil, errors.New("session is shut down") + } + v, hit := s.viewMap[uri] + if !hit { + // Cache miss: compute (and memoize) the best view. + fh, err := s.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + relevantViews, err := RelevantViews(ctx, s, fh.URI(), s.views) + if err != nil { + return nil, err + } + v = matchingView(fh, relevantViews) + if v == nil && len(relevantViews) > 0 { + // If we have relevant views, but none of them matched the file's build + // constraints, then we are still better off using one of them here. + // Otherwise, logic may fall back to an inferior view, which lacks + // relevant module information, leading to misleading diagnostics. + // (as in golang/go#60776). + v = relevantViews[0] + } + s.viewMap[uri] = v // may be nil + } + return v, nil +} + +func (s *Session) Views() []*View { + s.viewMu.Lock() + defer s.viewMu.Unlock() + result := make([]*View, len(s.views)) + copy(result, s.views) + return result +} + +// selectViewDefs constructs the best set of views covering the provided workspace +// folders and open files. +// +// This implements the zero-config algorithm of golang/go#57979. +func selectViewDefs(ctx context.Context, fs file.Source, folders []*Folder, openFiles []protocol.DocumentURI) ([]*viewDefinition, error) { + var defs []*viewDefinition + + // First, compute a default view for each workspace folder. + // TODO(golang/go#57979): technically, this is path dependent, since + // DidChangeWorkspaceFolders could introduce a path-dependent ordering on + // folders. We should keep folders sorted, or sort them here. + for _, folder := range folders { + def, err := defineView(ctx, fs, folder, nil) + if err != nil { + return nil, err + } + defs = append(defs, def) + } + + // Next, ensure that the set of views covers all open files contained in a + // workspace folder. + // + // We only do this for files contained in a workspace folder, because other + // open files are most likely the result of jumping to a definition from a + // workspace file; we don't want to create additional views in those cases: + // they should be resolved after initialization. + + folderForFile := func(uri protocol.DocumentURI) *Folder { + var longest *Folder + for _, folder := range folders { + // Check that this is a better match than longest, but not through a + // vendor directory. Count occurrences of "/vendor/" as a quick check + // that the vendor directory is between the folder and the file. Note the + // addition of a trailing "/" to handle the odd case where the folder is named + // vendor (which I hope is exceedingly rare in any case). + // + // Vendored packages are, by definition, part of an existing view. + if (longest == nil || len(folder.Dir) > len(longest.Dir)) && + folder.Dir.Encloses(uri) && + strings.Count(string(uri), "/vendor/") == strings.Count(string(folder.Dir)+"/", "/vendor/") { + + longest = folder + } + } + return longest + } + +checkFiles: + for _, uri := range openFiles { + folder := folderForFile(uri) + if folder == nil || !folder.Options.ZeroConfig { + continue // only guess views for open files + } + fh, err := fs.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + relevantViews, err := RelevantViews(ctx, fs, fh.URI(), defs) + if err != nil { + // We should never call selectViewDefs with a cancellable context, so + // this should never fail. + return nil, bug.Errorf("failed to find best view for open file: %v", err) + } + def := matchingView(fh, relevantViews) + if def != nil { + continue // file covered by an existing view + } + def, err = defineView(ctx, fs, folder, fh) + if err != nil { + // We should never call selectViewDefs with a cancellable context, so + // this should never fail. + return nil, bug.Errorf("failed to define view for open file: %v", err) + } + // It need not strictly be the case that the best view for a file is + // distinct from other views, as the logic of getViewDefinition and + // bestViewForURI does not align perfectly. This is not necessarily a bug: + // there may be files for which we can't construct a valid view. + // + // Nevertheless, we should not create redundant views. + for _, alt := range defs { + if viewDefinitionsEqual(alt, def) { + continue checkFiles + } + } + defs = append(defs, def) + } + + return defs, nil +} + +// The viewDefiner interface allows the bestView algorithm to operate on both +// Views and viewDefinitions. +type viewDefiner interface{ definition() *viewDefinition } + +// RelevantViews returns the views that may contain the given URI, or nil if +// none exist. A view is "relevant" if, ignoring build constraints, it may have +// a workspace package containing uri. Therefore, the definition of relevance +// depends on the view type. +func RelevantViews[V viewDefiner](ctx context.Context, fs file.Source, uri protocol.DocumentURI, views []V) ([]V, error) { + if len(views) == 0 { + return nil, nil // avoid the call to findRootPattern + } + dir := uri.Dir() + modURI, err := findRootPattern(ctx, dir, "go.mod", fs) + if err != nil { + return nil, err + } + + // Prefer GoWork > GoMod > GOPATH > GoPackages > AdHoc. + var ( + goPackagesViews []V // prefer longest + workViews []V // prefer longest + modViews []V // exact match + gopathViews []V // prefer longest + adHocViews []V // exact match + ) + + // pushView updates the views slice with the matching view v, using the + // heuristic that views with a longer root are preferable. Accordingly, + // pushView may be a no op if v's root is shorter than the roots in the views + // slice. + // + // Invariant: the length of all roots in views is the same. + pushView := func(views *[]V, v V) { + if len(*views) == 0 { + *views = []V{v} + return + } + better := func(l, r V) bool { + return len(l.definition().root) > len(r.definition().root) + } + existing := (*views)[0] + switch { + case better(existing, v): + case better(v, existing): + *views = []V{v} + default: + *views = append(*views, v) + } + } + + for _, view := range views { + switch def := view.definition(); def.Type() { + case GoPackagesDriverView: + if def.root.Encloses(dir) { + pushView(&goPackagesViews, view) + } + case GoWorkView: + if _, ok := def.workspaceModFiles[modURI]; ok || uri == def.gowork { + pushView(&workViews, view) + } + case GoModView: + if _, ok := def.workspaceModFiles[modURI]; ok { + modViews = append(modViews, view) + } + case GOPATHView: + if def.root.Encloses(dir) { + pushView(&gopathViews, view) + } + case AdHocView: + if def.root == dir { + adHocViews = append(adHocViews, view) + } + } + } + + // Now that we've collected matching views, choose the best match, + // considering ports. + // + // We only consider one type of view, since the matching view created by + // defineView should be of the best type. + var relevantViews []V + switch { + case len(workViews) > 0: + relevantViews = workViews + case len(modViews) > 0: + relevantViews = modViews + case len(gopathViews) > 0: + relevantViews = gopathViews + case len(goPackagesViews) > 0: + relevantViews = goPackagesViews + case len(adHocViews) > 0: + relevantViews = adHocViews + } + + return relevantViews, nil +} + +// matchingView returns the View or viewDefinition out of relevantViews that +// matches the given file's build constraints, or nil if no match is found. +// +// Making this function generic is convenient so that we can avoid mapping view +// definitions back to views inside Session.DidModifyFiles, where performance +// matters. It is, however, not the cleanest application of generics. +// +// Note: keep this function in sync with defineView. +func matchingView[V viewDefiner](fh file.Handle, relevantViews []V) V { + var zero V + + if len(relevantViews) == 0 { + return zero + } + + content, err := fh.Content() + + // Port matching doesn't apply to non-go files, or files that no longer exist. + // Note that the behavior here on non-existent files shouldn't matter much, + // since there will be a subsequent failure. + if fileKind(fh) != file.Go || err != nil { + return relevantViews[0] + } + + // Find the first view that matches constraints. + // Content trimming is nontrivial, so do this outside of the loop below. + path := fh.URI().Path() + content = trimContentForPortMatch(content) + for _, v := range relevantViews { + def := v.definition() + viewPort := port{def.GOOS(), def.GOARCH()} + if viewPort.matches(path, content) { + return v + } + } + + return zero // no view found +} + +// ResetView resets the best view for the given URI. +func (s *Session) ResetView(ctx context.Context, uri protocol.DocumentURI) (*View, error) { + s.viewMu.Lock() + defer s.viewMu.Unlock() + + if s.viewMap == nil { + return nil, fmt.Errorf("session is shut down") + } + + view, err := s.viewOfLocked(ctx, uri) + if err != nil { + return nil, err + } + if view == nil { + return nil, fmt.Errorf("no view for %s", uri) + } + + s.viewMap = make(map[protocol.DocumentURI]*View) + for i, v := range s.views { + if v == view { + v2, _, release := s.createView(ctx, view.viewDefinition) + release() // don't need the snapshot + v.shutdown() + s.views[i] = v2 + return v2, nil + } + } + + return nil, bug.Errorf("missing view") // can't happen... +} + +// DidModifyFiles reports a file modification to the session. It returns +// the new snapshots after the modifications have been applied, paired with +// the affected file URIs for those snapshots. +// On success, it returns a release function that +// must be called when the snapshots are no longer needed. +// +// TODO(rfindley): what happens if this function fails? It must leave us in a +// broken state, which we should surface to the user, probably as a request to +// restart gopls. +func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modification) (map[*View][]protocol.DocumentURI, error) { + s.viewMu.Lock() + defer s.viewMu.Unlock() + + // Short circuit the logic below if s is shut down. + if s.viewMap == nil { + return nil, fmt.Errorf("session is shut down") + } + + // Update overlays. + // + // This is done while holding viewMu because the set of open files affects + // the set of views, and to prevent views from seeing updated file content + // before they have processed invalidations. + replaced, err := s.updateOverlays(ctx, modifications) + if err != nil { + return nil, err + } + + // checkViews controls whether the set of views needs to be recomputed, for + // example because a go.mod file was created or deleted, or a go.work file + // changed on disk. + checkViews := false + + changed := make(map[protocol.DocumentURI]file.Handle) + for _, c := range modifications { + fh := mustReadFile(ctx, s, c.URI) + changed[c.URI] = fh + + // Any change to the set of open files causes views to be recomputed. + if c.Action == file.Open || c.Action == file.Close { + checkViews = true + } + + // Any on-disk change to a go.work or go.mod file causes recomputing views. + // + // TODO(rfindley): go.work files need not be named "go.work" -- we need to + // check each view's source to handle the case of an explicit GOWORK value. + // Write a test that fails, and fix this. + if (isGoWork(c.URI) || isGoMod(c.URI)) && (c.Action == file.Save || c.OnDisk) { + checkViews = true + } + + // Any change to the set of supported ports in a file may affect view + // selection. This is perhaps more subtle than it first seems: since the + // algorithm for selecting views considers open files in a deterministic + // order, a change in supported ports may cause a different port to be + // chosen, even if all open files still match an existing View! + // + // We endeavor to avoid that sort of path dependence, so must re-run the + // view selection algorithm whenever any input changes. + // + // However, extracting the build comment is nontrivial, so we don't want to + // pay this cost when e.g. processing a bunch of on-disk changes due to a + // branch change. Be careful to only do this if both files are open Go + // files. + if old, ok := replaced[c.URI]; ok && !checkViews && fileKind(fh) == file.Go { + if new, ok := fh.(*overlay); ok { + if buildComment(old.content) != buildComment(new.content) { + checkViews = true + } + } + } + } + + if checkViews { + // Hack: collect folders from existing views. + // TODO(golang/go#57979): we really should track folders independent of + // Views, but since we always have a default View for each folder, this + // works for now. + var folders []*Folder // preserve folder order + seen := make(map[*Folder]unit) + for _, v := range s.views { + if _, ok := seen[v.folder]; ok { + continue + } + seen[v.folder] = unit{} + folders = append(folders, v.folder) + } + + var openFiles []protocol.DocumentURI + for _, o := range s.Overlays() { + openFiles = append(openFiles, o.URI()) + } + // Sort for determinism. + sort.Slice(openFiles, func(i, j int) bool { + return openFiles[i] < openFiles[j] + }) + + // TODO(rfindley): can we avoid running the go command (go env) + // synchronously to change processing? Can we assume that the env did not + // change, and derive go.work using a combination of the configured + // GOWORK value and filesystem? + defs, err := selectViewDefs(ctx, s, folders, openFiles) + if err != nil { + // Catastrophic failure, equivalent to a failure of session + // initialization and therefore should almost never happen. One + // scenario where this failure mode could occur is if some file + // permissions have changed preventing us from reading go.mod + // files. + // + // TODO(rfindley): consider surfacing this error more loudly. We + // could report a bug, but it's not really a bug. + event.Error(ctx, "selecting new views", err) + } else { + kept := make(map[*View]unit) + var newViews []*View + for _, def := range defs { + var newView *View + // Reuse existing view? + for _, v := range s.views { + if viewDefinitionsEqual(def, v.viewDefinition) { + newView = v + kept[v] = unit{} + break + } + } + if newView == nil { + v, _, release := s.createView(ctx, def) + release() + newView = v + } + newViews = append(newViews, newView) + } + for _, v := range s.views { + if _, ok := kept[v]; !ok { + v.shutdown() + } + } + s.views = newViews + s.viewMap = make(map[protocol.DocumentURI]*View) + } + } + + // We only want to run fast-path diagnostics (i.e. diagnoseChangedFiles) once + // for each changed file, in its best view. + viewsToDiagnose := map[*View][]protocol.DocumentURI{} + for _, mod := range modifications { + v, err := s.viewOfLocked(ctx, mod.URI) + if err != nil { + // viewOfLocked only returns an error in the event of context + // cancellation, or if the session is shut down. Since state changes + // should occur on an uncancellable context, and s.viewMap was checked at + // the top of this function, an error here is a bug. + bug.Reportf("finding best view for change: %v", err) + continue + } + if v != nil { + viewsToDiagnose[v] = append(viewsToDiagnose[v], mod.URI) + } + } + + // ...but changes may be relevant to other views, for example if they are + // changes to a shared package. + for _, v := range s.views { + _, release, needsDiagnosis := s.invalidateViewLocked(ctx, v, StateChange{Modifications: modifications, Files: changed}) + release() + + if needsDiagnosis || checkViews { + if _, ok := viewsToDiagnose[v]; !ok { + viewsToDiagnose[v] = nil + } + } + } + + return viewsToDiagnose, nil +} + +// ExpandModificationsToDirectories returns the set of changes with the +// directory changes removed and expanded to include all of the files in +// the directory. +func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []file.Modification) []file.Modification { + var snapshots []*Snapshot + s.viewMu.Lock() + for _, v := range s.views { + snapshot, release, err := v.Snapshot() + if err != nil { + continue // view is shut down; continue with others + } + defer release() + snapshots = append(snapshots, snapshot) + } + s.viewMu.Unlock() + + // Expand the modification to any file we could care about, which we define + // to be any file observed by any of the snapshots. + // + // There may be other files in the directory, but if we haven't read them yet + // we don't need to invalidate them. + var result []file.Modification + for _, c := range changes { + expanded := make(map[protocol.DocumentURI]bool) + for _, snapshot := range snapshots { + for _, uri := range snapshot.filesInDir(c.URI) { + expanded[uri] = true + } + } + if len(expanded) == 0 { + result = append(result, c) + } else { + for uri := range expanded { + result = append(result, file.Modification{ + URI: uri, + Action: c.Action, + LanguageID: "", + OnDisk: c.OnDisk, + // changes to directories cannot include text or versions + }) + } + } + } + return result +} + +// updateOverlays updates the set of overlays and returns a map of any existing +// overlay values that were replaced. +// +// Precondition: caller holds s.viewMu lock. +// TODO(rfindley): move this to fs_overlay.go. +func (fs *overlayFS) updateOverlays(ctx context.Context, changes []file.Modification) (map[protocol.DocumentURI]*overlay, error) { + fs.mu.Lock() + defer fs.mu.Unlock() + + replaced := make(map[protocol.DocumentURI]*overlay) + for _, c := range changes { + o, ok := fs.overlays[c.URI] + if ok { + replaced[c.URI] = o + } + + // If the file is not opened in an overlay and the change is on disk, + // there's no need to update an overlay. If there is an overlay, we + // may need to update the overlay's saved value. + if !ok && c.OnDisk { + continue + } + + // Determine the file kind on open, otherwise, assume it has been cached. + var kind file.Kind + switch c.Action { + case file.Open: + kind = file.KindForLang(c.LanguageID) + default: + if !ok { + return nil, fmt.Errorf("updateOverlays: modifying unopened overlay %v", c.URI) + } + kind = o.kind + } + + // Closing a file just deletes its overlay. + if c.Action == file.Close { + delete(fs.overlays, c.URI) + continue + } + + // If the file is on disk, check if its content is the same as in the + // overlay. Saves and on-disk file changes don't come with the file's + // content. + text := c.Text + if text == nil && (c.Action == file.Save || c.OnDisk) { + if !ok { + return nil, fmt.Errorf("no known content for overlay for %s", c.Action) + } + text = o.content + } + // On-disk changes don't come with versions. + version := c.Version + if c.OnDisk || c.Action == file.Save { + version = o.version + } + hash := file.HashOf(text) + var sameContentOnDisk bool + switch c.Action { + case file.Delete: + // Do nothing. sameContentOnDisk should be false. + case file.Save: + // Make sure the version and content (if present) is the same. + if false && o.version != version { // Client no longer sends the version + return nil, fmt.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version) + } + if c.Text != nil && o.hash != hash { + return nil, fmt.Errorf("updateOverlays: overlay %s changed on save", c.URI) + } + sameContentOnDisk = true + default: + fh := mustReadFile(ctx, fs.delegate, c.URI) + _, readErr := fh.Content() + sameContentOnDisk = (readErr == nil && fh.Identity().Hash == hash) + } + o = &overlay{ + uri: c.URI, + version: version, + content: text, + kind: kind, + hash: hash, + saved: sameContentOnDisk, + } + + // NOTE: previous versions of this code checked here that the overlay had a + // view and file kind (but we don't know why). + + fs.overlays[c.URI] = o + } + + return replaced, nil +} + +func mustReadFile(ctx context.Context, fs file.Source, uri protocol.DocumentURI) file.Handle { + ctx = xcontext.Detach(ctx) + fh, err := fs.ReadFile(ctx, uri) + if err != nil { + // ReadFile cannot fail with an uncancellable context. + bug.Reportf("reading file failed unexpectedly: %v", err) + return brokenFile{uri, err} + } + return fh +} + +// A brokenFile represents an unexpected failure to read a file. +type brokenFile struct { + uri protocol.DocumentURI + err error +} + +func (b brokenFile) URI() protocol.DocumentURI { return b.uri } +func (b brokenFile) Identity() file.Identity { return file.Identity{URI: b.uri} } +func (b brokenFile) SameContentsOnDisk() bool { return false } +func (b brokenFile) Version() int32 { return 0 } +func (b brokenFile) Content() ([]byte, error) { return nil, b.err } + +// FileWatchingGlobPatterns returns a set of glob patterns that the client is +// required to watch for changes, and notify the server of them, in order to +// keep the server's state up to date. +// +// This set includes +// 1. all go.mod and go.work files in the workspace; and +// 2. for each Snapshot, its modules (or directory for ad-hoc views). In +// module mode, this is the set of active modules (and for VS Code, all +// workspace directories within them, due to golang/go#42348). +// +// The watch for workspace go.work and go.mod files in (1) is sufficient to +// capture changes to the repo structure that may affect the set of views. +// Whenever this set changes, we reload the workspace and invalidate memoized +// files. +// +// The watch for workspace directories in (2) should keep each View up to date, +// as it should capture any newly added/modified/deleted Go files. +// +// Patterns are returned as a set of protocol.RelativePatterns, since they can +// always be later translated to glob patterns (i.e. strings) if the client +// lacks relative pattern support. By convention, any pattern returned with +// empty baseURI should be served as a glob pattern. +// +// In general, we prefer to serve relative patterns, as they work better on +// most clients that support both, and do not have issues with Windows driver +// letter casing: +// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#relativePattern +// +// TODO(golang/go#57979): we need to reset the memoizedFS when a view changes. +// Consider the case where we incidentally read a file, then it moved outside +// of an active module, and subsequently changed: we would still observe the +// original file state. +func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[protocol.RelativePattern]unit { + s.viewMu.Lock() + defer s.viewMu.Unlock() + + // Always watch files that may change the set of views. + patterns := map[protocol.RelativePattern]unit{ + {Pattern: "**/*.{mod,work}"}: {}, + } + + for _, view := range s.views { + snapshot, release, err := view.Snapshot() + if err != nil { + continue // view is shut down; continue with others + } + for k, v := range snapshot.fileWatchingGlobPatterns() { + patterns[k] = v + } + release() + } + return patterns +} + +// OrphanedFileDiagnostics reports diagnostics describing why open files have +// no packages or have only command-line-arguments packages. +// +// If the resulting diagnostic is nil, the file is either not orphaned or we +// can't produce a good diagnostic. +// +// The caller must not mutate the result. +func (s *Session) OrphanedFileDiagnostics(ctx context.Context) (map[protocol.DocumentURI][]*Diagnostic, error) { + if err := ctx.Err(); err != nil { + // Avoid collecting diagnostics if the context is cancelled. + // (Previously, it was possible to get all the way to packages.Load on a cancelled context) + return nil, err + } + // Note: diagnostics holds a slice for consistency with other diagnostic + // funcs. + diagnostics := make(map[protocol.DocumentURI][]*Diagnostic) + + byView := make(map[*View][]*overlay) + for _, o := range s.Overlays() { + uri := o.URI() + snapshot, release, err := s.SnapshotOf(ctx, uri) + if err != nil { + // TODO(golang/go#57979): we have to use the .go suffix as an approximation for + // file kind here, because we don't have access to Options if no View was + // matched. + // + // But Options are really a property of Folder, not View, and we could + // match a folder here. + // + // Refactor so that Folders are tracked independently of Views, and use + // the correct options here to get the most accurate file kind. + // + // TODO(golang/go#57979): once we switch entirely to the zeroconfig + // logic, we should use this diagnostic for the fallback case of + // s.views[0] in the ViewOf logic. + if errors.Is(err, errNoViews) { + if strings.HasSuffix(string(uri), ".go") { + if _, rng, ok := orphanedFileDiagnosticRange(ctx, s.parseCache, o); ok { + diagnostics[uri] = []*Diagnostic{{ + URI: uri, + Range: rng, + Severity: protocol.SeverityWarning, + Source: ListError, + Message: fmt.Sprintf("No active builds contain %s: consider opening a new workspace folder containing it", uri.Path()), + }} + } + } + continue + } + return nil, err + } + v := snapshot.View() + release() + byView[v] = append(byView[v], o) + } + + for view, overlays := range byView { + snapshot, release, err := view.Snapshot() + if err != nil { + continue // view is shutting down + } + defer release() + diags, err := snapshot.orphanedFileDiagnostics(ctx, overlays) + if err != nil { + return nil, err + } + for _, d := range diags { + diagnostics[d.URI] = append(diagnostics[d.URI], d) + } + } + return diagnostics, nil +} diff --git a/contribs/gnopls/internal/cache/session_test.go b/contribs/gnopls/internal/cache/session_test.go new file mode 100644 index 00000000000..fe4e55e3d74 --- /dev/null +++ b/contribs/gnopls/internal/cache/session_test.go @@ -0,0 +1,402 @@ +// 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 cache + +import ( + "context" + "os" + "path" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/internal/testenv" +) + +func TestZeroConfigAlgorithm(t *testing.T) { + testenv.NeedsExec(t) // executes the Go command + t.Setenv("GOPACKAGESDRIVER", "off") + + type viewSummary struct { + // fields exported for cmp.Diff + Type ViewType + Root string + Env []string + } + + type folderSummary struct { + dir string + options func(dir string) map[string]any // options may refer to the temp dir + } + + includeReplaceInWorkspace := func(string) map[string]any { + return map[string]any{ + "includeReplaceInWorkspace": true, + } + } + + type test struct { + name string + files map[string]string // use a map rather than txtar as file content is tiny + folders []folderSummary + open []string // open files + want []viewSummary + } + + tests := []test{ + // TODO(rfindley): add a test for GOPACKAGESDRIVER. + // Doing so doesn't yet work using options alone (user env is not honored) + + // TODO(rfindley): add a test for degenerate cases, such as missing + // workspace folders (once we decide on the correct behavior). + { + "basic go.work workspace", + map[string]string{ + "go.work": "go 1.18\nuse (\n\t./a\n\t./b\n)\n", + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + }, + []folderSummary{{dir: "."}}, + nil, + []viewSummary{{GoWorkView, ".", nil}}, + }, + { + "basic go.mod workspace", + map[string]string{ + "go.mod": "module golang.org/a\ngo 1.18\n", + }, + []folderSummary{{dir: "."}}, + nil, + []viewSummary{{GoModView, ".", nil}}, + }, + { + "basic GOPATH workspace", + map[string]string{ + "src/golang.org/a/a.go": "package a", + "src/golang.org/b/b.go": "package b", + }, + []folderSummary{{ + dir: "src", + options: func(dir string) map[string]any { + return map[string]any{ + "env": map[string]any{ + "GOPATH": dir, + }, + } + }, + }}, + []string{"src/golang.org/a//a.go", "src/golang.org/b/b.go"}, + []viewSummary{{GOPATHView, "src", nil}}, + }, + { + "basic AdHoc workspace", + map[string]string{ + "foo.go": "package foo", + }, + []folderSummary{{dir: "."}}, + nil, + []viewSummary{{AdHocView, ".", nil}}, + }, + { + "multi-folder workspace", + map[string]string{ + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + }, + []folderSummary{{dir: "a"}, {dir: "b"}}, + nil, + []viewSummary{{GoModView, "a", nil}, {GoModView, "b", nil}}, + }, + { + "multi-module workspace", + map[string]string{ + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + }, + []folderSummary{{dir: "."}}, + nil, + []viewSummary{{AdHocView, ".", nil}}, + }, + { + "zero-config open module", + map[string]string{ + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "a/a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + "b/b.go": "package b", + }, + []folderSummary{{dir: "."}}, + []string{"a/a.go"}, + []viewSummary{ + {AdHocView, ".", nil}, + {GoModView, "a", nil}, + }, + }, + { + "zero-config open modules", + map[string]string{ + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "a/a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + "b/b.go": "package b", + }, + []folderSummary{{dir: "."}}, + []string{"a/a.go", "b/b.go"}, + []viewSummary{ + {AdHocView, ".", nil}, + {GoModView, "a", nil}, + {GoModView, "b", nil}, + }, + }, + { + "unified workspace", + map[string]string{ + "go.work": "go 1.18\nuse (\n\t./a\n\t./b\n)\n", + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "a/a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + "b/b.go": "package b", + }, + []folderSummary{{dir: "."}}, + []string{"a/a.go", "b/b.go"}, + []viewSummary{{GoWorkView, ".", nil}}, + }, + { + "go.work from env", + map[string]string{ + "nested/go.work": "go 1.18\nuse (\n\t../a\n\t../b\n)\n", + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "a/a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + "b/b.go": "package b", + }, + []folderSummary{{ + dir: ".", + options: func(dir string) map[string]any { + return map[string]any{ + "env": map[string]any{ + "GOWORK": filepath.Join(dir, "nested", "go.work"), + }, + } + }, + }}, + []string{"a/a.go", "b/b.go"}, + []viewSummary{{GoWorkView, ".", nil}}, + }, + { + "independent module view", + map[string]string{ + "go.work": "go 1.18\nuse (\n\t./a\n)\n", // not using b + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "a/a.go": "package a", + "b/go.mod": "module golang.org/a\ngo 1.18\n", + "b/b.go": "package b", + }, + []folderSummary{{dir: "."}}, + []string{"a/a.go", "b/b.go"}, + []viewSummary{ + {GoWorkView, ".", nil}, + {GoModView, "b", []string{"GOWORK=off"}}, + }, + }, + { + "multiple go.work", + map[string]string{ + "go.work": "go 1.18\nuse (\n\t./a\n\t./b\n)\n", + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "a/a.go": "package a", + "b/go.work": "go 1.18\nuse (\n\t.\n\t./c\n)\n", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + "b/b.go": "package b", + "b/c/go.mod": "module golang.org/c\ngo 1.18\n", + }, + []folderSummary{{dir: "."}}, + []string{"a/a.go", "b/b.go", "b/c/c.go"}, + []viewSummary{{GoWorkView, ".", nil}, {GoWorkView, "b", nil}}, + }, + { + "multiple go.work, c unused", + map[string]string{ + "go.work": "go 1.18\nuse (\n\t./a\n\t./b\n)\n", + "a/go.mod": "module golang.org/a\ngo 1.18\n", + "a/a.go": "package a", + "b/go.work": "go 1.18\nuse (\n\t.\n)\n", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + "b/b.go": "package b", + "b/c/go.mod": "module golang.org/c\ngo 1.18\n", + }, + []folderSummary{{dir: "."}}, + []string{"a/a.go", "b/b.go", "b/c/c.go"}, + []viewSummary{{GoWorkView, ".", nil}, {GoModView, "b/c", []string{"GOWORK=off"}}}, + }, + { + "go.mod with nested replace", + map[string]string{ + "go.mod": "module golang.org/a\n require golang.org/b v1.2.3\nreplace example.com/b => ./b", + "a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + "b/b.go": "package b", + }, + []folderSummary{{dir: ".", options: includeReplaceInWorkspace}}, + []string{"a/a.go", "b/b.go"}, + []viewSummary{{GoModView, ".", nil}}, + }, + { + "go.mod with parent replace, parent folder", + map[string]string{ + "go.mod": "module golang.org/a", + "a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18\nrequire golang.org/a v1.2.3\nreplace golang.org/a => ../", + "b/b.go": "package b", + }, + []folderSummary{{dir: ".", options: includeReplaceInWorkspace}}, + []string{"a/a.go", "b/b.go"}, + []viewSummary{{GoModView, ".", nil}, {GoModView, "b", nil}}, + }, + { + "go.mod with multiple replace", + map[string]string{ + "go.mod": ` +module golang.org/root + +require ( + golang.org/a v1.2.3 + golang.org/b v1.2.3 + golang.org/c v1.2.3 +) + +replace ( + golang.org/b => ./b + golang.org/c => ./c + // Note: d is not replaced +) +`, + "a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18", + "b/b.go": "package b", + "c/go.mod": "module golang.org/c\ngo 1.18", + "c/c.go": "package c", + "d/go.mod": "module golang.org/d\ngo 1.18", + "d/d.go": "package d", + }, + []folderSummary{{dir: ".", options: includeReplaceInWorkspace}}, + []string{"b/b.go", "c/c.go", "d/d.go"}, + []viewSummary{{GoModView, ".", nil}, {GoModView, "d", nil}}, + }, + { + "go.mod with replace outside the workspace", + map[string]string{ + "go.mod": "module golang.org/a\ngo 1.18", + "a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18\nrequire golang.org/a v1.2.3\nreplace golang.org/a => ../", + "b/b.go": "package b", + }, + []folderSummary{{dir: "b"}}, + []string{"a.go", "b/b.go"}, + []viewSummary{{GoModView, "b", nil}}, + }, + { + "go.mod with replace directive; workspace replace off", + map[string]string{ + "go.mod": "module golang.org/a\n require golang.org/b v1.2.3\nreplace example.com/b => ./b", + "a.go": "package a", + "b/go.mod": "module golang.org/b\ngo 1.18\n", + "b/b.go": "package b", + }, + []folderSummary{{ + dir: ".", + options: func(string) map[string]any { + return map[string]any{ + "includeReplaceInWorkspace": false, + } + }, + }}, + []string{"a/a.go", "b/b.go"}, + []viewSummary{{GoModView, ".", nil}, {GoModView, "b", nil}}, + }, + } + + for _, test := range tests { + ctx := context.Background() + t.Run(test.name, func(t *testing.T) { + dir := writeFiles(t, test.files) + rel := fake.RelativeTo(dir) + fs := newMemoizedFS() + + toURI := func(path string) protocol.DocumentURI { + return protocol.URIFromPath(rel.AbsPath(path)) + } + + var folders []*Folder + for _, f := range test.folders { + opts := settings.DefaultOptions() + if f.options != nil { + for _, err := range opts.Set(f.options(dir)) { + t.Fatal(err) + } + } + env, err := FetchGoEnv(ctx, toURI(f.dir), opts) + if err != nil { + t.Fatalf("FetchGoEnv failed: %v", err) + } + folders = append(folders, &Folder{ + Dir: toURI(f.dir), + Name: path.Base(f.dir), + Options: opts, + Env: *env, + }) + } + + var openFiles []protocol.DocumentURI + for _, path := range test.open { + openFiles = append(openFiles, toURI(path)) + } + + defs, err := selectViewDefs(ctx, fs, folders, openFiles) + if err != nil { + t.Fatal(err) + } + var got []viewSummary + for _, def := range defs { + got = append(got, viewSummary{ + Type: def.Type(), + Root: rel.RelPath(def.root.Path()), + Env: def.EnvOverlay(), + }) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("selectViews() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +// TODO(rfindley): this function could be meaningfully factored with the +// various other test helpers of this nature. +func writeFiles(t *testing.T, files map[string]string) string { + root := t.TempDir() + + // This unfortunate step is required because gopls output + // expands symbolic links in its input file names (arguably it + // should not), and on macOS the temp dir is in /var -> private/var. + root, err := filepath.EvalSymlinks(root) + if err != nil { + t.Fatal(err) + } + + for name, content := range files { + filename := filepath.Join(root, name) + if err := os.MkdirAll(filepath.Dir(filename), 0777); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filename, []byte(content), 0666); err != nil { + t.Fatal(err) + } + } + return root +} diff --git a/contribs/gnopls/internal/cache/snapshot.go b/contribs/gnopls/internal/cache/snapshot.go new file mode 100644 index 00000000000..95f222c0eec --- /dev/null +++ b/contribs/gnopls/internal/cache/snapshot.go @@ -0,0 +1,2324 @@ +// 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. + +package cache + +import ( + "bytes" + "context" + "errors" + "fmt" + "go/ast" + "go/build/constraint" + "go/parser" + "go/token" + "go/types" + "os" + "path" + "path/filepath" + "regexp" + "runtime" + "slices" + "sort" + "strconv" + "strings" + "sync" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/types/objectpath" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/methodsets" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/cache/testfuncs" + "golang.org/x/tools/gopls/internal/cache/typerefs" + "golang.org/x/tools/gopls/internal/cache/xrefs" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/filecache" + label1 "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/constraints" + "golang.org/x/tools/gopls/internal/util/immutable" + "golang.org/x/tools/gopls/internal/util/pathutil" + "golang.org/x/tools/gopls/internal/util/persistent" + "golang.org/x/tools/gopls/internal/vulncheck" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/label" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/memoize" + "golang.org/x/tools/internal/packagesinternal" + "golang.org/x/tools/internal/typesinternal" +) + +// A Snapshot represents the current state for a given view. +// +// It is first and foremost an idempotent implementation of file.Source whose +// ReadFile method returns consistent information about the existence and +// content of each file throughout its lifetime. +// +// However, the snapshot also manages additional state (such as parsed files +// and packages) that are derived from file content. +// +// Snapshots are responsible for bookkeeping and invalidation of this state, +// implemented in Snapshot.clone. +type Snapshot struct { + // sequenceID is the monotonically increasing ID of this snapshot within its View. + // + // Sequence IDs for Snapshots from different Views cannot be compared. + sequenceID uint64 + + // TODO(rfindley): the snapshot holding a reference to the view poses + // lifecycle problems: a view may be shut down and waiting for work + // associated with this snapshot to complete. While most accesses of the view + // are benign (options or workspace information), this is not formalized and + // it is wrong for the snapshot to use a shutdown view. + // + // Fix this by passing options and workspace information to the snapshot, + // both of which should be immutable for the snapshot. + view *View + + cancel func() + backgroundCtx context.Context + + store *memoize.Store // cache of handles shared by all snapshots + + refMu sync.Mutex + + // refcount holds the number of outstanding references to the current + // Snapshot. When refcount is decremented to 0, the Snapshot maps are + // destroyed and the done function is called. + // + // TODO(rfindley): use atomic.Int32 on Go 1.19+. + refcount int + done func() // for implementing Session.Shutdown + + // mu guards all of the maps in the snapshot, as well as the builtin URI and + // initialized. + mu sync.Mutex + + // initialized reports whether the snapshot has been initialized. Concurrent + // initialization is guarded by the view.initializationSema. Each snapshot is + // initialized at most once: concurrent initialization is guarded by + // view.initializationSema. + initialized bool + + // initialErr holds the last error resulting from initialization. If + // initialization fails, we only retry when the workspace modules change, + // to avoid too many go/packages calls. + // If initialized is false, initialErr stil holds the error resulting from + // the previous initialization. + // TODO(rfindley): can we unify the lifecycle of initialized and initialErr. + initialErr *InitializationError + + // builtin is the location of builtin.go in GOROOT. + // + // TODO(rfindley): would it make more sense to eagerly parse builtin, and + // instead store a *parsego.File here? + builtin protocol.DocumentURI + + // meta holds loaded metadata. + // + // meta is guarded by mu, but the Graph itself is immutable. + // + // TODO(rfindley): in many places we hold mu while operating on meta, even + // though we only need to hold mu while reading the pointer. + meta *metadata.Graph + + // files maps file URIs to their corresponding FileHandles. + // It may invalidated when a file's content changes. + files *fileMap + + // symbolizeHandles maps each file URI to a handle for the future + // result of computing the symbols declared in that file. + symbolizeHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[symbolizeResult] + + // packages maps a packageKey to a *packageHandle. + // It may be invalidated when a file's content changes. + // + // Invariants to preserve: + // - packages.Get(id).meta == meta.metadata[id] for all ids + // - if a package is in packages, then all of its dependencies should also + // be in packages, unless there is a missing import + packages *persistent.Map[PackageID, *packageHandle] + + // activePackages maps a package ID to a memoized active package, or nil if + // the package is known not to be open. + // + // IDs not contained in the map are not known to be open or not open. + activePackages *persistent.Map[PackageID, *Package] + + // workspacePackages contains the workspace's packages, which are loaded + // when the view is created. It does not contain intermediate test variants. + workspacePackages immutable.Map[PackageID, PackagePath] + + // shouldLoad tracks packages that need to be reloaded, mapping a PackageID + // to the package paths that should be used to reload it + // + // When we try to load a package, we clear it from the shouldLoad map + // regardless of whether the load succeeded, to prevent endless loads. + shouldLoad *persistent.Map[PackageID, []PackagePath] + + // unloadableFiles keeps track of files that we've failed to load. + unloadableFiles *persistent.Set[protocol.DocumentURI] + + // TODO(rfindley): rename the handles below to "promises". A promise is + // different from a handle (we mutate the package handle.) + + // parseModHandles keeps track of any parseModHandles for the snapshot. + // The handles need not refer to only the view's go.mod file. + parseModHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[parseModResult] + + // parseWorkHandles keeps track of any parseWorkHandles for the snapshot. + // The handles need not refer to only the view's go.work file. + parseWorkHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[parseWorkResult] + + // Preserve go.mod-related handles to avoid garbage-collecting the results + // of various calls to the go command. The handles need not refer to only + // the view's go.mod file. + modTidyHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modTidyResult] + modWhyHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modWhyResult] + modVulnHandles *persistent.Map[protocol.DocumentURI, *memoize.Promise] // *memoize.Promise[modVulnResult] + + // importGraph holds a shared import graph to use for type-checking. Adding + // more packages to this import graph can speed up type checking, at the + // expense of in-use memory. + // + // See getImportGraph for additional documentation. + importGraphDone chan struct{} // closed when importGraph is set; may be nil + importGraph *importGraph // copied from preceding snapshot and re-evaluated + + // pkgIndex is an index of package IDs, for efficient storage of typerefs. + pkgIndex *typerefs.PackageIndex + + // moduleUpgrades tracks known upgrades for module paths in each modfile. + // Each modfile has a map of module name to upgrade version. + moduleUpgrades *persistent.Map[protocol.DocumentURI, map[string]string] + + // vulns maps each go.mod file's URI to its known vulnerabilities. + vulns *persistent.Map[protocol.DocumentURI, *vulncheck.Result] + + // gcOptimizationDetails describes the packages for which we want + // optimization details to be included in the diagnostics. + gcOptimizationDetails map[metadata.PackageID]unit +} + +var _ memoize.RefCounted = (*Snapshot)(nil) // snapshots are reference-counted + +func (s *Snapshot) awaitPromise(ctx context.Context, p *memoize.Promise) (interface{}, error) { + return p.Get(ctx, s) +} + +// Acquire prevents the snapshot from being destroyed until the returned +// function is called. +// +// (s.Acquire().release() could instead be expressed as a pair of +// method calls s.IncRef(); s.DecRef(). The latter has the advantage +// that the DecRefs are fungible and don't require holding anything in +// addition to the refcounted object s, but paradoxically that is also +// an advantage of the current approach, which forces the caller to +// consider the release function at every stage, making a reference +// leak more obvious.) +func (s *Snapshot) Acquire() func() { + s.refMu.Lock() + defer s.refMu.Unlock() + assert(s.refcount > 0, "non-positive refs") + s.refcount++ + + return s.decref +} + +// decref should only be referenced by Acquire, and by View when it frees its +// reference to View.snapshot. +func (s *Snapshot) decref() { + s.refMu.Lock() + defer s.refMu.Unlock() + + assert(s.refcount > 0, "non-positive refs") + s.refcount-- + if s.refcount == 0 { + s.packages.Destroy() + s.activePackages.Destroy() + s.files.destroy() + s.symbolizeHandles.Destroy() + s.parseModHandles.Destroy() + s.parseWorkHandles.Destroy() + s.modTidyHandles.Destroy() + s.modVulnHandles.Destroy() + s.modWhyHandles.Destroy() + s.unloadableFiles.Destroy() + s.moduleUpgrades.Destroy() + s.vulns.Destroy() + s.done() + } +} + +// SequenceID is the sequence id of this snapshot within its containing +// view. +// +// Relative to their view sequence ids are monotonically increasing, but this +// does not hold globally: when new views are created their initial snapshot +// has sequence ID 0. +func (s *Snapshot) SequenceID() uint64 { + return s.sequenceID +} + +// SnapshotLabels returns a new slice of labels that should be used for events +// related to a snapshot. +func (s *Snapshot) Labels() []label.Label { + return []label.Label{ + label1.ViewID.Of(s.view.id), + label1.Snapshot.Of(s.SequenceID()), + label1.Directory.Of(s.Folder().Path()), + } +} + +// Folder returns the folder at the base of this snapshot. +func (s *Snapshot) Folder() protocol.DocumentURI { + return s.view.folder.Dir +} + +// View returns the View associated with this snapshot. +func (s *Snapshot) View() *View { + return s.view +} + +// FileKind returns the kind of a file. +// +// We can't reliably deduce the kind from the file name alone, +// as some editors can be told to interpret a buffer as +// language different from the file name heuristic, e.g. that +// an .html file actually contains Go "html/template" syntax, +// or even that a .go file contains Python. +func (s *Snapshot) FileKind(fh file.Handle) file.Kind { + if k := fileKind(fh); k != file.UnknownKind { + return k + } + fext := filepath.Ext(fh.URI().Path()) + exts := s.Options().TemplateExtensions + for _, ext := range exts { + if fext == ext || fext == "."+ext { + return file.Tmpl + } + } + + // and now what? This should never happen, but it does for cgo before go1.15 + // + // TODO(rfindley): this doesn't look right. We should default to UnknownKind. + // Also, I don't understand the comment above, though I'd guess before go1.15 + // we encountered cgo files without the .go extension. + return file.Go +} + +// fileKind returns the default file kind for a file, before considering +// template file extensions. See [Snapshot.FileKind]. +func fileKind(fh file.Handle) file.Kind { + // The kind of an unsaved buffer comes from the + // TextDocumentItem.LanguageID field in the didChange event, + // not from the file name. They may differ. + if o, ok := fh.(*overlay); ok { + if o.kind != file.UnknownKind { + return o.kind + } + } + + fext := filepath.Ext(fh.URI().Path()) + switch fext { + case ".go": + return file.Go + case ".mod": + return file.Mod + case ".sum": + return file.Sum + case ".work": + return file.Work + } + return file.UnknownKind +} + +// Options returns the options associated with this snapshot. +func (s *Snapshot) Options() *settings.Options { + return s.view.folder.Options +} + +// BackgroundContext returns a context used for all background processing +// on behalf of this snapshot. +func (s *Snapshot) BackgroundContext() context.Context { + return s.backgroundCtx +} + +// Templates returns the .tmpl files. +func (s *Snapshot) Templates() map[protocol.DocumentURI]file.Handle { + s.mu.Lock() + defer s.mu.Unlock() + + tmpls := map[protocol.DocumentURI]file.Handle{} + s.files.foreach(func(k protocol.DocumentURI, fh file.Handle) { + if s.FileKind(fh) == file.Tmpl { + tmpls[k] = fh + } + }) + return tmpls +} + +// config returns the configuration used for the snapshot's interaction with +// the go/packages API. It uses the given working directory. +// +// TODO(rstambler): go/packages requires that we do not provide overlays for +// multiple modules in one config, so buildOverlay needs to filter overlays by +// module. +func (s *Snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config { + + cfg := &packages.Config{ + Context: ctx, + Dir: inv.WorkingDir, + Env: inv.Env, + BuildFlags: inv.BuildFlags, + Mode: packages.NeedName | + packages.NeedFiles | + packages.NeedCompiledGoFiles | + packages.NeedImports | + packages.NeedDeps | + packages.NeedTypesSizes | + packages.NeedModule | + packages.NeedEmbedFiles | + packages.LoadMode(packagesinternal.DepsErrors) | + packages.LoadMode(packagesinternal.ForTest), + Fset: nil, // we do our own parsing + Overlay: s.buildOverlays(), + ParseFile: func(*token.FileSet, string, []byte) (*ast.File, error) { + panic("go/packages must not be used to parse files") + }, + Logf: func(format string, args ...interface{}) { + if s.Options().VerboseOutput { + event.Log(ctx, fmt.Sprintf(format, args...)) + } + }, + Tests: true, + } + packagesinternal.SetModFile(cfg, inv.ModFile) + packagesinternal.SetModFlag(cfg, inv.ModFlag) + // We want to type check cgo code if go/types supports it. + if typesinternal.SetUsesCgo(&types.Config{}) { + cfg.Mode |= packages.LoadMode(packagesinternal.TypecheckCgo) + } + return cfg +} + +// RunGoModUpdateCommands runs a series of `go` commands that updates the go.mod +// and go.sum file for wd, and returns their updated contents. +// +// TODO(rfindley): the signature of RunGoModUpdateCommands is very confusing, +// and is the only thing forcing the ModFlag and ModFile indirection. +// Simplify it. +func (s *Snapshot) RunGoModUpdateCommands(ctx context.Context, modURI protocol.DocumentURI, run func(invoke func(...string) (*bytes.Buffer, error)) error) ([]byte, []byte, error) { + tempDir, cleanupModDir, err := TempModDir(ctx, s, modURI) + if err != nil { + return nil, nil, err + } + defer cleanupModDir() + + // TODO(rfindley): we must use ModFlag and ModFile here (rather than simply + // setting Args), because without knowing the verb, we can't know whether + // ModFlag is appropriate. Refactor so that args can be set by the caller. + inv, cleanupInvocation, err := s.GoCommandInvocation(true, &gocommand.Invocation{ + WorkingDir: modURI.Dir().Path(), + ModFlag: "mod", + ModFile: filepath.Join(tempDir, "go.mod"), + Env: []string{"GOWORK=off"}, + }) + if err != nil { + return nil, nil, err + } + defer cleanupInvocation() + invoke := func(args ...string) (*bytes.Buffer, error) { + inv.Verb = args[0] + inv.Args = args[1:] + return s.view.gocmdRunner.Run(ctx, *inv) + } + if err := run(invoke); err != nil { + return nil, nil, err + } + var modBytes, sumBytes []byte + modBytes, err = os.ReadFile(filepath.Join(tempDir, "go.mod")) + if err != nil && !os.IsNotExist(err) { + return nil, nil, err + } + sumBytes, err = os.ReadFile(filepath.Join(tempDir, "go.sum")) + if err != nil && !os.IsNotExist(err) { + return nil, nil, err + } + return modBytes, sumBytes, nil +} + +// TempModDir creates a temporary directory with the contents of the provided +// modURI, as well as its corresponding go.sum file, if it exists. On success, +// it is the caller's responsibility to call the cleanup function to remove the +// directory when it is no longer needed. +func TempModDir(ctx context.Context, fs file.Source, modURI protocol.DocumentURI) (dir string, _ func(), rerr error) { + dir, err := os.MkdirTemp("", "gopls-tempmod") + if err != nil { + return "", nil, err + } + cleanup := func() { + if err := os.RemoveAll(dir); err != nil { + event.Error(ctx, "cleaning temp dir", err) + } + } + defer func() { + if rerr != nil { + cleanup() + } + }() + + // If go.mod exists, write it. + modFH, err := fs.ReadFile(ctx, modURI) + if err != nil { + return "", nil, err // context cancelled + } + if data, err := modFH.Content(); err == nil { + if err := os.WriteFile(filepath.Join(dir, "go.mod"), data, 0666); err != nil { + return "", nil, err + } + } + + // If go.sum exists, write it. + sumURI := protocol.DocumentURI(strings.TrimSuffix(string(modURI), ".mod") + ".sum") + sumFH, err := fs.ReadFile(ctx, sumURI) + if err != nil { + return "", nil, err // context cancelled + } + if data, err := sumFH.Content(); err == nil { + if err := os.WriteFile(filepath.Join(dir, "go.sum"), data, 0666); err != nil { + return "", nil, err + } + } + + return dir, cleanup, nil +} + +// GoCommandInvocation populates inv with configuration for running go commands +// on the snapshot. +// +// On success, the caller must call the cleanup function exactly once +// when the invocation is no longer needed. +// +// TODO(rfindley): although this function has been simplified significantly, +// additional refactoring is still required: the responsibility for Env and +// BuildFlags should be more clearly expressed in the API. +// +// If allowNetwork is set, do not set GOPROXY=off. +func (s *Snapshot) GoCommandInvocation(allowNetwork bool, inv *gocommand.Invocation) (_ *gocommand.Invocation, cleanup func(), _ error) { + // TODO(rfindley): it's not clear that this is doing the right thing. + // Should inv.Env really overwrite view.options? Should s.view.envOverlay + // overwrite inv.Env? (Do we ever invoke this with a non-empty inv.Env?) + // + // We should survey existing uses and write down rules for how env is + // applied. + inv.Env = slices.Concat( + os.Environ(), + s.Options().EnvSlice(), + inv.Env, + []string{"GO111MODULE=" + s.view.adjustedGO111MODULE()}, + s.view.EnvOverlay(), + ) + inv.BuildFlags = slices.Clone(s.Options().BuildFlags) + + if !allowNetwork && !s.Options().AllowImplicitNetworkAccess { + inv.Env = append(inv.Env, "GOPROXY=off") + } + + // Write overlay files for unsaved editor buffers. + overlay, cleanup, err := gocommand.WriteOverlays(s.buildOverlays()) + if err != nil { + return nil, nil, err + } + inv.Overlay = overlay + return inv, cleanup, nil +} + +// buildOverlays returns a new mapping from logical file name to +// effective content, for each unsaved editor buffer, in the same form +// as [packages.Cfg]'s Overlay field. +func (s *Snapshot) buildOverlays() map[string][]byte { + overlays := make(map[string][]byte) + for _, overlay := range s.Overlays() { + if overlay.saved { + continue + } + // TODO(rfindley): previously, there was a todo here to make sure we don't + // send overlays outside of the current view. IMO we should instead make + // sure this doesn't matter. + overlays[overlay.URI().Path()] = overlay.content + } + return overlays +} + +// Overlays returns the set of overlays at this snapshot. +// +// Note that this may differ from the set of overlays on the server, if the +// snapshot observed a historical state. +func (s *Snapshot) Overlays() []*overlay { + s.mu.Lock() + defer s.mu.Unlock() + + return s.files.getOverlays() +} + +// Package data kinds, identifying various package data that may be stored in +// the file cache. +const ( + xrefsKind = "xrefs" + methodSetsKind = "methodsets" + testsKind = "tests" + exportDataKind = "export" + diagnosticsKind = "diagnostics" + typerefsKind = "typerefs" +) + +// PackageDiagnostics returns diagnostics for files contained in specified +// packages. +// +// If these diagnostics cannot be loaded from cache, the requested packages +// may be type-checked. +func (s *Snapshot) PackageDiagnostics(ctx context.Context, ids ...PackageID) (map[protocol.DocumentURI][]*Diagnostic, error) { + ctx, done := event.Start(ctx, "cache.snapshot.PackageDiagnostics") + defer done() + + var mu sync.Mutex + perFile := make(map[protocol.DocumentURI][]*Diagnostic) + collect := func(diags []*Diagnostic) { + mu.Lock() + defer mu.Unlock() + for _, diag := range diags { + perFile[diag.URI] = append(perFile[diag.URI], diag) + } + } + pre := func(_ int, ph *packageHandle) bool { + data, err := filecache.Get(diagnosticsKind, ph.key) + if err == nil { // hit + collect(ph.loadDiagnostics) + collect(decodeDiagnostics(data)) + return false + } else if err != filecache.ErrNotFound { + event.Error(ctx, "reading diagnostics from filecache", err) + } + return true + } + post := func(_ int, pkg *Package) { + collect(pkg.loadDiagnostics) + collect(pkg.pkg.diagnostics) + } + return perFile, s.forEachPackage(ctx, ids, pre, post) +} + +// References returns cross-reference indexes for the specified packages. +// +// If these indexes cannot be loaded from cache, the requested packages may +// be type-checked. +func (s *Snapshot) References(ctx context.Context, ids ...PackageID) ([]xrefIndex, error) { + ctx, done := event.Start(ctx, "cache.snapshot.References") + defer done() + + indexes := make([]xrefIndex, len(ids)) + pre := func(i int, ph *packageHandle) bool { + data, err := filecache.Get(xrefsKind, ph.key) + if err == nil { // hit + indexes[i] = xrefIndex{mp: ph.mp, data: data} + return false + } else if err != filecache.ErrNotFound { + event.Error(ctx, "reading xrefs from filecache", err) + } + return true + } + post := func(i int, pkg *Package) { + indexes[i] = xrefIndex{mp: pkg.metadata, data: pkg.pkg.xrefs()} + } + return indexes, s.forEachPackage(ctx, ids, pre, post) +} + +// An xrefIndex is a helper for looking up references in a given package. +type xrefIndex struct { + mp *metadata.Package + data []byte +} + +func (index xrefIndex) Lookup(targets map[PackagePath]map[objectpath.Path]struct{}) []protocol.Location { + return xrefs.Lookup(index.mp, index.data, targets) +} + +// MethodSets returns method-set indexes for the specified packages. +// +// If these indexes cannot be loaded from cache, the requested packages may +// be type-checked. +func (s *Snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methodsets.Index, error) { + ctx, done := event.Start(ctx, "cache.snapshot.MethodSets") + defer done() + + indexes := make([]*methodsets.Index, len(ids)) + pre := func(i int, ph *packageHandle) bool { + data, err := filecache.Get(methodSetsKind, ph.key) + if err == nil { // hit + indexes[i] = methodsets.Decode(data) + return false + } else if err != filecache.ErrNotFound { + event.Error(ctx, "reading methodsets from filecache", err) + } + return true + } + post := func(i int, pkg *Package) { + indexes[i] = pkg.pkg.methodsets() + } + return indexes, s.forEachPackage(ctx, ids, pre, post) +} + +// Tests returns test-set indexes for the specified packages. There is a +// one-to-one correspondence between ID and Index. +// +// If these indexes cannot be loaded from cache, the requested packages may be +// type-checked. +func (s *Snapshot) Tests(ctx context.Context, ids ...PackageID) ([]*testfuncs.Index, error) { + ctx, done := event.Start(ctx, "cache.snapshot.Tests") + defer done() + + indexes := make([]*testfuncs.Index, len(ids)) + pre := func(i int, ph *packageHandle) bool { + data, err := filecache.Get(testsKind, ph.key) + if err == nil { // hit + indexes[i] = testfuncs.Decode(data) + return false + } else if err != filecache.ErrNotFound { + event.Error(ctx, "reading tests from filecache", err) + } + return true + } + post := func(i int, pkg *Package) { + indexes[i] = pkg.pkg.tests() + } + return indexes, s.forEachPackage(ctx, ids, pre, post) +} + +// MetadataForFile returns a new slice containing metadata for each +// package containing the Go file identified by uri, ordered by the +// number of CompiledGoFiles (i.e. "narrowest" to "widest" package), +// and secondarily by IsIntermediateTestVariant (false < true). +// The result may include tests and intermediate test variants of +// importable packages. +// It returns an error if the context was cancelled. +func (s *Snapshot) MetadataForFile(ctx context.Context, uri protocol.DocumentURI) ([]*metadata.Package, error) { + if s.view.typ == AdHocView { + // As described in golang/go#57209, in ad-hoc workspaces (where we load ./ + // rather than ./...), preempting the directory load with file loads can + // lead to an inconsistent outcome, where certain files are loaded with + // command-line-arguments packages and others are loaded only in the ad-hoc + // package. Therefore, ensure that the workspace is loaded before doing any + // file loads. + if err := s.awaitLoaded(ctx); err != nil { + return nil, err + } + } + + s.mu.Lock() + + // Start with the set of package associations derived from the last load. + ids := s.meta.IDs[uri] + + shouldLoad := false // whether any packages containing uri are marked 'shouldLoad' + for _, id := range ids { + if pkgs, _ := s.shouldLoad.Get(id); len(pkgs) > 0 { + shouldLoad = true + } + } + + // Check if uri is known to be unloadable. + unloadable := s.unloadableFiles.Contains(uri) + + s.mu.Unlock() + + // Reload if loading is likely to improve the package associations for uri: + // - uri is not contained in any valid packages + // - ...or one of the packages containing uri is marked 'shouldLoad' + // - ...but uri is not unloadable + if (shouldLoad || len(ids) == 0) && !unloadable { + scope := fileLoadScope(uri) + err := s.load(ctx, false, scope) + + // + // Return the context error here as the current operation is no longer + // valid. + if err != nil { + // Guard against failed loads due to context cancellation. We don't want + // to mark loads as completed if they failed due to context cancellation. + if ctx.Err() != nil { + return nil, ctx.Err() + } + + // Don't return an error here, as we may still return stale IDs. + // Furthermore, the result of MetadataForFile should be consistent upon + // subsequent calls, even if the file is marked as unloadable. + if !errors.Is(err, errNoPackages) { + event.Error(ctx, "MetadataForFile", err) + } + } + + // We must clear scopes after loading. + // + // TODO(rfindley): unlike reloadWorkspace, this is simply marking loaded + // packages as loaded. We could do this from snapshot.load and avoid + // raciness. + s.clearShouldLoad(scope) + } + + // Retrieve the metadata. + s.mu.Lock() + defer s.mu.Unlock() + ids = s.meta.IDs[uri] + metas := make([]*metadata.Package, len(ids)) + for i, id := range ids { + metas[i] = s.meta.Packages[id] + if metas[i] == nil { + panic("nil metadata") + } + } + // Metadata is only ever added by loading, + // so if we get here and still have + // no IDs, uri is unloadable. + if !unloadable && len(ids) == 0 { + s.unloadableFiles.Add(uri) + } + + // Sort packages "narrowest" to "widest" (in practice: + // non-tests before tests), and regular packages before + // their intermediate test variants (which have the same + // files but different imports). + sort.Slice(metas, func(i, j int) bool { + x, y := metas[i], metas[j] + xfiles, yfiles := len(x.CompiledGoFiles), len(y.CompiledGoFiles) + if xfiles != yfiles { + return xfiles < yfiles + } + return boolLess(x.IsIntermediateTestVariant(), y.IsIntermediateTestVariant()) + }) + + return metas, nil +} + +func boolLess(x, y bool) bool { return !x && y } // false < true + +// ReverseDependencies returns a new mapping whose entries are +// the ID and Metadata of each package in the workspace that +// directly or transitively depend on the package denoted by id, +// excluding id itself. +func (s *Snapshot) ReverseDependencies(ctx context.Context, id PackageID, transitive bool) (map[PackageID]*metadata.Package, error) { + if err := s.awaitLoaded(ctx); err != nil { + return nil, err + } + + meta := s.MetadataGraph() + var rdeps map[PackageID]*metadata.Package + if transitive { + rdeps = meta.ReverseReflexiveTransitiveClosure(id) + + // Remove the original package ID from the map. + // (Callers all want irreflexivity but it's easier + // to compute reflexively then subtract.) + delete(rdeps, id) + + } else { + // direct reverse dependencies + rdeps = make(map[PackageID]*metadata.Package) + for _, rdepID := range meta.ImportedBy[id] { + if rdep := meta.Packages[rdepID]; rdep != nil { + rdeps[rdepID] = rdep + } + } + } + + return rdeps, nil +} + +// -- Active package tracking -- +// +// We say a package is "active" if any of its files are open. +// This is an optimization: the "active" concept is an +// implementation detail of the cache and is not exposed +// in the source or Snapshot API. +// After type-checking we keep active packages in memory. +// The activePackages persistent map does bookkeeping for +// the set of active packages. + +// getActivePackage returns a the memoized active package for id, if it exists. +// If id is not active or has not yet been type-checked, it returns nil. +func (s *Snapshot) getActivePackage(id PackageID) *Package { + s.mu.Lock() + defer s.mu.Unlock() + + if value, ok := s.activePackages.Get(id); ok { + return value + } + return nil +} + +// setActivePackage checks if pkg is active, and if so either records it in +// the active packages map or returns the existing memoized active package for id. +func (s *Snapshot) setActivePackage(id PackageID, pkg *Package) { + s.mu.Lock() + defer s.mu.Unlock() + + if _, ok := s.activePackages.Get(id); ok { + return // already memoized + } + + if containsOpenFileLocked(s, pkg.Metadata()) { + s.activePackages.Set(id, pkg, nil) + } else { + s.activePackages.Set(id, (*Package)(nil), nil) // remember that pkg is not open + } +} + +func (s *Snapshot) resetActivePackagesLocked() { + s.activePackages.Destroy() + s.activePackages = new(persistent.Map[PackageID, *Package]) +} + +// See Session.FileWatchingGlobPatterns for a description of gopls' file +// watching heuristic. +func (s *Snapshot) fileWatchingGlobPatterns() map[protocol.RelativePattern]unit { + // Always watch files that may change the view definition. + patterns := make(map[protocol.RelativePattern]unit) + + // If GOWORK is outside the folder, ensure we are watching it. + if s.view.gowork != "" && !s.view.folder.Dir.Encloses(s.view.gowork) { + workPattern := protocol.RelativePattern{ + BaseURI: s.view.gowork.Dir(), + Pattern: path.Base(string(s.view.gowork)), + } + patterns[workPattern] = unit{} + } + + extensions := "go,mod,sum,work" + for _, ext := range s.Options().TemplateExtensions { + extensions += "," + ext + } + watchGoFiles := fmt.Sprintf("**/*.{%s}", extensions) + + var dirs []string + if s.view.typ.usesModules() { + if s.view.typ == GoWorkView { + workVendorDir := filepath.Join(s.view.gowork.Dir().Path(), "vendor") + workVendorURI := protocol.URIFromPath(workVendorDir) + patterns[protocol.RelativePattern{BaseURI: workVendorURI, Pattern: watchGoFiles}] = unit{} + } + + // In module mode, watch directories containing active modules, and collect + // these dirs for later filtering the set of known directories. + // + // The assumption is that the user is not actively editing non-workspace + // modules, so don't pay the price of file watching. + for modFile := range s.view.workspaceModFiles { + dir := filepath.Dir(modFile.Path()) + dirs = append(dirs, dir) + + // TODO(golang/go#64724): thoroughly test these patterns, particularly on + // on Windows. + // + // Note that glob patterns should use '/' on Windows: + // https://code.visualstudio.com/docs/editor/glob-patterns + patterns[protocol.RelativePattern{BaseURI: modFile.Dir(), Pattern: watchGoFiles}] = unit{} + } + } else { + // In non-module modes (GOPATH or AdHoc), we just watch the workspace root. + dirs = []string{s.view.root.Path()} + patterns[protocol.RelativePattern{Pattern: watchGoFiles}] = unit{} + } + + if s.watchSubdirs() { + // Some clients (e.g. VS Code) do not send notifications for changes to + // directories that contain Go code (golang/go#42348). To handle this, + // explicitly watch all of the directories in the workspace. We find them + // by adding the directories of every file in the snapshot's workspace + // directories. There may be thousands of patterns, each a single + // directory. + // + // We compute this set by looking at files that we've previously observed. + // This may miss changed to directories that we haven't observed, but that + // shouldn't matter as there is nothing to invalidate (if a directory falls + // in forest, etc). + // + // (A previous iteration created a single glob pattern holding a union of + // all the directories, but this was found to cause VS Code to get stuck + // for several minutes after a buffer was saved twice in a workspace that + // had >8000 watched directories.) + // + // Some clients (notably coc.nvim, which uses watchman for globs) perform + // poorly with a large list of individual directories. + s.addKnownSubdirs(patterns, dirs) + } + + return patterns +} + +func (s *Snapshot) addKnownSubdirs(patterns map[protocol.RelativePattern]unit, wsDirs []string) { + s.mu.Lock() + defer s.mu.Unlock() + + s.files.getDirs().Range(func(dir string) { + for _, wsDir := range wsDirs { + if pathutil.InDir(wsDir, dir) { + patterns[protocol.RelativePattern{Pattern: filepath.ToSlash(dir)}] = unit{} + } + } + }) +} + +// watchSubdirs reports whether gopls should request separate file watchers for +// each relevant subdirectory. This is necessary only for clients (namely VS +// Code) that do not send notifications for individual files in a directory +// when the entire directory is deleted. +func (s *Snapshot) watchSubdirs() bool { + switch p := s.Options().SubdirWatchPatterns; p { + case settings.SubdirWatchPatternsOn: + return true + case settings.SubdirWatchPatternsOff: + return false + case settings.SubdirWatchPatternsAuto: + // See the documentation of InternalOptions.SubdirWatchPatterns for an + // explanation of why VS Code gets a different default value here. + // + // Unfortunately, there is no authoritative list of client names, nor any + // requirements that client names do not change. We should update the VS + // Code extension to set a default value of "subdirWatchPatterns" to "on", + // so that this workaround is only temporary. + if s.Options().ClientInfo.Name == "Visual Studio Code" { + return true + } + return false + default: + bug.Reportf("invalid subdirWatchPatterns: %q", p) + return false + } +} + +// filesInDir returns all files observed by the snapshot that are contained in +// a directory with the provided URI. +func (s *Snapshot) filesInDir(uri protocol.DocumentURI) []protocol.DocumentURI { + s.mu.Lock() + defer s.mu.Unlock() + + dir := uri.Path() + if !s.files.getDirs().Contains(dir) { + return nil + } + var files []protocol.DocumentURI + s.files.foreach(func(uri protocol.DocumentURI, _ file.Handle) { + if pathutil.InDir(dir, uri.Path()) { + files = append(files, uri) + } + }) + return files +} + +// WorkspaceMetadata returns a new, unordered slice containing +// metadata for all ordinary and test packages (but not +// intermediate test variants) in the workspace. +// +// The workspace is the set of modules typically defined by a +// go.work file. It is not transitively closed: for example, +// the standard library is not usually part of the workspace +// even though every module in the workspace depends on it. +// +// Operations that must inspect all the dependencies of the +// workspace packages should instead use AllMetadata. +func (s *Snapshot) WorkspaceMetadata(ctx context.Context) ([]*metadata.Package, error) { + if err := s.awaitLoaded(ctx); err != nil { + return nil, err + } + + s.mu.Lock() + defer s.mu.Unlock() + + meta := make([]*metadata.Package, 0, s.workspacePackages.Len()) + s.workspacePackages.Range(func(id PackageID, _ PackagePath) { + meta = append(meta, s.meta.Packages[id]) + }) + return meta, nil +} + +// isWorkspacePackage reports whether the given package ID refers to a +// workspace package for the snapshot. +func (s *Snapshot) isWorkspacePackage(id PackageID) bool { + s.mu.Lock() + defer s.mu.Unlock() + _, ok := s.workspacePackages.Value(id) + return ok +} + +// Symbols extracts and returns symbol information for every file contained in +// a loaded package. It awaits snapshot loading. +// +// If workspaceOnly is set, this only includes symbols from files in a +// workspace package. Otherwise, it returns symbols from all loaded packages. +// +// TODO(rfindley): move to symbols.go. +func (s *Snapshot) Symbols(ctx context.Context, workspaceOnly bool) (map[protocol.DocumentURI][]Symbol, error) { + var ( + meta []*metadata.Package + err error + ) + if workspaceOnly { + meta, err = s.WorkspaceMetadata(ctx) + } else { + meta, err = s.AllMetadata(ctx) + } + if err != nil { + return nil, fmt.Errorf("loading metadata: %v", err) + } + + goFiles := make(map[protocol.DocumentURI]struct{}) + for _, mp := range meta { + for _, uri := range mp.GoFiles { + goFiles[uri] = struct{}{} + } + for _, uri := range mp.CompiledGoFiles { + goFiles[uri] = struct{}{} + } + } + + // Symbolize them in parallel. + var ( + group errgroup.Group + nprocs = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU + resultMu sync.Mutex + result = make(map[protocol.DocumentURI][]Symbol) + ) + group.SetLimit(nprocs) + for uri := range goFiles { + uri := uri + group.Go(func() error { + symbols, err := s.symbolize(ctx, uri) + if err != nil { + return err + } + resultMu.Lock() + result[uri] = symbols + resultMu.Unlock() + return nil + }) + } + // Keep going on errors, but log the first failure. + // Partial results are better than no symbol results. + if err := group.Wait(); err != nil { + event.Error(ctx, "getting snapshot symbols", err) + } + return result, nil +} + +// AllMetadata returns a new unordered array of metadata for +// all packages known to this snapshot, which includes the +// packages of all workspace modules plus their transitive +// import dependencies. +// +// It may also contain ad-hoc packages for standalone files. +// It includes all test variants. +// +// TODO(rfindley): Replace this with s.MetadataGraph(). +func (s *Snapshot) AllMetadata(ctx context.Context) ([]*metadata.Package, error) { + if err := s.awaitLoaded(ctx); err != nil { + return nil, err + } + + g := s.MetadataGraph() + + meta := make([]*metadata.Package, 0, len(g.Packages)) + for _, mp := range g.Packages { + meta = append(meta, mp) + } + return meta, nil +} + +// GoModForFile returns the URI of the go.mod file for the given URI. +// +// TODO(rfindley): clarify that this is only active modules. Or update to just +// use findRootPattern. +func (s *Snapshot) GoModForFile(uri protocol.DocumentURI) protocol.DocumentURI { + return moduleForURI(s.view.workspaceModFiles, uri) +} + +func moduleForURI(modFiles map[protocol.DocumentURI]struct{}, uri protocol.DocumentURI) protocol.DocumentURI { + var match protocol.DocumentURI + for modURI := range modFiles { + if !modURI.Dir().Encloses(uri) { + continue + } + if len(modURI) > len(match) { + match = modURI + } + } + return match +} + +// nearestModFile finds the nearest go.mod file contained in the directory +// containing uri, or a parent of that directory. +// +// The given uri must be a file, not a directory. +func nearestModFile(ctx context.Context, uri protocol.DocumentURI, fs file.Source) (protocol.DocumentURI, error) { + dir := filepath.Dir(uri.Path()) + return findRootPattern(ctx, protocol.URIFromPath(dir), "go.mod", fs) +} + +// Metadata returns the metadata for the specified package, +// or nil if it was not found. +func (s *Snapshot) Metadata(id PackageID) *metadata.Package { + s.mu.Lock() + defer s.mu.Unlock() + return s.meta.Packages[id] +} + +// clearShouldLoad clears package IDs that no longer need to be reloaded after +// scopes has been loaded. +func (s *Snapshot) clearShouldLoad(scopes ...loadScope) { + s.mu.Lock() + defer s.mu.Unlock() + + for _, scope := range scopes { + switch scope := scope.(type) { + case packageLoadScope: + scopePath := PackagePath(scope) + var toDelete []PackageID + s.shouldLoad.Range(func(id PackageID, pkgPaths []PackagePath) { + for _, pkgPath := range pkgPaths { + if pkgPath == scopePath { + toDelete = append(toDelete, id) + } + } + }) + for _, id := range toDelete { + s.shouldLoad.Delete(id) + } + case fileLoadScope: + uri := protocol.DocumentURI(scope) + ids := s.meta.IDs[uri] + for _, id := range ids { + s.shouldLoad.Delete(id) + } + } + } +} + +// FindFile returns the FileHandle for the given URI, if it is already +// in the given snapshot. +// TODO(adonovan): delete this operation; use ReadFile instead. +func (s *Snapshot) FindFile(uri protocol.DocumentURI) file.Handle { + s.mu.Lock() + defer s.mu.Unlock() + + result, _ := s.files.get(uri) + return result +} + +// ReadFile returns a File for the given URI. If the file is unknown it is added +// to the managed set. +// +// ReadFile succeeds even if the file does not exist. A non-nil error return +// indicates some type of internal error, for example if ctx is cancelled. +func (s *Snapshot) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) { + s.mu.Lock() + defer s.mu.Unlock() + + return lockedSnapshot{s}.ReadFile(ctx, uri) +} + +// lockedSnapshot implements the file.Source interface, while holding s.mu. +// +// TODO(rfindley): This unfortunate type had been eliminated, but it had to be +// restored to fix golang/go#65801. We should endeavor to remove it again. +type lockedSnapshot struct { + s *Snapshot +} + +func (s lockedSnapshot) ReadFile(ctx context.Context, uri protocol.DocumentURI) (file.Handle, error) { + fh, ok := s.s.files.get(uri) + if !ok { + var err error + fh, err = s.s.view.fs.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + s.s.files.set(uri, fh) + } + return fh, nil +} + +// preloadFiles delegates to the view FileSource to read the requested uris in +// parallel, without holding the snapshot lock. +func (s *Snapshot) preloadFiles(ctx context.Context, uris []protocol.DocumentURI) { + files := make([]file.Handle, len(uris)) + var wg sync.WaitGroup + iolimit := make(chan struct{}, 20) // I/O concurrency limiting semaphore + for i, uri := range uris { + wg.Add(1) + iolimit <- struct{}{} + go func(i int, uri protocol.DocumentURI) { + defer wg.Done() + fh, err := s.view.fs.ReadFile(ctx, uri) + <-iolimit + if err != nil && ctx.Err() == nil { + event.Error(ctx, fmt.Sprintf("reading %s", uri), err) + return + } + files[i] = fh + }(i, uri) + } + wg.Wait() + + s.mu.Lock() + defer s.mu.Unlock() + + for i, fh := range files { + if fh == nil { + continue // error logged above + } + uri := uris[i] + if _, ok := s.files.get(uri); !ok { + s.files.set(uri, fh) + } + } +} + +// IsOpen returns whether the editor currently has a file open. +func (s *Snapshot) IsOpen(uri protocol.DocumentURI) bool { + s.mu.Lock() + defer s.mu.Unlock() + + fh, _ := s.files.get(uri) + _, open := fh.(*overlay) + return open +} + +// MetadataGraph returns the current metadata graph for the Snapshot. +func (s *Snapshot) MetadataGraph() *metadata.Graph { + s.mu.Lock() + defer s.mu.Unlock() + return s.meta +} + +// InitializationError returns the last error from initialization. +func (s *Snapshot) InitializationError() *InitializationError { + s.mu.Lock() + defer s.mu.Unlock() + return s.initialErr +} + +// awaitLoaded awaits initialization and package reloading, and returns +// ctx.Err(). +func (s *Snapshot) awaitLoaded(ctx context.Context) error { + // Do not return results until the snapshot's view has been initialized. + s.AwaitInitialized(ctx) + s.reloadWorkspace(ctx) + return ctx.Err() +} + +// AwaitInitialized waits until the snapshot's view is initialized. +func (s *Snapshot) AwaitInitialized(ctx context.Context) { + select { + case <-ctx.Done(): + return + case <-s.view.initialWorkspaceLoad: + } + // We typically prefer to run something as intensive as the IWL without + // blocking. I'm not sure if there is a way to do that here. + s.initialize(ctx, false) +} + +// reloadWorkspace reloads the metadata for all invalidated workspace packages. +func (s *Snapshot) reloadWorkspace(ctx context.Context) { + if ctx.Err() != nil { + return + } + + var scopes []loadScope + var seen map[PackagePath]bool + s.mu.Lock() + s.shouldLoad.Range(func(_ PackageID, pkgPaths []PackagePath) { + for _, pkgPath := range pkgPaths { + if seen == nil { + seen = make(map[PackagePath]bool) + } + if seen[pkgPath] { + continue + } + seen[pkgPath] = true + scopes = append(scopes, packageLoadScope(pkgPath)) + } + }) + s.mu.Unlock() + + if len(scopes) == 0 { + return + } + + // For an ad-hoc view, we cannot reload by package path. Just reload the view. + if s.view.typ == AdHocView { + scopes = []loadScope{viewLoadScope{}} + } + + err := s.load(ctx, false, scopes...) + + // Unless the context was canceled, set "shouldLoad" to false for all + // of the metadata we attempted to load. + if !errors.Is(err, context.Canceled) { + s.clearShouldLoad(scopes...) + if err != nil { + event.Error(ctx, "reloading workspace", err, s.Labels()...) + } + } +} + +func (s *Snapshot) orphanedFileDiagnostics(ctx context.Context, overlays []*overlay) ([]*Diagnostic, error) { + if err := s.awaitLoaded(ctx); err != nil { + return nil, err + } + + var diagnostics []*Diagnostic + var orphaned []*overlay +searchOverlays: + for _, o := range overlays { + uri := o.URI() + if s.IsBuiltin(uri) || s.FileKind(o) != file.Go { + continue + } + mps, err := s.MetadataForFile(ctx, uri) + if err != nil { + return nil, err + } + for _, mp := range mps { + if !metadata.IsCommandLineArguments(mp.ID) || mp.Standalone { + continue searchOverlays + } + } + metadata.RemoveIntermediateTestVariants(&mps) + + // With zero-config gopls (golang/go#57979), orphaned file diagnostics + // include diagnostics for orphaned files -- not just diagnostics relating + // to the reason the files are opened. + // + // This is because orphaned files are never considered part of a workspace + // package: if they are loaded by a view, that view is arbitrary, and they + // may be loaded by multiple views. If they were to be diagnosed by + // multiple views, their diagnostics may become inconsistent. + if len(mps) > 0 { + diags, err := s.PackageDiagnostics(ctx, mps[0].ID) + if err != nil { + return nil, err + } + diagnostics = append(diagnostics, diags[uri]...) + } + orphaned = append(orphaned, o) + } + + if len(orphaned) == 0 { + return nil, nil + } + + loadedModFiles := make(map[protocol.DocumentURI]struct{}) // all mod files, including dependencies + ignoredFiles := make(map[protocol.DocumentURI]bool) // files reported in packages.Package.IgnoredFiles + + g := s.MetadataGraph() + for _, meta := range g.Packages { + if meta.Module != nil && meta.Module.GoMod != "" { + gomod := protocol.URIFromPath(meta.Module.GoMod) + loadedModFiles[gomod] = struct{}{} + } + for _, ignored := range meta.IgnoredFiles { + ignoredFiles[ignored] = true + } + } + + initialErr := s.InitializationError() + + for _, fh := range orphaned { + pgf, rng, ok := orphanedFileDiagnosticRange(ctx, s.view.parseCache, fh) + if !ok { + continue // e.g. cancellation or parse error + } + + var ( + msg string // if non-empty, report a diagnostic with this message + suggestedFixes []SuggestedFix // associated fixes, if any + ) + if initialErr != nil { + msg = fmt.Sprintf("initialization failed: %v", initialErr.MainError) + } else if goMod, err := nearestModFile(ctx, fh.URI(), s); err == nil && goMod != "" { + // Check if the file's module should be loadable by considering both + // loaded modules and workspace modules. The former covers cases where + // the file is outside of a workspace folder. The latter covers cases + // where the file is inside a workspace module, but perhaps no packages + // were loaded for that module. + _, loadedMod := loadedModFiles[goMod] + _, workspaceMod := s.view.viewDefinition.workspaceModFiles[goMod] + // If we have a relevant go.mod file, check whether the file is orphaned + // due to its go.mod file being inactive. We could also offer a + // prescriptive diagnostic in the case that there is no go.mod file, but + // it is harder to be precise in that case, and less important. + if !(loadedMod || workspaceMod) { + modDir := filepath.Dir(goMod.Path()) + viewDir := s.view.folder.Dir.Path() + + // When the module is underneath the view dir, we offer + // "use all modules" quick-fixes. + inDir := pathutil.InDir(viewDir, modDir) + + if rel, err := filepath.Rel(viewDir, modDir); err == nil { + modDir = rel + } + + var fix string + if s.view.folder.Env.GoVersion >= 18 { + if s.view.gowork != "" { + fix = fmt.Sprintf("To fix this problem, you can add this module to your go.work file (%s)", s.view.gowork) + cmd := command.NewRunGoWorkCommandCommand("Run `go work use`", command.RunGoWorkArgs{ + ViewID: s.view.ID(), + Args: []string{"use", modDir}, + }) + suggestedFixes = append(suggestedFixes, SuggestedFix{ + Title: "Use this module in your go.work file", + Command: cmd, + ActionKind: protocol.QuickFix, + }) + + if inDir { + cmd := command.NewRunGoWorkCommandCommand("Run `go work use -r`", command.RunGoWorkArgs{ + ViewID: s.view.ID(), + Args: []string{"use", "-r", "."}, + }) + suggestedFixes = append(suggestedFixes, SuggestedFix{ + Title: "Use all modules in your workspace", + Command: cmd, + ActionKind: protocol.QuickFix, + }) + } + } else { + fix = "To fix this problem, you can add a go.work file that uses this directory." + + cmd := command.NewRunGoWorkCommandCommand("Run `go work init && go work use`", command.RunGoWorkArgs{ + ViewID: s.view.ID(), + InitFirst: true, + Args: []string{"use", modDir}, + }) + suggestedFixes = []SuggestedFix{ + { + Title: "Add a go.work file using this module", + Command: cmd, + ActionKind: protocol.QuickFix, + }, + } + + if inDir { + cmd := command.NewRunGoWorkCommandCommand("Run `go work init && go work use -r`", command.RunGoWorkArgs{ + ViewID: s.view.ID(), + InitFirst: true, + Args: []string{"use", "-r", "."}, + }) + suggestedFixes = append(suggestedFixes, SuggestedFix{ + Title: "Add a go.work file using all modules in your workspace", + Command: cmd, + ActionKind: protocol.QuickFix, + }) + } + } + } else { + fix = `To work with multiple modules simultaneously, please upgrade to Go 1.18 or +later, reinstall gopls, and use a go.work file.` + } + + msg = fmt.Sprintf(`This file is within module %q, which is not included in your workspace. +%s +See the documentation for more information on setting up your workspace: +https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`, modDir, fix) + } + } + + if msg == "" { + if ignoredFiles[fh.URI()] { + // TODO(rfindley): use the constraint package to check if the file + // _actually_ satisfies the current build context. + hasConstraint := false + walkConstraints(pgf.File, func(constraint.Expr) bool { + hasConstraint = true + return false + }) + var fix string + if hasConstraint { + fix = `This file may be excluded due to its build tags; try adding "-tags=" to your gopls "buildFlags" configuration +See the documentation for more information on working with build tags: +https://github.com/golang/tools/blob/master/gopls/doc/settings.md#buildflags.` + } else if strings.Contains(filepath.Base(fh.URI().Path()), "_") { + fix = `This file may be excluded due to its GOOS/GOARCH, or other build constraints.` + } else { + fix = `This file is ignored by your gopls build.` // we don't know why + } + msg = fmt.Sprintf("No packages found for open file %s.\n%s", fh.URI().Path(), fix) + } else { + // Fall back: we're not sure why the file is orphaned. + // TODO(rfindley): we could do better here, diagnosing the lack of a + // go.mod file and malformed file names (see the perc%ent marker test). + msg = fmt.Sprintf("No packages found for open file %s.", fh.URI().Path()) + } + } + + if msg != "" { + d := &Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityWarning, + Source: ListError, + Message: msg, + SuggestedFixes: suggestedFixes, + } + if ok := bundleLazyFixes(d); !ok { + bug.Reportf("failed to bundle quick fixes for %v", d) + } + // Only report diagnostics if we detect an actual exclusion. + diagnostics = append(diagnostics, d) + } + } + return diagnostics, nil +} + +// orphanedFileDiagnosticRange returns the position to use for orphaned file diagnostics. +// We only warn about an orphaned file if it is well-formed enough to actually +// be part of a package. Otherwise, we need more information. +func orphanedFileDiagnosticRange(ctx context.Context, cache *parseCache, fh file.Handle) (*parsego.File, protocol.Range, bool) { + pgfs, err := cache.parseFiles(ctx, token.NewFileSet(), parsego.Header, false, fh) + if err != nil { + return nil, protocol.Range{}, false + } + pgf := pgfs[0] + if !pgf.File.Name.Pos().IsValid() { + return nil, protocol.Range{}, false + } + rng, err := pgf.PosRange(pgf.File.Name.Pos(), pgf.File.Name.End()) + if err != nil { + return nil, protocol.Range{}, false + } + return pgf, rng, true +} + +// TODO(golang/go#53756): this function needs to consider more than just the +// absolute URI, for example: +// - the position of /vendor/ with respect to the relevant module root +// - whether or not go.work is in use (as vendoring isn't supported in workspace mode) +// +// Most likely, each call site of inVendor needs to be reconsidered to +// understand and correctly implement the desired behavior. +func inVendor(uri protocol.DocumentURI) bool { + _, after, found := strings.Cut(string(uri), "/vendor/") + // Only subdirectories of /vendor/ are considered vendored + // (/vendor/a/foo.go is vendored, /vendor/foo.go is not). + return found && strings.Contains(after, "/") +} + +// clone copies state from the receiver into a new Snapshot, applying the given +// state changes. +// +// The caller of clone must call Snapshot.decref on the returned +// snapshot when they are finished using it. +// +// The resulting bool reports whether the change invalidates any derived +// diagnostics for the snapshot, for example because it invalidates Packages or +// parsed go.mod files. This is used to mark a view as needing diagnosis in the +// server. +// +// TODO(rfindley): long term, it may be better to move responsibility for +// diagnostics into the Snapshot (e.g. a Snapshot.Diagnostics method), at which +// point the Snapshot could be responsible for tracking and forwarding a +// 'viewsToDiagnose' field. As is, this field is instead externalized in the +// server.viewsToDiagnose map. Moving it to the snapshot would entirely +// eliminate any 'relevance' heuristics from Session.DidModifyFiles, but would +// also require more strictness about diagnostic dependencies. For example, +// template.Diagnostics currently re-parses every time: there is no Snapshot +// data responsible for providing these diagnostics. +func (s *Snapshot) clone(ctx, bgCtx context.Context, changed StateChange, done func()) (*Snapshot, bool) { + changedFiles := changed.Files + ctx, stop := event.Start(ctx, "cache.snapshot.clone") + defer stop() + + s.mu.Lock() + defer s.mu.Unlock() + + // TODO(rfindley): reorganize this function to make the derivation of + // needsDiagnosis clearer. + needsDiagnosis := len(changed.GCDetails) > 0 || len(changed.ModuleUpgrades) > 0 || len(changed.Vulns) > 0 + + bgCtx, cancel := context.WithCancel(bgCtx) + result := &Snapshot{ + sequenceID: s.sequenceID + 1, + store: s.store, + refcount: 1, // Snapshots are born referenced. + done: done, + view: s.view, + backgroundCtx: bgCtx, + cancel: cancel, + builtin: s.builtin, + initialized: s.initialized, + initialErr: s.initialErr, + packages: s.packages.Clone(), + activePackages: s.activePackages.Clone(), + files: s.files.clone(changedFiles), + symbolizeHandles: cloneWithout(s.symbolizeHandles, changedFiles, nil), + workspacePackages: s.workspacePackages, + shouldLoad: s.shouldLoad.Clone(), // not cloneWithout: shouldLoad is cleared on loads + unloadableFiles: s.unloadableFiles.Clone(), // not cloneWithout: typing in a file doesn't necessarily make it loadable + parseModHandles: cloneWithout(s.parseModHandles, changedFiles, &needsDiagnosis), + parseWorkHandles: cloneWithout(s.parseWorkHandles, changedFiles, &needsDiagnosis), + modTidyHandles: cloneWithout(s.modTidyHandles, changedFiles, &needsDiagnosis), + modWhyHandles: cloneWithout(s.modWhyHandles, changedFiles, &needsDiagnosis), + modVulnHandles: cloneWithout(s.modVulnHandles, changedFiles, &needsDiagnosis), + importGraph: s.importGraph, + pkgIndex: s.pkgIndex, + moduleUpgrades: cloneWith(s.moduleUpgrades, changed.ModuleUpgrades), + vulns: cloneWith(s.vulns, changed.Vulns), + } + + // Compute the new set of packages for which we want gc details, after + // applying changed.GCDetails. + if len(s.gcOptimizationDetails) > 0 || len(changed.GCDetails) > 0 { + newGCDetails := make(map[metadata.PackageID]unit) + for id := range s.gcOptimizationDetails { + if _, ok := changed.GCDetails[id]; !ok { + newGCDetails[id] = unit{} // no change + } + } + for id, want := range changed.GCDetails { + if want { + newGCDetails[id] = unit{} + } + } + if len(newGCDetails) > 0 { + result.gcOptimizationDetails = newGCDetails + } + } + + reinit := false + + // Changes to vendor tree may require reinitialization, + // either because of an initialization error + // (e.g. "inconsistent vendoring detected"), or because + // one or more modules may have moved into or out of the + // vendor tree after 'go mod vendor' or 'rm -fr vendor/'. + // + // In this case, we consider the actual modification to see if was a creation + // or deletion. + // + // TODO(rfindley): revisit the location of this check. + for _, mod := range changed.Modifications { + if inVendor(mod.URI) && (mod.Action == file.Create || mod.Action == file.Delete) || + strings.HasSuffix(string(mod.URI), "/vendor/modules.txt") { + + reinit = true + break + } + } + + // Collect observed file handles for changed URIs from the old snapshot, if + // they exist. Importantly, we don't call ReadFile here: consider the case + // where a file is added on disk; we don't want to read the newly added file + // into the old snapshot, as that will break our change detection below. + // + // TODO(rfindley): it may be more accurate to rely on the modification type + // here, similarly to what we do for vendored files above. If we happened not + // to have read a file in the previous snapshot, that's not the same as it + // actually being created. + oldFiles := make(map[protocol.DocumentURI]file.Handle) + for uri := range changedFiles { + if fh, ok := s.files.get(uri); ok { + oldFiles[uri] = fh + } + } + // changedOnDisk determines if the new file handle may have changed on disk. + // It over-approximates, returning true if the new file is saved and either + // the old file wasn't saved, or the on-disk contents changed. + // + // oldFH may be nil. + changedOnDisk := func(oldFH, newFH file.Handle) bool { + if !newFH.SameContentsOnDisk() { + return false + } + if oe, ne := (oldFH != nil && fileExists(oldFH)), fileExists(newFH); !oe || !ne { + return oe != ne + } + return !oldFH.SameContentsOnDisk() || oldFH.Identity() != newFH.Identity() + } + + // Reinitialize if any workspace mod file has changed on disk. + for uri, newFH := range changedFiles { + if _, ok := result.view.workspaceModFiles[uri]; ok && changedOnDisk(oldFiles[uri], newFH) { + reinit = true + } + } + + // Finally, process sumfile changes that may affect loading. + for uri, newFH := range changedFiles { + if !changedOnDisk(oldFiles[uri], newFH) { + continue // like with go.mod files, we only reinit when things change on disk + } + dir, base := filepath.Split(uri.Path()) + if base == "go.work.sum" && s.view.typ == GoWorkView && dir == filepath.Dir(s.view.gowork.Path()) { + reinit = true + } + if base == "go.sum" { + modURI := protocol.URIFromPath(filepath.Join(dir, "go.mod")) + if _, active := result.view.workspaceModFiles[modURI]; active { + reinit = true + } + } + } + + // The snapshot should be initialized if either s was uninitialized, or we've + // detected a change that triggers reinitialization. + if reinit { + result.initialized = false + needsDiagnosis = true + } + + // directIDs keeps track of package IDs that have directly changed. + // Note: this is not a set, it's a map from id to invalidateMetadata. + directIDs := map[PackageID]bool{} + + // Invalidate all package metadata if the workspace module has changed. + if reinit { + for k := range s.meta.Packages { + // TODO(rfindley): this seems brittle; can we just start over? + directIDs[k] = true + } + } + + // Compute invalidations based on file changes. + anyImportDeleted := false // import deletions can resolve cycles + anyFileOpenedOrClosed := false // opened files affect workspace packages + anyPkgFileChanged := false // adding a file to a package can resolve missing dependencies + + for uri, newFH := range changedFiles { + // The original FileHandle for this URI is cached on the snapshot. + oldFH := oldFiles[uri] // may be nil + _, oldOpen := oldFH.(*overlay) + _, newOpen := newFH.(*overlay) + + // TODO(rfindley): consolidate with 'metadataChanges' logic below, which + // also considers existential changes. + anyFileOpenedOrClosed = anyFileOpenedOrClosed || (oldOpen != newOpen) + anyPkgFileChanged = anyPkgFileChanged || (oldFH == nil || !fileExists(oldFH)) && fileExists(newFH) + + // If uri is a Go file, check if it has changed in a way that would + // invalidate metadata. Note that we can't use s.view.FileKind here, + // because the file type that matters is not what the *client* tells us, + // but what the Go command sees. + var invalidateMetadata, pkgFileChanged, importDeleted bool + if strings.HasSuffix(uri.Path(), ".go") { + invalidateMetadata, pkgFileChanged, importDeleted = metadataChanges(ctx, s, oldFH, newFH) + } + if invalidateMetadata { + // If this is a metadata-affecting change, perhaps a reload will succeed. + result.unloadableFiles.Remove(uri) + needsDiagnosis = true + } + + invalidateMetadata = invalidateMetadata || reinit + anyImportDeleted = anyImportDeleted || importDeleted + anyPkgFileChanged = anyPkgFileChanged || pkgFileChanged + + // Mark all of the package IDs containing the given file. + filePackageIDs := invalidatedPackageIDs(uri, s.meta.IDs, pkgFileChanged) + for id := range filePackageIDs { + directIDs[id] = directIDs[id] || invalidateMetadata // may insert 'false' + } + + // Invalidate the previous modTidyHandle if any of the files have been + // saved or if any of the metadata has been invalidated. + // + // TODO(rfindley): this seems like too-aggressive invalidation of mod + // results. We should instead thread through overlays to the Go command + // invocation and only run this if invalidateMetadata (and perhaps then + // still do it less frequently). + if invalidateMetadata || fileWasSaved(oldFH, newFH) { + // Only invalidate mod tidy results for the most relevant modfile in the + // workspace. This is a potentially lossy optimization for workspaces + // with many modules (such as google-cloud-go, which has 145 modules as + // of writing). + // + // While it is theoretically possible that a change in workspace module A + // could affect the mod-tidiness of workspace module B (if B transitively + // requires A), such changes are probably unlikely and not worth the + // penalty of re-running go mod tidy for everything. Note that mod tidy + // ignores GOWORK, so the two modules would have to be related by a chain + // of replace directives. + // + // We could improve accuracy by inspecting replace directives, using + // overlays in go mod tidy, and/or checking for metadata changes from the + // on-disk content. + // + // Note that we iterate the modTidyHandles map here, rather than e.g. + // using nearestModFile, because we don't have access to an accurate + // FileSource at this point in the snapshot clone. + const onlyInvalidateMostRelevant = true + if onlyInvalidateMostRelevant { + deleteMostRelevantModFile(result.modTidyHandles, uri) + } else { + result.modTidyHandles.Clear() + } + + // TODO(rfindley): should we apply the above heuristic to mod vuln or mod + // why handles as well? + // + // TODO(rfindley): no tests fail if I delete the line below. + result.modWhyHandles.Clear() + result.modVulnHandles.Clear() + } + } + + // Deleting an import can cause list errors due to import cycles to be + // resolved. The best we can do without parsing the list error message is to + // hope that list errors may have been resolved by a deleted import. + // + // We could do better by parsing the list error message. We already do this + // to assign a better range to the list error, but for such critical + // functionality as metadata, it's better to be conservative until it proves + // impractical. + // + // We could also do better by looking at which imports were deleted and + // trying to find cycles they are involved in. This fails when the file goes + // from an unparseable state to a parseable state, as we don't have a + // starting point to compare with. + if anyImportDeleted { + for id, mp := range s.meta.Packages { + if len(mp.Errors) > 0 { + directIDs[id] = true + } + } + } + + // Adding a file can resolve missing dependencies from existing packages. + // + // We could be smart here and try to guess which packages may have been + // fixed, but until that proves necessary, just invalidate metadata for any + // package with missing dependencies. + if anyPkgFileChanged { + for id, mp := range s.meta.Packages { + for _, impID := range mp.DepsByImpPath { + if impID == "" { // missing import + directIDs[id] = true + break + } + } + } + } + + // Invalidate reverse dependencies too. + // idsToInvalidate keeps track of transitive reverse dependencies. + // If an ID is present in the map, invalidate its types. + // If an ID's value is true, invalidate its metadata too. + idsToInvalidate := map[PackageID]bool{} + var addRevDeps func(PackageID, bool) + addRevDeps = func(id PackageID, invalidateMetadata bool) { + current, seen := idsToInvalidate[id] + newInvalidateMetadata := current || invalidateMetadata + + // If we've already seen this ID, and the value of invalidate + // metadata has not changed, we can return early. + if seen && current == newInvalidateMetadata { + return + } + idsToInvalidate[id] = newInvalidateMetadata + for _, rid := range s.meta.ImportedBy[id] { + addRevDeps(rid, invalidateMetadata) + } + } + for id, invalidateMetadata := range directIDs { + addRevDeps(id, invalidateMetadata) + } + + // Invalidated package information. + for id, invalidateMetadata := range idsToInvalidate { + if _, ok := directIDs[id]; ok || invalidateMetadata { + if result.packages.Delete(id) { + needsDiagnosis = true + } + } else { + if entry, hit := result.packages.Get(id); hit { + needsDiagnosis = true + ph := entry.clone(false) + result.packages.Set(id, ph, nil) + } + } + if result.activePackages.Delete(id) { + needsDiagnosis = true + } + } + + // Compute which metadata updates are required. We only need to invalidate + // packages directly containing the affected file, and only if it changed in + // a relevant way. + metadataUpdates := make(map[PackageID]*metadata.Package) + for id, mp := range s.meta.Packages { + invalidateMetadata := idsToInvalidate[id] + + // For metadata that has been newly invalidated, capture package paths + // requiring reloading in the shouldLoad map. + if invalidateMetadata && !metadata.IsCommandLineArguments(mp.ID) { + needsReload := []PackagePath{mp.PkgPath} + if mp.ForTest != "" && mp.ForTest != mp.PkgPath { + // When reloading test variants, always reload their ForTest package as + // well. Otherwise, we may miss test variants in the resulting load. + // + // TODO(rfindley): is this actually sufficient? Is it possible that + // other test variants may be invalidated? Either way, we should + // determine exactly what needs to be reloaded here. + needsReload = append(needsReload, mp.ForTest) + } + result.shouldLoad.Set(id, needsReload, nil) + } + + // Check whether the metadata should be deleted. + if invalidateMetadata { + needsDiagnosis = true + metadataUpdates[id] = nil + continue + } + } + + // Update metadata, if necessary. + result.meta = s.meta.Update(metadataUpdates) + + // Update workspace and active packages, if necessary. + if result.meta != s.meta || anyFileOpenedOrClosed { + needsDiagnosis = true + result.workspacePackages = computeWorkspacePackagesLocked(ctx, result, result.meta) + result.resetActivePackagesLocked() + } else { + result.workspacePackages = s.workspacePackages + } + + return result, needsDiagnosis +} + +// cloneWithout clones m then deletes from it the keys of changes. +// +// The optional didDelete variable is set to true if there were deletions. +func cloneWithout[K constraints.Ordered, V1, V2 any](m *persistent.Map[K, V1], changes map[K]V2, didDelete *bool) *persistent.Map[K, V1] { + m2 := m.Clone() + for k := range changes { + if m2.Delete(k) && didDelete != nil { + *didDelete = true + } + } + return m2 +} + +// cloneWith clones m then inserts the changes into it. +func cloneWith[K constraints.Ordered, V any](m *persistent.Map[K, V], changes map[K]V) *persistent.Map[K, V] { + m2 := m.Clone() + for k, v := range changes { + m2.Set(k, v, nil) + } + return m2 +} + +// deleteMostRelevantModFile deletes the mod file most likely to be the mod +// file for the changed URI, if it exists. +// +// Specifically, this is the longest mod file path in a directory containing +// changed. This might not be accurate if there is another mod file closer to +// changed that happens not to be present in the map, but that's OK: the goal +// of this function is to guarantee that IF the nearest mod file is present in +// the map, it is invalidated. +func deleteMostRelevantModFile(m *persistent.Map[protocol.DocumentURI, *memoize.Promise], changed protocol.DocumentURI) { + var mostRelevant protocol.DocumentURI + changedFile := changed.Path() + + m.Range(func(modURI protocol.DocumentURI, _ *memoize.Promise) { + if len(modURI) > len(mostRelevant) { + if pathutil.InDir(filepath.Dir(modURI.Path()), changedFile) { + mostRelevant = modURI + } + } + }) + if mostRelevant != "" { + m.Delete(mostRelevant) + } +} + +// invalidatedPackageIDs returns all packages invalidated by a change to uri. +// If we haven't seen this URI before, we guess based on files in the same +// directory. This is of course incorrect in build systems where packages are +// not organized by directory. +// +// If packageFileChanged is set, the file is either a new file, or has a new +// package name. In this case, all known packages in the directory will be +// invalidated. +func invalidatedPackageIDs(uri protocol.DocumentURI, known map[protocol.DocumentURI][]PackageID, packageFileChanged bool) map[PackageID]struct{} { + invalidated := make(map[PackageID]struct{}) + + // At a minimum, we invalidate packages known to contain uri. + for _, id := range known[uri] { + invalidated[id] = struct{}{} + } + + // If the file didn't move to a new package, we should only invalidate the + // packages it is currently contained inside. + if !packageFileChanged && len(invalidated) > 0 { + return invalidated + } + + // This is a file we don't yet know about, or which has moved packages. Guess + // relevant packages by considering files in the same directory. + + // Cache of FileInfo to avoid unnecessary stats for multiple files in the + // same directory. + stats := make(map[string]struct { + os.FileInfo + error + }) + getInfo := func(dir string) (os.FileInfo, error) { + if res, ok := stats[dir]; ok { + return res.FileInfo, res.error + } + fi, err := os.Stat(dir) + stats[dir] = struct { + os.FileInfo + error + }{fi, err} + return fi, err + } + dir := filepath.Dir(uri.Path()) + fi, err := getInfo(dir) + if err == nil { + // Aggregate all possibly relevant package IDs. + for knownURI, ids := range known { + knownDir := filepath.Dir(knownURI.Path()) + knownFI, err := getInfo(knownDir) + if err != nil { + continue + } + if os.SameFile(fi, knownFI) { + for _, id := range ids { + invalidated[id] = struct{}{} + } + } + } + } + return invalidated +} + +// fileWasSaved reports whether the FileHandle passed in has been saved. It +// accomplishes this by checking to see if the original and current FileHandles +// are both overlays, and if the current FileHandle is saved while the original +// FileHandle was not saved. +func fileWasSaved(originalFH, currentFH file.Handle) bool { + c, ok := currentFH.(*overlay) + if !ok || c == nil { + return true + } + o, ok := originalFH.(*overlay) + if !ok || o == nil { + return c.saved + } + return !o.saved && c.saved +} + +// metadataChanges detects features of the change from oldFH->newFH that may +// affect package metadata. +// +// It uses lockedSnapshot to access cached parse information. lockedSnapshot +// must be locked. +// +// The result parameters have the following meaning: +// - invalidate means that package metadata for packages containing the file +// should be invalidated. +// - pkgFileChanged means that the file->package associates for the file have +// changed (possibly because the file is new, or because its package name has +// changed). +// - importDeleted means that an import has been deleted, or we can't +// determine if an import was deleted due to errors. +func metadataChanges(ctx context.Context, lockedSnapshot *Snapshot, oldFH, newFH file.Handle) (invalidate, pkgFileChanged, importDeleted bool) { + if oe, ne := oldFH != nil && fileExists(oldFH), fileExists(newFH); !oe || !ne { // existential changes + changed := oe != ne + return changed, changed, !ne // we don't know if an import was deleted + } + + // If the file hasn't changed, there's no need to reload. + if oldFH.Identity() == newFH.Identity() { + return false, false, false + } + + fset := token.NewFileSet() + // Parse headers to compare package names and imports. + oldHeads, oldErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, parsego.Header, false, oldFH) + newHeads, newErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, parsego.Header, false, newFH) + + if oldErr != nil || newErr != nil { + errChanged := (oldErr == nil) != (newErr == nil) + return errChanged, errChanged, (newErr != nil) // we don't know if an import was deleted + } + + oldHead := oldHeads[0] + newHead := newHeads[0] + + // `go list` fails completely if the file header cannot be parsed. If we go + // from a non-parsing state to a parsing state, we should reload. + if oldHead.ParseErr != nil && newHead.ParseErr == nil { + return true, true, true // We don't know what changed, so fall back on full invalidation. + } + + // If a package name has changed, the set of package imports may have changed + // in ways we can't detect here. Assume an import has been deleted. + if oldHead.File.Name.Name != newHead.File.Name.Name { + return true, true, true + } + + // Check whether package imports have changed. Only consider potentially + // valid imports paths. + oldImports := validImports(oldHead.File.Imports) + newImports := validImports(newHead.File.Imports) + + for path := range newImports { + if _, ok := oldImports[path]; ok { + delete(oldImports, path) + } else { + invalidate = true // a new, potentially valid import was added + } + } + + if len(oldImports) > 0 { + invalidate = true + importDeleted = true + } + + // If the change does not otherwise invalidate metadata, get the full ASTs in + // order to check magic comments. + // + // Note: if this affects performance we can probably avoid parsing in the + // common case by first scanning the source for potential comments. + if !invalidate { + origFulls, oldErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, parsego.Full, false, oldFH) + newFulls, newErr := lockedSnapshot.view.parseCache.parseFiles(ctx, fset, parsego.Full, false, newFH) + if oldErr == nil && newErr == nil { + invalidate = magicCommentsChanged(origFulls[0].File, newFulls[0].File) + } else { + // At this point, we shouldn't ever fail to produce a parsego.File, as + // we're already past header parsing. + bug.Reportf("metadataChanges: unparseable file %v (old error: %v, new error: %v)", oldFH.URI(), oldErr, newErr) + } + } + + return invalidate, pkgFileChanged, importDeleted +} + +func magicCommentsChanged(original *ast.File, current *ast.File) bool { + oldComments := extractMagicComments(original) + newComments := extractMagicComments(current) + if len(oldComments) != len(newComments) { + return true + } + for i := range oldComments { + if oldComments[i] != newComments[i] { + return true + } + } + return false +} + +// validImports extracts the set of valid import paths from imports. +func validImports(imports []*ast.ImportSpec) map[string]struct{} { + m := make(map[string]struct{}) + for _, spec := range imports { + if path := spec.Path.Value; validImportPath(path) { + m[path] = struct{}{} + } + } + return m +} + +func validImportPath(path string) bool { + path, err := strconv.Unquote(path) + if err != nil { + return false + } + if path == "" { + return false + } + if path[len(path)-1] == '/' { + return false + } + return true +} + +var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`) + +// extractMagicComments finds magic comments that affect metadata in f. +func extractMagicComments(f *ast.File) []string { + var results []string + for _, cg := range f.Comments { + for _, c := range cg.List { + if buildConstraintOrEmbedRe.MatchString(c.Text) { + results = append(results, c.Text) + } + } + } + return results +} + +// BuiltinFile returns information about the special builtin package. +func (s *Snapshot) BuiltinFile(ctx context.Context) (*parsego.File, error) { + s.AwaitInitialized(ctx) + + s.mu.Lock() + builtin := s.builtin + s.mu.Unlock() + + if builtin == "" { + return nil, fmt.Errorf("no builtin package for view %s", s.view.folder.Name) + } + + fh, err := s.ReadFile(ctx, builtin) + if err != nil { + return nil, err + } + // For the builtin file only, we need syntactic object resolution + // (since we can't type check). + mode := parsego.Full &^ parser.SkipObjectResolution + pgfs, err := s.view.parseCache.parseFiles(ctx, token.NewFileSet(), mode, false, fh) + if err != nil { + return nil, err + } + return pgfs[0], nil +} + +// IsBuiltin reports whether uri is part of the builtin package. +func (s *Snapshot) IsBuiltin(uri protocol.DocumentURI) bool { + s.mu.Lock() + defer s.mu.Unlock() + // We should always get the builtin URI in a canonical form, so use simple + // string comparison here. span.CompareURI is too expensive. + return uri == s.builtin +} + +func (s *Snapshot) setBuiltin(path string) { + s.mu.Lock() + defer s.mu.Unlock() + + s.builtin = protocol.URIFromPath(path) +} + +// WantGCDetails reports whether to compute GC optimization details for the +// specified package. +func (s *Snapshot) WantGCDetails(id metadata.PackageID) bool { + _, ok := s.gcOptimizationDetails[id] + return ok +} + +// A CodeLensSourceFunc is a function that reports CodeLenses (range-associated +// commands) for a given file. +type CodeLensSourceFunc func(context.Context, *Snapshot, file.Handle) ([]protocol.CodeLens, error) diff --git a/contribs/gnopls/internal/cache/symbols.go b/contribs/gnopls/internal/cache/symbols.go new file mode 100644 index 00000000000..9954c747798 --- /dev/null +++ b/contribs/gnopls/internal/cache/symbols.go @@ -0,0 +1,201 @@ +// 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 cache + +import ( + "context" + "go/ast" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/astutil" +) + +// Symbol holds a precomputed symbol value. Note: we avoid using the +// protocol.SymbolInformation struct here in order to reduce the size of each +// symbol. +type Symbol struct { + Name string + Kind protocol.SymbolKind + Range protocol.Range +} + +// symbolize returns the result of symbolizing the file identified by uri, using a cache. +func (s *Snapshot) symbolize(ctx context.Context, uri protocol.DocumentURI) ([]Symbol, error) { + + s.mu.Lock() + entry, hit := s.symbolizeHandles.Get(uri) + s.mu.Unlock() + + type symbolizeResult struct { + symbols []Symbol + err error + } + + // Cache miss? + if !hit { + fh, err := s.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + type symbolHandleKey file.Hash + key := symbolHandleKey(fh.Identity().Hash) + promise, release := s.store.Promise(key, func(ctx context.Context, arg interface{}) interface{} { + symbols, err := symbolizeImpl(ctx, arg.(*Snapshot), fh) + return symbolizeResult{symbols, err} + }) + + entry = promise + + s.mu.Lock() + s.symbolizeHandles.Set(uri, entry, func(_, _ interface{}) { release() }) + s.mu.Unlock() + } + + // Await result. + v, err := s.awaitPromise(ctx, entry) + if err != nil { + return nil, err + } + res := v.(symbolizeResult) + return res.symbols, res.err +} + +// symbolizeImpl reads and parses a file and extracts symbols from it. +func symbolizeImpl(ctx context.Context, snapshot *Snapshot, fh file.Handle) ([]Symbol, error) { + pgfs, err := snapshot.view.parseCache.parseFiles(ctx, token.NewFileSet(), parsego.Full, false, fh) + if err != nil { + return nil, err + } + + w := &symbolWalker{ + tokFile: pgfs[0].Tok, + mapper: pgfs[0].Mapper, + } + w.fileDecls(pgfs[0].File.Decls) + + return w.symbols, w.firstError +} + +type symbolWalker struct { + // for computing positions + tokFile *token.File + mapper *protocol.Mapper + + symbols []Symbol + firstError error +} + +func (w *symbolWalker) atNode(node ast.Node, name string, kind protocol.SymbolKind, path ...*ast.Ident) { + var b strings.Builder + for _, ident := range path { + if ident != nil { + b.WriteString(ident.Name) + b.WriteString(".") + } + } + b.WriteString(name) + + rng, err := w.mapper.NodeRange(w.tokFile, node) + if err != nil { + w.error(err) + return + } + sym := Symbol{ + Name: b.String(), + Kind: kind, + Range: rng, + } + w.symbols = append(w.symbols, sym) +} + +func (w *symbolWalker) error(err error) { + if err != nil && w.firstError == nil { + w.firstError = err + } +} + +func (w *symbolWalker) fileDecls(decls []ast.Decl) { + for _, decl := range decls { + switch decl := decl.(type) { + case *ast.FuncDecl: + kind := protocol.Function + var recv *ast.Ident + if decl.Recv.NumFields() > 0 { + kind = protocol.Method + _, recv, _ = astutil.UnpackRecv(decl.Recv.List[0].Type) + } + w.atNode(decl.Name, decl.Name.Name, kind, recv) + case *ast.GenDecl: + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.TypeSpec: + kind := guessKind(spec) + w.atNode(spec.Name, spec.Name.Name, kind) + w.walkType(spec.Type, spec.Name) + case *ast.ValueSpec: + for _, name := range spec.Names { + kind := protocol.Variable + if decl.Tok == token.CONST { + kind = protocol.Constant + } + w.atNode(name, name.Name, kind) + } + } + } + } + } +} + +func guessKind(spec *ast.TypeSpec) protocol.SymbolKind { + switch spec.Type.(type) { + case *ast.InterfaceType: + return protocol.Interface + case *ast.StructType: + return protocol.Struct + case *ast.FuncType: + return protocol.Function + } + return protocol.Class +} + +// walkType processes symbols related to a type expression. path is path of +// nested type identifiers to the type expression. +func (w *symbolWalker) walkType(typ ast.Expr, path ...*ast.Ident) { + switch st := typ.(type) { + case *ast.StructType: + for _, field := range st.Fields.List { + w.walkField(field, protocol.Field, protocol.Field, path...) + } + case *ast.InterfaceType: + for _, field := range st.Methods.List { + w.walkField(field, protocol.Interface, protocol.Method, path...) + } + } +} + +// walkField processes symbols related to the struct field or interface method. +// +// unnamedKind and namedKind are the symbol kinds if the field is resp. unnamed +// or named. path is the path of nested identifiers containing the field. +func (w *symbolWalker) walkField(field *ast.Field, unnamedKind, namedKind protocol.SymbolKind, path ...*ast.Ident) { + if len(field.Names) == 0 { + switch typ := field.Type.(type) { + case *ast.SelectorExpr: + // embedded qualified type + w.atNode(field, typ.Sel.Name, unnamedKind, path...) + default: + w.atNode(field, types.ExprString(field.Type), unnamedKind, path...) + } + } + for _, name := range field.Names { + w.atNode(name, name.Name, namedKind, path...) + w.walkType(field.Type, append(path, name)...) + } +} diff --git a/contribs/gnopls/internal/cache/testfuncs/match.go b/contribs/gnopls/internal/cache/testfuncs/match.go new file mode 100644 index 00000000000..a7b5cb7dd58 --- /dev/null +++ b/contribs/gnopls/internal/cache/testfuncs/match.go @@ -0,0 +1,116 @@ +// 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 testfuncs + +import ( + "fmt" + "strconv" + "strings" +) + +// The functions in this file are copies of those from the testing package. +// +// https://cs.opensource.google/go/go/+/refs/tags/go1.22.5:src/testing/match.go + +// uniqueName creates a unique name for the given parent and subname by affixing +// it with one or more counts, if necessary. +func (b *indexBuilder) uniqueName(parent, subname string) string { + base := parent + "/" + subname + + for { + n := b.subNames[base] + if n < 0 { + panic("subtest count overflow") + } + b.subNames[base] = n + 1 + + if n == 0 && subname != "" { + prefix, nn := parseSubtestNumber(base) + if len(prefix) < len(base) && nn < b.subNames[prefix] { + // This test is explicitly named like "parent/subname#NN", + // and #NN was already used for the NNth occurrence of "parent/subname". + // Loop to add a disambiguating suffix. + continue + } + return base + } + + name := fmt.Sprintf("%s#%02d", base, n) + if b.subNames[name] != 0 { + // This is the nth occurrence of base, but the name "parent/subname#NN" + // collides with the first occurrence of a subtest *explicitly* named + // "parent/subname#NN". Try the next number. + continue + } + + return name + } +} + +// parseSubtestNumber splits a subtest name into a "#%02d"-formatted int +// suffix (if present), and a prefix preceding that suffix (always). +func parseSubtestNumber(s string) (prefix string, nn int) { + i := strings.LastIndex(s, "#") + if i < 0 { + return s, 0 + } + + prefix, suffix := s[:i], s[i+1:] + if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') { + // Even if suffix is numeric, it is not a possible output of a "%02" format + // string: it has either too few digits or too many leading zeroes. + return s, 0 + } + if suffix == "00" { + if !strings.HasSuffix(prefix, "/") { + // We only use "#00" as a suffix for subtests named with the empty + // string — it isn't a valid suffix if the subtest name is non-empty. + return s, 0 + } + } + + n, err := strconv.ParseInt(suffix, 10, 32) + if err != nil || n < 0 { + return s, 0 + } + return prefix, int(n) +} + +// rewrite rewrites a subname to having only printable characters and no white +// space. +func rewrite(s string) string { + b := []byte{} + for _, r := range s { + switch { + case isSpace(r): + b = append(b, '_') + case !strconv.IsPrint(r): + s := strconv.QuoteRune(r) + b = append(b, s[1:len(s)-1]...) + default: + b = append(b, string(r)...) + } + } + return string(b) +} + +func isSpace(r rune) bool { + if r < 0x2000 { + switch r { + // Note: not the same as Unicode Z class. + case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680: + return true + } + } else { + if r <= 0x200a { + return true + } + switch r { + case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000: + return true + } + } + return false +} diff --git a/contribs/gnopls/internal/cache/testfuncs/tests.go b/contribs/gnopls/internal/cache/testfuncs/tests.go new file mode 100644 index 00000000000..cfef3c54164 --- /dev/null +++ b/contribs/gnopls/internal/cache/testfuncs/tests.go @@ -0,0 +1,324 @@ +// 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 testfuncs + +import ( + "go/ast" + "go/constant" + "go/types" + "regexp" + "strings" + + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/frob" + "golang.org/x/tools/gopls/internal/util/safetoken" +) + +// An Index records the test set of a package. +type Index struct { + pkg gobPackage +} + +// Decode decodes the given gob-encoded data as an Index. +func Decode(data []byte) *Index { + var pkg gobPackage + packageCodec.Decode(data, &pkg) + return &Index{pkg} +} + +// Encode encodes the receiver as gob-encoded data. +func (index *Index) Encode() []byte { + return packageCodec.Encode(index.pkg) +} + +func (index *Index) All() []Result { + var results []Result + for _, file := range index.pkg.Files { + for _, test := range file.Tests { + results = append(results, test.result()) + } + } + return results +} + +// A Result reports a test function +type Result struct { + Location protocol.Location // location of the test + Name string // name of the test +} + +// NewIndex returns a new index of method-set information for all +// package-level types in the specified package. +func NewIndex(files []*parsego.File, info *types.Info) *Index { + b := &indexBuilder{ + fileIndex: make(map[protocol.DocumentURI]int), + subNames: make(map[string]int), + } + return b.build(files, info) +} + +// build adds to the index all tests of the specified package. +func (b *indexBuilder) build(files []*parsego.File, info *types.Info) *Index { + for _, file := range files { + if !strings.HasSuffix(file.Tok.Name(), "_test.go") { + continue + } + + for _, decl := range file.File.Decls { + decl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + obj, ok := info.ObjectOf(decl.Name).(*types.Func) + if !ok || !obj.Exported() { + continue + } + + // error.Error has empty Position, PkgPath, and ObjectPath. + if obj.Pkg() == nil { + continue + } + + isTest, isExample := isTestOrExample(obj) + if !isTest && !isExample { + continue + } + + var t gobTest + t.Name = decl.Name.Name + t.Location.URI = file.URI + t.Location.Range, _ = file.NodeRange(decl) + + i, ok := b.fileIndex[t.Location.URI] + if !ok { + i = len(b.Files) + b.Files = append(b.Files, gobFile{}) + b.fileIndex[t.Location.URI] = i + } + + b.Files[i].Tests = append(b.Files[i].Tests, t) + + // Check for subtests + if isTest { + b.Files[i].Tests = append(b.Files[i].Tests, b.findSubtests(t, decl.Type, decl.Body, file, files, info)...) + } + } + } + + return &Index{pkg: b.gobPackage} +} + +func (b *indexBuilder) findSubtests(parent gobTest, typ *ast.FuncType, body *ast.BlockStmt, file *parsego.File, files []*parsego.File, info *types.Info) []gobTest { + if body == nil { + return nil + } + + // If the [testing.T] parameter is unnamed, the func cannot call + // [testing.T.Run] and thus cannot create any subtests + if len(typ.Params.List[0].Names) == 0 { + return nil + } + + // This "can't fail" because testKind should guarantee that the function has + // one parameter and the check above guarantees that parameter is named + param := info.ObjectOf(typ.Params.List[0].Names[0]) + + // Find statements of form t.Run(name, func(...) {...}) where t is the + // parameter of the enclosing test function. + var tests []gobTest + for _, stmt := range body.List { + expr, ok := stmt.(*ast.ExprStmt) + if !ok { + continue + } + + call, ok := expr.X.(*ast.CallExpr) + if !ok || len(call.Args) != 2 { + continue + } + fun, ok := call.Fun.(*ast.SelectorExpr) + if !ok || fun.Sel.Name != "Run" { + continue + } + recv, ok := fun.X.(*ast.Ident) + if !ok || info.ObjectOf(recv) != param { + continue + } + + sig, ok := info.TypeOf(call.Args[1]).(*types.Signature) + if !ok { + continue + } + if _, ok := testKind(sig); !ok { + continue // subtest has wrong signature + } + + val := info.Types[call.Args[0]].Value + if val == nil || val.Kind() != constant.String { + continue + } + + var t gobTest + t.Name = b.uniqueName(parent.Name, rewrite(constant.StringVal(val))) + t.Location.URI = file.URI + t.Location.Range, _ = file.NodeRange(call) + tests = append(tests, t) + + if typ, body := findFunc(files, info, body, call.Args[1]); typ != nil { + tests = append(tests, b.findSubtests(t, typ, body, file, files, info)...) + } + } + return tests +} + +// findFunc finds the type and body of the given expr, which may be a function +// literal or reference to a declared function. +// +// If no function is found, findFunc returns (nil, nil). +func findFunc(files []*parsego.File, info *types.Info, body *ast.BlockStmt, expr ast.Expr) (*ast.FuncType, *ast.BlockStmt) { + var obj types.Object + switch arg := expr.(type) { + case *ast.FuncLit: + return arg.Type, arg.Body + + case *ast.Ident: + obj = info.ObjectOf(arg) + if obj == nil { + return nil, nil + } + + case *ast.SelectorExpr: + // Look for methods within the current package. We will not handle + // imported functions and methods for now, as that would require access + // to the source of other packages and would be substantially more + // complex. However, those cases should be rare. + sel, ok := info.Selections[arg] + if !ok { + return nil, nil + } + obj = sel.Obj() + + default: + return nil, nil + } + + if v, ok := obj.(*types.Var); ok { + // TODO: Handle vars. This could handled by walking over the body (and + // the file), but that doesn't account for assignment. If the variable + // is assigned multiple times, we could easily get the wrong one. + _, _ = v, body + return nil, nil + } + + for _, file := range files { + // Skip files that don't contain the object (there should only be a + // single file that _does_ contain it) + if _, err := safetoken.Offset(file.Tok, obj.Pos()); err != nil { + continue + } + + for _, decl := range file.File.Decls { + decl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + + if info.ObjectOf(decl.Name) == obj { + return decl.Type, decl.Body + } + } + } + return nil, nil +} + +var ( + reTest = regexp.MustCompile(`^Test([A-Z]|$)`) + reBenchmark = regexp.MustCompile(`^Benchmark([A-Z]|$)`) + reFuzz = regexp.MustCompile(`^Fuzz([A-Z]|$)`) + reExample = regexp.MustCompile(`^Example([A-Z]|$)`) +) + +// isTestOrExample reports whether the given func is a testing func or an +// example func (or neither). isTestOrExample returns (true, false) for testing +// funcs, (false, true) for example funcs, and (false, false) otherwise. +func isTestOrExample(fn *types.Func) (isTest, isExample bool) { + sig := fn.Type().(*types.Signature) + if sig.Params().Len() == 0 && + sig.Results().Len() == 0 { + return false, reExample.MatchString(fn.Name()) + } + + kind, ok := testKind(sig) + if !ok { + return false, false + } + switch kind.Name() { + case "T": + return reTest.MatchString(fn.Name()), false + case "B": + return reBenchmark.MatchString(fn.Name()), false + case "F": + return reFuzz.MatchString(fn.Name()), false + default: + return false, false // "can't happen" (see testKind) + } +} + +// testKind returns the parameter type TypeName of a test, benchmark, or fuzz +// function (one of testing.[TBF]). +func testKind(sig *types.Signature) (*types.TypeName, bool) { + if sig.Params().Len() != 1 || + sig.Results().Len() != 0 { + return nil, false + } + + ptr, ok := sig.Params().At(0).Type().(*types.Pointer) + if !ok { + return nil, false + } + + named, ok := ptr.Elem().(*types.Named) + if !ok || named.Obj().Pkg().Path() != "testing" { + return nil, false + } + + switch named.Obj().Name() { + case "T", "B", "F": + return named.Obj(), true + } + return nil, false +} + +// An indexBuilder builds an index for a single package. +type indexBuilder struct { + gobPackage + fileIndex map[protocol.DocumentURI]int + subNames map[string]int +} + +// -- serial format of index -- + +// (The name says gob but in fact we use frob.) +var packageCodec = frob.CodecFor[gobPackage]() + +// A gobPackage records the test set of each package-level type for a single package. +type gobPackage struct { + Files []gobFile +} + +type gobFile struct { + Tests []gobTest +} + +// A gobTest records the name, type, and position of a single test. +type gobTest struct { + Location protocol.Location // location of the test + Name string // name of the test +} + +func (t *gobTest) result() Result { + return Result(*t) +} diff --git a/contribs/gnopls/internal/cache/typerefs/doc.go b/contribs/gnopls/internal/cache/typerefs/doc.go new file mode 100644 index 00000000000..18042c623bc --- /dev/null +++ b/contribs/gnopls/internal/cache/typerefs/doc.go @@ -0,0 +1,151 @@ +// 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 typerefs extracts symbol-level reachability information +// from the syntax of a Go package. +// +// # Background +// +// The goal of this analysis is to determine, for each package P, a nearly +// minimal set of packages that could affect the type checking of P. This set +// may contain false positives, but the smaller this set the better we can +// invalidate and prune packages in gopls. +// +// More precisely, for each package P we define the set of "reachable" packages +// from P as the set of packages that may affect the (deep) export data of the +// direct dependencies of P. By this definition, the complement of this set +// cannot affect any information derived from type checking P, such as +// diagnostics, cross references, or method sets. Therefore we need not +// invalidate any results for P when a package in the complement of this set +// changes. +// +// # Computing references +// +// For a given declaration D, references are computed based on identifiers or +// dotted identifiers referenced in the declaration of D, that may affect +// the type of D. However, these references reflect only local knowledge of the +// package and its dependency metadata, and do not depend on any analysis of +// the dependencies themselves. This allows the reference information for +// a package to be cached independent of all others. +// +// Specifically, if a referring identifier I appears in the declaration, we +// record an edge from D to each object possibly referenced by I. We search for +// references within type syntax, but do not actually type-check, so we can't +// reliably determine whether an expression is a type or a term, or whether a +// function is a builtin or generic. For example, the type of x in var x = +// p.F(W) only depends on W if p.F is a builtin or generic function, which we +// cannot know without type-checking package p. So we may over-approximate in +// this way. +// +// - If I is declared in the current package, record a reference to its +// declaration. +// - Otherwise, if there are any dot imports in the current +// file and I is exported, record a (possibly dangling) edge to +// the corresponding declaration in each dot-imported package. +// +// If a dotted identifier q.I appears in the declaration, we +// perform a similar operation: +// +// - If q is declared in the current package, we record a reference to that +// object. It may be a var or const that has a field or method I. +// - Otherwise, if q is a valid import name based on imports in the current file +// and the provided metadata for dependency package names, record a +// reference to the object I in that package. +// - Additionally, handle the case where Q is exported, and Q.I may refer to +// a field or method in a dot-imported package. +// +// That is essentially the entire algorithm, though there is some subtlety to +// visiting the set of identifiers or dotted identifiers that may affect the +// declaration type. See the visitDeclOrSpec function for the details of this +// analysis. Notably, we also skip identifiers that refer to type parameters in +// generic declarations. +// +// # Graph optimizations +// +// The references extracted from the syntax are used to construct +// edges between nodes representing declarations. Edges are of two +// kinds: internal references, from one package-level declaration to +// another; and external references, from a symbol in this package to +// a symbol imported from a direct dependency. +// +// Once the symbol reference graph is constructed, we find its +// strongly connected components (SCCs) using Tarjan's algorithm. +// As we coalesce the nodes of each SCC we compute the union of +// external references reached by each package-level declaration. +// The final result is the mapping from each exported package-level +// declaration to the set of external (imported) declarations that it +// reaches. +// +// Because it is common for many package members to have the same +// reachability, the result takes the form of a set of equivalence +// classes, each mapping a set of package-level declarations to a set +// of external symbols. We use a hash table to canonicalize sets so that +// repeated occurrences of the same set (which are common) are only +// represented once in memory or in the file system. +// For example, all declarations that ultimately reference only +// {fmt.Println,strings.Join} would be classed as equivalent. +// +// This approach was inspired by the Hash-Value Numbering (HVN) +// optimization described by Hardekopf and Lin. See +// golang.org/x/tools/go/pointer/hvn.go for an implementation. (Like +// pointer analysis, this problem is fundamentally one of graph +// reachability.) The HVN algorithm takes the compression a step +// further by preserving the topology of the SCC DAG, in which edges +// represent "is a superset of" constraints. Redundant edges that +// don't increase the solution can be deleted. We could apply the same +// technique here to further reduce the worst-case size of the result, +// but the current implementation seems adequate. +// +// # API +// +// The main entry point for this analysis is the [Encode] function, +// which implements the analysis described above for one package, and +// encodes the result as a binary message. +// +// The [Decode] function decodes the message into a usable form: a set +// of equivalence classes. The decoder uses a shared [PackageIndex] to +// enable more compact representations of sets of packages +// ([PackageSet]) during the global reacahability computation. +// +// The [BuildPackageGraph] constructor implements a whole-graph analysis similar +// to that which will be implemented by gopls, but for various reasons the +// logic for this analysis will eventually live in the +// [golang.org/x/tools/gopls/internal/cache] package. Nevertheless, +// BuildPackageGraph and its test serve to verify the syntactic analysis, and +// may serve as a proving ground for new optimizations of the whole-graph analysis. +// +// # Export data is insufficient +// +// At first it may seem that the simplest way to implement this analysis would +// be to consider the types.Packages of the dependencies of P, for example +// during export. After all, it makes sense that the type checked packages +// themselves could describe their dependencies. However, this does not work as +// type information does not describe certain syntactic relationships. +// +// For example, the following scenarios cause type information to miss +// syntactic relationships: +// +// Named type forwarding: +// +// package a; type A b.B +// package b; type B int +// +// Aliases: +// +// package a; func A(f b.B) +// package b; type B = func() +// +// Initializers: +// +// package a; var A = b.B() +// package b; func B() string { return "hi" } +// +// Use of the unsafe package: +// +// package a; type A [unsafe.Sizeof(B{})]int +// package b; type B struct { f1, f2, f3 int } +// +// In all of these examples, types do not contain information about the edge +// between the a.A and b.B declarations. +package typerefs diff --git a/contribs/gnopls/internal/cache/typerefs/packageset.go b/contribs/gnopls/internal/cache/typerefs/packageset.go new file mode 100644 index 00000000000..29c37cd1c4c --- /dev/null +++ b/contribs/gnopls/internal/cache/typerefs/packageset.go @@ -0,0 +1,148 @@ +// 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 typerefs + +import ( + "fmt" + "math/bits" + "sort" + "strings" + "sync" + + "golang.org/x/tools/gopls/internal/cache/metadata" +) + +// PackageIndex stores common data to enable efficient representation of +// references and package sets. +type PackageIndex struct { + // For now, PackageIndex just indexes package ids, to save space and allow for + // faster unions via sparse int vectors. + mu sync.Mutex + ids []metadata.PackageID + m map[metadata.PackageID]IndexID +} + +// NewPackageIndex creates a new PackageIndex instance for use in building +// reference and package sets. +func NewPackageIndex() *PackageIndex { + return &PackageIndex{ + m: make(map[metadata.PackageID]IndexID), + } +} + +// IndexID returns the packageIdx referencing id, creating one if id is not yet +// tracked by the receiver. +func (index *PackageIndex) IndexID(id metadata.PackageID) IndexID { + index.mu.Lock() + defer index.mu.Unlock() + if i, ok := index.m[id]; ok { + return i + } + i := IndexID(len(index.ids)) + index.m[id] = i + index.ids = append(index.ids, id) + return i +} + +// PackageID returns the PackageID for idx. +// +// idx must have been created by this PackageIndex instance. +func (index *PackageIndex) PackageID(idx IndexID) metadata.PackageID { + index.mu.Lock() + defer index.mu.Unlock() + return index.ids[idx] +} + +// A PackageSet is a set of metadata.PackageIDs, optimized for inuse memory +// footprint and efficient union operations. +type PackageSet struct { + // PackageSet is a sparse int vector of package indexes from parent. + parent *PackageIndex + sparse map[int]blockType // high bits in key, set of low bits in value +} + +type blockType = uint // type of each sparse vector element +const blockSize = bits.UintSize + +// NewSet creates a new PackageSet bound to this PackageIndex instance. +// +// PackageSets may only be combined with other PackageSets from the same +// instance. +func (index *PackageIndex) NewSet() *PackageSet { + return &PackageSet{ + parent: index, + sparse: make(map[int]blockType), + } +} + +// DeclaringPackage returns the ID of the symbol's declaring package. +// The package index must be the one used during decoding. +func (index *PackageIndex) DeclaringPackage(sym Symbol) metadata.PackageID { + return index.PackageID(sym.Package) +} + +// Add records a new element in the package set, for the provided package ID. +func (s *PackageSet) AddPackage(id metadata.PackageID) { + s.Add(s.parent.IndexID(id)) +} + +// Add records a new element in the package set. +// It is the caller's responsibility to ensure that idx was created with the +// same PackageIndex as the PackageSet. +func (s *PackageSet) Add(idx IndexID) { + i := int(idx) + s.sparse[i/blockSize] |= 1 << (i % blockSize) +} + +// Union records all elements from other into the receiver, mutating the +// receiver set but not the argument set. The receiver must not be nil, but the +// argument set may be nil. +// +// Precondition: both package sets were created with the same PackageIndex. +func (s *PackageSet) Union(other *PackageSet) { + if other == nil { + return // e.g. unsafe + } + if other.parent != s.parent { + panic("other set is from a different PackageIndex instance") + } + for k, v := range other.sparse { + if v0 := s.sparse[k]; v0 != v { + s.sparse[k] = v0 | v + } + } +} + +// Contains reports whether id is contained in the receiver set. +func (s *PackageSet) Contains(id metadata.PackageID) bool { + i := int(s.parent.IndexID(id)) + return s.sparse[i/blockSize]&(1<<(i%blockSize)) != 0 +} + +// Elems calls f for each element of the set in ascending order. +func (s *PackageSet) Elems(f func(IndexID)) { + blockIndexes := make([]int, 0, len(s.sparse)) + for k := range s.sparse { + blockIndexes = append(blockIndexes, k) + } + sort.Ints(blockIndexes) + for _, i := range blockIndexes { + v := s.sparse[i] + for b := 0; b < blockSize; b++ { + if (v & (1 << b)) != 0 { + f(IndexID(i*blockSize + b)) + } + } + } +} + +// String returns a human-readable representation of the set: {A, B, ...}. +func (s *PackageSet) String() string { + var ids []string + s.Elems(func(id IndexID) { + ids = append(ids, string(s.parent.PackageID(id))) + }) + return fmt.Sprintf("{%s}", strings.Join(ids, ", ")) +} diff --git a/contribs/gnopls/internal/cache/typerefs/pkggraph_test.go b/contribs/gnopls/internal/cache/typerefs/pkggraph_test.go new file mode 100644 index 00000000000..01cd1a86f0f --- /dev/null +++ b/contribs/gnopls/internal/cache/typerefs/pkggraph_test.go @@ -0,0 +1,244 @@ +// 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 typerefs_test + +// This file is logically part of the test in pkgrefs_test.go: that +// file defines the test assertion logic; this file provides a +// reference implementation of a client of the typerefs package. + +import ( + "bytes" + "context" + "fmt" + "os" + "runtime" + "sync" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/cache/typerefs" + "golang.org/x/tools/gopls/internal/protocol" +) + +const ( + // trace enables additional trace output to stdout, for debugging. + // + // Warning: produces a lot of output! Best to run with small package queries. + trace = false +) + +// A Package holds reference information for a single package. +type Package struct { + // metapkg holds metapkg about this package and its dependencies. + metapkg *metadata.Package + + // transitiveRefs records, for each exported declaration in the package, the + // transitive set of packages within the containing graph that are + // transitively reachable through references, starting with the given decl. + transitiveRefs map[string]*typerefs.PackageSet + + // ReachesViaDeps records the set of packages in the containing graph whose + // syntax may affect the current package's types. See the package + // documentation for more details of what this means. + ReachesByDeps *typerefs.PackageSet +} + +// A PackageGraph represents a fully analyzed graph of packages and their +// dependencies. +type PackageGraph struct { + pkgIndex *typerefs.PackageIndex + meta metadata.Source + parse func(context.Context, protocol.DocumentURI) (*parsego.File, error) + + mu sync.Mutex + packages map[metadata.PackageID]*futurePackage +} + +// BuildPackageGraph analyzes the package graph for the requested ids, whose +// metadata is described by meta. +// +// The provided parse function is used to parse the CompiledGoFiles of each package. +// +// The resulting PackageGraph is fully evaluated, and may be investigated using +// the Package method. +// +// See the package documentation for more information on the package reference +// algorithm. +func BuildPackageGraph(ctx context.Context, meta metadata.Source, ids []metadata.PackageID, parse func(context.Context, protocol.DocumentURI) (*parsego.File, error)) (*PackageGraph, error) { + g := &PackageGraph{ + pkgIndex: typerefs.NewPackageIndex(), + meta: meta, + parse: parse, + packages: make(map[metadata.PackageID]*futurePackage), + } + metadata.SortPostOrder(meta, ids) + + workers := runtime.GOMAXPROCS(0) + if trace { + workers = 1 + } + + var eg errgroup.Group + eg.SetLimit(workers) + for _, id := range ids { + id := id + eg.Go(func() error { + _, err := g.Package(ctx, id) + return err + }) + } + return g, eg.Wait() +} + +// futurePackage is a future result of analyzing a package, for use from Package only. +type futurePackage struct { + done chan struct{} + pkg *Package + err error +} + +// Package gets the result of analyzing references for a single package. +func (g *PackageGraph) Package(ctx context.Context, id metadata.PackageID) (*Package, error) { + g.mu.Lock() + fut, ok := g.packages[id] + if ok { + g.mu.Unlock() + select { + case <-fut.done: + case <-ctx.Done(): + return nil, ctx.Err() + } + } else { + fut = &futurePackage{done: make(chan struct{})} + g.packages[id] = fut + g.mu.Unlock() + fut.pkg, fut.err = g.buildPackage(ctx, id) + close(fut.done) + } + return fut.pkg, fut.err +} + +// buildPackage parses a package and extracts its reference graph. It should +// only be called from Package. +func (g *PackageGraph) buildPackage(ctx context.Context, id metadata.PackageID) (*Package, error) { + p := &Package{ + metapkg: g.meta.Metadata(id), + transitiveRefs: make(map[string]*typerefs.PackageSet), + } + var files []*parsego.File + for _, filename := range p.metapkg.CompiledGoFiles { + f, err := g.parse(ctx, filename) + if err != nil { + return nil, err + } + files = append(files, f) + } + imports := make(map[metadata.ImportPath]*metadata.Package) + for impPath, depID := range p.metapkg.DepsByImpPath { + if depID != "" { + imports[impPath] = g.meta.Metadata(depID) + } + } + + // Compute the symbol-level dependencies through this package. + data := typerefs.Encode(files, imports) + + // data can be persisted in a filecache, keyed + // by hash(id, CompiledGoFiles, imports). + + // This point separates the local preprocessing + // -- of a single package (above) from the global -- + // transitive reachability query (below). + + // classes records syntactic edges between declarations in this + // package and declarations in this package or another + // package. See the package documentation for a detailed + // description of what these edges do (and do not) represent. + classes := typerefs.Decode(g.pkgIndex, data) + + // Debug + if trace && len(classes) > 0 { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s\n", id) + for _, class := range classes { + for i, name := range class.Decls { + if i == 0 { + fmt.Fprintf(&buf, "\t") + } + fmt.Fprintf(&buf, " .%s", name) + } + // Group symbols by package. + var prevID PackageID + for _, sym := range class.Refs { + id := g.pkgIndex.DeclaringPackage(sym) + if id != prevID { + prevID = id + fmt.Fprintf(&buf, "\n\t\t-> %s:", id) + } + fmt.Fprintf(&buf, " .%s", sym.Name) + } + fmt.Fprintln(&buf) + } + os.Stderr.Write(buf.Bytes()) + } + + // Now compute the transitive closure of packages reachable + // from any exported symbol of this package. + for _, class := range classes { + set := g.pkgIndex.NewSet() + + // The Refs slice is sorted by (PackageID, name), + // so we can economize by calling g.Package only + // when the package id changes. + depP := p + for _, sym := range class.Refs { + symPkgID := g.pkgIndex.DeclaringPackage(sym) + if symPkgID == id { + panic("intra-package edge") + } + if depP.metapkg.ID != symPkgID { + // package changed + var err error + depP, err = g.Package(ctx, symPkgID) + if err != nil { + return nil, err + } + } + set.Add(sym.Package) + set.Union(depP.transitiveRefs[sym.Name]) + } + for _, name := range class.Decls { + p.transitiveRefs[name] = set + } + } + + // Finally compute the union of transitiveRefs + // across the direct deps of this package. + byDeps, err := g.reachesByDeps(ctx, p.metapkg) + if err != nil { + return nil, err + } + p.ReachesByDeps = byDeps + + return p, nil +} + +// reachesByDeps computes the set of packages that are reachable through +// dependencies of the package m. +func (g *PackageGraph) reachesByDeps(ctx context.Context, mp *metadata.Package) (*typerefs.PackageSet, error) { + transitive := g.pkgIndex.NewSet() + for _, depID := range mp.DepsByPkgPath { + dep, err := g.Package(ctx, depID) + if err != nil { + return nil, err + } + transitive.AddPackage(dep.metapkg.ID) + for _, set := range dep.transitiveRefs { + transitive.Union(set) + } + } + return transitive, nil +} diff --git a/contribs/gnopls/internal/cache/typerefs/pkgrefs_test.go b/contribs/gnopls/internal/cache/typerefs/pkgrefs_test.go new file mode 100644 index 00000000000..9d4b5c011d3 --- /dev/null +++ b/contribs/gnopls/internal/cache/typerefs/pkgrefs_test.go @@ -0,0 +1,406 @@ +// 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 typerefs_test + +import ( + "bytes" + "context" + "flag" + "fmt" + "go/token" + "go/types" + "os" + "sort" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/tools/go/gcexportdata" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/cache/typerefs" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/internal/packagesinternal" + "golang.org/x/tools/internal/testenv" +) + +var ( + dir = flag.String("dir", "", "dir to run go/packages from") + query = flag.String("query", "std", "go/packages load query to use for walkdecl tests") + verify = flag.Bool("verify", true, "whether to verify reachable packages using export data (may be slow on large graphs)") +) + +type ( + packageName = metadata.PackageName + PackageID = metadata.PackageID + ImportPath = metadata.ImportPath + PackagePath = metadata.PackagePath + Metadata = metadata.Package + MetadataSource = metadata.Source +) + +// TestBuildPackageGraph tests the BuildPackageGraph constructor, which uses +// the reference analysis of the Refs function to build a graph of +// relationships between packages. +// +// It simulates the operation of gopls at startup: packages are loaded via +// go/packages, and their syntax+metadata analyzed to determine which packages +// are reachable from others. +// +// The test then verifies that the 'load' graph (the graph of relationships in +// export data) is a subgraph of the 'reach' graph constructed by +// BuildPackageGraph. While doing so, it constructs some statistics about the +// relative sizes of these graphs, along with the 'transitive imports' graph, +// to report the effectiveness of the reachability analysis. +// +// The following flags affect this test: +// - dir sets the dir from which to run go/packages +// - query sets the go/packages query to load +// - verify toggles the verification w.r.t. the load graph (which may be +// prohibitively expensive with large queries). +func TestBuildPackageGraph(t *testing.T) { + if testing.Short() { + t.Skip("skipping with -short: loading the packages can take a long time with a cold cache") + } + testenv.NeedsGoBuild(t) // for go/packages + + t0 := time.Now() + exports, meta, err := loadPackages(*query, *verify) + if err != nil { + t.Fatalf("loading failed: %v", err) + } + t.Logf("loaded %d packages in %v", len(exports), time.Since(t0)) + + ctx := context.Background() + var ids []PackageID + for id := range exports { + ids = append(ids, id) + } + sort.Slice(ids, func(i, j int) bool { + return ids[i] < ids[j] + }) + + t0 = time.Now() + g, err := BuildPackageGraph(ctx, meta, ids, newParser().parse) + if err != nil { + t.Fatal(err) + } + t.Logf("building package graph took %v", time.Since(t0)) + + // Collect information about the edges between packages for later analysis. + // + // We compare the following package graphs: + // - the imports graph: edges are transitive imports + // - the reaches graph: edges are reachability relationships through syntax + // of imports (as defined in the package doc) + // - the loads graph: edges are packages loaded through the export data of + // imports + // + // By definition, loads < reaches < imports. + type edgeSet map[PackageID]map[PackageID]bool + var ( + imports = make(edgeSet) // A imports B transitively + importedBy = make(edgeSet) // A is imported by B transitively + reaches = make(edgeSet) // A reaches B through top-level declaration syntax + reachedBy = make(edgeSet) // A is reached by B through top-level declaration syntax + loads = make(edgeSet) // A loads B through export data of its direct dependencies + loadedBy = make(edgeSet) // A is loaded by B through export data of B's direct dependencies + ) + recordEdge := func(from, to PackageID, fwd, rev edgeSet) { + if fwd[from] == nil { + fwd[from] = make(map[PackageID]bool) + } + fwd[from][to] = true + if rev[to] == nil { + rev[to] = make(map[PackageID]bool) + } + rev[to][from] = true + } + + exportedPackages := make(map[PackageID]*types.Package) + importPackage := func(id PackageID) *types.Package { + exportFile := exports[id] + if exportFile == "" { + return nil // no exported symbols + } + mp := meta.Metadata(id) + tpkg, ok := exportedPackages[id] + if !ok { + pkgPath := string(mp.PkgPath) + tpkg, err = importFromExportData(pkgPath, exportFile) + if err != nil { + t.Fatalf("importFromExportData(%s, %s) failed: %v", pkgPath, exportFile, err) + } + exportedPackages[id] = tpkg + } + return tpkg + } + + for _, id := range ids { + pkg, err := g.Package(ctx, id) + if err != nil { + t.Fatal(err) + } + pkg.ReachesByDeps.Elems(func(id2 typerefs.IndexID) { + recordEdge(id, g.pkgIndex.PackageID(id2), reaches, reachedBy) + }) + + importMap := importMap(id, meta) + for _, id2 := range importMap { + recordEdge(id, id2, imports, importedBy) + } + + if *verify { + for _, depID := range meta.Metadata(id).DepsByPkgPath { + tpkg := importPackage(depID) + if tpkg == nil { + continue + } + for _, imp := range tpkg.Imports() { + depID, ok := importMap[PackagePath(imp.Path())] + if !ok { + t.Errorf("import map (len: %d) for %s missing imported types.Package %s", len(importMap), id, imp.Path()) + continue + } + recordEdge(id, depID, loads, loadedBy) + } + } + + for depID := range loads[id] { + if !pkg.ReachesByDeps.Contains(depID) { + t.Errorf("package %s was imported by %s, but not detected as reachable", depID, id) + } + } + } + } + + if testing.Verbose() { + fmt.Printf("%-52s%8s%8s%8s%8s%8s%8s\n", "package ID", "imp", "impBy", "reach", "reachBy", "load", "loadBy") + for _, id := range ids { + fmt.Printf("%-52s%8d%8d%8d%8d%8d%8d\n", id, len(imports[id]), len(importedBy[id]), len(reaches[id]), len(reachedBy[id]), len(loads[id]), len(loadedBy[id])) + } + fmt.Println(strings.Repeat("-", 100)) + fmt.Printf("%-52s%8s%8s%8s%8s%8s%8s\n", "package ID", "imp", "impBy", "reach", "reachBy", "load", "loadBy") + + avg := func(m edgeSet) float64 { + var avg float64 + for _, id := range ids { + s := m[id] + avg += float64(len(s)) / float64(len(ids)) + } + return avg + } + fmt.Printf("%52s%8.1f%8.1f%8.1f%8.1f%8.1f%8.1f\n", "averages:", avg(imports), avg(importedBy), avg(reaches), avg(reachedBy), avg(loads), avg(loadedBy)) + } +} + +func importMap(id PackageID, meta MetadataSource) map[PackagePath]PackageID { + imports := make(map[PackagePath]PackageID) + var recordIDs func(PackageID) + recordIDs = func(id PackageID) { + mp := meta.Metadata(id) + if _, ok := imports[mp.PkgPath]; ok { + return + } + imports[mp.PkgPath] = id + for _, id := range mp.DepsByPkgPath { + recordIDs(id) + } + } + for _, id := range meta.Metadata(id).DepsByPkgPath { + recordIDs(id) + } + return imports +} + +func importFromExportData(pkgPath, exportFile string) (*types.Package, error) { + file, err := os.Open(exportFile) + if err != nil { + return nil, err + } + r, err := gcexportdata.NewReader(file) + if err != nil { + file.Close() + return nil, err + } + fset := token.NewFileSet() + tpkg, err := gcexportdata.Read(r, fset, make(map[string]*types.Package), pkgPath) + file.Close() + if err != nil { + return nil, err + } + // The export file reported by go/packages is produced by the compiler, which + // has additional package dependencies due to inlining. + // + // Export and re-import so that we only observe dependencies from the + // exported API. + var out bytes.Buffer + err = gcexportdata.Write(&out, fset, tpkg) + if err != nil { + return nil, err + } + return gcexportdata.Read(&out, token.NewFileSet(), make(map[string]*types.Package), pkgPath) +} + +func BenchmarkBuildPackageGraph(b *testing.B) { + t0 := time.Now() + exports, meta, err := loadPackages(*query, *verify) + if err != nil { + b.Fatalf("loading failed: %v", err) + } + b.Logf("loaded %d packages in %v", len(exports), time.Since(t0)) + ctx := context.Background() + var ids []PackageID + for id := range exports { + ids = append(ids, id) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err := BuildPackageGraph(ctx, meta, ids, newParser().parse) + if err != nil { + b.Fatal(err) + } + } +} + +type memoizedParser struct { + mu sync.Mutex + files map[protocol.DocumentURI]*futureParse +} + +type futureParse struct { + done chan struct{} + pgf *parsego.File + err error +} + +func newParser() *memoizedParser { + return &memoizedParser{ + files: make(map[protocol.DocumentURI]*futureParse), + } +} + +func (p *memoizedParser) parse(ctx context.Context, uri protocol.DocumentURI) (*parsego.File, error) { + doParse := func(ctx context.Context, uri protocol.DocumentURI) (*parsego.File, error) { + // TODO(adonovan): hoist this operation outside the benchmark critsec. + content, err := os.ReadFile(uri.Path()) + if err != nil { + return nil, err + } + content = astutil.PurgeFuncBodies(content) + pgf, _ := parsego.Parse(ctx, token.NewFileSet(), uri, content, parsego.Full, false) + return pgf, nil + } + + p.mu.Lock() + fut, ok := p.files[uri] + if ok { + p.mu.Unlock() + select { + case <-fut.done: + case <-ctx.Done(): + return nil, ctx.Err() + } + } else { + fut = &futureParse{done: make(chan struct{})} + p.files[uri] = fut + p.mu.Unlock() + fut.pgf, fut.err = doParse(ctx, uri) + close(fut.done) + } + return fut.pgf, fut.err +} + +type mapMetadataSource struct { + m map[PackageID]*Metadata +} + +func (s mapMetadataSource) Metadata(id PackageID) *Metadata { + return s.m[id] +} + +// This function is a compressed version of snapshot.load from the +// internal/cache package, for use in testing. +// +// TODO(rfindley): it may be valuable to extract this logic from the snapshot, +// since it is otherwise standalone. +func loadPackages(query string, needExport bool) (map[PackageID]string, MetadataSource, error) { + cfg := &packages.Config{ + Dir: *dir, + Mode: packages.NeedName | + packages.NeedFiles | + packages.NeedCompiledGoFiles | + packages.NeedImports | + packages.NeedDeps | + packages.NeedTypesSizes | + packages.NeedModule | + packages.NeedEmbedFiles | + packages.LoadMode(packagesinternal.DepsErrors) | + packages.LoadMode(packagesinternal.ForTest), + Tests: true, + } + if needExport { + cfg.Mode |= packages.NeedExportFile // ExportFile is not requested by gopls: this is used to verify reachability + } + pkgs, err := packages.Load(cfg, query) + if err != nil { + return nil, nil, err + } + + meta := make(map[PackageID]*Metadata) + var buildMetadata func(pkg *packages.Package) + buildMetadata = func(pkg *packages.Package) { + id := PackageID(pkg.ID) + if meta[id] != nil { + return + } + mp := &Metadata{ + ID: id, + PkgPath: PackagePath(pkg.PkgPath), + Name: packageName(pkg.Name), + ForTest: PackagePath(packagesinternal.GetForTest(pkg)), + TypesSizes: pkg.TypesSizes, + LoadDir: cfg.Dir, + Module: pkg.Module, + Errors: pkg.Errors, + DepsErrors: packagesinternal.GetDepsErrors(pkg), + } + meta[id] = mp + + for _, filename := range pkg.CompiledGoFiles { + mp.CompiledGoFiles = append(mp.CompiledGoFiles, protocol.URIFromPath(filename)) + } + for _, filename := range pkg.GoFiles { + mp.GoFiles = append(mp.GoFiles, protocol.URIFromPath(filename)) + } + + mp.DepsByImpPath = make(map[ImportPath]PackageID) + mp.DepsByPkgPath = make(map[PackagePath]PackageID) + for importPath, imported := range pkg.Imports { + importPath := ImportPath(importPath) + + // see note in gopls/internal/cache/load.go for an explanation of this check. + if importPath != "unsafe" && len(imported.CompiledGoFiles) == 0 { + mp.DepsByImpPath[importPath] = "" // missing + continue + } + + mp.DepsByImpPath[importPath] = PackageID(imported.ID) + mp.DepsByPkgPath[PackagePath(imported.PkgPath)] = PackageID(imported.ID) + buildMetadata(imported) + } + } + + exportFiles := make(map[PackageID]string) + for _, pkg := range pkgs { + exportFiles[PackageID(pkg.ID)] = pkg.ExportFile + buildMetadata(pkg) + } + return exportFiles, &mapMetadataSource{meta}, nil +} diff --git a/contribs/gnopls/internal/cache/typerefs/refs.go b/contribs/gnopls/internal/cache/typerefs/refs.go new file mode 100644 index 00000000000..b389667ae7f --- /dev/null +++ b/contribs/gnopls/internal/cache/typerefs/refs.go @@ -0,0 +1,832 @@ +// 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 typerefs + +import ( + "fmt" + "go/ast" + "go/token" + "sort" + "strings" + + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/gopls/internal/util/frob" +) + +// Encode analyzes the Go syntax trees of a package, constructs a +// reference graph, and uses it to compute, for each exported +// declaration, the set of exported symbols of directly imported +// packages that it references, perhaps indirectly. +// +// It returns a serializable index of this information. +// Use Decode to expand the result. +func Encode(files []*parsego.File, imports map[metadata.ImportPath]*metadata.Package) []byte { + return index(files, imports) +} + +// Decode decodes a serializable index of symbol +// reachability produced by Encode. +// +// Because many declarations reference the exact same set of symbols, +// the results are grouped into equivalence classes. +// Classes are sorted by Decls[0], ascending. +// The class with empty reachability is omitted. +// +// See the package documentation for more details as to what a +// reference does (and does not) represent. +func Decode(pkgIndex *PackageIndex, data []byte) []Class { + return decode(pkgIndex, data) +} + +// A Class is a reachability equivalence class. +// +// It attests that each exported package-level declaration in Decls +// references (perhaps indirectly) one of the external (imported) +// symbols in Refs. +// +// Because many Decls reach the same Refs, +// it is more efficient to group them into classes. +type Class struct { + Decls []string // sorted set of names of exported decls with same reachability + Refs []Symbol // set of external symbols, in ascending (PackageID, Name) order +} + +// A Symbol represents an external (imported) symbol +// referenced by the analyzed package. +type Symbol struct { + Package IndexID // w.r.t. PackageIndex passed to decoder + Name string +} + +// An IndexID is a small integer that uniquely identifies a package within a +// given PackageIndex. +type IndexID int + +// -- internals -- + +// A symbolSet is a set of symbols used internally during index construction. +// +// TODO(adonovan): opt: evaluate unifying Symbol and symbol. +// (Encode would have to create a private PackageIndex.) +type symbolSet map[symbol]bool + +// A symbol is the internal representation of an external +// (imported) symbol referenced by the analyzed package. +type symbol struct { + pkg metadata.PackageID + name string +} + +// declNode holds information about a package-level declaration +// (or more than one with the same name, in ill-typed code). +// +// It is a node in the symbol reference graph, whose outgoing edges +// are of two kinds: intRefs and extRefs. +type declNode struct { + name string + rep *declNode // canonical representative of this SCC (initially self) + + // outgoing graph edges + intRefs map[*declNode]bool // to symbols in this package + extRefs symbolSet // to imported symbols + extRefsClass int // extRefs equivalence class number (-1 until set at end) + + // Tarjan's SCC algorithm + index, lowlink int32 // Tarjan numbering + scc int32 // -ve => on stack; 0 => unvisited; +ve => node is root of a found SCC +} + +// state holds the working state of the Refs algorithm for a single package. +// +// The number of distinct symbols referenced by a single package +// (measured across all of kubernetes), was found to be: +// - max = 1750. +// - Several packages reference > 100 symbols. +// - p95 = 32, p90 = 22, p50 = 8. +type state struct { + // numbering of unique symbol sets + class []symbolSet // unique symbol sets + classIndex map[string]int // index of above (using SymbolSet.hash as key) + + // Tarjan's SCC algorithm + index int32 + stack []*declNode +} + +// getClassIndex returns the small integer (an index into +// state.class) that identifies the given set. +func (st *state) getClassIndex(set symbolSet) int { + key := classKey(set) + i, ok := st.classIndex[key] + if !ok { + i = len(st.class) + st.classIndex[key] = i + st.class = append(st.class, set) + } + return i +} + +// appendSorted appends the symbols to syms, sorts by ascending +// (PackageID, name), and returns the result. +// The argument must be an empty slice, ideally with capacity len(set). +func (set symbolSet) appendSorted(syms []symbol) []symbol { + for sym := range set { + syms = append(syms, sym) + } + sort.Slice(syms, func(i, j int) bool { + x, y := syms[i], syms[j] + if x.pkg != y.pkg { + return x.pkg < y.pkg + } + return x.name < y.name + }) + return syms +} + +// classKey returns a key such that equal keys imply equal sets. +// (e.g. a sorted string representation, or a cryptographic hash of same). +func classKey(set symbolSet) string { + // Sort symbols into a stable order. + // TODO(adonovan): opt: a cheap crypto hash (e.g. BLAKE2b) might + // make a cheaper map key than a large string. + // Try using a hasher instead of a builder. + var s strings.Builder + for _, sym := range set.appendSorted(make([]symbol, 0, len(set))) { + fmt.Fprintf(&s, "%s:%s;", sym.pkg, sym.name) + } + return s.String() +} + +// index builds the reference graph and encodes the index. +func index(pgfs []*parsego.File, imports map[metadata.ImportPath]*metadata.Package) []byte { + // First pass: gather package-level names and create a declNode for each. + // + // In ill-typed code, there may be multiple declarations of the + // same name; a single declInfo node will represent them all. + decls := make(map[string]*declNode) + addDecl := func(id *ast.Ident) { + if name := id.Name; name != "_" && decls[name] == nil { + node := &declNode{name: name, extRefsClass: -1} + node.rep = node + decls[name] = node + } + } + for _, pgf := range pgfs { + for _, d := range pgf.File.Decls { + switch d := d.(type) { + case *ast.GenDecl: + switch d.Tok { + case token.TYPE: + for _, spec := range d.Specs { + addDecl(spec.(*ast.TypeSpec).Name) + } + + case token.VAR, token.CONST: + for _, spec := range d.Specs { + for _, ident := range spec.(*ast.ValueSpec).Names { + addDecl(ident) + } + } + } + + case *ast.FuncDecl: + // non-method functions + if d.Recv.NumFields() == 0 { + addDecl(d.Name) + } + } + } + } + + // Second pass: process files to collect referring identifiers. + st := &state{classIndex: make(map[string]int)} + for _, pgf := range pgfs { + visitFile(pgf.File, imports, decls) + } + + // Find the strong components of the declNode graph + // using Tarjan's algorithm, and coalesce each component. + st.index = 1 + for _, decl := range decls { + if decl.index == 0 { // unvisited + st.visit(decl) + } + } + + // TODO(adonovan): opt: consider compressing the serialized + // representation by recording not the classes but the DAG of + // non-trivial union operations (the "pointer equivalence" + // optimization of Hardekopf & Lin). Unlike that algorithm, + // which piggybacks on SCC coalescing, in our case it would + // be better to make a forward traversal from the exported + // decls, since it avoids visiting unreachable nodes, and + // results in a dense (not sparse) numbering of the sets. + + // Tabulate the unique reachability sets of + // each exported package member. + classNames := make(map[int][]string) // set of decls (names) for a given reachability set + for name, decl := range decls { + if !ast.IsExported(name) { + continue + } + + decl = decl.find() + + // Skip decls with empty reachability. + if len(decl.extRefs) == 0 { + continue + } + + // Canonicalize the set (and memoize). + class := decl.extRefsClass + if class < 0 { + class = st.getClassIndex(decl.extRefs) + decl.extRefsClass = class + } + classNames[class] = append(classNames[class], name) + } + + return encode(classNames, st.class) +} + +// visitFile inspects the file syntax for referring identifiers, and +// populates the internal and external references of decls. +func visitFile(file *ast.File, imports map[metadata.ImportPath]*metadata.Package, decls map[string]*declNode) { + // Import information for this file. Multiple packages + // may be referenced by a given name in the presence + // of type errors (or multiple dot imports, which are + // keyed by "."). + fileImports := make(map[string][]metadata.PackageID) + + // importEdge records a reference from decl to an imported symbol + // (pkgname.name). The package name may be ".". + importEdge := func(decl *declNode, pkgname, name string) { + if token.IsExported(name) { + for _, depID := range fileImports[pkgname] { + if decl.extRefs == nil { + decl.extRefs = make(symbolSet) + } + decl.extRefs[symbol{depID, name}] = true + } + } + } + + // visit finds refs within node and builds edges from fromId's decl. + // References to the type parameters are ignored. + visit := func(fromId *ast.Ident, node ast.Node, tparams map[string]bool) { + if fromId.Name == "_" { + return + } + from := decls[fromId.Name] + // When visiting a method, there may not be a valid type declaration for + // the receiver. In this case there is no way to refer to the method, so + // we need not record edges. + if from == nil { + return + } + + // Visit each reference to name or name.sel. + visitDeclOrSpec(node, func(name, sel string) { + // Ignore references to type parameters. + if tparams[name] { + return + } + + // If name is declared in the package scope, + // record an edge whether or not sel is empty. + // A field or method selector may affect the + // type of the current decl via initializers: + // + // package p + // var x = y.F + // var y = struct{ F int }{} + if to, ok := decls[name]; ok { + if from.intRefs == nil { + from.intRefs = make(map[*declNode]bool) + } + from.intRefs[to] = true + + } else { + // Only record an edge to dot-imported packages + // if there was no edge to a local name. + // This assumes that there are no duplicate declarations. + // We conservatively, assume that this name comes from + // every dot-imported package. + importEdge(from, ".", name) + } + + // Record an edge to an import if it matches the name, even if that + // name collides with a package level name. Unlike the case of dotted + // imports, we know the package is invalid here, and choose to fail + // conservatively. + if sel != "" { + importEdge(from, name, sel) + } + }) + } + + // Visit the declarations and gather reference edges. + // Import declarations appear before all others. + for _, d := range file.Decls { + switch d := d.(type) { + case *ast.GenDecl: + switch d.Tok { + case token.IMPORT: + // Record local import names for this file. + for _, spec := range d.Specs { + spec := spec.(*ast.ImportSpec) + path := metadata.UnquoteImportPath(spec) + if path == "" { + continue + } + dep := imports[path] + if dep == nil { + // Note here that we don't try to "guess" + // the name of an import based on e.g. + // its importPath. Doing so would only + // result in edges that don't go anywhere. + continue + } + name := string(dep.Name) + if spec.Name != nil { + if spec.Name.Name == "_" { + continue + } + name = spec.Name.Name // possibly "." + } + fileImports[name] = append(fileImports[name], dep.ID) + } + + case token.TYPE: + for _, spec := range d.Specs { + spec := spec.(*ast.TypeSpec) + tparams := tparamsMap(spec.TypeParams) + visit(spec.Name, spec, tparams) + } + + case token.VAR, token.CONST: + for _, spec := range d.Specs { + spec := spec.(*ast.ValueSpec) + for _, name := range spec.Names { + visit(name, spec, nil) + } + } + } + + case *ast.FuncDecl: + // This check for NumFields() > 0 is consistent with go/types, + // which reports an error but treats the declaration like a + // normal function when Recv is non-nil but empty + // (as in func () f()). + if d.Recv.NumFields() > 0 { + // Method. Associate it with the receiver. + _, id, typeParams := astutil.UnpackRecv(d.Recv.List[0].Type) + if id != nil { + var tparams map[string]bool + if len(typeParams) > 0 { + tparams = make(map[string]bool) + for _, tparam := range typeParams { + if tparam.Name != "_" { + tparams[tparam.Name] = true + } + } + } + visit(id, d, tparams) + } + } else { + // Non-method. + tparams := tparamsMap(d.Type.TypeParams) + visit(d.Name, d, tparams) + } + } + } +} + +// tparamsMap returns a set recording each name declared by the provided field +// list. It so happens that we only care about names declared by type parameter +// lists. +func tparamsMap(tparams *ast.FieldList) map[string]bool { + if tparams == nil || len(tparams.List) == 0 { + return nil + } + m := make(map[string]bool) + for _, f := range tparams.List { + for _, name := range f.Names { + if name.Name != "_" { + m[name.Name] = true + } + } + } + return m +} + +// A refVisitor visits referring identifiers and dotted identifiers. +// +// For a referring identifier I, name="I" and sel="". For a dotted identifier +// q.I, name="q" and sel="I". +type refVisitor = func(name, sel string) + +// visitDeclOrSpec visits referring idents or dotted idents that may affect +// the type of the declaration at the given node, which must be an ast.Decl or +// ast.Spec. +func visitDeclOrSpec(node ast.Node, f refVisitor) { + // Declarations + switch n := node.(type) { + // ImportSpecs should not appear here, and will panic in the default case. + + case *ast.ValueSpec: + // Skip Doc, Names, Comments, which do not affect the decl type. + // Initializers only affect the type of a value spec if the type is unset. + if n.Type != nil { + visitExpr(n.Type, f) + } else { // only need to walk expr list if type is nil + visitExprList(n.Values, f) + } + + case *ast.TypeSpec: + // Skip Doc, Name, and Comment, which do not affect the decl type. + if tparams := n.TypeParams; tparams != nil { + visitFieldList(tparams, f) + } + visitExpr(n.Type, f) + + case *ast.BadDecl: + // nothing to do + + // We should not reach here with a GenDecl, so panic below in the default case. + + case *ast.FuncDecl: + // Skip Doc, Name, and Body, which do not affect the type. + // Recv is handled by Refs: methods are associated with their type. + visitExpr(n.Type, f) + + default: + panic(fmt.Sprintf("unexpected node type %T", node)) + } +} + +// visitExpr visits referring idents and dotted idents that may affect the +// type of expr. +// +// visitExpr can't reliably distinguish a dotted ident pkg.X from a +// selection expr.f or T.method. +func visitExpr(expr ast.Expr, f refVisitor) { + switch n := expr.(type) { + // These four cases account for about two thirds of all nodes, + // so we place them first to shorten the common control paths. + // (See go.dev/cl/480915.) + case *ast.Ident: + f(n.Name, "") + + case *ast.BasicLit: + // nothing to do + + case *ast.SelectorExpr: + if ident, ok := n.X.(*ast.Ident); ok { + f(ident.Name, n.Sel.Name) + } else { + visitExpr(n.X, f) + // Skip n.Sel as we don't care about which field or method is selected, + // as we'll have recorded an edge to all declarations relevant to the + // receiver type via visiting n.X above. + } + + case *ast.CallExpr: + visitExpr(n.Fun, f) + visitExprList(n.Args, f) // args affect types for unsafe.Sizeof or builtins or generics + + // Expressions + case *ast.Ellipsis: + if n.Elt != nil { + visitExpr(n.Elt, f) + } + + case *ast.FuncLit: + visitExpr(n.Type, f) + // Skip Body, which does not affect the type. + + case *ast.CompositeLit: + if n.Type != nil { + visitExpr(n.Type, f) + } + // Skip Elts, which do not affect the type. + + case *ast.ParenExpr: + visitExpr(n.X, f) + + case *ast.IndexExpr: + visitExpr(n.X, f) + visitExpr(n.Index, f) // may affect type for instantiations + + case *ast.IndexListExpr: + visitExpr(n.X, f) + for _, index := range n.Indices { + visitExpr(index, f) // may affect the type for instantiations + } + + case *ast.SliceExpr: + visitExpr(n.X, f) + // skip Low, High, and Max, which do not affect type. + + case *ast.TypeAssertExpr: + // Skip X, as it doesn't actually affect the resulting type of the type + // assertion. + if n.Type != nil { + visitExpr(n.Type, f) + } + + case *ast.StarExpr: + visitExpr(n.X, f) + + case *ast.UnaryExpr: + visitExpr(n.X, f) + + case *ast.BinaryExpr: + visitExpr(n.X, f) + visitExpr(n.Y, f) + + case *ast.KeyValueExpr: + panic("unreachable") // unreachable, as we don't descend into elts of composite lits. + + case *ast.ArrayType: + if n.Len != nil { + visitExpr(n.Len, f) + } + visitExpr(n.Elt, f) + + case *ast.StructType: + visitFieldList(n.Fields, f) + + case *ast.FuncType: + if tparams := n.TypeParams; tparams != nil { + visitFieldList(tparams, f) + } + if n.Params != nil { + visitFieldList(n.Params, f) + } + if n.Results != nil { + visitFieldList(n.Results, f) + } + + case *ast.InterfaceType: + visitFieldList(n.Methods, f) + + case *ast.MapType: + visitExpr(n.Key, f) + visitExpr(n.Value, f) + + case *ast.ChanType: + visitExpr(n.Value, f) + + case *ast.BadExpr: + // nothing to do + + default: + panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n)) + } +} + +func visitExprList(list []ast.Expr, f refVisitor) { + for _, x := range list { + visitExpr(x, f) + } +} + +func visitFieldList(n *ast.FieldList, f refVisitor) { + for _, field := range n.List { + visitExpr(field.Type, f) + } +} + +// -- strong component graph construction (plundered from go/pointer) -- + +// visit implements the depth-first search of Tarjan's SCC algorithm +// (see https://doi.org/10.1137/0201010). +// Precondition: x is canonical. +func (st *state) visit(x *declNode) { + checkCanonical(x) + x.index = st.index + x.lowlink = st.index + st.index++ + + st.stack = append(st.stack, x) // push + assert(x.scc == 0, "node revisited") + x.scc = -1 + + for y := range x.intRefs { + // Loop invariant: x is canonical. + + y := y.find() + + if x == y { + continue // nodes already coalesced + } + + switch { + case y.scc > 0: + // y is already a collapsed SCC + + case y.scc < 0: + // y is on the stack, and thus in the current SCC. + if y.index < x.lowlink { + x.lowlink = y.index + } + + default: + // y is unvisited; visit it now. + st.visit(y) + // Note: x and y are now non-canonical. + + x = x.find() + + if y.lowlink < x.lowlink { + x.lowlink = y.lowlink + } + } + } + checkCanonical(x) + + // Is x the root of an SCC? + if x.lowlink == x.index { + // Coalesce all nodes in the SCC. + for { + // Pop y from stack. + i := len(st.stack) - 1 + y := st.stack[i] + st.stack = st.stack[:i] + + checkCanonical(x) + checkCanonical(y) + + if x == y { + break // SCC is complete. + } + coalesce(x, y) + } + + // Accumulate union of extRefs over + // internal edges (to other SCCs). + for y := range x.intRefs { + y := y.find() + if y == x { + continue // already coalesced + } + assert(y.scc == 1, "edge to non-scc node") + for z := range y.extRefs { + if x.extRefs == nil { + x.extRefs = make(symbolSet) + } + x.extRefs[z] = true // extRefs: x U= y + } + } + + x.scc = 1 + } +} + +// coalesce combines two nodes in the strong component graph. +// Precondition: x and y are canonical. +func coalesce(x, y *declNode) { + // x becomes y's canonical representative. + y.rep = x + + // x accumulates y's internal references. + for z := range y.intRefs { + x.intRefs[z] = true + } + y.intRefs = nil + + // x accumulates y's external references. + for z := range y.extRefs { + if x.extRefs == nil { + x.extRefs = make(symbolSet) + } + x.extRefs[z] = true + } + y.extRefs = nil +} + +// find returns the canonical node decl. +// (The nodes form a disjoint set forest.) +func (decl *declNode) find() *declNode { + rep := decl.rep + if rep != decl { + rep = rep.find() + decl.rep = rep // simple path compression (no union-by-rank) + } + return rep +} + +const debugSCC = false // enable assertions in strong-component algorithm + +func checkCanonical(x *declNode) { + if debugSCC { + assert(x == x.find(), "not canonical") + } +} + +func assert(cond bool, msg string) { + if debugSCC && !cond { + panic(msg) + } +} + +// -- serialization -- + +// (The name says gob but in fact we use frob.) +var classesCodec = frob.CodecFor[gobClasses]() + +type gobClasses struct { + Strings []string // table of strings (PackageIDs and names) + Classes []gobClass +} + +type gobClass struct { + Decls []int32 // indices into gobClasses.Strings + Refs []int32 // list of (package, name) pairs, each an index into gobClasses.Strings +} + +// encode encodes the equivalence classes, +// (classNames[i], classes[i]), for i in range classes. +// +// With the current encoding, across kubernetes, +// the encoded size distribution has +// p50 = 511B, p95 = 4.4KB, max = 108K. +func encode(classNames map[int][]string, classes []symbolSet) []byte { + payload := gobClasses{ + Classes: make([]gobClass, 0, len(classNames)), + } + + // index of unique strings + strings := make(map[string]int32) + stringIndex := func(s string) int32 { + i, ok := strings[s] + if !ok { + i = int32(len(payload.Strings)) + strings[s] = i + payload.Strings = append(payload.Strings, s) + } + return i + } + + var refs []symbol // recycled temporary + for class, names := range classNames { + set := classes[class] + + // names, sorted + sort.Strings(names) + gobDecls := make([]int32, len(names)) + for i, name := range names { + gobDecls[i] = stringIndex(name) + } + + // refs, sorted by ascending (PackageID, name) + gobRefs := make([]int32, 0, 2*len(set)) + for _, sym := range set.appendSorted(refs[:0]) { + gobRefs = append(gobRefs, + stringIndex(string(sym.pkg)), + stringIndex(sym.name)) + } + payload.Classes = append(payload.Classes, gobClass{ + Decls: gobDecls, + Refs: gobRefs, + }) + } + + return classesCodec.Encode(payload) +} + +func decode(pkgIndex *PackageIndex, data []byte) []Class { + var payload gobClasses + classesCodec.Decode(data, &payload) + + classes := make([]Class, len(payload.Classes)) + for i, gobClass := range payload.Classes { + decls := make([]string, len(gobClass.Decls)) + for i, decl := range gobClass.Decls { + decls[i] = payload.Strings[decl] + } + refs := make([]Symbol, len(gobClass.Refs)/2) + for i := range refs { + pkgID := pkgIndex.IndexID(metadata.PackageID(payload.Strings[gobClass.Refs[2*i]])) + name := payload.Strings[gobClass.Refs[2*i+1]] + refs[i] = Symbol{Package: pkgID, Name: name} + } + classes[i] = Class{ + Decls: decls, + Refs: refs, + } + } + + // Sort by ascending Decls[0]. + // TODO(adonovan): move sort to encoder. Determinism is good. + sort.Slice(classes, func(i, j int) bool { + return classes[i].Decls[0] < classes[j].Decls[0] + }) + + return classes +} diff --git a/contribs/gnopls/internal/cache/typerefs/refs_test.go b/contribs/gnopls/internal/cache/typerefs/refs_test.go new file mode 100644 index 00000000000..1e98fb585ed --- /dev/null +++ b/contribs/gnopls/internal/cache/typerefs/refs_test.go @@ -0,0 +1,549 @@ +// 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 typerefs_test + +import ( + "context" + "fmt" + "go/token" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/cache/typerefs" + "golang.org/x/tools/gopls/internal/protocol" +) + +// TestRefs checks that the analysis reports, for each exported member +// of the test package ("p"), its correct dependencies on exported +// members of its direct imports (e.g. "ext"). +func TestRefs(t *testing.T) { + ctx := context.Background() + + tests := []struct { + label string + srcs []string // source for the local package; package name must be p + imports map[string]string // for simplicity: importPath -> pkgID/pkgName (we set pkgName == pkgID); 'ext' is always available. + want map[string][]string // decl name -> id. + allowErrs bool // whether we expect parsing errors + }{ + { + label: "empty package", + want: map[string][]string{}, + }, + { + label: "fields", + srcs: []string{` +package p + +import "ext" + +type A struct{ b B } +type B func(c C) (d D) +type C ext.C +type D ext.D + +// Should not be referenced by field names. +type b ext.B_ +type c int.C_ +type d ext.D_ +`}, + want: map[string][]string{ + "A": {"ext.C", "ext.D"}, + "B": {"ext.C", "ext.D"}, + "C": {"ext.C"}, + "D": {"ext.D"}, + }, + }, + { + label: "embedding", + srcs: []string{` +package p + +import "ext" + +type A struct{ + B + _ struct { + C + } + D +} +type B ext.B +type C ext.C +type D interface{ + B +} +`}, + want: map[string][]string{ + "A": {"ext.B", "ext.C"}, + "B": {"ext.B"}, + "C": {"ext.C"}, + "D": {"ext.B"}, + }, + }, + { + label: "constraint embedding", + srcs: []string{` +package p + +import "ext" + +type A interface{ + int | B | ~C + struct{D} +} + +type B ext.B +type C ext.C +type D ext.D +`}, + want: map[string][]string{ + "A": {"ext.B", "ext.C", "ext.D"}, + "B": {"ext.B"}, + "C": {"ext.C"}, + "D": {"ext.D"}, + }, + }, + { + label: "funcs", + srcs: []string{` +package p + +import "ext" + +type A ext.A +type B ext.B +const C B = 2 +func F(A) B { + return C +} +var V = F(W) +var W A +`}, + want: map[string][]string{ + "A": {"ext.A"}, + "B": {"ext.B"}, + "C": {"ext.B"}, + "F": {"ext.A", "ext.B"}, + "V": { + "ext.A", // via F + "ext.B", // via W: can't be eliminated: F could be builtin or generic + }, + "W": {"ext.A"}, + }, + }, + { + label: "methods", + srcs: []string{`package p + +import "ext" + +type A ext.A +type B ext.B +`, `package p + +func (A) M(B) +func (*B) M(A) +`}, + want: map[string][]string{ + "A": {"ext.A", "ext.B"}, + "B": {"ext.A", "ext.B"}, + }, + }, + { + label: "initializers", + srcs: []string{` +package p + +import "ext" + +var A b = C // type does not depend on C +type b ext.B +var C = d // type does depend on D +var d b + +var e = d + a + +var F = func() B { return E } + +var G = struct{ + A b + _ [unsafe.Sizeof(ext.V)]int // array size + Sizeof creates edge to a var + _ [unsafe.Sizeof(G)]int // creates a self edge; doesn't affect output though +}{} + +var H = (D + A + C*C) + +var I = (A+C).F +`}, + want: map[string][]string{ + "A": {"ext.B"}, + "C": {"ext.B"}, // via d + "G": {"ext.B", "ext.V"}, // via b,C + "H": {"ext.B"}, // via d,A,C + "I": {"ext.B"}, + }, + }, + { + label: "builtins", + srcs: []string{`package p + +import "ext" + +var A = new(b) +type b struct{ ext.B } + +type C chan d +type d ext.D + +type S []ext.S +type t ext.T +var U = append(([]*S)(nil), new(t)) + +type X map[k]v +type k ext.K +type v ext.V + +var Z = make(map[k]A) + +// close, delete, and panic cannot occur outside of statements +`}, + want: map[string][]string{ + "A": {"ext.B"}, + "C": {"ext.D"}, + "S": {"ext.S"}, + "U": {"ext.S", "ext.T"}, // ext.T edge could be eliminated + "X": {"ext.K", "ext.V"}, + "Z": {"ext.B", "ext.K"}, + }, + }, + { + label: "builtin shadowing", + srcs: []string{`package p + +import "ext" + +var A = new(ext.B) +func new() c +type c ext.C +`}, + want: map[string][]string{ + "A": {"ext.B", "ext.C"}, + }, + }, + { + label: "named forwarding", + srcs: []string{`package p + +import "ext" + +type A B +type B c +type c ext.C +`}, + want: map[string][]string{ + "A": {"ext.C"}, + "B": {"ext.C"}, + }, + }, + { + label: "aliases", + srcs: []string{`package p + +import "ext" + +type A = B +type B = C +type C = ext.C +`}, + want: map[string][]string{ + "A": {"ext.C"}, + "B": {"ext.C"}, + "C": {"ext.C"}, + }, + }, + { + label: "array length", + srcs: []string{`package p + +import "ext" +import "unsafe" + +type A [unsafe.Sizeof(ext.B{ext.C})]int +type A2 [unsafe.Sizeof(ext.B{f:ext.C})]int // use a KeyValueExpr + +type D [unsafe.Sizeof(struct{ f E })]int +type E ext.E + +type F [3]G +type G [ext.C]int +`}, + want: map[string][]string{ + "A": {"ext.B"}, // no ext.C: doesn't enter CompLit + "A2": {"ext.B"}, // ditto + "D": {"ext.E"}, + "E": {"ext.E"}, + "F": {"ext.C"}, + "G": {"ext.C"}, + }, + }, + { + label: "imports", + srcs: []string{`package p + +import "ext" + +import ( + "q" + r2 "r" + "s" // note: package name is t + "z" +) + +type A struct { + q.Q + r2.R + s.S // invalid ref + z.Z // references both external z.Z as well as package-level type z +} + +type B struct { + r.R // invalid ref + t.T +} + +var X int = q.V // X={}: no descent into RHS of 'var v T = rhs' +var Y = q.V.W + +type z ext.Z +`}, + imports: map[string]string{"q": "q", "r": "r", "s": "t", "z": "z"}, + want: map[string][]string{ + "A": {"ext.Z", "q.Q", "r.R", "z.Z"}, + "B": {"t.T"}, + "Y": {"q.V"}, + }, + }, + { + label: "import blank", + srcs: []string{`package p + +import _ "q" + +type A q.Q +`}, + imports: map[string]string{"q": "q"}, + want: map[string][]string{}, + }, + { + label: "import dot", + srcs: []string{`package p + +import . "q" + +type A q.Q // not actually an edge, since q is imported . +type B struct { + C // assumed to be an edge to q + D // resolved to package decl +} + + +type E error // unexported, therefore must be universe.error +type F Field +var G = Field.X +`, `package p + +import "ext" +import "q" + +type D ext.D +`}, + imports: map[string]string{"q": "q"}, + want: map[string][]string{ + "B": {"ext.D", "q.C"}, + "D": {"ext.D"}, + "F": {"q.Field"}, + "G": {"q.Field"}, + }, + }, + { + label: "typeparams", + srcs: []string{`package p + +import "ext" + +type A[T any] struct { + t T + b B +} + +type B ext.B + +func F1[T any](T, B) +func F2[T C]()(T, B) + +type T ext.T + +type C ext.C + +func F3[T1 ~[]T2, T2 ~[]T3](t1 T1, t2 T2) +type T3 ext.T3 +`, `package p + +func (A[B]) M(C) {} +`}, + want: map[string][]string{ + "A": {"ext.B", "ext.C"}, + "B": {"ext.B"}, + "C": {"ext.C"}, + "F1": {"ext.B"}, + "F2": {"ext.B", "ext.C"}, + "F3": {"ext.T3"}, + "T": {"ext.T"}, + "T3": {"ext.T3"}, + }, + }, + { + label: "instances", + srcs: []string{`package p + +import "ext" + +type A[T any] ext.A +type B[T1, T2 any] ext.B + +type C A[int] +type D B[int, A[E]] +type E ext.E +`}, + want: map[string][]string{ + "A": {"ext.A"}, + "B": {"ext.B"}, + "C": {"ext.A"}, + "D": {"ext.A", "ext.B", "ext.E"}, + "E": {"ext.E"}, + }, + }, + { + label: "duplicate decls", + srcs: []string{`package p + +import "a" +import "ext" + +type a a.A +type A a +type b ext.B +type C a.A +func (C) Foo(x) {} // invalid parameter, but that does not matter +type C b +func (C) Bar(y) {} // invalid parameter, but that does not matter + +var x ext.X +var y ext.Y +`}, + imports: map[string]string{"a": "a", "b": "b"}, // "b" import should not matter, since it isn't in this file + want: map[string][]string{ + "A": {"a.A"}, + "C": {"a.A", "ext.B", "ext.X", "ext.Y"}, + }, + }, + { + label: "invalid decls", + srcs: []string{`package p + +import "ext" + +type A B + +func () Foo(B){} + +var B struct{ ext.B +`}, + want: map[string][]string{ + "A": {"ext.B"}, + "B": {"ext.B"}, + "Foo": {"ext.B"}, + }, + allowErrs: true, + }, + { + label: "unmapped receiver", + srcs: []string{`package p + +type P struct{} + +func (a) x(P) +`}, + want: map[string][]string{}, + allowErrs: true, + }, + { + label: "SCC special case", + srcs: []string{`package p + +import "ext" + +type X Y +type Y struct { Z; *X } +type Z map[ext.A]ext.B +`}, + want: map[string][]string{ + "X": {"ext.A", "ext.B"}, + "Y": {"ext.A", "ext.B"}, + "Z": {"ext.A", "ext.B"}, + }, + allowErrs: true, + }, + } + + for _, test := range tests { + t.Run(test.label, func(t *testing.T) { + var pgfs []*parsego.File + for i, src := range test.srcs { + uri := protocol.DocumentURI(fmt.Sprintf("file:///%d.go", i)) + pgf, _ := parsego.Parse(ctx, token.NewFileSet(), uri, []byte(src), parsego.Full, false) + if !test.allowErrs && pgf.ParseErr != nil { + t.Fatalf("ParseGoSrc(...) returned parse errors: %v", pgf.ParseErr) + } + pgfs = append(pgfs, pgf) + } + + imports := map[metadata.ImportPath]*metadata.Package{ + "ext": {ID: "ext", Name: "ext"}, // this one comes for free + } + for path, mp := range test.imports { + imports[metadata.ImportPath(path)] = &metadata.Package{ + ID: metadata.PackageID(mp), + Name: metadata.PackageName(mp), + } + } + + data := typerefs.Encode(pgfs, imports) + + got := make(map[string][]string) + index := typerefs.NewPackageIndex() + for _, class := range typerefs.Decode(index, data) { + // We redundantly expand out the name x refs cross product + // here since that's what the existing tests expect. + for _, name := range class.Decls { + var syms []string + for _, sym := range class.Refs { + syms = append(syms, fmt.Sprintf("%s.%s", index.DeclaringPackage(sym), sym.Name)) + } + sort.Strings(syms) + got[name] = syms + } + } + + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("Refs(...) returned unexpected refs (-want +got):\n%s", diff) + } + }) + } +} diff --git a/contribs/gnopls/internal/cache/view.go b/contribs/gnopls/internal/cache/view.go new file mode 100644 index 00000000000..8a5a701d890 --- /dev/null +++ b/contribs/gnopls/internal/cache/view.go @@ -0,0 +1,1249 @@ +// Copyright 2018 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 cache is the core of gopls: it is concerned with state +// management, dependency analysis, and invalidation; and it holds the +// machinery of type checking and modular static analysis. Its +// principal types are [Session], [Folder], [View], [Snapshot], +// [Cache], and [Package]. +package cache + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "slices" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/moremaps" + "golang.org/x/tools/gopls/internal/util/pathutil" + "golang.org/x/tools/gopls/internal/vulncheck" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/imports" + "golang.org/x/tools/internal/xcontext" +) + +// A Folder represents an LSP workspace folder, together with its per-folder +// options and environment variables that affect build configuration. +// +// Folders (Name and Dir) are specified by the 'initialize' and subsequent +// 'didChangeWorkspaceFolders' requests; their options come from +// didChangeConfiguration. +// +// Folders must not be mutated, as they may be shared across multiple views. +type Folder struct { + Dir protocol.DocumentURI + Name string // decorative name for UI; not necessarily unique + Options *settings.Options + Env GoEnv +} + +// GoEnv holds the environment variables and data from the Go command that is +// required for operating on a workspace folder. +type GoEnv struct { + // Go environment variables. These correspond directly with the Go env var of + // the same name. + GOOS string + GOARCH string + GOCACHE string + GOMODCACHE string + GOPATH string + GOPRIVATE string + GOFLAGS string + GO111MODULE string + GOTOOLCHAIN string + + // Go version output. + GoVersion int // The X in Go 1.X + GoVersionOutput string // complete go version output + + // OS environment variables (notably not go env). + + // ExplicitGOWORK is the GOWORK value set explicitly in the environment. This + // may differ from `go env GOWORK` when the GOWORK value is implicit from the + // working directory. + ExplicitGOWORK string + + // EffectiveGOPACKAGESDRIVER is the effective go/packages driver binary that + // will be used. This may be set via GOPACKAGESDRIVER, or may be discovered + // via os.LookPath("gopackagesdriver"). The latter functionality is + // undocumented and may be removed in the future. + // + // If GOPACKAGESDRIVER is set to "off", EffectiveGOPACKAGESDRIVER is "". + EffectiveGOPACKAGESDRIVER string +} + +// View represents a single build for a workspace. +// +// A View is a logical build (the viewDefinition) along with a state of that +// build (the Snapshot). +type View struct { + id string // a unique string to identify this View in (e.g.) serialized Commands + + *viewDefinition // build configuration + + gocmdRunner *gocommand.Runner // limits go command concurrency + + // baseCtx is the context handed to NewView. This is the parent of all + // background contexts created for this view. + baseCtx context.Context + + importsState *importsState + + // parseCache holds an LRU cache of recently parsed files. + parseCache *parseCache + + // fs is the file source used to populate this view. + fs *overlayFS + + // ignoreFilter is used for fast checking of ignored files. + ignoreFilter *ignoreFilter + + // cancelInitialWorkspaceLoad can be used to terminate the view's first + // attempt at initialization. + cancelInitialWorkspaceLoad context.CancelFunc + + snapshotMu sync.Mutex + snapshot *Snapshot // latest snapshot; nil after shutdown has been called + + // initialWorkspaceLoad is closed when the first workspace initialization has + // completed. If we failed to load, we only retry if the go.mod file changes, + // to avoid too many go/packages calls. + initialWorkspaceLoad chan struct{} + + // initializationSema is used limit concurrent initialization of snapshots in + // the view. We use a channel instead of a mutex to avoid blocking when a + // context is canceled. + // + // This field (along with snapshot.initialized) guards against duplicate + // initialization of snapshots. Do not change it without adjusting snapshot + // accordingly. + initializationSema chan struct{} + + // Document filters are constructed once, in View.filterFunc. + filterFuncOnce sync.Once + _filterFunc func(protocol.DocumentURI) bool // only accessed by View.filterFunc +} + +// definition implements the viewDefiner interface. +func (v *View) definition() *viewDefinition { return v.viewDefinition } + +// A viewDefinition is a logical build, i.e. configuration (Folder) along with +// a build directory and possibly an environment overlay (e.g. GOWORK=off or +// GOOS, GOARCH=...) to affect the build. +// +// This type is immutable, and compared to see if the View needs to be +// reconstructed. +// +// Note: whenever modifying this type, also modify the equivalence relation +// implemented by viewDefinitionsEqual. +// +// TODO(golang/go#57979): viewDefinition should be sufficient for running +// go/packages. Enforce this in the API. +type viewDefinition struct { + folder *Folder // pointer comparison is OK, as any new Folder creates a new def + + typ ViewType + root protocol.DocumentURI // root directory; where to run the Go command + gomod protocol.DocumentURI // the nearest go.mod file, or "" + gowork protocol.DocumentURI // the nearest go.work file, or "" + + // workspaceModFiles holds the set of mod files active in this snapshot. + // + // For a go.work workspace, this is the set of workspace modfiles. For a + // go.mod workspace, this contains the go.mod file defining the workspace + // root, as well as any locally replaced modules (if + // "includeReplaceInWorkspace" is set). + // + // TODO(rfindley): should we just run `go list -m` to compute this set? + workspaceModFiles map[protocol.DocumentURI]struct{} + workspaceModFilesErr error // error encountered computing workspaceModFiles + + // envOverlay holds additional environment to apply to this viewDefinition. + envOverlay map[string]string +} + +// definition implements the viewDefiner interface. +func (d *viewDefinition) definition() *viewDefinition { return d } + +// Type returns the ViewType type, which determines how go/packages are loaded +// for this View. +func (d *viewDefinition) Type() ViewType { return d.typ } + +// Root returns the view root, which determines where packages are loaded from. +func (d *viewDefinition) Root() protocol.DocumentURI { return d.root } + +// GoMod returns the nearest go.mod file for this view's root, or "". +func (d *viewDefinition) GoMod() protocol.DocumentURI { return d.gomod } + +// GoWork returns the nearest go.work file for this view's root, or "". +func (d *viewDefinition) GoWork() protocol.DocumentURI { return d.gowork } + +// EnvOverlay returns a new sorted slice of environment variables (in the form +// "k=v") for this view definition's env overlay. +func (d *viewDefinition) EnvOverlay() []string { + var env []string + for k, v := range d.envOverlay { + env = append(env, fmt.Sprintf("%s=%s", k, v)) + } + sort.Strings(env) + return env +} + +// GOOS returns the effective GOOS value for this view definition, accounting +// for its env overlay. +func (d *viewDefinition) GOOS() string { + if goos, ok := d.envOverlay["GOOS"]; ok { + return goos + } + return d.folder.Env.GOOS +} + +// GOARCH returns the effective GOARCH value for this view definition, accounting +// for its env overlay. +func (d *viewDefinition) GOARCH() string { + if goarch, ok := d.envOverlay["GOARCH"]; ok { + return goarch + } + return d.folder.Env.GOARCH +} + +// adjustedGO111MODULE is the value of GO111MODULE to use for loading packages. +// It is adjusted to default to "auto" rather than "on", since if we are in +// GOPATH and have no module, we may as well allow a GOPATH view to work. +func (d viewDefinition) adjustedGO111MODULE() string { + if d.folder.Env.GO111MODULE != "" { + return d.folder.Env.GO111MODULE + } + return "auto" +} + +// ModFiles are the go.mod files enclosed in the snapshot's view and known +// to the snapshot. +func (d viewDefinition) ModFiles() []protocol.DocumentURI { + var uris []protocol.DocumentURI + for modURI := range d.workspaceModFiles { + uris = append(uris, modURI) + } + return uris +} + +// viewDefinitionsEqual reports whether x and y are equivalent. +func viewDefinitionsEqual(x, y *viewDefinition) bool { + if (x.workspaceModFilesErr == nil) != (y.workspaceModFilesErr == nil) { + return false + } + if x.workspaceModFilesErr != nil { + if x.workspaceModFilesErr.Error() != y.workspaceModFilesErr.Error() { + return false + } + } else if !moremaps.SameKeys(x.workspaceModFiles, y.workspaceModFiles) { + return false + } + if len(x.envOverlay) != len(y.envOverlay) { + return false + } + for i, xv := range x.envOverlay { + if xv != y.envOverlay[i] { + return false + } + } + return x.folder == y.folder && + x.typ == y.typ && + x.root == y.root && + x.gomod == y.gomod && + x.gowork == y.gowork +} + +// A ViewType describes how we load package information for a view. +// +// This is used for constructing the go/packages.Load query, and for +// interpreting missing packages, imports, or errors. +// +// See the documentation for individual ViewType values for details. +type ViewType int + +const ( + // GoPackagesDriverView is a view with a non-empty GOPACKAGESDRIVER + // environment variable. + // + // Load: ./... from the workspace folder. + GoPackagesDriverView ViewType = iota + + // GOPATHView is a view in GOPATH mode. + // + // I.e. in GOPATH, with GO111MODULE=off, or GO111MODULE=auto with no + // go.mod file. + // + // Load: ./... from the workspace folder. + GOPATHView + + // GoModView is a view in module mode with a single Go module. + // + // Load: /... from the module root. + GoModView + + // GoWorkView is a view in module mode with a go.work file. + // + // Load: /... from the workspace folder, for each module. + GoWorkView + + // An AdHocView is a collection of files in a given directory, not in GOPATH + // or a module. + // + // Load: . from the workspace folder. + AdHocView +) + +func (t ViewType) String() string { + switch t { + case GoPackagesDriverView: + return "GoPackagesDriver" + case GOPATHView: + return "GOPATH" + case GoModView: + return "GoMod" + case GoWorkView: + return "GoWork" + case AdHocView: + return "AdHoc" + default: + return "Unknown" + } +} + +// usesModules reports whether the view uses Go modules. +func (typ ViewType) usesModules() bool { + switch typ { + case GoModView, GoWorkView: + return true + default: + return false + } +} + +// ID returns the unique ID of this View. +func (v *View) ID() string { return v.id } + +// GoCommandRunner returns the shared gocommand.Runner for this view. +func (v *View) GoCommandRunner() *gocommand.Runner { + return v.gocmdRunner +} + +// Folder returns the folder at the base of this view. +func (v *View) Folder() *Folder { + return v.folder +} + +// UpdateFolders updates the set of views for the new folders. +// +// Calling this causes each view to be reinitialized. +func (s *Session) UpdateFolders(ctx context.Context, newFolders []*Folder) error { + s.viewMu.Lock() + defer s.viewMu.Unlock() + + overlays := s.Overlays() + var openFiles []protocol.DocumentURI + for _, o := range overlays { + openFiles = append(openFiles, o.URI()) + } + + defs, err := selectViewDefs(ctx, s, newFolders, openFiles) + if err != nil { + return err + } + var newViews []*View + for _, def := range defs { + v, _, release := s.createView(ctx, def) + release() + newViews = append(newViews, v) + } + for _, v := range s.views { + v.shutdown() + } + s.views = newViews + return nil +} + +// RunProcessEnvFunc runs fn with the process env for this snapshot's view. +// Note: the process env contains cached module and filesystem state. +func (s *Snapshot) RunProcessEnvFunc(ctx context.Context, fn func(context.Context, *imports.Options) error) error { + return s.view.importsState.runProcessEnvFunc(ctx, s, fn) +} + +// separated out from its sole use in locateTemplateFiles for testability +func fileHasExtension(path string, suffixes []string) bool { + ext := filepath.Ext(path) + if ext != "" && ext[0] == '.' { + ext = ext[1:] + } + for _, s := range suffixes { + if s != "" && ext == s { + return true + } + } + return false +} + +// locateTemplateFiles ensures that the snapshot has mapped template files +// within the workspace folder. +func (s *Snapshot) locateTemplateFiles(ctx context.Context) { + suffixes := s.Options().TemplateExtensions + if len(suffixes) == 0 { + return + } + + searched := 0 + filterFunc := s.view.filterFunc() + err := filepath.WalkDir(s.view.folder.Dir.Path(), func(path string, entry os.DirEntry, err error) error { + if err != nil { + return err + } + if entry.IsDir() { + return nil + } + if fileLimit > 0 && searched > fileLimit { + return errExhausted + } + searched++ + if !fileHasExtension(path, suffixes) { + return nil + } + uri := protocol.URIFromPath(path) + if filterFunc(uri) { + return nil + } + // Get the file in order to include it in the snapshot. + // TODO(golang/go#57558): it is fundamentally broken to track files in this + // way; we may lose them if configuration or layout changes cause a view to + // be recreated. + // + // Furthermore, this operation must ignore errors, including context + // cancellation, or risk leaving the snapshot in an undefined state. + s.ReadFile(ctx, uri) + return nil + }) + if err != nil { + event.Error(ctx, "searching for template files failed", err) + } +} + +// filterFunc returns a func that reports whether uri is filtered by the currently configured +// directoryFilters. +func (v *View) filterFunc() func(protocol.DocumentURI) bool { + v.filterFuncOnce.Do(func() { + folderDir := v.folder.Dir.Path() + gomodcache := v.folder.Env.GOMODCACHE + var filters []string + filters = append(filters, v.folder.Options.DirectoryFilters...) + if pref := strings.TrimPrefix(gomodcache, folderDir); pref != gomodcache { + modcacheFilter := "-" + strings.TrimPrefix(filepath.ToSlash(pref), "/") + filters = append(filters, modcacheFilter) + } + filterer := NewFilterer(filters) + v._filterFunc = func(uri protocol.DocumentURI) bool { + // Only filter relative to the configured root directory. + if pathutil.InDir(folderDir, uri.Path()) { + return relPathExcludedByFilter(strings.TrimPrefix(uri.Path(), folderDir), filterer) + } + return false + } + }) + return v._filterFunc +} + +// shutdown releases resources associated with the view. +func (v *View) shutdown() { + // Cancel the initial workspace load if it is still running. + v.cancelInitialWorkspaceLoad() + v.importsState.stopTimer() + + v.snapshotMu.Lock() + if v.snapshot != nil { + v.snapshot.cancel() + v.snapshot.decref() + v.snapshot = nil + } + v.snapshotMu.Unlock() +} + +// ScanImports scans the module cache synchronously. +// For use in tests. +func (v *View) ScanImports() { + gomodcache := v.folder.Env.GOMODCACHE + dirCache := v.importsState.modCache.dirCache(gomodcache) + imports.ScanModuleCache(gomodcache, dirCache, log.Printf) +} + +// IgnoredFile reports if a file would be ignored by a `go list` of the whole +// workspace. +// +// While go list ./... skips directories starting with '.', '_', or 'testdata', +// gopls may still load them via file queries. Explicitly filter them out. +func (s *Snapshot) IgnoredFile(uri protocol.DocumentURI) bool { + // Fast path: if uri doesn't contain '.', '_', or 'testdata', it is not + // possible that it is ignored. + { + uriStr := string(uri) + if !strings.Contains(uriStr, ".") && !strings.Contains(uriStr, "_") && !strings.Contains(uriStr, "testdata") { + return false + } + } + + return s.view.ignoreFilter.ignored(uri.Path()) +} + +// An ignoreFilter implements go list's exclusion rules via its 'ignored' method. +type ignoreFilter struct { + prefixes []string // root dirs, ending in filepath.Separator +} + +// newIgnoreFilter returns a new ignoreFilter implementing exclusion rules +// relative to the provided directories. +func newIgnoreFilter(dirs []string) *ignoreFilter { + f := new(ignoreFilter) + for _, d := range dirs { + f.prefixes = append(f.prefixes, filepath.Clean(d)+string(filepath.Separator)) + } + return f +} + +func (f *ignoreFilter) ignored(filename string) bool { + for _, prefix := range f.prefixes { + if suffix := strings.TrimPrefix(filename, prefix); suffix != filename { + if checkIgnored(suffix) { + return true + } + } + } + return false +} + +// checkIgnored implements go list's exclusion rules. +// Quoting “go help list”: +// +// Directory and file names that begin with "." or "_" are ignored +// by the go tool, as are directories named "testdata". +func checkIgnored(suffix string) bool { + // Note: this could be further optimized by writing a HasSegment helper, a + // segment-boundary respecting variant of strings.Contains. + for _, component := range strings.Split(suffix, string(filepath.Separator)) { + if len(component) == 0 { + continue + } + if component[0] == '.' || component[0] == '_' || component == "testdata" { + return true + } + } + return false +} + +// Snapshot returns the current snapshot for the view, and a +// release function that must be called when the Snapshot is +// no longer needed. +// +// The resulting error is non-nil if and only if the view is shut down, in +// which case the resulting release function will also be nil. +func (v *View) Snapshot() (*Snapshot, func(), error) { + v.snapshotMu.Lock() + defer v.snapshotMu.Unlock() + if v.snapshot == nil { + return nil, nil, errors.New("view is shutdown") + } + return v.snapshot, v.snapshot.Acquire(), nil +} + +// initialize loads the metadata (and currently, file contents, due to +// golang/go#57558) for the main package query of the View, which depends on +// the view type (see ViewType). If s.initialized is already true, initialize +// is a no op. +// +// The first attempt--which populates the first snapshot for a new view--must +// be allowed to run to completion without being cancelled. +// +// Subsequent attempts are triggered by conditions where gopls can't enumerate +// specific packages that require reloading, such as a change to a go.mod file. +// These attempts may be cancelled, and then retried by a later call. +// +// Postcondition: if ctx was not cancelled, s.initialized is true, s.initialErr +// holds the error resulting from initialization, if any, and s.metadata holds +// the resulting metadata graph. +func (s *Snapshot) initialize(ctx context.Context, firstAttempt bool) { + // Acquire initializationSema, which is + // (in effect) a mutex with a timeout. + select { + case <-ctx.Done(): + return + case s.view.initializationSema <- struct{}{}: + } + + defer func() { + <-s.view.initializationSema + }() + + s.mu.Lock() + initialized := s.initialized + s.mu.Unlock() + + if initialized { + return + } + + defer func() { + if firstAttempt { + close(s.view.initialWorkspaceLoad) + } + }() + + // TODO(rFindley): we should only locate template files on the first attempt, + // or guard it via a different mechanism. + s.locateTemplateFiles(ctx) + + // Collect module paths to load by parsing go.mod files. If a module fails to + // parse, capture the parsing failure as a critical diagnostic. + var scopes []loadScope // scopes to load + var modDiagnostics []*Diagnostic // diagnostics for broken go.mod files + addError := func(uri protocol.DocumentURI, err error) { + modDiagnostics = append(modDiagnostics, &Diagnostic{ + URI: uri, + Severity: protocol.SeverityError, + Source: ListError, + Message: err.Error(), + }) + } + + if len(s.view.workspaceModFiles) > 0 { + for modURI := range s.view.workspaceModFiles { + // Verify that the modfile is valid before trying to load it. + // + // TODO(rfindley): now that we no longer need to parse the modfile in + // order to load scope, we could move these diagnostics to a more general + // location where we diagnose problems with modfiles or the workspace. + // + // Be careful not to add context cancellation errors as critical module + // errors. + fh, err := s.ReadFile(ctx, modURI) + if err != nil { + if ctx.Err() != nil { + return + } + addError(modURI, err) + continue + } + parsed, err := s.ParseMod(ctx, fh) + if err != nil { + if ctx.Err() != nil { + return + } + addError(modURI, err) + continue + } + if parsed.File == nil || parsed.File.Module == nil { + addError(modURI, fmt.Errorf("no module path for %s", modURI)) + continue + } + moduleDir := filepath.Dir(modURI.Path()) + // Previously, we loaded /... for each module path, but that + // is actually incorrect when the pattern may match packages in more than + // one module. See golang/go#59458 for more details. + scopes = append(scopes, moduleLoadScope{dir: moduleDir, modulePath: parsed.File.Module.Mod.Path}) + } + } else { + scopes = append(scopes, viewLoadScope{}) + } + + // If we're loading anything, ensure we also load builtin, + // since it provides fake definitions (and documentation) + // for types like int that are used everywhere. + if len(scopes) > 0 { + scopes = append(scopes, packageLoadScope("builtin")) + } + loadErr := s.load(ctx, true, scopes...) + + // A failure is retryable if it may have been due to context cancellation, + // and this is not the initial workspace load (firstAttempt==true). + // + // The IWL runs on a detached context with a long (~10m) timeout, so + // if the context was canceled we consider loading to have failed + // permanently. + if loadErr != nil && ctx.Err() != nil && !firstAttempt { + return + } + + var initialErr *InitializationError + switch { + case loadErr != nil && ctx.Err() != nil: + event.Error(ctx, fmt.Sprintf("initial workspace load: %v", loadErr), loadErr) + initialErr = &InitializationError{ + MainError: loadErr, + } + case loadErr != nil: + event.Error(ctx, "initial workspace load failed", loadErr) + extractedDiags := s.extractGoCommandErrors(ctx, loadErr) + initialErr = &InitializationError{ + MainError: loadErr, + Diagnostics: moremaps.Group(extractedDiags, byURI), + } + case s.view.workspaceModFilesErr != nil: + initialErr = &InitializationError{ + MainError: s.view.workspaceModFilesErr, + } + case len(modDiagnostics) > 0: + initialErr = &InitializationError{ + MainError: errors.New(modDiagnostics[0].Message), + } + } + + s.mu.Lock() + defer s.mu.Unlock() + + s.initialized = true + s.initialErr = initialErr +} + +// A StateChange describes external state changes that may affect a snapshot. +// +// By far the most common of these is a change to file state, but a query of +// module upgrade information or vulnerabilities also affects gopls' behavior. +type StateChange struct { + Modifications []file.Modification // if set, the raw modifications originating this change + Files map[protocol.DocumentURI]file.Handle + ModuleUpgrades map[protocol.DocumentURI]map[string]string + Vulns map[protocol.DocumentURI]*vulncheck.Result + GCDetails map[metadata.PackageID]bool // package -> whether or not we want details +} + +// InvalidateView processes the provided state change, invalidating any derived +// results that depend on the changed state. +// +// The resulting snapshot is non-nil, representing the outcome of the state +// change. The second result is a function that must be called to release the +// snapshot when the snapshot is no longer needed. +// +// An error is returned if the given view is no longer active in the session. +func (s *Session) InvalidateView(ctx context.Context, view *View, changed StateChange) (*Snapshot, func(), error) { + s.viewMu.Lock() + defer s.viewMu.Unlock() + + if !slices.Contains(s.views, view) { + return nil, nil, fmt.Errorf("view is no longer active") + } + snapshot, release, _ := s.invalidateViewLocked(ctx, view, changed) + return snapshot, release, nil +} + +// invalidateViewLocked invalidates the content of the given view. +// (See [Session.InvalidateView]). +// +// The resulting bool reports whether the View needs to be re-diagnosed. +// (See [Snapshot.clone]). +// +// s.viewMu must be held while calling this method. +func (s *Session) invalidateViewLocked(ctx context.Context, v *View, changed StateChange) (*Snapshot, func(), bool) { + // Detach the context so that content invalidation cannot be canceled. + ctx = xcontext.Detach(ctx) + + // This should be the only time we hold the view's snapshot lock for any period of time. + v.snapshotMu.Lock() + defer v.snapshotMu.Unlock() + + prevSnapshot := v.snapshot + + if prevSnapshot == nil { + panic("invalidateContent called after shutdown") + } + + // Cancel all still-running previous requests, since they would be + // operating on stale data. + prevSnapshot.cancel() + + // Do not clone a snapshot until its view has finished initializing. + // + // TODO(rfindley): shouldn't we do this before canceling? + prevSnapshot.AwaitInitialized(ctx) + + var needsDiagnosis bool + s.snapshotWG.Add(1) + v.snapshot, needsDiagnosis = prevSnapshot.clone(ctx, v.baseCtx, changed, s.snapshotWG.Done) + + // Remove the initial reference created when prevSnapshot was created. + prevSnapshot.decref() + + // Return a second lease to the caller. + return v.snapshot, v.snapshot.Acquire(), needsDiagnosis +} + +// defineView computes the view definition for the provided workspace folder +// and URI. +// +// If forURI is non-empty, this view should be the best view including forURI. +// Otherwise, it is the default view for the folder. +// +// defineView only returns an error in the event of context cancellation. +// +// Note: keep this function in sync with bestView. +// +// TODO(rfindley): we should be able to remove the error return, as +// findModules is going away, and all other I/O is memoized. +// +// TODO(rfindley): pass in a narrower interface for the file.Source +// (e.g. fileExists func(DocumentURI) bool) to make clear that this +// process depends only on directory information, not file contents. +func defineView(ctx context.Context, fs file.Source, folder *Folder, forFile file.Handle) (*viewDefinition, error) { + if err := checkPathValid(folder.Dir.Path()); err != nil { + return nil, fmt.Errorf("invalid workspace folder path: %w; check that the spelling of the configured workspace folder path agrees with the spelling reported by the operating system", err) + } + dir := folder.Dir.Path() + if forFile != nil { + dir = filepath.Dir(forFile.URI().Path()) + } + + def := new(viewDefinition) + def.folder = folder + + if forFile != nil && fileKind(forFile) == file.Go { + // If the file has GOOS/GOARCH build constraints that + // don't match the folder's environment (which comes from + // 'go env' in the folder, plus user options), + // add those constraints to the viewDefinition's environment. + + // Content trimming is nontrivial, so do this outside of the loop below. + // Keep this in sync with bestView. + path := forFile.URI().Path() + if content, err := forFile.Content(); err == nil { + // Note the err == nil condition above: by convention a non-existent file + // does not have any constraints. See the related note in bestView: this + // choice of behavior shouldn't actually matter. In this case, we should + // only call defineView with Overlays, which always have content. + content = trimContentForPortMatch(content) + viewPort := port{def.folder.Env.GOOS, def.folder.Env.GOARCH} + if !viewPort.matches(path, content) { + for _, p := range preferredPorts { + if p.matches(path, content) { + if def.envOverlay == nil { + def.envOverlay = make(map[string]string) + } + def.envOverlay["GOOS"] = p.GOOS + def.envOverlay["GOARCH"] = p.GOARCH + break + } + } + } + } + } + + var err error + dirURI := protocol.URIFromPath(dir) + goworkFromEnv := false + if folder.Env.ExplicitGOWORK != "off" && folder.Env.ExplicitGOWORK != "" { + goworkFromEnv = true + def.gowork = protocol.URIFromPath(folder.Env.ExplicitGOWORK) + } else { + def.gowork, err = findRootPattern(ctx, dirURI, "go.work", fs) + if err != nil { + return nil, err + } + } + + // When deriving the best view for a given file, we only want to search + // up the directory hierarchy for modfiles. + def.gomod, err = findRootPattern(ctx, dirURI, "go.mod", fs) + if err != nil { + return nil, err + } + + // Determine how we load and where to load package information for this view + // + // Specifically, set + // - def.typ + // - def.root + // - def.workspaceModFiles, and + // - def.envOverlay. + + // If GOPACKAGESDRIVER is set it takes precedence. + if def.folder.Env.EffectiveGOPACKAGESDRIVER != "" { + def.typ = GoPackagesDriverView + def.root = dirURI + return def, nil + } + + // From go.dev/ref/mod, module mode is active if GO111MODULE=on, or + // GO111MODULE=auto or "" and we are inside a module or have a GOWORK value. + // But gopls is less strict, allowing GOPATH mode if GO111MODULE="", and + // AdHoc views if no module is found. + + // gomodWorkspace is a helper to compute the correct set of workspace + // modfiles for a go.mod file, based on folder options. + gomodWorkspace := func() map[protocol.DocumentURI]unit { + modFiles := map[protocol.DocumentURI]struct{}{def.gomod: {}} + if folder.Options.IncludeReplaceInWorkspace { + includingReplace, err := goModModules(ctx, def.gomod, fs) + if err == nil { + modFiles = includingReplace + } else { + // If the go.mod file fails to parse, we don't know anything about + // replace directives, so fall back to a view of just the root module. + } + } + return modFiles + } + + // Prefer a go.work file if it is available and contains the module relevant + // to forURI. + if def.adjustedGO111MODULE() != "off" && folder.Env.ExplicitGOWORK != "off" && def.gowork != "" { + def.typ = GoWorkView + if goworkFromEnv { + // The go.work file could be anywhere, which can lead to confusing error + // messages. + def.root = dirURI + } else { + // The go.work file could be anywhere, which can lead to confusing error + def.root = def.gowork.Dir() + } + def.workspaceModFiles, def.workspaceModFilesErr = goWorkModules(ctx, def.gowork, fs) + + // If forURI is in a module but that module is not + // included in the go.work file, use a go.mod view with GOWORK=off. + if forFile != nil && def.workspaceModFilesErr == nil && def.gomod != "" { + if _, ok := def.workspaceModFiles[def.gomod]; !ok { + def.typ = GoModView + def.root = def.gomod.Dir() + def.workspaceModFiles = gomodWorkspace() + if def.envOverlay == nil { + def.envOverlay = make(map[string]string) + } + def.envOverlay["GOWORK"] = "off" + } + } + return def, nil + } + + // Otherwise, use the active module, if in module mode. + // + // Note, we could override GO111MODULE here via envOverlay if we wanted to + // support the case where someone opens a module with GO111MODULE=off. But + // that is probably not worth worrying about (at this point, folks probably + // shouldn't be setting GO111MODULE). + if def.adjustedGO111MODULE() != "off" && def.gomod != "" { + def.typ = GoModView + def.root = def.gomod.Dir() + def.workspaceModFiles = gomodWorkspace() + return def, nil + } + + // Check if the workspace is within any GOPATH directory. + inGOPATH := false + for _, gp := range filepath.SplitList(folder.Env.GOPATH) { + if pathutil.InDir(filepath.Join(gp, "src"), dir) { + inGOPATH = true + break + } + } + if def.adjustedGO111MODULE() != "on" && inGOPATH { + def.typ = GOPATHView + def.root = dirURI + return def, nil + } + + // We're not in a workspace, module, or GOPATH, so have no better choice than + // an ad-hoc view. + def.typ = AdHocView + def.root = dirURI + return def, nil +} + +// FetchGoEnv queries the environment and Go command to collect environment +// variables necessary for the workspace folder. +func FetchGoEnv(ctx context.Context, folder protocol.DocumentURI, opts *settings.Options) (*GoEnv, error) { + dir := folder.Path() + // All of the go commands invoked here should be fast. No need to share a + // runner with other operations. + runner := new(gocommand.Runner) + inv := gocommand.Invocation{ + WorkingDir: dir, + Env: opts.EnvSlice(), + } + + var ( + env = new(GoEnv) + err error + ) + envvars := map[string]*string{ + "GOOS": &env.GOOS, + "GOARCH": &env.GOARCH, + "GOCACHE": &env.GOCACHE, + "GOPATH": &env.GOPATH, + "GOPRIVATE": &env.GOPRIVATE, + "GOMODCACHE": &env.GOMODCACHE, + "GOFLAGS": &env.GOFLAGS, + "GO111MODULE": &env.GO111MODULE, + "GOTOOLCHAIN": &env.GOTOOLCHAIN, + } + if err := loadGoEnv(ctx, dir, opts.EnvSlice(), runner, envvars); err != nil { + return nil, err + } + + env.GoVersion, err = gocommand.GoVersion(ctx, inv, runner) + if err != nil { + return nil, err + } + env.GoVersionOutput, err = gocommand.GoVersionOutput(ctx, inv, runner) + if err != nil { + return nil, err + } + + // The value of GOPACKAGESDRIVER is not returned through the go command. + if driver, ok := opts.Env["GOPACKAGESDRIVER"]; ok { + if driver != "off" { + env.EffectiveGOPACKAGESDRIVER = driver + } + } else if driver := os.Getenv("GOPACKAGESDRIVER"); driver != "off" { + env.EffectiveGOPACKAGESDRIVER = driver + // A user may also have a gopackagesdriver binary on their machine, which + // works the same way as setting GOPACKAGESDRIVER. + // + // TODO(rfindley): remove this call to LookPath. We should not support this + // undocumented method of setting GOPACKAGESDRIVER. + if env.EffectiveGOPACKAGESDRIVER == "" { + tool, err := exec.LookPath("gopackagesdriver") + if err == nil && tool != "" { + env.EffectiveGOPACKAGESDRIVER = tool + } + } + } + + // While GOWORK is available through the Go command, we want to differentiate + // between an explicit GOWORK value and one which is implicit from the file + // system. The former doesn't change unless the environment changes. + if gowork, ok := opts.Env["GOWORK"]; ok { + env.ExplicitGOWORK = gowork + } else { + env.ExplicitGOWORK = os.Getenv("GOWORK") + } + return env, nil +} + +// loadGoEnv loads `go env` values into the provided map, keyed by Go variable +// name. +func loadGoEnv(ctx context.Context, dir string, configEnv []string, runner *gocommand.Runner, vars map[string]*string) error { + // We can save ~200 ms by requesting only the variables we care about. + args := []string{"-json"} + for k := range vars { + args = append(args, k) + } + + inv := gocommand.Invocation{ + Verb: "env", + Args: args, + Env: configEnv, + WorkingDir: dir, + } + stdout, err := runner.Run(ctx, inv) + if err != nil { + return err + } + envMap := make(map[string]string) + if err := json.Unmarshal(stdout.Bytes(), &envMap); err != nil { + return fmt.Errorf("internal error unmarshaling JSON from 'go env': %w", err) + } + for key, ptr := range vars { + *ptr = envMap[key] + } + + return nil +} + +// findRootPattern looks for files with the given basename in dir or any parent +// directory of dir, using the provided FileSource. It returns the first match, +// starting from dir and search parents. +// +// The resulting string is either the file path of a matching file with the +// given basename, or "" if none was found. +// +// findRootPattern only returns an error in the case of context cancellation. +func findRootPattern(ctx context.Context, dirURI protocol.DocumentURI, basename string, fs file.Source) (protocol.DocumentURI, error) { + dir := dirURI.Path() + for dir != "" { + target := filepath.Join(dir, basename) + uri := protocol.URIFromPath(target) + fh, err := fs.ReadFile(ctx, uri) + if err != nil { + return "", err // context cancelled + } + if fileExists(fh) { + return uri, nil + } + // Trailing separators must be trimmed, otherwise filepath.Split is a noop. + next, _ := filepath.Split(strings.TrimRight(dir, string(filepath.Separator))) + if next == dir { + break + } + dir = next + } + return "", nil +} + +// checkPathValid performs an OS-specific path validity check. The +// implementation varies for filesystems that are case-insensitive +// (e.g. macOS, Windows), and for those that disallow certain file +// names (e.g. path segments ending with a period on Windows, or +// reserved names such as "com"; see +// https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file). +var checkPathValid = defaultCheckPathValid + +// CheckPathValid checks whether a directory is suitable as a workspace folder. +func CheckPathValid(dir string) error { return checkPathValid(dir) } + +func defaultCheckPathValid(path string) error { + return nil +} + +// IsGoPrivatePath reports whether target is a private import path, as identified +// by the GOPRIVATE environment variable. +func (s *Snapshot) IsGoPrivatePath(target string) bool { + return globsMatchPath(s.view.folder.Env.GOPRIVATE, target) +} + +// ModuleUpgrades returns known module upgrades for the dependencies of +// modfile. +func (s *Snapshot) ModuleUpgrades(modfile protocol.DocumentURI) map[string]string { + s.mu.Lock() + defer s.mu.Unlock() + upgrades := map[string]string{} + orig, _ := s.moduleUpgrades.Get(modfile) + for mod, ver := range orig { + upgrades[mod] = ver + } + return upgrades +} + +// MaxGovulncheckResultsAge defines the maximum vulnerability age considered +// valid by gopls. +// +// Mutable for testing. +var MaxGovulncheckResultAge = 1 * time.Hour + +// Vulnerabilities returns known vulnerabilities for the given modfile. +// +// Results more than an hour old are excluded. +// +// TODO(suzmue): replace command.Vuln with a different type, maybe +// https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck/govulnchecklib#Summary? +// +// TODO(rfindley): move to snapshot.go +func (s *Snapshot) Vulnerabilities(modfiles ...protocol.DocumentURI) map[protocol.DocumentURI]*vulncheck.Result { + m := make(map[protocol.DocumentURI]*vulncheck.Result) + now := time.Now() + + s.mu.Lock() + defer s.mu.Unlock() + + if len(modfiles) == 0 { // empty means all modfiles + modfiles = s.vulns.Keys() + } + for _, modfile := range modfiles { + vuln, _ := s.vulns.Get(modfile) + if vuln != nil && now.Sub(vuln.AsOf) > MaxGovulncheckResultAge { + vuln = nil + } + m[modfile] = vuln + } + return m +} + +// GoVersion returns the effective release Go version (the X in go1.X) for this +// view. +func (v *View) GoVersion() int { + return v.folder.Env.GoVersion +} + +// GoVersionString returns the effective Go version string for this view. +// +// Unlike [GoVersion], this encodes the minor version and commit hash information. +func (v *View) GoVersionString() string { + return gocommand.ParseGoVersionOutput(v.folder.Env.GoVersionOutput) +} + +// GoVersionString is temporarily available from the snapshot. +// +// TODO(rfindley): refactor so that this method is not necessary. +func (s *Snapshot) GoVersionString() string { + return s.view.GoVersionString() +} + +// Copied from +// https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/str/path.go;l=58;drc=2910c5b4a01a573ebc97744890a07c1a3122c67a +func globsMatchPath(globs, target string) bool { + for globs != "" { + // Extract next non-empty glob in comma-separated list. + var glob string + if i := strings.Index(globs, ","); i >= 0 { + glob, globs = globs[:i], globs[i+1:] + } else { + glob, globs = globs, "" + } + if glob == "" { + continue + } + + // A glob with N+1 path elements (N slashes) needs to be matched + // against the first N+1 path elements of target, + // which end just before the N+1'th slash. + n := strings.Count(glob, "/") + prefix := target + // Walk target, counting slashes, truncating at the N+1'th slash. + for i := 0; i < len(target); i++ { + if target[i] == '/' { + if n == 0 { + prefix = target[:i] + break + } + n-- + } + } + if n > 0 { + // Not enough prefix elements. + continue + } + matched, _ := path.Match(glob, prefix) + if matched { + return true + } + } + return false +} + +var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) + +// TODO(rfindley): clean up the redundancy of allFilesExcluded, +// pathExcludedByFilterFunc, pathExcludedByFilter, view.filterFunc... +func allFilesExcluded(files []string, filterFunc func(protocol.DocumentURI) bool) bool { + for _, f := range files { + uri := protocol.URIFromPath(f) + if !filterFunc(uri) { + return false + } + } + return true +} + +func relPathExcludedByFilter(path string, filterer *Filterer) bool { + path = strings.TrimPrefix(filepath.ToSlash(path), "/") + return filterer.Disallow(path) +} diff --git a/contribs/gnopls/internal/cache/view_test.go b/contribs/gnopls/internal/cache/view_test.go new file mode 100644 index 00000000000..992a3d61828 --- /dev/null +++ b/contribs/gnopls/internal/cache/view_test.go @@ -0,0 +1,175 @@ +// 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 cache + +import ( + "os" + "path/filepath" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +func TestCaseInsensitiveFilesystem(t *testing.T) { + base := t.TempDir() + + inner := filepath.Join(base, "a/B/c/DEFgh") + if err := os.MkdirAll(inner, 0777); err != nil { + t.Fatal(err) + } + file := filepath.Join(inner, "f.go") + if err := os.WriteFile(file, []byte("hi"), 0777); err != nil { + t.Fatal(err) + } + if _, err := os.Stat(filepath.Join(inner, "F.go")); err != nil { + t.Skip("filesystem is case-sensitive") + } + + tests := []struct { + path string + err bool + }{ + {file, false}, + {filepath.Join(inner, "F.go"), true}, + {filepath.Join(base, "a/b/c/defgh/f.go"), true}, + } + for _, tt := range tests { + err := checkPathValid(tt.path) + if err != nil != tt.err { + t.Errorf("checkPathValid(%q) = %v, wanted error: %v", tt.path, err, tt.err) + } + } +} + +func TestInVendor(t *testing.T) { + for _, tt := range []struct { + path string + inVendor bool + }{ + {"foo/vendor/x.go", false}, + {"foo/vendor/x/x.go", true}, + {"foo/x.go", false}, + {"foo/vendor/foo.txt", false}, + {"foo/vendor/modules.txt", false}, + } { + if got := inVendor(protocol.URIFromPath(tt.path)); got != tt.inVendor { + t.Errorf("expected %s inVendor %v, got %v", tt.path, tt.inVendor, got) + } + } +} + +func TestFilters(t *testing.T) { + tests := []struct { + filters []string + included []string + excluded []string + }{ + { + included: []string{"x"}, + }, + { + filters: []string{"-"}, + excluded: []string{"x", "x/a"}, + }, + { + filters: []string{"-x", "+y"}, + included: []string{"y", "y/a", "z"}, + excluded: []string{"x", "x/a"}, + }, + { + filters: []string{"-x", "+x/y", "-x/y/z"}, + included: []string{"x/y", "x/y/a", "a"}, + excluded: []string{"x", "x/a", "x/y/z/a"}, + }, + { + filters: []string{"+foobar", "-foo"}, + included: []string{"foobar", "foobar/a"}, + excluded: []string{"foo", "foo/a"}, + }, + } + + for _, tt := range tests { + filterer := NewFilterer(tt.filters) + for _, inc := range tt.included { + if relPathExcludedByFilter(inc, filterer) { + t.Errorf("filters %q excluded %v, wanted included", tt.filters, inc) + } + } + for _, exc := range tt.excluded { + if !relPathExcludedByFilter(exc, filterer) { + t.Errorf("filters %q included %v, wanted excluded", tt.filters, exc) + } + } + } +} + +func TestSuffixes(t *testing.T) { + type file struct { + path string + want bool + } + type cases struct { + option []string + files []file + } + tests := []cases{ + {[]string{"tmpl", "gotmpl"}, []file{ // default + {"foo", false}, + {"foo.tmpl", true}, + {"foo.gotmpl", true}, + {"tmpl", false}, + {"tmpl.go", false}}, + }, + {[]string{"tmpl", "gotmpl", "html", "gohtml"}, []file{ + {"foo.gotmpl", true}, + {"foo.html", true}, + {"foo.gohtml", true}, + {"html", false}}, + }, + {[]string{"tmpl", "gotmpl", ""}, []file{ // possible user mistake + {"foo.gotmpl", true}, + {"foo.go", false}, + {"foo", false}}, + }, + } + for _, a := range tests { + suffixes := a.option + for _, b := range a.files { + got := fileHasExtension(b.path, suffixes) + if got != b.want { + t.Errorf("got %v, want %v, option %q, file %q (%+v)", + got, b.want, a.option, b.path, b) + } + } + } +} + +func TestIgnoreFilter(t *testing.T) { + tests := []struct { + dirs []string + path string + want bool + }{ + {[]string{"a"}, "a/testdata/foo", true}, + {[]string{"a"}, "a/_ignore/foo", true}, + {[]string{"a"}, "a/.ignore/foo", true}, + {[]string{"a"}, "b/testdata/foo", false}, + {[]string{"a"}, "testdata/foo", false}, + {[]string{"a", "b"}, "b/testdata/foo", true}, + {[]string{"a"}, "atestdata/foo", false}, + } + + for _, test := range tests { + // convert to filepaths, for convenience + for i, dir := range test.dirs { + test.dirs[i] = filepath.FromSlash(dir) + } + test.path = filepath.FromSlash(test.path) + + f := newIgnoreFilter(test.dirs) + if got := f.ignored(test.path); got != test.want { + t.Errorf("newIgnoreFilter(%q).ignore(%q) = %t, want %t", test.dirs, test.path, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/cache/workspace.go b/contribs/gnopls/internal/cache/workspace.go new file mode 100644 index 00000000000..07134b3da00 --- /dev/null +++ b/contribs/gnopls/internal/cache/workspace.go @@ -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 cache + +import ( + "context" + "errors" + "fmt" + "path/filepath" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" +) + +// isGoWork reports if uri is a go.work file. +func isGoWork(uri protocol.DocumentURI) bool { + return filepath.Base(uri.Path()) == "go.work" +} + +// goWorkModules returns the URIs of go.mod files named by the go.work file. +func goWorkModules(ctx context.Context, gowork protocol.DocumentURI, fs file.Source) (map[protocol.DocumentURI]unit, error) { + fh, err := fs.ReadFile(ctx, gowork) + if err != nil { + return nil, err // canceled + } + content, err := fh.Content() + if err != nil { + return nil, err + } + filename := gowork.Path() + dir := filepath.Dir(filename) + workFile, err := modfile.ParseWork(filename, content, nil) + if err != nil { + return nil, fmt.Errorf("parsing go.work: %w", err) + } + var usedDirs []string + for _, use := range workFile.Use { + usedDirs = append(usedDirs, use.Path) + } + return localModFiles(dir, usedDirs), nil +} + +// localModFiles builds a set of local go.mod files referenced by +// goWorkOrModPaths, which is a slice of paths as contained in a go.work 'use' +// directive or go.mod 'replace' directive (and which therefore may use either +// '/' or '\' as a path separator). +func localModFiles(relativeTo string, goWorkOrModPaths []string) map[protocol.DocumentURI]unit { + modFiles := make(map[protocol.DocumentURI]unit) + for _, path := range goWorkOrModPaths { + modDir := filepath.FromSlash(path) + if !filepath.IsAbs(modDir) { + modDir = filepath.Join(relativeTo, modDir) + } + modURI := protocol.URIFromPath(filepath.Join(modDir, "go.mod")) + modFiles[modURI] = unit{} + } + return modFiles +} + +// isGoMod reports if uri is a go.mod file. +func isGoMod(uri protocol.DocumentURI) bool { + return filepath.Base(uri.Path()) == "go.mod" +} + +// goModModules returns the URIs of "workspace" go.mod files defined by a +// go.mod file. This set is defined to be the given go.mod file itself, as well +// as the modfiles of any locally replaced modules in the go.mod file. +func goModModules(ctx context.Context, gomod protocol.DocumentURI, fs file.Source) (map[protocol.DocumentURI]unit, error) { + fh, err := fs.ReadFile(ctx, gomod) + if err != nil { + return nil, err // canceled + } + content, err := fh.Content() + if err != nil { + return nil, err + } + filename := gomod.Path() + dir := filepath.Dir(filename) + modFile, err := modfile.Parse(filename, content, nil) + if err != nil { + return nil, err + } + var localReplaces []string + for _, replace := range modFile.Replace { + if modfile.IsDirectoryPath(replace.New.Path) { + localReplaces = append(localReplaces, replace.New.Path) + } + } + modFiles := localModFiles(dir, localReplaces) + modFiles[gomod] = unit{} + return modFiles, nil +} + +// fileExists reports whether the file has a Content (which may be empty). +// An overlay exists even if it is not reflected in the file system. +func fileExists(fh file.Handle) bool { + _, err := fh.Content() + return err == nil +} + +// errExhausted is returned by findModules if the file scan limit is reached. +var errExhausted = errors.New("exhausted") + +// Limit go.mod search to 1 million files. As a point of reference, +// Kubernetes has 22K files (as of 2020-11-24). +// +// Note: per golang/go#56496, the previous limit of 1M files was too slow, at +// which point this limit was decreased to 100K. +const fileLimit = 100_000 diff --git a/contribs/gnopls/internal/cache/xrefs/xrefs.go b/contribs/gnopls/internal/cache/xrefs/xrefs.go new file mode 100644 index 00000000000..4113e08716e --- /dev/null +++ b/contribs/gnopls/internal/cache/xrefs/xrefs.go @@ -0,0 +1,193 @@ +// 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 xrefs defines the serializable index of cross-package +// references that is computed during type checking. +// +// See ../references.go for the 'references' query. +package xrefs + +import ( + "go/ast" + "go/types" + "sort" + + "golang.org/x/tools/go/types/objectpath" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/frob" +) + +// Index constructs a serializable index of outbound cross-references +// for the specified type-checked package. +func Index(files []*parsego.File, pkg *types.Package, info *types.Info) []byte { + // pkgObjects maps each referenced package Q to a mapping: + // from each referenced symbol in Q to the ordered list + // of references to that symbol from this package. + // A nil types.Object indicates a reference + // to the package as a whole: an import. + pkgObjects := make(map[*types.Package]map[types.Object]*gobObject) + + // getObjects returns the object-to-references mapping for a package. + getObjects := func(pkg *types.Package) map[types.Object]*gobObject { + objects, ok := pkgObjects[pkg] + if !ok { + objects = make(map[types.Object]*gobObject) + pkgObjects[pkg] = objects + } + return objects + } + + objectpathFor := new(objectpath.Encoder).For + + for fileIndex, pgf := range files { + + nodeRange := func(n ast.Node) protocol.Range { + rng, err := pgf.PosRange(n.Pos(), n.End()) + if err != nil { + panic(err) // can't fail + } + return rng + } + + ast.Inspect(pgf.File, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.Ident: + // Report a reference for each identifier that + // uses a symbol exported from another package. + // (The built-in error.Error method has no package.) + if n.IsExported() { + if obj, ok := info.Uses[n]; ok && + obj.Pkg() != nil && + obj.Pkg() != pkg { + + // For instantiations of generic methods, + // use the generic object (see issue #60622). + if fn, ok := obj.(*types.Func); ok { + obj = fn.Origin() + } + + objects := getObjects(obj.Pkg()) + gobObj, ok := objects[obj] + if !ok { + path, err := objectpathFor(obj) + if err != nil { + // Capitalized but not exported + // (e.g. local const/var/type). + return true + } + gobObj = &gobObject{Path: path} + objects[obj] = gobObj + } + + gobObj.Refs = append(gobObj.Refs, gobRef{ + FileIndex: fileIndex, + Range: nodeRange(n), + }) + } + } + + case *ast.ImportSpec: + // Report a reference from each import path + // string to the imported package. + pkgname := info.PkgNameOf(n) + if pkgname == nil { + return true // missing import + } + objects := getObjects(pkgname.Imported()) + gobObj, ok := objects[nil] + if !ok { + gobObj = &gobObject{Path: ""} + objects[nil] = gobObj + } + gobObj.Refs = append(gobObj.Refs, gobRef{ + FileIndex: fileIndex, + Range: nodeRange(n.Path), + }) + } + return true + }) + } + + // Flatten the maps into slices, and sort for determinism. + var packages []*gobPackage + for p := range pkgObjects { + objects := pkgObjects[p] + gp := &gobPackage{ + PkgPath: metadata.PackagePath(p.Path()), + Objects: make([]*gobObject, 0, len(objects)), + } + for _, gobObj := range objects { + gp.Objects = append(gp.Objects, gobObj) + } + sort.Slice(gp.Objects, func(i, j int) bool { + return gp.Objects[i].Path < gp.Objects[j].Path + }) + packages = append(packages, gp) + } + sort.Slice(packages, func(i, j int) bool { + return packages[i].PkgPath < packages[j].PkgPath + }) + + return packageCodec.Encode(packages) +} + +// Lookup searches a serialized index produced by an indexPackage +// operation on m, and returns the locations of all references from m +// to any object in the target set. Each object is denoted by a pair +// of (package path, object path). +func Lookup(mp *metadata.Package, data []byte, targets map[metadata.PackagePath]map[objectpath.Path]struct{}) (locs []protocol.Location) { + var packages []*gobPackage + packageCodec.Decode(data, &packages) + for _, gp := range packages { + if objectSet, ok := targets[gp.PkgPath]; ok { + for _, gobObj := range gp.Objects { + if _, ok := objectSet[gobObj.Path]; ok { + for _, ref := range gobObj.Refs { + uri := mp.CompiledGoFiles[ref.FileIndex] + locs = append(locs, protocol.Location{ + URI: uri, + Range: ref.Range, + }) + } + } + } + } + } + + return locs +} + +// -- serialized representation -- + +// The cross-reference index records the location of all references +// from one package to symbols defined in other packages +// (dependencies). It does not record within-package references. +// The index for package P consists of a list of gopPackage records, +// each enumerating references to symbols defined a single dependency, Q. + +// TODO(adonovan): opt: choose a more compact encoding. +// The gobRef.Range field is the obvious place to begin. + +// (The name says gob but in fact we use frob.) +var packageCodec = frob.CodecFor[[]*gobPackage]() + +// A gobPackage records the set of outgoing references from the index +// package to symbols defined in a dependency package. +type gobPackage struct { + PkgPath metadata.PackagePath // defining package (Q) + Objects []*gobObject // set of Q objects referenced by P +} + +// A gobObject records all references to a particular symbol. +type gobObject struct { + Path objectpath.Path // symbol name within package; "" => import of package itself + Refs []gobRef // locations of references within P, in lexical order +} + +type gobRef struct { + FileIndex int // index of enclosing file within P's CompiledGoFiles + Range protocol.Range // source range of reference +} diff --git a/contribs/gnopls/internal/clonetest/clonetest.go b/contribs/gnopls/internal/clonetest/clonetest.go new file mode 100644 index 00000000000..3542476ae09 --- /dev/null +++ b/contribs/gnopls/internal/clonetest/clonetest.go @@ -0,0 +1,152 @@ +// 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 clonetest provides utility functions for testing Clone operations. +// +// The [NonZero] helper may be used to construct a type in which fields are +// recursively set to a non-zero value. This value can then be cloned, and the +// [ZeroOut] helper can set values stored in the clone to zero, recursively. +// Doing so should not mutate the original. +package clonetest + +import ( + "fmt" + "reflect" +) + +// NonZero returns a T set to some appropriate nonzero value: +// - Values of basic type are set to an arbitrary non-zero value. +// - Struct fields are set to a non-zero value. +// - Array indices are set to a non-zero value. +// - Pointers point to a non-zero value. +// - Maps and slices are given a non-zero element. +// - Chan, Func, Interface, UnsafePointer are all unsupported. +// +// NonZero breaks cycles by returning a zero value for recursive types. +func NonZero[T any]() T { + var x T + t := reflect.TypeOf(x) + if t == nil { + panic("untyped nil") + } + v := nonZeroValue(t, nil) + return v.Interface().(T) +} + +// nonZeroValue returns a non-zero, addressable value of the given type. +func nonZeroValue(t reflect.Type, seen []reflect.Type) reflect.Value { + for _, t2 := range seen { + if t == t2 { + // Cycle: return the zero value. + return reflect.Zero(t) + } + } + seen = append(seen, t) + v := reflect.New(t).Elem() + switch t.Kind() { + case reflect.Bool: + v.SetBool(true) + + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + v.SetInt(1) + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v.SetUint(1) + + case reflect.Float32, reflect.Float64: + v.SetFloat(1) + + case reflect.Complex64, reflect.Complex128: + v.SetComplex(1) + + case reflect.Array: + for i := 0; i < v.Len(); i++ { + v.Index(i).Set(nonZeroValue(t.Elem(), seen)) + } + + case reflect.Map: + v2 := reflect.MakeMap(t) + v2.SetMapIndex(nonZeroValue(t.Key(), seen), nonZeroValue(t.Elem(), seen)) + v.Set(v2) + + case reflect.Pointer: + v2 := nonZeroValue(t.Elem(), seen) + v.Set(v2.Addr()) + + case reflect.Slice: + v2 := reflect.Append(v, nonZeroValue(t.Elem(), seen)) + v.Set(v2) + + case reflect.String: + v.SetString(".") + + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + v.Field(i).Set(nonZeroValue(t.Field(i).Type, seen)) + } + + default: // Chan, Func, Interface, UnsafePointer + panic(fmt.Sprintf("reflect kind %v not supported", t.Kind())) + } + return v +} + +// ZeroOut recursively sets values contained in t to zero. +// Values of king Chan, Func, Interface, UnsafePointer are all unsupported. +// +// No attempt is made to handle cyclic values. +func ZeroOut[T any](t *T) { + v := reflect.ValueOf(t).Elem() + zeroOutValue(v) +} + +func zeroOutValue(v reflect.Value) { + if v.IsZero() { + return // nothing to do; this also handles untyped nil values + } + + switch v.Kind() { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Float32, reflect.Float64, + reflect.Complex64, reflect.Complex128, + reflect.String: + + v.Set(reflect.Zero(v.Type())) + + case reflect.Array: + for i := 0; i < v.Len(); i++ { + zeroOutValue(v.Index(i)) + } + + case reflect.Map: + iter := v.MapRange() + for iter.Next() { + mv := iter.Value() + if mv.CanAddr() { + zeroOutValue(mv) + } else { + mv = reflect.New(mv.Type()).Elem() + } + v.SetMapIndex(iter.Key(), mv) + } + + case reflect.Pointer: + zeroOutValue(v.Elem()) + + case reflect.Slice: + for i := 0; i < v.Len(); i++ { + zeroOutValue(v.Index(i)) + } + + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + zeroOutValue(v.Field(i)) + } + + default: + panic(fmt.Sprintf("reflect kind %v not supported", v.Kind())) + } +} diff --git a/contribs/gnopls/internal/clonetest/clonetest_test.go b/contribs/gnopls/internal/clonetest/clonetest_test.go new file mode 100644 index 00000000000..bbb803f2447 --- /dev/null +++ b/contribs/gnopls/internal/clonetest/clonetest_test.go @@ -0,0 +1,74 @@ +// 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 clonetest_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/clonetest" +) + +func Test(t *testing.T) { + doTest(t, true, false) + type B bool + doTest(t, B(true), false) + doTest(t, 1, 0) + doTest(t, int(1), 0) + doTest(t, int8(1), 0) + doTest(t, int16(1), 0) + doTest(t, int32(1), 0) + doTest(t, int64(1), 0) + doTest(t, uint(1), 0) + doTest(t, uint8(1), 0) + doTest(t, uint16(1), 0) + doTest(t, uint32(1), 0) + doTest(t, uint64(1), 0) + doTest(t, uintptr(1), 0) + doTest(t, float32(1), 0) + doTest(t, float64(1), 0) + doTest(t, complex64(1), 0) + doTest(t, complex128(1), 0) + doTest(t, [3]int{1, 1, 1}, [3]int{0, 0, 0}) + doTest(t, ".", "") + m1, m2 := map[string]int{".": 1}, map[string]int{".": 0} + doTest(t, m1, m2) + doTest(t, &m1, &m2) + doTest(t, []int{1}, []int{0}) + i, j := 1, 0 + doTest(t, &i, &j) + k, l := &i, &j + doTest(t, &k, &l) + + s1, s2 := []int{1}, []int{0} + doTest(t, &s1, &s2) + + type S struct { + Field int + } + doTest(t, S{1}, S{0}) + + doTest(t, []*S{{1}}, []*S{{0}}) + + // An arbitrary recursive type. + type LinkedList[T any] struct { + V T + Next *LinkedList[T] + } + doTest(t, &LinkedList[int]{V: 1}, &LinkedList[int]{V: 0}) +} + +// doTest checks that the result of NonZero matches the nonzero argument, and +// that zeroing out that result matches the zero argument. +func doTest[T any](t *testing.T, nonzero, zero T) { + got := clonetest.NonZero[T]() + if diff := cmp.Diff(nonzero, got); diff != "" { + t.Fatalf("NonZero() returned unexpected diff (-want +got):\n%s", diff) + } + clonetest.ZeroOut(&got) + if diff := cmp.Diff(zero, got); diff != "" { + t.Errorf("ZeroOut() returned unexpected diff (-want +got):\n%s", diff) + } +} diff --git a/contribs/gnopls/internal/cmd/call_hierarchy.go b/contribs/gnopls/internal/cmd/call_hierarchy.go new file mode 100644 index 00000000000..0ac6956144e --- /dev/null +++ b/contribs/gnopls/internal/cmd/call_hierarchy.go @@ -0,0 +1,143 @@ +// 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 cmd + +import ( + "context" + "flag" + "fmt" + "strings" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// callHierarchy implements the callHierarchy verb for gopls. +type callHierarchy struct { + app *Application +} + +func (c *callHierarchy) Name() string { return "call_hierarchy" } +func (c *callHierarchy) Parent() string { return c.app.Name() } +func (c *callHierarchy) Usage() string { return "" } +func (c *callHierarchy) ShortHelp() string { return "display selected identifier's call hierarchy" } +func (c *callHierarchy) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls call_hierarchy helper/helper.go:8:6 + $ gopls call_hierarchy helper/helper.go:#53 +`) + printFlagDefaults(f) +} + +func (c *callHierarchy) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("call_hierarchy expects 1 argument (position)") + } + + conn, err := c.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + file, err := conn.openFile(ctx, from.URI()) + if err != nil { + return err + } + + loc, err := file.spanLocation(from) + if err != nil { + return err + } + + p := protocol.CallHierarchyPrepareParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + + callItems, err := conn.PrepareCallHierarchy(ctx, &p) + if err != nil { + return err + } + if len(callItems) == 0 { + return fmt.Errorf("function declaration identifier not found at %v", args[0]) + } + + for _, item := range callItems { + incomingCalls, err := conn.IncomingCalls(ctx, &protocol.CallHierarchyIncomingCallsParams{Item: item}) + if err != nil { + return err + } + for i, call := range incomingCalls { + // From the spec: CallHierarchyIncomingCall.FromRanges is relative to + // the caller denoted by CallHierarchyIncomingCall.from. + printString, err := callItemPrintString(ctx, conn, call.From, call.From.URI, call.FromRanges) + if err != nil { + return err + } + fmt.Printf("caller[%d]: %s\n", i, printString) + } + + printString, err := callItemPrintString(ctx, conn, item, "", nil) + if err != nil { + return err + } + fmt.Printf("identifier: %s\n", printString) + + outgoingCalls, err := conn.OutgoingCalls(ctx, &protocol.CallHierarchyOutgoingCallsParams{Item: item}) + if err != nil { + return err + } + for i, call := range outgoingCalls { + // From the spec: CallHierarchyOutgoingCall.FromRanges is the range + // relative to the caller, e.g the item passed to + printString, err := callItemPrintString(ctx, conn, call.To, item.URI, call.FromRanges) + if err != nil { + return err + } + fmt.Printf("callee[%d]: %s\n", i, printString) + } + } + + return nil +} + +// callItemPrintString returns a protocol.CallHierarchyItem object represented as a string. +// item and call ranges (protocol.Range) are converted to user friendly spans (1-indexed). +func callItemPrintString(ctx context.Context, conn *connection, item protocol.CallHierarchyItem, callsURI protocol.DocumentURI, calls []protocol.Range) (string, error) { + itemFile, err := conn.openFile(ctx, item.URI) + if err != nil { + return "", err + } + itemSpan, err := itemFile.rangeSpan(item.Range) + if err != nil { + return "", err + } + + var callRanges []string + if callsURI != "" { + callsFile, err := conn.openFile(ctx, callsURI) + if err != nil { + return "", err + } + for _, rng := range calls { + call, err := callsFile.rangeSpan(rng) + if err != nil { + return "", err + } + callRange := fmt.Sprintf("%d:%d-%d", call.Start().Line(), call.Start().Column(), call.End().Column()) + callRanges = append(callRanges, callRange) + } + } + + printString := fmt.Sprintf("function %s in %v", item.Name, itemSpan) + if len(calls) > 0 { + printString = fmt.Sprintf("ranges %s in %s from/to %s", strings.Join(callRanges, ", "), callsURI.Path(), printString) + } + return printString, nil +} diff --git a/contribs/gnopls/internal/cmd/capabilities_test.go b/contribs/gnopls/internal/cmd/capabilities_test.go new file mode 100644 index 00000000000..97eb49652d0 --- /dev/null +++ b/contribs/gnopls/internal/cmd/capabilities_test.go @@ -0,0 +1,178 @@ +// 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. + +package cmd + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/server" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/testenv" +) + +// TestCapabilities does some minimal validation of the server's adherence to the LSP. +// The checks in the test are added as changes are made and errors noticed. +func TestCapabilities(t *testing.T) { + // TODO(bcmills): This test fails on js/wasm, which is not unexpected, but the + // failure mode is that the DidOpen call below reports "no views in session", + // which seems a little too cryptic. + // Is there some missing error reporting somewhere? + testenv.NeedsTool(t, "go") + + tmpDir, err := os.MkdirTemp("", "fake") + if err != nil { + t.Fatal(err) + } + tmpFile := filepath.Join(tmpDir, "fake.go") + if err := os.WriteFile(tmpFile, []byte(""), 0775); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module fake\n\ngo 1.12\n"), 0775); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + app := New() + + params := &protocol.ParamInitialize{} + params.RootURI = protocol.URIFromPath(tmpDir) + params.Capabilities.Workspace.Configuration = true + + // Send an initialize request to the server. + ctx := context.Background() + client := newClient(app) + options := settings.DefaultOptions(app.options) + server := server.New(cache.NewSession(ctx, cache.New(nil)), client, options) + result, err := server.Initialize(ctx, params) + if err != nil { + t.Fatal(err) + } + // Validate initialization result. + if err := validateCapabilities(result); err != nil { + t.Error(err) + } + // Complete initialization of server. + if err := server.Initialized(ctx, &protocol.InitializedParams{}); err != nil { + t.Fatal(err) + } + + c := newConnection(server, client) + defer c.terminate(ctx) + + // Open the file on the server side. + uri := protocol.URIFromPath(tmpFile) + if err := c.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{ + TextDocument: protocol.TextDocumentItem{ + URI: uri, + LanguageID: "go", + Version: 1, + Text: `package main; func main() {};`, + }, + }); err != nil { + t.Fatal(err) + } + + // If we are sending a full text change, the change.Range must be nil. + // It is not enough for the Change to be empty, as that is ambiguous. + if err := c.Server.DidChange(ctx, &protocol.DidChangeTextDocumentParams{ + TextDocument: protocol.VersionedTextDocumentIdentifier{ + TextDocumentIdentifier: protocol.TextDocumentIdentifier{ + URI: uri, + }, + Version: 2, + }, + ContentChanges: []protocol.TextDocumentContentChangeEvent{ + { + Range: nil, + Text: `package main; func main() { fmt.Println("") }`, + }, + }, + }); err != nil { + t.Fatal(err) + } + + // Send a code action request to validate expected types. + actions, err := c.Server.CodeAction(ctx, &protocol.CodeActionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: uri, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, action := range actions { + // Validate that an empty command is sent along with import organization responses. + if action.Kind == protocol.SourceOrganizeImports && action.Command != nil { + t.Errorf("unexpected command for import organization") + } + } + + if err := c.Server.DidSave(ctx, &protocol.DidSaveTextDocumentParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: uri, + }, + // LSP specifies that a file can be saved with optional text, so this field must be nil. + Text: nil, + }); err != nil { + t.Fatal(err) + } + + // Send a completion request to validate expected types. + list, err := c.Server.Completion(ctx, &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: uri, + }, + Position: protocol.Position{ + Line: 0, + Character: 28, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + for _, item := range list.Items { + // All other completion items should have nil commands. + // An empty command will be treated as a command with the name '' by VS Code. + // This causes VS Code to report errors to users about invalid commands. + if item.Command != nil { + t.Errorf("unexpected command for completion item") + } + // The item's TextEdit must be a pointer, as VS Code considers TextEdits + // that don't contain the cursor position to be invalid. + var textEdit = item.TextEdit.Value + switch textEdit.(type) { + case protocol.TextEdit, protocol.InsertReplaceEdit: + default: + t.Errorf("textEdit is not TextEdit nor InsertReplaceEdit, instead it is %T", textEdit) + } + } + if err := c.Server.Shutdown(ctx); err != nil { + t.Fatal(err) + } + if err := c.Server.Exit(ctx); err != nil { + t.Fatal(err) + } +} + +func validateCapabilities(result *protocol.InitializeResult) error { + // If the client sends "false" for RenameProvider.PrepareSupport, + // the server must respond with a boolean. + if v, ok := result.Capabilities.RenameProvider.(bool); !ok { + return fmt.Errorf("RenameProvider must be a boolean if PrepareSupport is false (got %T)", v) + } + // The same goes for CodeActionKind.ValueSet. + if v, ok := result.Capabilities.CodeActionProvider.(bool); !ok { + return fmt.Errorf("CodeActionSupport must be a boolean if CodeActionKind.ValueSet has length 0 (got %T)", v) + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/check.go b/contribs/gnopls/internal/cmd/check.go new file mode 100644 index 00000000000..d256fa9de2a --- /dev/null +++ b/contribs/gnopls/internal/cmd/check.go @@ -0,0 +1,110 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + "slices" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" +) + +// check implements the check verb for gopls. +type check struct { + app *Application +} + +func (c *check) Name() string { return "check" } +func (c *check) Parent() string { return c.app.Name() } +func (c *check) Usage() string { return "" } +func (c *check) ShortHelp() string { return "show diagnostic results for the specified file" } +func (c *check) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: show the diagnostic results of this file: + + $ gopls check internal/cmd/check.go +`) + printFlagDefaults(f) +} + +// Run performs the check on the files specified by args and prints the +// results to stdout. +func (c *check) Run(ctx context.Context, args ...string) error { + if len(args) == 0 { + return nil + } + + // TODO(adonovan): formally, we are required to set this + // option if we want RelatedInformation, but it appears to + // have no effect on the server, even though the default is + // false. Investigate. + origOptions := c.app.options + c.app.options = func(opts *settings.Options) { + if origOptions != nil { + origOptions(opts) + } + opts.RelatedInformationSupported = true + } + + conn, err := c.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + // Open and diagnose the requested files. + var ( + uris []protocol.DocumentURI + checking = make(map[protocol.DocumentURI]*cmdFile) + ) + for _, arg := range args { + uri := protocol.URIFromPath(arg) + uris = append(uris, uri) + file, err := conn.openFile(ctx, uri) + if err != nil { + return err + } + checking[uri] = file + } + if err := conn.diagnoseFiles(ctx, uris); err != nil { + return err + } + + // print prints a single element of a diagnostic. + print := func(uri protocol.DocumentURI, rng protocol.Range, message string) error { + file, err := conn.openFile(ctx, uri) + if err != nil { + return err + } + spn, err := file.rangeSpan(rng) + if err != nil { + return fmt.Errorf("could not convert position %v for %q", rng, message) + } + fmt.Printf("%v: %v\n", spn, message) + return nil + } + + for _, file := range checking { + file.diagnosticsMu.Lock() + diags := slices.Clone(file.diagnostics) + file.diagnosticsMu.Unlock() + + for _, diag := range diags { + if err := print(file.uri, diag.Range, diag.Message); err != nil { + return err + } + for _, rel := range diag.RelatedInformation { + if err := print(rel.Location.URI, rel.Location.Range, "- "+rel.Message); err != nil { + return err + } + } + + } + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/cmd.go b/contribs/gnopls/internal/cmd/cmd.go new file mode 100644 index 00000000000..9ec5b630d6c --- /dev/null +++ b/contribs/gnopls/internal/cmd/cmd.go @@ -0,0 +1,949 @@ +// Copyright 2018 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 cmd handles the gopls command line. +// It contains a handler for each of the modes, along with all the flag handling +// and the command line output format. +package cmd + +import ( + "context" + "flag" + "fmt" + "log" + "math/rand" + "os" + "path/filepath" + "reflect" + "sort" + "strings" + "sync" + "text/tabwriter" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/filecache" + "golang.org/x/tools/gopls/internal/lsprpc" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/server" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/browser" + bugpkg "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/tool" +) + +// Application is the main application as passed to tool.Main +// It handles the main command line parsing and dispatch to the sub commands. +type Application struct { + // Core application flags + + // Embed the basic profiling flags supported by the tool package + tool.Profile + + // We include the server configuration directly for now, so the flags work + // even without the verb. + // TODO: Remove this when we stop allowing the serve verb by default. + Serve Serve + + // the options configuring function to invoke when building a server + options func(*settings.Options) + + // Support for remote LSP server. + Remote string `flag:"remote" help:"forward all commands to a remote lsp specified by this flag. With no special prefix, this is assumed to be a TCP address. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. If 'auto', or prefixed by 'auto;', the remote address is automatically resolved based on the executing environment."` + + // Verbose enables verbose logging. + Verbose bool `flag:"v,verbose" help:"verbose output"` + + // VeryVerbose enables a higher level of verbosity in logging output. + VeryVerbose bool `flag:"vv,veryverbose" help:"very verbose output"` + + // Control ocagent export of telemetry + OCAgent string `flag:"ocagent" help:"the address of the ocagent (e.g. http://localhost:55678), or off"` + + // PrepareOptions is called to update the options when a new view is built. + // It is primarily to allow the behavior of gopls to be modified by hooks. + PrepareOptions func(*settings.Options) + + // editFlags holds flags that control how file edit operations + // are applied, in particular when the server makes an ApplyEdits + // downcall to the client. Present only for commands that apply edits. + editFlags *EditFlags +} + +// EditFlags defines flags common to {code{action,lens},format,imports,rename} +// that control how edits are applied to the client's files. +// +// The type is exported for flag reflection. +// +// The -write, -diff, and -list flags are orthogonal but any +// of them suppresses the default behavior, which is to print +// the edited file contents. +type EditFlags struct { + Write bool `flag:"w,write" help:"write edited content to source files"` + Preserve bool `flag:"preserve" help:"with -write, make copies of original files"` + Diff bool `flag:"d,diff" help:"display diffs instead of edited file content"` + List bool `flag:"l,list" help:"display names of edited files"` +} + +func (app *Application) verbose() bool { + return app.Verbose || app.VeryVerbose +} + +// New returns a new Application ready to run. +func New() *Application { + app := &Application{ + OCAgent: "off", //TODO: Remove this line to default the exporter to on + + Serve: Serve{ + RemoteListenTimeout: 1 * time.Minute, + }, + } + app.Serve.app = app + return app +} + +// Name implements tool.Application returning the binary name. +func (app *Application) Name() string { return "gopls" } + +// Usage implements tool.Application returning empty extra argument usage. +func (app *Application) Usage() string { return "" } + +// ShortHelp implements tool.Application returning the main binary help. +func (app *Application) ShortHelp() string { + return "" +} + +// DetailedHelp implements tool.Application returning the main binary help. +// This includes the short help for all the sub commands. +func (app *Application) DetailedHelp(f *flag.FlagSet) { + w := tabwriter.NewWriter(f.Output(), 0, 0, 2, ' ', 0) + defer w.Flush() + + fmt.Fprint(w, ` +gopls is a Go language server. + +It is typically used with an editor to provide language features. When no +command is specified, gopls will default to the 'serve' command. The language +features can also be accessed via the gopls command-line interface. + +For documentation of all its features, see: + + https://github.com/golang/tools/blob/master/gopls/doc/features + +Usage: + gopls help [] + +Command: +`) + fmt.Fprint(w, "\nMain\t\n") + for _, c := range app.mainCommands() { + fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) + } + fmt.Fprint(w, "\t\nFeatures\t\n") + for _, c := range app.featureCommands() { + fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) + } + if app.verbose() { + fmt.Fprint(w, "\t\nInternal Use Only\t\n") + for _, c := range app.internalCommands() { + fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) + } + } + fmt.Fprint(w, "\nflags:\n") + printFlagDefaults(f) +} + +// this is a slightly modified version of flag.PrintDefaults to give us control +func printFlagDefaults(s *flag.FlagSet) { + var flags [][]*flag.Flag + seen := map[flag.Value]int{} + s.VisitAll(func(f *flag.Flag) { + if i, ok := seen[f.Value]; !ok { + seen[f.Value] = len(flags) + flags = append(flags, []*flag.Flag{f}) + } else { + flags[i] = append(flags[i], f) + } + }) + for _, entry := range flags { + sort.SliceStable(entry, func(i, j int) bool { + return len(entry[i].Name) < len(entry[j].Name) + }) + var b strings.Builder + for i, f := range entry { + switch i { + case 0: + b.WriteString(" -") + default: + b.WriteString(",-") + } + b.WriteString(f.Name) + } + + f := entry[0] + name, usage := flag.UnquoteUsage(f) + if len(name) > 0 { + b.WriteString("=") + b.WriteString(name) + } + // Boolean flags of one ASCII letter are so common we + // treat them specially, putting their usage on the same line. + if b.Len() <= 4 { // space, space, '-', 'x'. + b.WriteString("\t") + } else { + // Four spaces before the tab triggers good alignment + // for both 4- and 8-space tab stops. + b.WriteString("\n \t") + } + b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t")) + if !isZeroValue(f, f.DefValue) { + if reflect.TypeOf(f.Value).Elem().Name() == "stringValue" { + fmt.Fprintf(&b, " (default %q)", f.DefValue) + } else { + fmt.Fprintf(&b, " (default %v)", f.DefValue) + } + } + fmt.Fprint(s.Output(), b.String(), "\n") + } +} + +// isZeroValue is copied from the flags package +func isZeroValue(f *flag.Flag, value string) bool { + // Build a zero value of the flag's Value type, and see if the + // result of calling its String method equals the value passed in. + // This works unless the Value type is itself an interface type. + typ := reflect.TypeOf(f.Value) + var z reflect.Value + if typ.Kind() == reflect.Ptr { + z = reflect.New(typ.Elem()) + } else { + z = reflect.Zero(typ) + } + return value == z.Interface().(flag.Value).String() +} + +// Run takes the args after top level flag processing, and invokes the correct +// sub command as specified by the first argument. +// If no arguments are passed it will invoke the server sub command, as a +// temporary measure for compatibility. +func (app *Application) Run(ctx context.Context, args ...string) error { + // In the category of "things we can do while waiting for the Go command": + // Pre-initialize the filecache, which takes ~50ms to hash the gopls + // executable, and immediately runs a gc. + filecache.Start() + + ctx = debug.WithInstance(ctx, app.OCAgent) + if len(args) == 0 { + s := flag.NewFlagSet(app.Name(), flag.ExitOnError) + return tool.Run(ctx, s, &app.Serve, args) + } + command, args := args[0], args[1:] + for _, c := range app.Commands() { + if c.Name() == command { + s := flag.NewFlagSet(app.Name(), flag.ExitOnError) + return tool.Run(ctx, s, c, args) + } + } + return tool.CommandLineErrorf("Unknown command %v", command) +} + +// Commands returns the set of commands supported by the gopls tool on the +// command line. +// The command is specified by the first non flag argument. +func (app *Application) Commands() []tool.Application { + var commands []tool.Application + commands = append(commands, app.mainCommands()...) + commands = append(commands, app.featureCommands()...) + commands = append(commands, app.internalCommands()...) + return commands +} + +func (app *Application) mainCommands() []tool.Application { + return []tool.Application{ + &app.Serve, + &version{app: app}, + &bug{app: app}, + &help{app: app}, + &apiJSON{app: app}, + &licenses{app: app}, + } +} + +func (app *Application) internalCommands() []tool.Application { + return []tool.Application{ + &vulncheck{app: app}, + } +} + +func (app *Application) featureCommands() []tool.Application { + return []tool.Application{ + &callHierarchy{app: app}, + &check{app: app}, + &codeaction{app: app}, + &codelens{app: app}, + &definition{app: app}, + &execute{app: app}, + &fix{app: app}, // (non-functional) + &foldingRanges{app: app}, + &format{app: app}, + &highlight{app: app}, + &implementation{app: app}, + &imports{app: app}, + newRemote(app, ""), + newRemote(app, "inspect"), + &links{app: app}, + &prepareRename{app: app}, + &references{app: app}, + &rename{app: app}, + &semtok{app: app}, + &signature{app: app}, + &stats{app: app}, + &symbols{app: app}, + + &workspaceSymbol{app: app}, + } +} + +var ( + internalMu sync.Mutex + internalConnections = make(map[string]*connection) +) + +// connect creates and initializes a new in-process gopls session. +func (app *Application) connect(ctx context.Context) (*connection, error) { + client := newClient(app) + var svr protocol.Server + if app.Remote == "" { + // local + options := settings.DefaultOptions(app.options) + svr = server.New(cache.NewSession(ctx, cache.New(nil)), client, options) + ctx = protocol.WithClient(ctx, client) + + } else { + // remote + netConn, err := lsprpc.ConnectToRemote(ctx, app.Remote) + if err != nil { + return nil, err + } + stream := jsonrpc2.NewHeaderStream(netConn) + jsonConn := jsonrpc2.NewConn(stream) + svr = protocol.ServerDispatcher(jsonConn) + ctx = protocol.WithClient(ctx, client) + jsonConn.Go(ctx, + protocol.Handlers( + protocol.ClientHandler(client, jsonrpc2.MethodNotFound))) + } + conn := newConnection(svr, client) + return conn, conn.initialize(ctx, app.options) +} + +func (c *connection) initialize(ctx context.Context, options func(*settings.Options)) error { + wd, err := os.Getwd() + if err != nil { + return fmt.Errorf("finding workdir: %v", err) + } + params := &protocol.ParamInitialize{} + params.RootURI = protocol.URIFromPath(wd) + params.Capabilities.Workspace.Configuration = true + + // Make sure to respect configured options when sending initialize request. + opts := settings.DefaultOptions(options) + // If you add an additional option here, you must update the map key in connect. + params.Capabilities.TextDocument.Hover = &protocol.HoverClientCapabilities{ + ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat}, + } + params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = opts.HierarchicalDocumentSymbolSupport + params.Capabilities.TextDocument.SemanticTokens = protocol.SemanticTokensClientCapabilities{} + params.Capabilities.TextDocument.SemanticTokens.Formats = []protocol.TokenFormat{"relative"} + params.Capabilities.TextDocument.SemanticTokens.Requests.Range = &protocol.Or_ClientSemanticTokensRequestOptions_range{Value: true} + //params.Capabilities.TextDocument.SemanticTokens.Requests.Range.Value = true + params.Capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true} + params.Capabilities.TextDocument.SemanticTokens.TokenTypes = protocol.SemanticTypes() + params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = protocol.SemanticModifiers() + params.Capabilities.Window.WorkDoneProgress = true + + params.InitializationOptions = map[string]interface{}{ + "symbolMatcher": string(opts.SymbolMatcher), + } + if _, err := c.Server.Initialize(ctx, params); err != nil { + return err + } + if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil { + return err + } + return nil +} + +type connection struct { + protocol.Server + client *cmdClient +} + +// registerProgressHandler registers a handler for progress notifications. +// The caller must call unregister when the handler is no longer needed. +func (cli *cmdClient) registerProgressHandler(handler func(*protocol.ProgressParams)) (token protocol.ProgressToken, unregister func()) { + token = fmt.Sprintf("tok%d", rand.Uint64()) + + // register + cli.progressHandlersMu.Lock() + if cli.progressHandlers == nil { + cli.progressHandlers = make(map[protocol.ProgressToken]func(*protocol.ProgressParams)) + } + cli.progressHandlers[token] = handler + cli.progressHandlersMu.Unlock() + + unregister = func() { + cli.progressHandlersMu.Lock() + delete(cli.progressHandlers, token) + cli.progressHandlersMu.Unlock() + } + return token, unregister +} + +// cmdClient defines the protocol.Client interface behavior of the gopls CLI tool. +type cmdClient struct { + app *Application + + progressHandlersMu sync.Mutex + progressHandlers map[protocol.ProgressToken]func(*protocol.ProgressParams) + iwlToken protocol.ProgressToken + iwlDone chan struct{} + + filesMu sync.Mutex // guards files map + files map[protocol.DocumentURI]*cmdFile +} + +type cmdFile struct { + uri protocol.DocumentURI + mapper *protocol.Mapper + err error + diagnosticsMu sync.Mutex + diagnostics []protocol.Diagnostic +} + +func newClient(app *Application) *cmdClient { + return &cmdClient{ + app: app, + files: make(map[protocol.DocumentURI]*cmdFile), + iwlDone: make(chan struct{}), + } +} + +func newConnection(server protocol.Server, client *cmdClient) *connection { + return &connection{ + Server: server, + client: client, + } +} + +func (c *cmdClient) CodeLensRefresh(context.Context) error { return nil } + +func (c *cmdClient) FoldingRangeRefresh(context.Context) error { return nil } + +func (c *cmdClient) LogTrace(context.Context, *protocol.LogTraceParams) error { return nil } + +func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { + fmt.Fprintf(os.Stderr, "%s: %s\n", p.Type, p.Message) + return nil +} + +func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) { + return nil, nil +} + +func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error { + // This logic causes server logging to be double-prefixed with a timestamp. + // 2023/11/08 10:50:21 Error:2023/11/08 10:50:21 + // TODO(adonovan): print just p.Message, plus a newline if needed? + switch p.Type { + case protocol.Error: + log.Print("Error:", p.Message) + case protocol.Warning: + log.Print("Warning:", p.Message) + case protocol.Info: + if c.app.verbose() { + log.Print("Info:", p.Message) + } + case protocol.Log: + if c.app.verbose() { + log.Print("Log:", p.Message) + } + default: + if c.app.verbose() { + log.Print(p.Message) + } + } + return nil +} + +func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil } + +func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error { + return nil +} + +func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error { + return nil +} + +func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) { + return nil, nil +} + +func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) { + results := make([]interface{}, len(p.Items)) + for i, item := range p.Items { + if item.Section != "gopls" { + continue + } + m := map[string]interface{}{ + "analyses": map[string]any{ + "fillreturns": true, + "nonewvars": true, + "noresultvalues": true, + "undeclaredname": true, + }, + } + if c.app.VeryVerbose { + m["verboseOutput"] = true + } + results[i] = m + } + return results, nil +} + +func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) { + if err := c.applyWorkspaceEdit(&p.Edit); err != nil { + return &protocol.ApplyWorkspaceEditResult{FailureReason: err.Error()}, nil + } + return &protocol.ApplyWorkspaceEditResult{Applied: true}, nil +} + +// applyWorkspaceEdit applies a complete WorkspaceEdit to the client's +// files, honoring the preferred edit mode specified by cli.app.editMode. +// (Used by rename and by ApplyEdit downcalls.) +// +// See also: +// - changedFiles in ../test/marker/marker_test.go for the golden-file capturing variant +// - applyWorkspaceEdit in ../test/integration/fake/editor.go for the Editor variant +func (cli *cmdClient) applyWorkspaceEdit(wsedit *protocol.WorkspaceEdit) error { + + create := func(uri protocol.DocumentURI, content []byte) error { + edits := []diff.Edit{{Start: 0, End: 0, New: string(content)}} + return updateFile(uri.Path(), nil, content, edits, cli.app.editFlags) + } + + delete := func(uri protocol.DocumentURI, content []byte) error { + edits := []diff.Edit{{Start: 0, End: len(content), New: ""}} + return updateFile(uri.Path(), content, nil, edits, cli.app.editFlags) + } + + for _, c := range wsedit.DocumentChanges { + switch { + case c.TextDocumentEdit != nil: + f := cli.openFile(c.TextDocumentEdit.TextDocument.URI) + if f.err != nil { + return f.err + } + // TODO(adonovan): sanity-check c.TextDocumentEdit.TextDocument.Version + edits := protocol.AsTextEdits(c.TextDocumentEdit.Edits) + if err := applyTextEdits(f.mapper, edits, cli.app.editFlags); err != nil { + return err + } + + case c.CreateFile != nil: + if err := create(c.CreateFile.URI, []byte{}); err != nil { + return err + } + + case c.RenameFile != nil: + // Analyze as creation + deletion. (NB: loses file mode.) + f := cli.openFile(c.RenameFile.OldURI) + if f.err != nil { + return f.err + } + if err := create(c.RenameFile.NewURI, f.mapper.Content); err != nil { + return err + } + if err := delete(f.mapper.URI, f.mapper.Content); err != nil { + return err + } + + case c.DeleteFile != nil: + f := cli.openFile(c.DeleteFile.URI) + if f.err != nil { + return f.err + } + if err := delete(f.mapper.URI, f.mapper.Content); err != nil { + return err + } + + default: + return fmt.Errorf("unknown DocumentChange: %#v", c) + } + } + return nil +} + +// applyTextEdits applies a list of edits to the mapper file content, +// using the preferred edit mode. It is a no-op if there are no edits. +func applyTextEdits(mapper *protocol.Mapper, edits []protocol.TextEdit, flags *EditFlags) error { + if len(edits) == 0 { + return nil + } + newContent, diffEdits, err := protocol.ApplyEdits(mapper, edits) + if err != nil { + return err + } + return updateFile(mapper.URI.Path(), mapper.Content, newContent, diffEdits, flags) +} + +// updateFile performs a content update operation on the specified file. +// If the old content is nil, the operation creates the file. +// If the new content is nil, the operation deletes the file. +// The flags control whether the operation is written, or merely listed, diffed, or printed. +func updateFile(filename string, old, new []byte, edits []diff.Edit, flags *EditFlags) error { + if flags.List { + fmt.Println(filename) + } + + if flags.Write { + if flags.Preserve && old != nil { // edit or delete + if err := os.WriteFile(filename+".orig", old, 0666); err != nil { + return err + } + } + + if new != nil { + // create or edit + if err := os.WriteFile(filename, new, 0666); err != nil { + return err + } + } else { + // delete + if err := os.Remove(filename); err != nil { + return err + } + } + } + + if flags.Diff { + // For diffing, creations and deletions are equivalent + // updating an empty file and making an existing file empty. + unified, err := diff.ToUnified(filename+".orig", filename, string(old), edits, diff.DefaultContextLines) + if err != nil { + return err + } + fmt.Print(unified) + } + + // No flags: just print edited file content. + // + // This makes no sense for multiple files. + // (We should probably change the default to -diff.) + if !(flags.List || flags.Write || flags.Diff) { + os.Stdout.Write(new) + } + + return nil +} + +func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error { + // Don't worry about diagnostics without versions. + if p.Version == 0 { + return nil + } + + c.filesMu.Lock() + file := c.getFile(p.URI) + c.filesMu.Unlock() + + file.diagnosticsMu.Lock() + defer file.diagnosticsMu.Unlock() + file.diagnostics = append(file.diagnostics, p.Diagnostics...) + + // Perform a crude in-place deduplication. + // TODO(golang/go#60122): replace the gopls.diagnose_files + // command with support for textDocument/diagnostic, + // so that we don't need to do this de-duplication. + type key [6]interface{} + seen := make(map[key]bool) + out := file.diagnostics[:0] + for _, d := range file.diagnostics { + var codeHref string + if desc := d.CodeDescription; desc != nil { + codeHref = desc.Href + } + k := key{d.Range, d.Severity, d.Code, codeHref, d.Source, d.Message} + if !seen[k] { + seen[k] = true + out = append(out, d) + } + } + file.diagnostics = out + + return nil +} + +func (c *cmdClient) Progress(_ context.Context, params *protocol.ProgressParams) error { + token, ok := params.Token.(string) + if !ok { + return fmt.Errorf("unexpected progress token: %[1]T %[1]v", params.Token) + } + + c.progressHandlersMu.Lock() + handler := c.progressHandlers[token] + c.progressHandlersMu.Unlock() + if handler == nil { + handler = c.defaultProgressHandler + } + handler(params) + return nil +} + +// defaultProgressHandler is the default handler of progress messages, +// used during the initialize request. +func (c *cmdClient) defaultProgressHandler(params *protocol.ProgressParams) { + switch v := params.Value.(type) { + case *protocol.WorkDoneProgressBegin: + if v.Title == server.DiagnosticWorkTitle(server.FromInitialWorkspaceLoad) { + c.progressHandlersMu.Lock() + c.iwlToken = params.Token + c.progressHandlersMu.Unlock() + } + + case *protocol.WorkDoneProgressEnd: + c.progressHandlersMu.Lock() + iwlToken := c.iwlToken + c.progressHandlersMu.Unlock() + + if params.Token == iwlToken { + close(c.iwlDone) + } + } +} + +func (c *cmdClient) ShowDocument(ctx context.Context, params *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) { + var success bool + if params.External { + // Open URI in external browser. + success = browser.Open(params.URI) + } else { + // Open file in editor, optionally taking focus and selecting a range. + // (cmdClient has no editor. Should it fork+exec $EDITOR?) + log.Printf("Server requested that client editor open %q (takeFocus=%t, selection=%+v)", + params.URI, params.TakeFocus, params.Selection) + success = true + } + return &protocol.ShowDocumentResult{Success: success}, nil +} + +func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error { + return nil +} + +func (c *cmdClient) DiagnosticRefresh(context.Context) error { + return nil +} + +func (c *cmdClient) InlayHintRefresh(context.Context) error { + return nil +} + +func (c *cmdClient) SemanticTokensRefresh(context.Context) error { + return nil +} + +func (c *cmdClient) InlineValueRefresh(context.Context) error { + return nil +} + +func (c *cmdClient) getFile(uri protocol.DocumentURI) *cmdFile { + file, found := c.files[uri] + if !found || file.err != nil { + file = &cmdFile{ + uri: uri, + } + c.files[uri] = file + } + if file.mapper == nil { + content, err := os.ReadFile(uri.Path()) + if err != nil { + file.err = fmt.Errorf("getFile: %v: %v", uri, err) + return file + } + file.mapper = protocol.NewMapper(uri, content) + } + return file +} + +func (c *cmdClient) openFile(uri protocol.DocumentURI) *cmdFile { + c.filesMu.Lock() + defer c.filesMu.Unlock() + return c.getFile(uri) +} + +// TODO(adonovan): provide convenience helpers to: +// - map a (URI, protocol.Range) to a MappedRange; +// - parse a command-line argument to a MappedRange. +func (c *connection) openFile(ctx context.Context, uri protocol.DocumentURI) (*cmdFile, error) { + file := c.client.openFile(uri) + if file.err != nil { + return nil, file.err + } + + p := &protocol.DidOpenTextDocumentParams{ + TextDocument: protocol.TextDocumentItem{ + URI: uri, + LanguageID: "go", + Version: 1, + Text: string(file.mapper.Content), + }, + } + if err := c.Server.DidOpen(ctx, p); err != nil { + // TODO(adonovan): is this assignment concurrency safe? + file.err = fmt.Errorf("%v: %v", uri, err) + return nil, file.err + } + return file, nil +} + +func (c *connection) semanticTokens(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) { + // use range to avoid limits on full + resp, err := c.Server.SemanticTokensRange(ctx, p) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *connection) diagnoseFiles(ctx context.Context, files []protocol.DocumentURI) error { + cmd := command.NewDiagnoseFilesCommand("Diagnose files", command.DiagnoseFilesArgs{ + Files: files, + }) + _, err := c.executeCommand(ctx, cmd) + return err +} + +func (c *connection) terminate(ctx context.Context) { + //TODO: do we need to handle errors on these calls? + c.Shutdown(ctx) + //TODO: right now calling exit terminates the process, we should rethink that + //server.Exit(ctx) +} + +// Implement io.Closer. +func (c *cmdClient) Close() error { + return nil +} + +// -- conversions to span (UTF-8) domain -- + +// locationSpan converts a protocol (UTF-16) Location to a (UTF-8) span. +// Precondition: the URIs of Location and Mapper match. +func (f *cmdFile) locationSpan(loc protocol.Location) (span, error) { + // TODO(adonovan): check that l.URI matches m.URI. + return f.rangeSpan(loc.Range) +} + +// rangeSpan converts a protocol (UTF-16) range to a (UTF-8) span. +// The resulting span has valid Positions and Offsets. +func (f *cmdFile) rangeSpan(r protocol.Range) (span, error) { + start, end, err := f.mapper.RangeOffsets(r) + if err != nil { + return span{}, err + } + return f.offsetSpan(start, end) +} + +// offsetSpan converts a byte-offset interval to a (UTF-8) span. +// The resulting span contains line, column, and offset information. +func (f *cmdFile) offsetSpan(start, end int) (span, error) { + if start > end { + return span{}, fmt.Errorf("start offset (%d) > end (%d)", start, end) + } + startPoint, err := offsetPoint(f.mapper, start) + if err != nil { + return span{}, fmt.Errorf("start: %v", err) + } + endPoint, err := offsetPoint(f.mapper, end) + if err != nil { + return span{}, fmt.Errorf("end: %v", err) + } + return newSpan(f.mapper.URI, startPoint, endPoint), nil +} + +// offsetPoint converts a byte offset to a span (UTF-8) point. +// The resulting point contains line, column, and offset information. +func offsetPoint(m *protocol.Mapper, offset int) (point, error) { + if !(0 <= offset && offset <= len(m.Content)) { + return point{}, fmt.Errorf("invalid offset %d (want 0-%d)", offset, len(m.Content)) + } + line, col8 := m.OffsetLineCol8(offset) + return newPoint(line, col8, offset), nil +} + +// -- conversions from span (UTF-8) domain -- + +// spanLocation converts a (UTF-8) span to a protocol (UTF-16) range. +// Precondition: the URIs of spanLocation and Mapper match. +func (f *cmdFile) spanLocation(s span) (protocol.Location, error) { + rng, err := f.spanRange(s) + if err != nil { + return protocol.Location{}, err + } + return f.mapper.RangeLocation(rng), nil +} + +// spanRange converts a (UTF-8) span to a protocol (UTF-16) range. +// Precondition: the URIs of span and Mapper match. +func (f *cmdFile) spanRange(s span) (protocol.Range, error) { + // Assert that we aren't using the wrong mapper. + // We check only the base name, and case insensitively, + // because we can't assume clean paths, no symbolic links, + // case-sensitive directories. The authoritative answer + // requires querying the file system, and we don't want + // to do that. + if !strings.EqualFold(filepath.Base(string(f.mapper.URI)), filepath.Base(string(s.URI()))) { + return protocol.Range{}, bugpkg.Errorf("mapper is for file %q instead of %q", f.mapper.URI, s.URI()) + } + start, err := pointPosition(f.mapper, s.Start()) + if err != nil { + return protocol.Range{}, fmt.Errorf("start: %w", err) + } + end, err := pointPosition(f.mapper, s.End()) + if err != nil { + return protocol.Range{}, fmt.Errorf("end: %w", err) + } + return protocol.Range{Start: start, End: end}, nil +} + +// pointPosition converts a valid span (UTF-8) point to a protocol (UTF-16) position. +func pointPosition(m *protocol.Mapper, p point) (protocol.Position, error) { + if p.HasPosition() { + return m.LineCol8Position(p.Line(), p.Column()) + } + if p.HasOffset() { + return m.OffsetPosition(p.Offset()) + } + return protocol.Position{}, fmt.Errorf("point has neither offset nor line/column") +} + +// TODO(adonovan): delete in 2025. +type fix struct{ app *Application } + +func (*fix) Name() string { return "fix" } +func (cmd *fix) Parent() string { return cmd.app.Name() } +func (*fix) Usage() string { return "" } +func (*fix) ShortHelp() string { return "apply suggested fixes (obsolete)" } +func (*fix) DetailedHelp(flags *flag.FlagSet) { + fmt.Fprintf(flags.Output(), `No longer supported; use "gopls codeaction" instead.`) +} +func (*fix) Run(ctx context.Context, args ...string) error { + return tool.CommandLineErrorf(`no longer supported; use "gopls codeaction" instead`) +} diff --git a/contribs/gnopls/internal/cmd/codeaction.go b/contribs/gnopls/internal/cmd/codeaction.go new file mode 100644 index 00000000000..84d7d181b88 --- /dev/null +++ b/contribs/gnopls/internal/cmd/codeaction.go @@ -0,0 +1,224 @@ +// 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 cmd + +import ( + "context" + "flag" + "fmt" + "regexp" + "slices" + "strings" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// codeaction implements the codeaction verb for gopls. +type codeaction struct { + EditFlags + Kind string `flag:"kind" help:"comma-separated list of code action kinds to filter"` + Title string `flag:"title" help:"regular expression to match title"` + Exec bool `flag:"exec" help:"execute the first matching code action"` + + app *Application +} + +func (cmd *codeaction) Name() string { return "codeaction" } +func (cmd *codeaction) Parent() string { return cmd.app.Name() } +func (cmd *codeaction) Usage() string { return "[codeaction-flags] filename[:line[:col]]" } +func (cmd *codeaction) ShortHelp() string { return "list or execute code actions" } +func (cmd *codeaction) DetailedHelp(f *flag.FlagSet) { + fmt.Fprintf(f.Output(), ` + +The codeaction command lists or executes code actions for the +specified file or range of a file. Each code action contains +either an edit to be directly applied to the file, or a command +to be executed by the server, which may have an effect such as: +- requesting that the client apply an edit; +- changing the state of the server; or +- requesting that the client open a document. + +The -kind and and -title flags filter the list of actions. + +The -kind flag specifies a comma-separated list of LSP CodeAction kinds. +Only actions of these kinds will be requested from the server. +Valid kinds include: + + gopls.doc.features + quickfix + refactor + refactor.extract + refactor.extract.function + refactor.extract.method + refactor.extract.toNewFile + refactor.extract.variable + refactor.inline + refactor.inline.call + refactor.rewrite + refactor.rewrite.changeQuote + refactor.rewrite.fillStruct + refactor.rewrite.fillSwitch + refactor.rewrite.invertIf + refactor.rewrite.joinLines + refactor.rewrite.removeUnusedParam + refactor.rewrite.splitLines + source + source.assembly + source.doc + source.fixAll + source.freesymbols + source.organizeImports + source.test + +Kinds are hierarchical, so "refactor" includes "refactor.inline". +(Note: actions of kind "source.test" are not returned unless explicitly +requested.) + +The -title flag specifies a regular expression that must match the +action's title. (Ideally kinds would be specific enough that this +isn't necessary; we really need to subdivide refactor.rewrite; see +gopls/internal/settings/codeactionkind.go.) + +The -exec flag causes the first matching code action to be executed. +Without the flag, the matching actions are merely listed. + +It is not currently possible to execute more than one action, +as that requires a way to detect and resolve conflicts. +TODO(adonovan): support it when golang/go#67049 is resolved. + +If executing an action causes the server to send a patch to the +client, the usual -write, -preserve, -diff, and -list flags govern how +the client deals with the patch. + +Example: execute the first "quick fix" in the specified file and show the diff: + + $ gopls codeaction -kind=quickfix -exec -diff ./gopls/main.go + +codeaction-flags: +`) + printFlagDefaults(f) +} + +func (cmd *codeaction) Run(ctx context.Context, args ...string) error { + if len(args) < 1 { + return tool.CommandLineErrorf("codeaction expects at least 1 argument") + } + cmd.app.editFlags = &cmd.EditFlags + conn, err := cmd.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + uri := from.URI() + file, err := conn.openFile(ctx, uri) + if err != nil { + return err + } + rng, err := file.spanRange(from) + if err != nil { + return err + } + + titleRE, err := regexp.Compile(cmd.Title) + if err != nil { + return err + } + + // Get diagnostics, as they may encode various lazy code actions. + if err := conn.diagnoseFiles(ctx, []protocol.DocumentURI{uri}); err != nil { + return err + } + diagnostics := []protocol.Diagnostic{} // LSP wants non-nil slice + conn.client.filesMu.Lock() + diagnostics = append(diagnostics, file.diagnostics...) + conn.client.filesMu.Unlock() + + // Request code actions of the desired kinds. + var kinds []protocol.CodeActionKind + if cmd.Kind != "" { + for _, kind := range strings.Split(cmd.Kind, ",") { + kinds = append(kinds, protocol.CodeActionKind(kind)) + } + } + actions, err := conn.CodeAction(ctx, &protocol.CodeActionParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: uri}, + Range: rng, + Context: protocol.CodeActionContext{ + Only: kinds, + Diagnostics: diagnostics, + }, + }) + if err != nil { + return fmt.Errorf("%v: %v", from, err) + } + + // Gather edits from matching code actions. + var edits []protocol.TextEdit + for _, act := range actions { + if act.Disabled != nil { + continue + } + if !titleRE.MatchString(act.Title) { + continue + } + + // If the provided span has a position (not just offsets), + // and the action has diagnostics, the action must have a + // diagnostic with the same range as it. + if from.HasPosition() && len(act.Diagnostics) > 0 && + !slices.ContainsFunc(act.Diagnostics, func(diag protocol.Diagnostic) bool { + return diag.Range.Start == rng.Start + }) { + continue + } + + if cmd.Exec { + // -exec: run the first matching code action. + if act.Command != nil { + // This may cause the server to make + // an ApplyEdit downcall to the client. + if _, err := conn.executeCommand(ctx, act.Command); err != nil { + return err + } + // The specification says that commands should + // be executed _after_ edits are applied, not + // instead of them, but we don't want to + // duplicate edits. + } else { + // Partially apply CodeAction.Edit, a WorkspaceEdit. + // (See also conn.Client.applyWorkspaceEdit(a.Edit)). + for _, c := range act.Edit.DocumentChanges { + tde := c.TextDocumentEdit + if tde != nil && tde.TextDocument.URI == uri { + // TODO(adonovan): this logic will butcher an edit that spans files. + // It will also ignore create/delete/rename operations. + // Fix or document. Need a three-way merge. + edits = append(edits, protocol.AsTextEdits(tde.Edits)...) + } + } + return applyTextEdits(file.mapper, edits, cmd.app.editFlags) + } + return nil + } else { + // No -exec: list matching code actions. + action := "edit" + if act.Command != nil { + action = "command" + } + fmt.Printf("%s\t%q [%s]\n", + action, + act.Title, + act.Kind) + } + } + + if cmd.Exec { + return fmt.Errorf("no matching code action at %s", from) + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/codelens.go b/contribs/gnopls/internal/cmd/codelens.go new file mode 100644 index 00000000000..452e978094f --- /dev/null +++ b/contribs/gnopls/internal/cmd/codelens.go @@ -0,0 +1,137 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/tool" +) + +// codelens implements the codelens verb for gopls. +type codelens struct { + EditFlags + app *Application + + Exec bool `flag:"exec" help:"execute the first matching code lens"` +} + +func (r *codelens) Name() string { return "codelens" } +func (r *codelens) Parent() string { return r.app.Name() } +func (r *codelens) Usage() string { return "[codelens-flags] file[:line[:col]] [title]" } +func (r *codelens) ShortHelp() string { return "List or execute code lenses for a file" } +func (r *codelens) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +The codelens command lists or executes code lenses for the specified +file, or line within a file. A code lens is a command associated with +a position in the code. + +With an optional title argment, only code lenses matching that +title are considered. + +By default, the codelens command lists the available lenses for the +specified file or line within a file, including the title and +title of the command. With the -exec flag, the first matching command +is executed, and its output is printed to stdout. + +Example: + + $ gopls codelens a_test.go # list code lenses in a file + $ gopls codelens a_test.go:10 # list code lenses on line 10 + $ gopls codelens a_test.go gopls.test # list gopls.test commands + $ gopls codelens -run a_test.go:10 gopls.test # run a specific test + +codelens-flags: +`) + printFlagDefaults(f) +} + +func (r *codelens) Run(ctx context.Context, args ...string) error { + var filename, title string + switch len(args) { + case 0: + return tool.CommandLineErrorf("codelens requires a file name") + case 2: + title = args[1] + fallthrough + case 1: + filename = args[0] + default: + return tool.CommandLineErrorf("codelens expects at most two arguments") + } + + r.app.editFlags = &r.EditFlags // in case a codelens perform an edit + + // Override the default setting for codelenses["test"], which is + // off by default because VS Code has a superior client-side + // implementation. But this client is not VS Code. + // See golang.LensFuncs(). + origOptions := r.app.options + r.app.options = func(opts *settings.Options) { + if origOptions != nil { + origOptions(opts) + } + if opts.Codelenses == nil { + opts.Codelenses = make(map[settings.CodeLensSource]bool) + } + opts.Codelenses[settings.CodeLensTest] = true + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + filespan := parseSpan(filename) + file, err := conn.openFile(ctx, filespan.URI()) + if err != nil { + return err + } + loc, err := file.spanLocation(filespan) + if err != nil { + return err + } + + p := protocol.CodeLensParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + } + lenses, err := conn.CodeLens(ctx, &p) + if err != nil { + return err + } + + for _, lens := range lenses { + sp, err := file.rangeSpan(lens.Range) + if err != nil { + return nil + } + + if title != "" && lens.Command.Title != title { + continue // title was specified but does not match + } + if filespan.HasPosition() && !protocol.Intersect(loc.Range, lens.Range) { + continue // position was specified but does not match + } + + // -exec: run the first matching code lens. + if r.Exec { + _, err := conn.executeCommand(ctx, lens.Command) + return err + } + + // No -exec: list matching code lenses. + fmt.Printf("%v: %q [%s]\n", sp, lens.Command.Title, lens.Command.Command) + } + + if r.Exec { + return fmt.Errorf("no code lens at %s with title %q", filespan, title) + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/definition.go b/contribs/gnopls/internal/cmd/definition.go new file mode 100644 index 00000000000..d9cd98554e3 --- /dev/null +++ b/contribs/gnopls/internal/cmd/definition.go @@ -0,0 +1,137 @@ +// 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. + +package cmd + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/tool" +) + +// A Definition is the result of a 'definition' query. +type Definition struct { + Span span `json:"span"` // span of the definition + Description string `json:"description"` // description of the denoted object +} + +// These constant is printed in the help, and then used in a test to verify the +// help is still valid. +// They refer to "Set" in "flag.FlagSet" from the DetailedHelp method below. +const ( + exampleLine = 44 + exampleColumn = 47 + exampleOffset = 1270 +) + +// definition implements the definition verb for gopls. +type definition struct { + app *Application + + JSON bool `flag:"json" help:"emit output in JSON format"` + MarkdownSupported bool `flag:"markdown" help:"support markdown in responses"` +} + +func (d *definition) Name() string { return "definition" } +func (d *definition) Parent() string { return d.app.Name() } +func (d *definition) Usage() string { return "[definition-flags] " } +func (d *definition) ShortHelp() string { return "show declaration of selected identifier" } +func (d *definition) DetailedHelp(f *flag.FlagSet) { + fmt.Fprintf(f.Output(), ` +Example: show the definition of the identifier at syntax at offset %[1]v in this file (flag.FlagSet): + + $ gopls definition internal/cmd/definition.go:%[1]v:%[2]v + $ gopls definition internal/cmd/definition.go:#%[3]v + +definition-flags: +`, exampleLine, exampleColumn, exampleOffset) + printFlagDefaults(f) +} + +// Run performs the definition query as specified by args and prints the +// results to stdout. +func (d *definition) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("definition expects 1 argument") + } + // Plaintext makes more sense for the command line. + opts := d.app.options + d.app.options = func(o *settings.Options) { + if opts != nil { + opts(o) + } + o.PreferredContentFormat = protocol.PlainText + if d.MarkdownSupported { + o.PreferredContentFormat = protocol.Markdown + } + } + conn, err := d.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + from := parseSpan(args[0]) + file, err := conn.openFile(ctx, from.URI()) + if err != nil { + return err + } + loc, err := file.spanLocation(from) + if err != nil { + return err + } + p := protocol.DefinitionParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + locs, err := conn.Definition(ctx, &p) + if err != nil { + return fmt.Errorf("%v: %v", from, err) + } + + if len(locs) == 0 { + return fmt.Errorf("%v: not an identifier", from) + } + file, err = conn.openFile(ctx, locs[0].URI) + if err != nil { + return fmt.Errorf("%v: %v", from, err) + } + definition, err := file.locationSpan(locs[0]) + if err != nil { + return fmt.Errorf("%v: %v", from, err) + } + + q := protocol.HoverParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + hover, err := conn.Hover(ctx, &q) + if err != nil { + return fmt.Errorf("%v: %v", from, err) + } + var description string + if hover != nil { + description = strings.TrimSpace(hover.Contents.Value) + } + + result := &Definition{ + Span: definition, + Description: description, + } + if d.JSON { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", "\t") + return enc.Encode(result) + } + fmt.Printf("%v", result.Span) + if len(result.Description) > 0 { + fmt.Printf(": defined here as %s", result.Description) + } + fmt.Printf("\n") + return nil +} diff --git a/contribs/gnopls/internal/cmd/execute.go b/contribs/gnopls/internal/cmd/execute.go new file mode 100644 index 00000000000..96b3cf3b81d --- /dev/null +++ b/contribs/gnopls/internal/cmd/execute.go @@ -0,0 +1,135 @@ +// 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 cmd + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "log" + "os" + "slices" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/server" + "golang.org/x/tools/internal/tool" +) + +// execute implements the LSP ExecuteCommand verb for gopls. +type execute struct { + EditFlags + app *Application +} + +func (e *execute) Name() string { return "execute" } +func (e *execute) Parent() string { return e.app.Name() } +func (e *execute) Usage() string { return "[flags] command argument..." } +func (e *execute) ShortHelp() string { return "Execute a gopls custom LSP command" } +func (e *execute) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +The execute command sends an LSP ExecuteCommand request to gopls, +with a set of optional JSON argument values. +Some commands return a result, also JSON. + +Gopls' command set is defined by the command.Interface type; see +https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol/command#Interface. +It is not a stable interface: commands may change or disappear without notice. + +Examples: + + $ gopls execute gopls.add_import '{"ImportPath": "fmt", "URI": "file:///hello.go"}' + $ gopls execute gopls.run_tests '{"URI": "file:///a_test.go", "Tests": ["Test"]}' + $ gopls execute gopls.list_known_packages '{"URI": "file:///hello.go"}' + +execute-flags: +`) + printFlagDefaults(f) +} + +func (e *execute) Run(ctx context.Context, args ...string) error { + if len(args) == 0 { + return tool.CommandLineErrorf("execute requires a command name") + } + cmd := args[0] + if !slices.Contains(command.Commands, command.Command(cmd)) { + return tool.CommandLineErrorf("unrecognized command: %s", cmd) + } + + // A command may have multiple arguments, though the only one + // that currently does so is the "legacy" gopls.test, + // so we don't show an example of it. + var jsonArgs []json.RawMessage + for i, arg := range args[1:] { + var dummy any + if err := json.Unmarshal([]byte(arg), &dummy); err != nil { + return fmt.Errorf("argument %d is not valid JSON: %v", i+1, err) + } + jsonArgs = append(jsonArgs, json.RawMessage(arg)) + } + + e.app.editFlags = &e.EditFlags // in case command performs an edit + + conn, err := e.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + res, err := conn.executeCommand(ctx, &protocol.Command{ + Command: cmd, + Arguments: jsonArgs, + }) + if err != nil { + return err + } + if res != nil { + data, err := json.MarshalIndent(res, "", "\t") + if err != nil { + log.Fatal(err) + } + fmt.Printf("%s\n", data) + } + return nil +} + +// executeCommand executes a protocol.Command, displaying progress +// messages and awaiting completion of asynchronous commands. +func (conn *connection) executeCommand(ctx context.Context, cmd *protocol.Command) (any, error) { + endStatus := make(chan string, 1) + token, unregister := conn.client.registerProgressHandler(func(params *protocol.ProgressParams) { + switch v := params.Value.(type) { + case *protocol.WorkDoneProgressReport: + fmt.Fprintln(os.Stderr, v.Message) // combined std{out,err} + + case *protocol.WorkDoneProgressEnd: + endStatus <- v.Message // = canceled | failed | completed + } + }) + defer unregister() + + res, err := conn.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{ + Command: cmd.Command, + Arguments: cmd.Arguments, + WorkDoneProgressParams: protocol.WorkDoneProgressParams{ + WorkDoneToken: token, + }, + }) + if err != nil { + return nil, err + } + + // Some commands are asynchronous, so clients + // must wait for the "end" progress notification. + if command.Command(cmd.Command).IsAsync() { + status := <-endStatus + if status != server.CommandCompleted { + return nil, fmt.Errorf("command %s", status) + } + } + + return res, nil +} diff --git a/contribs/gnopls/internal/cmd/folding_range.go b/contribs/gnopls/internal/cmd/folding_range.go new file mode 100644 index 00000000000..f48feee5b2c --- /dev/null +++ b/contribs/gnopls/internal/cmd/folding_range.go @@ -0,0 +1,71 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// foldingRanges implements the folding_ranges verb for gopls +type foldingRanges struct { + app *Application +} + +func (r *foldingRanges) Name() string { return "folding_ranges" } +func (r *foldingRanges) Parent() string { return r.app.Name() } +func (r *foldingRanges) Usage() string { return "" } +func (r *foldingRanges) ShortHelp() string { return "display selected file's folding ranges" } +func (r *foldingRanges) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ gopls folding_ranges helper/helper.go +`) + printFlagDefaults(f) +} + +func (r *foldingRanges) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("folding_ranges expects 1 argument (file)") + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + if _, err := conn.openFile(ctx, from.URI()); err != nil { + return err + } + + p := protocol.FoldingRangeParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: from.URI(), + }, + } + + ranges, err := conn.FoldingRange(ctx, &p) + if err != nil { + return err + } + + for _, r := range ranges { + fmt.Printf("%v:%v-%v:%v\n", + r.StartLine+1, + r.StartCharacter+1, + r.EndLine+1, + r.EndCharacter+1, + ) + } + + return nil +} diff --git a/contribs/gnopls/internal/cmd/format.go b/contribs/gnopls/internal/cmd/format.go new file mode 100644 index 00000000000..eb68d73d527 --- /dev/null +++ b/contribs/gnopls/internal/cmd/format.go @@ -0,0 +1,75 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// format implements the format verb for gopls. +type format struct { + EditFlags + app *Application +} + +func (c *format) Name() string { return "format" } +func (c *format) Parent() string { return c.app.Name() } +func (c *format) Usage() string { return "[format-flags] " } +func (c *format) ShortHelp() string { return "format the code according to the go standard" } +func (c *format) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +The arguments supplied may be simple file names, or ranges within files. + +Example: reformat this file: + + $ gopls format -w internal/cmd/check.go + +format-flags: +`) + printFlagDefaults(f) +} + +// Run performs the check on the files specified by args and prints the +// results to stdout. +func (c *format) Run(ctx context.Context, args ...string) error { + if len(args) == 0 { + return nil + } + c.app.editFlags = &c.EditFlags + conn, err := c.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + for _, arg := range args { + spn := parseSpan(arg) + file, err := conn.openFile(ctx, spn.URI()) + if err != nil { + return err + } + loc, err := file.spanLocation(spn) + if err != nil { + return err + } + if loc.Range.Start != loc.Range.End { + return fmt.Errorf("only full file formatting supported") + } + p := protocol.DocumentFormattingParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + } + edits, err := conn.Formatting(ctx, &p) + if err != nil { + return fmt.Errorf("%v: %v", spn, err) + } + if err := applyTextEdits(file.mapper, edits, c.app.editFlags); err != nil { + return err + } + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/help_test.go b/contribs/gnopls/internal/cmd/help_test.go new file mode 100644 index 00000000000..74fb07fbe75 --- /dev/null +++ b/contribs/gnopls/internal/cmd/help_test.go @@ -0,0 +1,90 @@ +// 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. + +package cmd_test + +// This file defines tests to ensure the cmd/usage/*.hlp files match +// the output of the tool. The .hlp files are not actually needed by +// the executable (they are not //go:embed-ded, say), but they make it +// easier to review changes to the gopls command's help logic since +// any effects are manifest as changes to these files. + +//go:generate go test -run Help -update-help-files + +import ( + "bytes" + "context" + "flag" + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/cmd" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/internal/tool" +) + +var updateHelpFiles = flag.Bool("update-help-files", false, "Write out the help files instead of checking them") + +const appName = "gopls" + +func TestHelpFiles(t *testing.T) { + testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code. + app := cmd.New() + ctx := context.Background() + for _, page := range append(app.Commands(), app) { + t.Run(page.Name(), func(t *testing.T) { + var buf bytes.Buffer + s := flag.NewFlagSet(page.Name(), flag.ContinueOnError) + s.SetOutput(&buf) + tool.Run(ctx, s, page, []string{"-h"}) + name := page.Name() + if name == appName { + name = "usage" + } + helpFile := filepath.Join("usage", name+".hlp") + got := buf.Bytes() + if *updateHelpFiles { + if err := os.WriteFile(helpFile, got, 0666); err != nil { + t.Errorf("Failed writing %v: %v", helpFile, err) + } + return + } + want, err := os.ReadFile(helpFile) + if err != nil { + t.Fatalf("Missing help file %q", helpFile) + } + if diff := cmp.Diff(string(want), string(got)); diff != "" { + t.Errorf("Help file %q did not match, run with -update-help-files to fix (-want +got)\n%s", helpFile, diff) + } + }) + } +} + +func TestVerboseHelp(t *testing.T) { + testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code. + app := cmd.New() + ctx := context.Background() + var buf bytes.Buffer + s := flag.NewFlagSet(appName, flag.ContinueOnError) + s.SetOutput(&buf) + tool.Run(ctx, s, app, []string{"-v", "-h"}) + got := buf.Bytes() + + helpFile := filepath.Join("usage", "usage-v.hlp") + if *updateHelpFiles { + if err := os.WriteFile(helpFile, got, 0666); err != nil { + t.Errorf("Failed writing %v: %v", helpFile, err) + } + return + } + want, err := os.ReadFile(helpFile) + if err != nil { + t.Fatalf("Missing help file %q", helpFile) + } + if diff := cmp.Diff(string(want), string(got)); diff != "" { + t.Errorf("Help file %q did not match, run with -update-help-files to fix (-want +got)\n%s", helpFile, diff) + } +} diff --git a/contribs/gnopls/internal/cmd/highlight.go b/contribs/gnopls/internal/cmd/highlight.go new file mode 100644 index 00000000000..43af063f53f --- /dev/null +++ b/contribs/gnopls/internal/cmd/highlight.go @@ -0,0 +1,81 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// highlight implements the highlight verb for gopls. +type highlight struct { + app *Application +} + +func (r *highlight) Name() string { return "highlight" } +func (r *highlight) Parent() string { return r.app.Name() } +func (r *highlight) Usage() string { return "" } +func (r *highlight) ShortHelp() string { return "display selected identifier's highlights" } +func (r *highlight) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls highlight helper/helper.go:8:6 + $ gopls highlight helper/helper.go:#53 +`) + printFlagDefaults(f) +} + +func (r *highlight) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("highlight expects 1 argument (position)") + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + file, err := conn.openFile(ctx, from.URI()) + if err != nil { + return err + } + + loc, err := file.spanLocation(from) + if err != nil { + return err + } + + p := protocol.DocumentHighlightParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + highlights, err := conn.DocumentHighlight(ctx, &p) + if err != nil { + return err + } + + var results []span + for _, h := range highlights { + s, err := file.rangeSpan(h.Range) + if err != nil { + return err + } + results = append(results, s) + } + // Sort results to make tests deterministic since DocumentHighlight uses a map. + sortSpans(results) + + for _, s := range results { + fmt.Println(s) + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/implementation.go b/contribs/gnopls/internal/cmd/implementation.go new file mode 100644 index 00000000000..858026540ad --- /dev/null +++ b/contribs/gnopls/internal/cmd/implementation.go @@ -0,0 +1,86 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + "sort" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// implementation implements the implementation verb for gopls +type implementation struct { + app *Application +} + +func (i *implementation) Name() string { return "implementation" } +func (i *implementation) Parent() string { return i.app.Name() } +func (i *implementation) Usage() string { return "" } +func (i *implementation) ShortHelp() string { return "display selected identifier's implementation" } +func (i *implementation) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls implementation helper/helper.go:8:6 + $ gopls implementation helper/helper.go:#53 +`) + printFlagDefaults(f) +} + +func (i *implementation) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("implementation expects 1 argument (position)") + } + + conn, err := i.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + file, err := conn.openFile(ctx, from.URI()) + if err != nil { + return err + } + + loc, err := file.spanLocation(from) + if err != nil { + return err + } + + p := protocol.ImplementationParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + implementations, err := conn.Implementation(ctx, &p) + if err != nil { + return err + } + + var spans []string + for _, impl := range implementations { + f, err := conn.openFile(ctx, impl.URI) + if err != nil { + return err + } + span, err := f.locationSpan(impl) + if err != nil { + return err + } + spans = append(spans, fmt.Sprint(span)) + } + sort.Strings(spans) + + for _, s := range spans { + fmt.Println(s) + } + + return nil +} diff --git a/contribs/gnopls/internal/cmd/imports.go b/contribs/gnopls/internal/cmd/imports.go new file mode 100644 index 00000000000..c64c871e390 --- /dev/null +++ b/contribs/gnopls/internal/cmd/imports.go @@ -0,0 +1,80 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// imports implements the import verb for gopls. +type imports struct { + EditFlags + app *Application +} + +func (t *imports) Name() string { return "imports" } +func (t *imports) Parent() string { return t.app.Name() } +func (t *imports) Usage() string { return "[imports-flags] " } +func (t *imports) ShortHelp() string { return "updates import statements" } +func (t *imports) DetailedHelp(f *flag.FlagSet) { + fmt.Fprintf(f.Output(), ` +Example: update imports statements in a file: + + $ gopls imports -w internal/cmd/check.go + +imports-flags: +`) + printFlagDefaults(f) +} + +// Run performs diagnostic checks on the file specified and either; +// - if -w is specified, updates the file in place; +// - if -d is specified, prints out unified diffs of the changes; or +// - otherwise, prints the new versions to stdout. +func (t *imports) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("imports expects 1 argument") + } + t.app.editFlags = &t.EditFlags + conn, err := t.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + uri := from.URI() + file, err := conn.openFile(ctx, uri) + if err != nil { + return err + } + actions, err := conn.CodeAction(ctx, &protocol.CodeActionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: uri, + }, + }) + if err != nil { + return fmt.Errorf("%v: %v", from, err) + } + var edits []protocol.TextEdit + for _, a := range actions { + if a.Title != "Organize Imports" { + continue + } + for _, c := range a.Edit.DocumentChanges { + // This code action should affect only the specified file; + // it is safe to ignore others. + if c.TextDocumentEdit != nil && c.TextDocumentEdit.TextDocument.URI == uri { + edits = append(edits, protocol.AsTextEdits(c.TextDocumentEdit.Edits)...) + } + } + } + return applyTextEdits(file.mapper, edits, t.app.editFlags) +} diff --git a/contribs/gnopls/internal/cmd/info.go b/contribs/gnopls/internal/cmd/info.go new file mode 100644 index 00000000000..93a66880234 --- /dev/null +++ b/contribs/gnopls/internal/cmd/info.go @@ -0,0 +1,313 @@ +// 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. + +package cmd + +// This file defines the help, bug, version, api-json, licenses commands. + +import ( + "bytes" + "context" + "flag" + "fmt" + "net/url" + "os" + "sort" + "strings" + + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/doc" + "golang.org/x/tools/gopls/internal/filecache" + licensespkg "golang.org/x/tools/gopls/internal/licenses" + "golang.org/x/tools/gopls/internal/util/browser" + goplsbug "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/tool" +) + +// help implements the help command. +type help struct { + app *Application +} + +func (h *help) Name() string { return "help" } +func (h *help) Parent() string { return h.app.Name() } +func (h *help) Usage() string { return "" } +func (h *help) ShortHelp() string { return "print usage information for subcommands" } +func (h *help) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` + +Examples: +$ gopls help # main gopls help message +$ gopls help remote # help on 'remote' command +$ gopls help remote sessions # help on 'remote sessions' subcommand +`) + printFlagDefaults(f) +} + +// Run prints help information about a subcommand. +func (h *help) Run(ctx context.Context, args ...string) error { + find := func(cmds []tool.Application, name string) tool.Application { + for _, cmd := range cmds { + if cmd.Name() == name { + return cmd + } + } + return nil + } + + // Find the subcommand denoted by args (empty => h.app). + var cmd tool.Application = h.app + for i, arg := range args { + cmd = find(getSubcommands(cmd), arg) + if cmd == nil { + return tool.CommandLineErrorf( + "no such subcommand: %s", strings.Join(args[:i+1], " ")) + } + } + + // 'gopls help cmd subcmd' is equivalent to 'gopls cmd subcmd -h'. + // The flag package prints the usage information (defined by tool.Run) + // when it sees the -h flag. + fs := flag.NewFlagSet(cmd.Name(), flag.ExitOnError) + return tool.Run(ctx, fs, h.app, append(args[:len(args):len(args)], "-h")) +} + +// version implements the version command. +type version struct { + JSON bool `flag:"json" help:"outputs in json format."` + + app *Application +} + +func (v *version) Name() string { return "version" } +func (v *version) Parent() string { return v.app.Name() } +func (v *version) Usage() string { return "" } +func (v *version) ShortHelp() string { return "print the gopls version information" } +func (v *version) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ``) + printFlagDefaults(f) +} + +// Run prints version information to stdout. +func (v *version) Run(ctx context.Context, args ...string) error { + var mode = debug.PlainText + if v.JSON { + mode = debug.JSON + } + + return debug.PrintVersionInfo(ctx, os.Stdout, v.app.verbose(), mode) +} + +// bug implements the bug command. +type bug struct { + app *Application +} + +func (b *bug) Name() string { return "bug" } +func (b *bug) Parent() string { return b.app.Name() } +func (b *bug) Usage() string { return "" } +func (b *bug) ShortHelp() string { return "report a bug in gopls" } +func (b *bug) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ``) + printFlagDefaults(f) +} + +const goplsBugPrefix = "x/tools/gopls: " +const goplsBugHeader = `ATTENTION: Please answer these questions BEFORE submitting your issue. Thanks! + +#### What did you do? +If possible, provide a recipe for reproducing the error. +A complete runnable program is good. +A link on play.golang.org is better. +A failing unit test is the best. + +#### What did you expect to see? + + +#### What did you see instead? + + +` + +// Run collects some basic information and then prepares an issue ready to +// be reported. +func (b *bug) Run(ctx context.Context, args ...string) error { + // This undocumented environment variable allows + // the cmd integration test (and maintainers) to + // trigger a call to bug.Report. + if msg := os.Getenv("TEST_GOPLS_BUG"); msg != "" { + filecache.Start() // register bug handler + goplsbug.Report(msg) + return nil + } + + // Enumerate bug reports, grouped and sorted. + _, reports := filecache.BugReports() + sort.Slice(reports, func(i, j int) bool { + x, y := reports[i], reports[i] + if x.Key != y.Key { + return x.Key < y.Key // ascending key order + } + return y.AtTime.Before(x.AtTime) // most recent first + }) + keyDenom := make(map[string]int) // key is "file:line" + for _, report := range reports { + keyDenom[report.Key]++ + } + + // Privacy: the content of 'public' will be posted to GitHub + // to populate an issue textarea. Even though the user must + // submit the form to share the information with the world, + // merely populating the form causes us to share the + // information with GitHub itself. + // + // For that reason, we cannot write private information to + // public, such as bug reports, which may quote source code. + public := &bytes.Buffer{} + fmt.Fprint(public, goplsBugHeader) + if len(reports) > 0 { + fmt.Fprintf(public, "#### Internal errors\n\n") + fmt.Fprintf(public, "Gopls detected %d internal errors, %d distinct:\n", + len(reports), len(keyDenom)) + for key, denom := range keyDenom { + fmt.Fprintf(public, "- %s (%d)\n", key, denom) + } + fmt.Fprintf(public, "\nPlease copy the full information printed by `gopls bug` here, if you are comfortable sharing it.\n\n") + } + debug.PrintVersionInfo(ctx, public, true, debug.Markdown) + body := public.String() + title := strings.Join(args, " ") + if !strings.HasPrefix(title, goplsBugPrefix) { + title = goplsBugPrefix + title + } + if !browser.Open("https://github.com/golang/go/issues/new?title=" + url.QueryEscape(title) + "&body=" + url.QueryEscape(body)) { + fmt.Print("Please file a new issue at golang.org/issue/new using this template:\n\n") + fmt.Print(body) + } + + // Print bug reports to stdout (not GitHub). + keyNum := make(map[string]int) + for _, report := range reports { + fmt.Printf("-- %v -- \n", report.AtTime) + + // Append seq number (e.g. " (1/2)") for repeated keys. + var seq string + if denom := keyDenom[report.Key]; denom > 1 { + keyNum[report.Key]++ + seq = fmt.Sprintf(" (%d/%d)", keyNum[report.Key], denom) + } + + // Privacy: + // - File and Stack may contain the name of the user that built gopls. + // - Description may contain names of the user's packages/files/symbols. + fmt.Printf("%s:%d: %s%s\n\n", report.File, report.Line, report.Description, seq) + fmt.Printf("%s\n\n", report.Stack) + } + if len(reports) > 0 { + fmt.Printf("Please copy the above information into the GitHub issue, if you are comfortable sharing it.\n") + } + + return nil +} + +type apiJSON struct { + app *Application +} + +func (j *apiJSON) Name() string { return "api-json" } +func (j *apiJSON) Parent() string { return j.app.Name() } +func (j *apiJSON) Usage() string { return "" } +func (j *apiJSON) ShortHelp() string { return "print JSON describing gopls API" } +func (j *apiJSON) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +The api-json command prints a JSON value that describes +and documents all gopls' public interfaces. +Its schema is defined by golang.org/x/tools/gopls/internal/doc.API. +`) + printFlagDefaults(f) +} + +func (j *apiJSON) Run(ctx context.Context, args ...string) error { + os.Stdout.WriteString(doc.JSON) + fmt.Println() + return nil +} + +type licenses struct { + app *Application +} + +func (l *licenses) Name() string { return "licenses" } +func (l *licenses) Parent() string { return l.app.Name() } +func (l *licenses) Usage() string { return "" } +func (l *licenses) ShortHelp() string { return "print licenses of included software" } +func (l *licenses) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ``) + printFlagDefaults(f) +} + +const licensePreamble = ` +gopls is made available under the following BSD-style license: + +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. + +gopls implements the LSP specification, which is made available under the following license: + +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT +OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +gopls also includes software made available under these licenses: +` + +func (l *licenses) Run(ctx context.Context, args ...string) error { + txt := licensePreamble + if licensespkg.Text == "" { + txt += "(development gopls, license information not available)" + } else { + txt += licensespkg.Text + } + fmt.Fprint(os.Stdout, txt) + return nil +} diff --git a/contribs/gnopls/internal/cmd/integration_test.go b/contribs/gnopls/internal/cmd/integration_test.go new file mode 100644 index 00000000000..dfb2a164a42 --- /dev/null +++ b/contribs/gnopls/internal/cmd/integration_test.go @@ -0,0 +1,1231 @@ +// 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 cmdtest contains the test suite for the command line behavior of gopls. +package cmd_test + +// This file defines integration tests of each gopls subcommand that +// fork+exec the command in a separate process. +// +// (Rather than execute 'go build gopls' during the test, we reproduce +// the main entrypoint in the test executable.) +// +// The purpose of this test is to exercise client-side logic such as +// argument parsing and formatting of LSP RPC responses, not server +// behavior; see lsp_test for that. +// +// All tests run in parallel. +// +// TODO(adonovan): +// - Use markers to represent positions in the input and in assertions. +// - Coverage of cross-cutting things like cwd, environ, span parsing, etc. +// - Subcommands that accept -write and -diff flags implement them +// consistently; factor their tests. +// - Add missing test for 'vulncheck' subcommand. +// - Add tests for client-only commands: serve, bug, help, api-json, licenses. + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "math/rand" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/cmd" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/version" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/internal/tool" + "golang.org/x/tools/txtar" +) + +// TestVersion tests the 'version' subcommand (../info.go). +func TestVersion(t *testing.T) { + t.Parallel() + + tree := writeTree(t, "") + + // There's not much we can robustly assert about the actual version. + want := version.Version() // e.g. "master" + + // basic + { + res := gopls(t, tree, "version") + res.checkExit(true) + res.checkStdout(want) + } + + // basic, with version override + { + res := goplsWithEnv(t, tree, []string{"TEST_GOPLS_VERSION=v1.2.3"}, "version") + res.checkExit(true) + res.checkStdout(`v1\.2\.3`) + } + + // -json flag + { + res := gopls(t, tree, "version", "-json") + res.checkExit(true) + var v debug.ServerVersion + if res.toJSON(&v) { + if v.Version != want { + t.Errorf("expected Version %q, got %q (%v)", want, v.Version, res) + } + } + } +} + +// TestCheck tests the 'check' subcommand (../check.go). +func TestCheck(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +import "fmt" +var _ = fmt.Sprintf("%s", 123) + +-- b.go -- +package a +import "fmt" +var _ = fmt.Sprintf("%d", "123") +-- c/c.go -- +package c +var C int +-- c/c2.go -- +package c +var C int +`) + + // no files + { + res := gopls(t, tree, "check") + res.checkExit(true) + if res.stdout != "" { + t.Errorf("unexpected output: %v", res) + } + } + + // one file + { + res := gopls(t, tree, "check", "./a.go") + res.checkExit(true) + res.checkStdout("fmt.Sprintf format %s has arg 123 of wrong type int") + } + + // two files + { + res := gopls(t, tree, "check", "./a.go", "./b.go") + res.checkExit(true) + res.checkStdout(`a.go:.* fmt.Sprintf format %s has arg 123 of wrong type int`) + res.checkStdout(`b.go:.* fmt.Sprintf format %d has arg "123" of wrong type string`) + } + + // diagnostic with related information spanning files + { + res := gopls(t, tree, "check", "./c/c2.go") + res.checkExit(true) + res.checkStdout(`c2.go:2:5-6: C redeclared in this block`) + res.checkStdout(`c.go:2:5-6: - other declaration of C`) + } +} + +// TestCallHierarchy tests the 'call_hierarchy' subcommand (../call_hierarchy.go). +func TestCallHierarchy(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +func f() {} +func g() { + f() +} +func h() { + f() + f() +} +`) + // missing position + { + res := gopls(t, tree, "call_hierarchy") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // wrong place + { + res := gopls(t, tree, "call_hierarchy", "a.go:1") + res.checkExit(false) + res.checkStderr("identifier not found") + } + // f is called once from g and twice from h. + { + res := gopls(t, tree, "call_hierarchy", "a.go:2:6") + res.checkExit(true) + // We use regexp '.' as an OS-agnostic path separator. + res.checkStdout("ranges 7:2-3, 8:2-3 in ..a.go from/to function h in ..a.go:6:6-7") + res.checkStdout("ranges 4:2-3 in ..a.go from/to function g in ..a.go:3:6-7") + res.checkStdout("identifier: function f in ..a.go:2:6-7") + } +} + +// TestCodeLens tests the 'codelens' subcommand (../codelens.go). +func TestCodeLens(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a/a.go -- +package a +-- a/a_test.go -- +package a_test +import "testing" +func TestPass(t *testing.T) {} +func TestFail(t *testing.T) { t.Fatal("fail") } +`) + // missing position + { + res := gopls(t, tree, "codelens") + res.checkExit(false) + res.checkStderr("requires a file name") + } + // list code lenses + { + res := gopls(t, tree, "codelens", "./a/a_test.go") + res.checkExit(true) + res.checkStdout(`a_test.go:3: "run test" \[gopls.test\]`) + res.checkStdout(`a_test.go:4: "run test" \[gopls.test\]`) + } + // no codelens with title/position + { + res := gopls(t, tree, "codelens", "-exec", "./a/a_test.go:1", "nope") + res.checkExit(false) + res.checkStderr(`no code lens at .* with title "nope"`) + } + // run the passing test + { + res := gopls(t, tree, "codelens", "-exec", "./a/a_test.go:3", "run test") + res.checkExit(true) + res.checkStderr(`PASS: TestPass`) // from go test + res.checkStderr("Info: all tests passed") // from gopls.test + } + // run the failing test + { + res := gopls(t, tree, "codelens", "-exec", "./a/a_test.go:4", "run test") + res.checkExit(false) + res.checkStderr(`FAIL example.com/a`) + res.checkStderr("Info: 1 / 1 tests failed") + } +} + +// TestDefinition tests the 'definition' subcommand (../definition.go). +func TestDefinition(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +import "fmt" +func f() { + fmt.Println() +} +func g() { + f() +} +`) + // missing position + { + res := gopls(t, tree, "definition") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // intra-package + { + res := gopls(t, tree, "definition", "a.go:7:2") // "f()" + res.checkExit(true) + res.checkStdout("a.go:3:6-7: defined here as func f") + } + // cross-package + { + res := gopls(t, tree, "definition", "a.go:4:7") // "Println" + res.checkExit(true) + res.checkStdout("print.go.* defined here as func fmt.Println") + res.checkStdout("Println formats using the default formats for its operands") + } + // -json and -markdown + { + res := gopls(t, tree, "definition", "-json", "-markdown", "a.go:4:7") + res.checkExit(true) + var defn cmd.Definition + if res.toJSON(&defn) { + if !strings.HasPrefix(defn.Description, "```go\nfunc fmt.Println") { + t.Errorf("Description does not start with markdown code block. Got: %s", defn.Description) + } + } + } +} + +// TestExecute tests the 'execute' subcommand (../execute.go). +func TestExecute(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- hello.go -- +package a +func main() {} + +-- hello_test.go -- +package a +import "testing" +func TestHello(t *testing.T) { + t.Fatal("oops") +} +`) + // missing command name + { + res := gopls(t, tree, "execute") + res.checkExit(false) + res.checkStderr("requires a command") + } + // bad command + { + res := gopls(t, tree, "execute", "gopls.foo") + res.checkExit(false) + res.checkStderr("unrecognized command: gopls.foo") + } + // too few arguments + { + res := gopls(t, tree, "execute", "gopls.run_tests") + res.checkExit(false) + res.checkStderr("expected 1 input arguments, got 0") + } + // too many arguments + { + res := gopls(t, tree, "execute", "gopls.run_tests", "null", "null") + res.checkExit(false) + res.checkStderr("expected 1 input arguments, got 2") + } + // argument is not JSON + { + res := gopls(t, tree, "execute", "gopls.run_tests", "hello") + res.checkExit(false) + res.checkStderr("argument 1 is not valid JSON: invalid character 'h'") + } + // add import, show diff + hello := "file://" + filepath.ToSlash(tree) + "/hello.go" + { + res := gopls(t, tree, "execute", "-d", "gopls.add_import", `{"ImportPath": "fmt", "URI": "`+hello+`"}`) + res.checkExit(true) + res.checkStdout(`[+]import "fmt"`) + } + // list known packages (has a result) + { + res := gopls(t, tree, "execute", "gopls.list_known_packages", `{"URI": "`+hello+`"}`) + res.checkExit(true) + res.checkStdout(`"fmt"`) + res.checkStdout(`"encoding/json"`) + } + // run tests + { + helloTest := "file://" + filepath.ToSlash(tree) + "/hello_test.go" + res := gopls(t, tree, "execute", "gopls.run_tests", `{"URI": "`+helloTest+`", "Tests": ["TestHello"]}`) + res.checkExit(false) + res.checkStderr(`hello_test.go:4: oops`) + res.checkStderr(`1 / 1 tests failed`) + } +} + +// TestFoldingRanges tests the 'folding_ranges' subcommand (../folding_range.go). +func TestFoldingRanges(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +func f(x int) { + // hello +} +`) + // missing filename + { + res := gopls(t, tree, "folding_ranges") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // success + { + res := gopls(t, tree, "folding_ranges", "a.go") + res.checkExit(true) + res.checkStdout("2:8-2:13") // params (x int) + res.checkStdout("2:16-4:1") // body { ... } + } +} + +// TestFormat tests the 'format' subcommand (../format.go). +func TestFormat(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- a.go -- +package a ; func f ( ) { } +`) + const want = `package a + +func f() {} +` + + // no files => nop + { + res := gopls(t, tree, "format") + res.checkExit(true) + } + // default => print formatted result + { + res := gopls(t, tree, "format", "a.go") + res.checkExit(true) + if res.stdout != want { + t.Errorf("format: got <<%s>>, want <<%s>>", res.stdout, want) + } + } + // start/end position not supported (unless equal to start/end of file) + { + res := gopls(t, tree, "format", "a.go:1-2") + res.checkExit(false) + res.checkStderr("only full file formatting supported") + } + // -list: show only file names + { + res := gopls(t, tree, "format", "-list", "a.go") + res.checkExit(true) + res.checkStdout("a.go") + } + // -diff prints a unified diff + { + res := gopls(t, tree, "format", "-diff", "a.go") + res.checkExit(true) + // We omit the filenames as they vary by OS. + want := ` +-package a ; func f ( ) { } ++package a ++ ++func f() {} +` + res.checkStdout(regexp.QuoteMeta(want)) + } + // -write updates the file + { + res := gopls(t, tree, "format", "-write", "a.go") + res.checkExit(true) + res.checkStdout("^$") // empty + checkContent(t, filepath.Join(tree, "a.go"), want) + } +} + +// TestHighlight tests the 'highlight' subcommand (../highlight.go). +func TestHighlight(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- a.go -- +package a +import "fmt" +func f() { + fmt.Println() + fmt.Println() +} +`) + + // no arguments + { + res := gopls(t, tree, "highlight") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // all occurrences of Println + { + res := gopls(t, tree, "highlight", "a.go:4:7") + res.checkExit(true) + res.checkStdout("a.go:4:6-13") + res.checkStdout("a.go:5:6-13") + } +} + +// TestImplementations tests the 'implementation' subcommand (../implementation.go). +func TestImplementations(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- a.go -- +package a +import "fmt" +type T int +func (T) String() string { return "" } +`) + + // no arguments + { + res := gopls(t, tree, "implementation") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // T.String + { + res := gopls(t, tree, "implementation", "a.go:4:10") + res.checkExit(true) + // TODO(adonovan): extract and check the content of the reported ranges? + // We use regexp '.' as an OS-agnostic path separator. + res.checkStdout("fmt.print.go:") // fmt.Stringer.String + res.checkStdout("runtime.error.go:") // runtime.stringer.String + } +} + +// TestImports tests the 'imports' subcommand (../imports.go). +func TestImports(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- a.go -- +package a +func _() { + fmt.Println() +} +`) + + want := ` +package a + +import "fmt" +func _() { + fmt.Println() +} +`[1:] + + // no arguments + { + res := gopls(t, tree, "imports") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // default: print with imports + { + res := gopls(t, tree, "imports", "a.go") + res.checkExit(true) + if res.stdout != want { + t.Errorf("imports: got <<%s>>, want <<%s>>", res.stdout, want) + } + } + // -diff: show a unified diff + { + res := gopls(t, tree, "imports", "-diff", "a.go") + res.checkExit(true) + res.checkStdout(regexp.QuoteMeta(`+import "fmt"`)) + } + // -write: update file + { + res := gopls(t, tree, "imports", "-write", "a.go") + res.checkExit(true) + checkContent(t, filepath.Join(tree, "a.go"), want) + } +} + +// TestLinks tests the 'links' subcommand (../links.go). +func TestLinks(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- a.go -- +// Link in package doc: https://pkg.go.dev/ +package a + +// Link in internal comment: https://go.dev/cl + +// Doc comment link: https://blog.go.dev/ +func f() {} +`) + // no arguments + { + res := gopls(t, tree, "links") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // success + { + res := gopls(t, tree, "links", "a.go") + res.checkExit(true) + res.checkStdout("https://go.dev/cl") + res.checkStdout("https://pkg.go.dev") + res.checkStdout("https://blog.go.dev/") + } + // -json + { + res := gopls(t, tree, "links", "-json", "a.go") + res.checkExit(true) + res.checkStdout("https://pkg.go.dev") + res.checkStdout("https://go.dev/cl") + res.checkStdout("https://blog.go.dev/") // at 5:21-5:41 + var links []protocol.DocumentLink + if res.toJSON(&links) { + // Check just one of the three locations. + if got, want := fmt.Sprint(links[2].Range), "5:21-5:41"; got != want { + t.Errorf("wrong link location: got %v, want %v", got, want) + } + } + } +} + +// TestReferences tests the 'references' subcommand (../references.go). +func TestReferences(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +import "fmt" +func f() { + fmt.Println() +} + +-- b.go -- +package a +import "fmt" +func g() { + fmt.Println() +} +`) + // no arguments + { + res := gopls(t, tree, "references") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // fmt.Println + { + res := gopls(t, tree, "references", "a.go:4:10") + res.checkExit(true) + res.checkStdout("a.go:4:6-13") + res.checkStdout("b.go:4:6-13") + } +} + +// TestSignature tests the 'signature' subcommand (../signature.go). +func TestSignature(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +import "fmt" +func f() { + fmt.Println(123) +} +`) + // no arguments + { + res := gopls(t, tree, "signature") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // at 123 inside fmt.Println() call + { + res := gopls(t, tree, "signature", "a.go:4:15") + res.checkExit(true) + res.checkStdout("Println\\(a ...") + res.checkStdout("Println formats using the default formats...") + } +} + +// TestPrepareRename tests the 'prepare_rename' subcommand (../prepare_rename.go). +func TestPrepareRename(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +func oldname() {} +`) + // no arguments + { + res := gopls(t, tree, "prepare_rename") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // in 'package' keyword + { + res := gopls(t, tree, "prepare_rename", "a.go:1:3") + res.checkExit(false) + res.checkStderr("request is not valid at the given position") + } + // in 'package' identifier (not supported by client) + { + res := gopls(t, tree, "prepare_rename", "a.go:1:9") + res.checkExit(false) + res.checkStderr("can't rename package") + } + // in func oldname + { + res := gopls(t, tree, "prepare_rename", "a.go:2:9") + res.checkExit(true) + res.checkStdout("a.go:2:6-13") // all of "oldname" + } +} + +// TestRename tests the 'rename' subcommand (../rename.go). +func TestRename(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +func oldname() {} +`) + // no arguments + { + res := gopls(t, tree, "rename") + res.checkExit(false) + res.checkStderr("expects 2 arguments") + } + // missing newname + { + res := gopls(t, tree, "rename", "a.go:1:3") + res.checkExit(false) + res.checkStderr("expects 2 arguments") + } + // in 'package' keyword + { + res := gopls(t, tree, "rename", "a.go:1:3", "newname") + res.checkExit(false) + res.checkStderr("no identifier found") + } + // in 'package' identifier + { + res := gopls(t, tree, "rename", "a.go:1:9", "newname") + res.checkExit(false) + res.checkStderr(`cannot rename package: module path .* same as the package path, so .* no effect`) + } + // success, func oldname (and -diff) + { + res := gopls(t, tree, "rename", "-diff", "a.go:2:9", "newname") + res.checkExit(true) + res.checkStdout(regexp.QuoteMeta("-func oldname() {}")) + res.checkStdout(regexp.QuoteMeta("+func newname() {}")) + } +} + +// TestSymbols tests the 'symbols' subcommand (../symbols.go). +func TestSymbols(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +func f() +var v int +const c = 0 +`) + // no files + { + res := gopls(t, tree, "symbols") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // success + { + res := gopls(t, tree, "symbols", "a.go:123:456") // (line/col ignored) + res.checkExit(true) + res.checkStdout("f Function 2:6-2:7") + res.checkStdout("v Variable 3:5-3:6") + res.checkStdout("c Constant 4:7-4:8") + } +} + +// TestSemtok tests the 'semtok' subcommand (../semantictokens.go). +func TestSemtok(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +func f() +var v int +const c = 0 +`) + // no files + { + res := gopls(t, tree, "semtok") + res.checkExit(false) + res.checkStderr("expected one file name") + } + // success + { + res := gopls(t, tree, "semtok", "a.go") + res.checkExit(true) + got := res.stdout + want := ` +/*⇒7,keyword,[]*/package /*⇒1,namespace,[]*/a +/*⇒4,keyword,[]*/func /*⇒1,function,[definition]*/f() +/*⇒3,keyword,[]*/var /*⇒1,variable,[definition]*/v /*⇒3,type,[defaultLibrary number]*/int +/*⇒5,keyword,[]*/const /*⇒1,variable,[definition readonly]*/c = /*⇒1,number,[]*/0 +`[1:] + if got != want { + t.Errorf("semtok: got <<%s>>, want <<%s>>", got, want) + } + } +} + +func TestStats(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +-- b/b.go -- +package b +-- testdata/foo.go -- +package foo +`) + + // Trigger a bug report with a distinctive string + // and check that it was durably recorded. + oops := fmt.Sprintf("oops-%d", rand.Int()) + { + env := []string{"TEST_GOPLS_BUG=" + oops} + res := goplsWithEnv(t, tree, env, "bug") + res.checkExit(true) + } + + res := gopls(t, tree, "stats") + res.checkExit(true) + + var stats cmd.GoplsStats + if err := json.Unmarshal([]byte(res.stdout), &stats); err != nil { + t.Fatalf("failed to unmarshal JSON output of stats command: %v", err) + } + + // a few sanity checks + checks := []struct { + field string + got int + want int + }{ + { + "WorkspaceStats.Views[0].WorkspaceModules", + stats.WorkspaceStats.Views[0].WorkspacePackages.Modules, + 1, + }, + { + "WorkspaceStats.Views[0].WorkspacePackages", + stats.WorkspaceStats.Views[0].WorkspacePackages.Packages, + 2, + }, + {"DirStats.Files", stats.DirStats.Files, 4}, + {"DirStats.GoFiles", stats.DirStats.GoFiles, 2}, + {"DirStats.ModFiles", stats.DirStats.ModFiles, 1}, + {"DirStats.TestdataFiles", stats.DirStats.TestdataFiles, 1}, + } + for _, check := range checks { + if check.got != check.want { + t.Errorf("stats.%s = %d, want %d", check.field, check.got, check.want) + } + } + + // Check that we got a BugReport with the expected message. + { + got := fmt.Sprint(stats.BugReports) + wants := []string{ + "cmd/info.go", // File containing call to bug.Report + oops, // Description + } + for _, want := range wants { + if !strings.Contains(got, want) { + t.Errorf("BugReports does not contain %q. Got:<<%s>>", want, got) + break + } + } + } + + // Check that -anon suppresses fields containing user information. + { + res2 := gopls(t, tree, "stats", "-anon") + res2.checkExit(true) + + var stats2 cmd.GoplsStats + if err := json.Unmarshal([]byte(res2.stdout), &stats2); err != nil { + t.Fatalf("failed to unmarshal JSON output of stats command: %v", err) + } + if got := len(stats2.BugReports); got > 0 { + t.Errorf("Got %d bug reports with -anon, want 0. Reports:%+v", got, stats2.BugReports) + } + var stats2AsMap map[string]any + if err := json.Unmarshal([]byte(res2.stdout), &stats2AsMap); err != nil { + t.Fatalf("failed to unmarshal JSON output of stats command: %v", err) + } + // GOPACKAGESDRIVER is user information, but is ok to print zero value. + if v, ok := stats2AsMap["GOPACKAGESDRIVER"]; ok && v != "" { + t.Errorf(`Got GOPACKAGESDRIVER=(%v, %v); want ("", true(found))`, v, ok) + } + } + + // Check that -anon suppresses fields containing non-zero user information. + { + res3 := goplsWithEnv(t, tree, []string{"GOPACKAGESDRIVER=off"}, "stats", "-anon") + res3.checkExit(true) + + var statsAsMap3 map[string]interface{} + if err := json.Unmarshal([]byte(res3.stdout), &statsAsMap3); err != nil { + t.Fatalf("failed to unmarshal JSON output of stats command: %v", err) + } + // GOPACKAGESDRIVER is user information, want non-empty value to be omitted. + if v, ok := statsAsMap3["GOPACKAGESDRIVER"]; ok { + t.Errorf(`Got GOPACKAGESDRIVER=(%q, %v); want ("", false(not found))`, v, ok) + } + } +} + +// TestCodeAction tests the 'codeaction' subcommand (../codeaction.go). +func TestCodeAction(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +type T int +func f() (int, string) { return } + +-- b.go -- +package a +import "io" +var _ io.Reader = C{} +type C struct{} +`) + + // no arguments + { + res := gopls(t, tree, "codeaction") + res.checkExit(false) + res.checkStderr("expects at least 1 argument") + } + // list code actions in file + { + res := gopls(t, tree, "codeaction", "a.go") + res.checkExit(true) + res.checkStdout(`edit "Fill in return values" \[quickfix\]`) + res.checkStdout(`command "Browse documentation for package a" \[source.doc\]`) + } + // list code actions in file, filtering by title + { + res := gopls(t, tree, "codeaction", "-title=Browse.*doc", "a.go") + res.checkExit(true) + got := res.stdout + want := `command "Browse gopls feature documentation" [gopls.doc.features]` + + "\n" + + `command "Browse documentation for package a" [source.doc]` + + "\n" + if got != want { + t.Errorf("codeaction: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr) + } + } + // list code actions in file, filtering (hierarchically) by kind + { + res := gopls(t, tree, "codeaction", "-kind=source", "a.go") + res.checkExit(true) + got := res.stdout + want := `command "Browse documentation for package a" [source.doc]` + + "\n" + if got != want { + t.Errorf("codeaction: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr) + } + } + // list code actions at position (of io.Reader) + { + res := gopls(t, tree, "codeaction", "b.go:#31") + res.checkExit(true) + res.checkStdout(`command "Browse documentation for type io.Reader" \[source.doc]`) + } + // list quick fixes at position (of type T) + { + res := gopls(t, tree, "codeaction", "-kind=quickfix", "a.go:#15") + res.checkExit(true) + got := res.stdout + want := `edit "Fill in return values" [quickfix]` + "\n" + if got != want { + t.Errorf("codeaction: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr) + } + } + // success, with explicit CodeAction kind and diagnostics span. + { + res := gopls(t, tree, "codeaction", "-kind=quickfix", "-exec", "b.go:#40") + res.checkExit(true) + got := res.stdout + want := ` +package a + +import "io" + +var _ io.Reader = C{} + +type C struct{} + +// Read implements io.Reader. +func (c C) Read(p []byte) (n int, err error) { + panic("unimplemented") +} +`[1:] + if got != want { + t.Errorf("codeaction: got <<%s>>, want <<%s>>\nstderr:\n%s", got, want, res.stderr) + } + } +} + +// TestWorkspaceSymbol tests the 'workspace_symbol' subcommand (../workspace_symbol.go). +func TestWorkspaceSymbol(t *testing.T) { + t.Parallel() + + tree := writeTree(t, ` +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a +func someFunctionName() +`) + // no files + { + res := gopls(t, tree, "workspace_symbol") + res.checkExit(false) + res.checkStderr("expects 1 argument") + } + // success + { + res := gopls(t, tree, "workspace_symbol", "meFun") + res.checkExit(true) + res.checkStdout("a.go:2:6-22 someFunctionName Function") + } +} + +// -- test framework -- + +func TestMain(m *testing.M) { + switch os.Getenv("ENTRYPOINT") { + case "goplsMain": + goplsMain() + default: + os.Exit(m.Run()) + } +} + +// This function is a stand-in for gopls.main in ../../../../main.go. +func goplsMain() { + // Panic on bugs (unlike the production gopls command), + // except in tests that inject calls to bug.Report. + if os.Getenv("TEST_GOPLS_BUG") == "" { + bug.PanicOnBugs = true + } + + if v := os.Getenv("TEST_GOPLS_VERSION"); v != "" { + version.VersionOverride = v + } + + tool.Main(context.Background(), cmd.New(), os.Args[1:]) +} + +// writeTree extracts a txtar archive into a new directory and returns its path. +func writeTree(t *testing.T, archive string) string { + root := t.TempDir() + + // This unfortunate step is required because gopls output + // expands symbolic links in its input file names (arguably it + // should not), and on macOS the temp dir is in /var -> private/var. + root, err := filepath.EvalSymlinks(root) + if err != nil { + t.Fatal(err) + } + + for _, f := range txtar.Parse([]byte(archive)).Files { + filename := filepath.Join(root, f.Name) + if err := os.MkdirAll(filepath.Dir(filename), 0777); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filename, f.Data, 0666); err != nil { + t.Fatal(err) + } + } + return root +} + +// gopls executes gopls in a child process. +func gopls(t *testing.T, dir string, args ...string) *result { + return goplsWithEnv(t, dir, nil, args...) +} + +func goplsWithEnv(t *testing.T, dir string, env []string, args ...string) *result { + testenv.NeedsTool(t, "go") + + // Catch inadvertent use of dir=".", which would make + // the ReplaceAll below unpredictable. + if !filepath.IsAbs(dir) { + t.Fatalf("dir is not absolute: %s", dir) + } + + goplsCmd := exec.Command(os.Args[0], args...) + goplsCmd.Env = append(os.Environ(), "ENTRYPOINT=goplsMain") + goplsCmd.Env = append(goplsCmd.Env, "GOPACKAGESDRIVER=off") + goplsCmd.Env = append(goplsCmd.Env, env...) + goplsCmd.Dir = dir + goplsCmd.Stdout = new(bytes.Buffer) + goplsCmd.Stderr = new(bytes.Buffer) + + cmdErr := goplsCmd.Run() + + stdout := strings.ReplaceAll(fmt.Sprint(goplsCmd.Stdout), dir, ".") + stderr := strings.ReplaceAll(fmt.Sprint(goplsCmd.Stderr), dir, ".") + exitcode := 0 + if cmdErr != nil { + if exitErr, ok := cmdErr.(*exec.ExitError); ok { + exitcode = exitErr.ExitCode() + } else { + stderr = cmdErr.Error() // (execve failure) + exitcode = -1 + } + } + res := &result{ + t: t, + command: "gopls " + strings.Join(args, " "), + exitcode: exitcode, + stdout: stdout, + stderr: stderr, + } + if false { + t.Log(res) + } + return res +} + +// A result holds the result of a gopls invocation, and provides assertion helpers. +type result struct { + t *testing.T + command string + exitcode int + stdout, stderr string +} + +func (res *result) String() string { + return fmt.Sprintf("%s: exit=%d stdout=<<%s>> stderr=<<%s>>", + res.command, res.exitcode, res.stdout, res.stderr) +} + +// checkExit asserts that gopls returned the expected exit code. +func (res *result) checkExit(success bool) { + res.t.Helper() + if (res.exitcode == 0) != success { + res.t.Errorf("%s: exited with code %d, want success: %t (%s)", + res.command, res.exitcode, success, res) + } +} + +// checkStdout asserts that the gopls standard output matches the pattern. +func (res *result) checkStdout(pattern string) { + res.t.Helper() + res.checkOutput(pattern, "stdout", res.stdout) +} + +// checkStderr asserts that the gopls standard error matches the pattern. +func (res *result) checkStderr(pattern string) { + res.t.Helper() + res.checkOutput(pattern, "stderr", res.stderr) +} + +func (res *result) checkOutput(pattern, name, content string) { + res.t.Helper() + if match, err := regexp.MatchString(pattern, content); err != nil { + res.t.Errorf("invalid regexp: %v", err) + } else if !match { + res.t.Errorf("%s: %s does not match [%s]; got <<%s>>", + res.command, name, pattern, content) + } +} + +// toJSON decodes res.stdout as JSON into to *ptr and reports its success. +func (res *result) toJSON(ptr interface{}) bool { + if err := json.Unmarshal([]byte(res.stdout), ptr); err != nil { + res.t.Errorf("invalid JSON %v", err) + return false + } + return true +} + +// checkContent checks that the contents of the file are as expected. +func checkContent(t *testing.T, filename, want string) { + data, err := os.ReadFile(filename) + if err != nil { + t.Error(err) + return + } + if got := string(data); got != want { + t.Errorf("content of %s is <<%s>>, want <<%s>>", filename, got, want) + } +} diff --git a/contribs/gnopls/internal/cmd/links.go b/contribs/gnopls/internal/cmd/links.go new file mode 100644 index 00000000000..3c14f4e6608 --- /dev/null +++ b/contribs/gnopls/internal/cmd/links.go @@ -0,0 +1,76 @@ +// 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. + +package cmd + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// links implements the links verb for gopls. +type links struct { + JSON bool `flag:"json" help:"emit document links in JSON format"` + + app *Application +} + +func (l *links) Name() string { return "links" } +func (l *links) Parent() string { return l.app.Name() } +func (l *links) Usage() string { return "[links-flags] " } +func (l *links) ShortHelp() string { return "list links in a file" } +func (l *links) DetailedHelp(f *flag.FlagSet) { + fmt.Fprintf(f.Output(), ` +Example: list links contained within a file: + + $ gopls links internal/cmd/check.go + +links-flags: +`) + printFlagDefaults(f) +} + +// Run finds all the links within a document +// - if -json is specified, outputs location range and uri +// - otherwise, prints the a list of unique links +func (l *links) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("links expects 1 argument") + } + conn, err := l.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + uri := from.URI() + + if _, err := conn.openFile(ctx, uri); err != nil { + return err + } + results, err := conn.DocumentLink(ctx, &protocol.DocumentLinkParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: uri, + }, + }) + if err != nil { + return fmt.Errorf("%v: %v", from, err) + } + if l.JSON { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", "\t") + return enc.Encode(results) + } + for _, v := range results { + fmt.Println(*v.Target) + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/parsespan.go b/contribs/gnopls/internal/cmd/parsespan.go new file mode 100644 index 00000000000..556beb9730e --- /dev/null +++ b/contribs/gnopls/internal/cmd/parsespan.go @@ -0,0 +1,106 @@ +// 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. + +package cmd + +import ( + "strconv" + "strings" + "unicode/utf8" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// parseSpan returns the location represented by the input. +// Only file paths are accepted, not URIs. +// The returned span will be normalized, and thus if printed may produce a +// different string. +func parseSpan(input string) span { + uri := protocol.URIFromPath + + // :0:0#0-0:0#0 + valid := input + var hold, offset int + hadCol := false + suf := rstripSuffix(input) + if suf.sep == "#" { + offset = suf.num + suf = rstripSuffix(suf.remains) + } + if suf.sep == ":" { + valid = suf.remains + hold = suf.num + hadCol = true + suf = rstripSuffix(suf.remains) + } + switch { + case suf.sep == ":": + return newSpan(uri(suf.remains), newPoint(suf.num, hold, offset), point{}) + case suf.sep == "-": + // we have a span, fall out of the case to continue + default: + // separator not valid, rewind to either the : or the start + return newSpan(uri(valid), newPoint(hold, 0, offset), point{}) + } + // only the span form can get here + // at this point we still don't know what the numbers we have mean + // if have not yet seen a : then we might have either a line or a column depending + // on whether start has a column or not + // we build an end point and will fix it later if needed + end := newPoint(suf.num, hold, offset) + hold, offset = 0, 0 + suf = rstripSuffix(suf.remains) + if suf.sep == "#" { + offset = suf.num + suf = rstripSuffix(suf.remains) + } + if suf.sep != ":" { + // turns out we don't have a span after all, rewind + return newSpan(uri(valid), end, point{}) + } + valid = suf.remains + hold = suf.num + suf = rstripSuffix(suf.remains) + if suf.sep != ":" { + // line#offset only + return newSpan(uri(valid), newPoint(hold, 0, offset), end) + } + // we have a column, so if end only had one number, it is also the column + if !hadCol { + end = newPoint(suf.num, end.v.Line, end.v.Offset) + } + return newSpan(uri(suf.remains), newPoint(suf.num, hold, offset), end) +} + +type suffix struct { + remains string + sep string + num int +} + +func rstripSuffix(input string) suffix { + if len(input) == 0 { + return suffix{"", "", -1} + } + remains := input + + // Remove optional trailing decimal number. + num := -1 + last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' }) + if last >= 0 && last < len(remains)-1 { + number, err := strconv.ParseInt(remains[last+1:], 10, 64) + if err == nil { + num = int(number) + remains = remains[:last+1] + } + } + // now see if we have a trailing separator + r, w := utf8.DecodeLastRuneInString(remains) + // TODO(adonovan): this condition is clearly wrong. Should the third byte be '-'? + if r != ':' && r != '#' && r == '#' { + return suffix{input, "", -1} + } + remains = remains[:len(remains)-w] + return suffix{remains, string(r), num} +} diff --git a/contribs/gnopls/internal/cmd/prepare_rename.go b/contribs/gnopls/internal/cmd/prepare_rename.go new file mode 100644 index 00000000000..3ff38356d55 --- /dev/null +++ b/contribs/gnopls/internal/cmd/prepare_rename.go @@ -0,0 +1,79 @@ +// 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. + +package cmd + +import ( + "context" + "errors" + "flag" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// prepareRename implements the prepare_rename verb for gopls. +type prepareRename struct { + app *Application +} + +func (r *prepareRename) Name() string { return "prepare_rename" } +func (r *prepareRename) Parent() string { return r.app.Name() } +func (r *prepareRename) Usage() string { return "" } +func (r *prepareRename) ShortHelp() string { return "test validity of a rename operation at location" } +func (r *prepareRename) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls prepare_rename helper/helper.go:8:6 + $ gopls prepare_rename helper/helper.go:#53 +`) + printFlagDefaults(f) +} + +// ErrInvalidRenamePosition is returned when prepareRename is run at a position that +// is not a candidate for renaming. +var ErrInvalidRenamePosition = errors.New("request is not valid at the given position") + +func (r *prepareRename) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("prepare_rename expects 1 argument (file)") + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + file, err := conn.openFile(ctx, from.URI()) + if err != nil { + return err + } + loc, err := file.spanLocation(from) + if err != nil { + return err + } + p := protocol.PrepareRenameParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + result, err := conn.PrepareRename(ctx, &p) + if err != nil { + return fmt.Errorf("prepare_rename failed: %w", err) + } + if result == nil { + return ErrInvalidRenamePosition + } + + s, err := file.rangeSpan(result.Range) + if err != nil { + return err + } + + fmt.Println(s) + return nil +} diff --git a/contribs/gnopls/internal/cmd/references.go b/contribs/gnopls/internal/cmd/references.go new file mode 100644 index 00000000000..1483bf12db0 --- /dev/null +++ b/contribs/gnopls/internal/cmd/references.go @@ -0,0 +1,91 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + "sort" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// references implements the references verb for gopls +type references struct { + IncludeDeclaration bool `flag:"d,declaration" help:"include the declaration of the specified identifier in the results"` + + app *Application +} + +func (r *references) Name() string { return "references" } +func (r *references) Parent() string { return r.app.Name() } +func (r *references) Usage() string { return "[references-flags] " } +func (r *references) ShortHelp() string { return "display selected identifier's references" } +func (r *references) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls references helper/helper.go:8:6 + $ gopls references helper/helper.go:#53 + +references-flags: +`) + printFlagDefaults(f) +} + +func (r *references) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("references expects 1 argument (position)") + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + file, err := conn.openFile(ctx, from.URI()) + if err != nil { + return err + } + loc, err := file.spanLocation(from) + if err != nil { + return err + } + p := protocol.ReferenceParams{ + Context: protocol.ReferenceContext{ + IncludeDeclaration: r.IncludeDeclaration, + }, + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + locations, err := conn.References(ctx, &p) + if err != nil { + return err + } + var spans []string + for _, l := range locations { + f, err := conn.openFile(ctx, l.URI) + if err != nil { + return err + } + // convert location to span for user-friendly 1-indexed line + // and column numbers + span, err := f.locationSpan(l) + if err != nil { + return err + } + spans = append(spans, fmt.Sprint(span)) + } + + sort.Strings(spans) + for _, s := range spans { + fmt.Println(s) + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/remote.go b/contribs/gnopls/internal/cmd/remote.go new file mode 100644 index 00000000000..ae4aa55ab61 --- /dev/null +++ b/contribs/gnopls/internal/cmd/remote.go @@ -0,0 +1,164 @@ +// 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 cmd + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "log" + "os" + + "golang.org/x/tools/gopls/internal/lsprpc" + "golang.org/x/tools/gopls/internal/protocol/command" +) + +type remote struct { + app *Application + subcommands + + // For backward compatibility, allow aliasing this command (it was previously + // called 'inspect'). + // + // TODO(rFindley): delete this after allowing some transition time in case + // there were any users of 'inspect' (I suspect not). + alias string +} + +func newRemote(app *Application, alias string) *remote { + return &remote{ + app: app, + subcommands: subcommands{ + &listSessions{app: app}, + &startDebugging{app: app}, + }, + alias: alias, + } +} + +func (r *remote) Name() string { + if r.alias != "" { + return r.alias + } + return "remote" +} + +func (r *remote) Parent() string { return r.app.Name() } + +func (r *remote) ShortHelp() string { + short := "interact with the gopls daemon" + if r.alias != "" { + short += " (deprecated: use 'remote')" + } + return short +} + +// listSessions is an inspect subcommand to list current sessions. +type listSessions struct { + app *Application +} + +func (c *listSessions) Name() string { return "sessions" } +func (c *listSessions) Parent() string { return c.app.Name() } +func (c *listSessions) Usage() string { return "" } +func (c *listSessions) ShortHelp() string { + return "print information about current gopls sessions" +} + +const listSessionsExamples = ` +Examples: + +1) list sessions for the default daemon: + +$ gopls -remote=auto remote sessions +or just +$ gopls remote sessions + +2) list sessions for a specific daemon: + +$ gopls -remote=localhost:8082 remote sessions +` + +func (c *listSessions) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), listSessionsExamples) + printFlagDefaults(f) +} + +func (c *listSessions) Run(ctx context.Context, args ...string) error { + remote := c.app.Remote + if remote == "" { + remote = "auto" + } + state, err := lsprpc.QueryServerState(ctx, remote) + if err != nil { + return err + } + v, err := json.MarshalIndent(state, "", "\t") + if err != nil { + log.Fatal(err) + } + os.Stdout.Write(v) + return nil +} + +type startDebugging struct { + app *Application +} + +func (c *startDebugging) Name() string { return "debug" } +func (c *startDebugging) Usage() string { return "[host:port]" } +func (c *startDebugging) ShortHelp() string { + return "start the debug server" +} + +const startDebuggingExamples = ` +Examples: + +1) start a debug server for the default daemon, on an arbitrary port: + +$ gopls -remote=auto remote debug +or just +$ gopls remote debug + +2) start for a specific daemon, on a specific port: + +$ gopls -remote=localhost:8082 remote debug localhost:8083 +` + +func (c *startDebugging) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), startDebuggingExamples) + printFlagDefaults(f) +} + +func (c *startDebugging) Run(ctx context.Context, args ...string) error { + if len(args) > 1 { + fmt.Fprintln(os.Stderr, c.Usage()) + return errors.New("invalid usage") + } + remote := c.app.Remote + if remote == "" { + remote = "auto" + } + debugAddr := "" + if len(args) > 0 { + debugAddr = args[0] + } + debugArgs := command.DebuggingArgs{ + Addr: debugAddr, + } + var result command.DebuggingResult + if err := lsprpc.ExecuteCommand(ctx, remote, command.StartDebugging.String(), debugArgs, &result); err != nil { + return err + } + if len(result.URLs) == 0 { + return errors.New("no debugging URLs") + } + for _, url := range result.URLs { + fmt.Printf("debugging on %s\n", url) + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/rename.go b/contribs/gnopls/internal/cmd/rename.go new file mode 100644 index 00000000000..e96850cd1c8 --- /dev/null +++ b/contribs/gnopls/internal/cmd/rename.go @@ -0,0 +1,73 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// rename implements the rename verb for gopls. +type rename struct { + EditFlags + app *Application +} + +func (r *rename) Name() string { return "rename" } +func (r *rename) Parent() string { return r.app.Name() } +func (r *rename) Usage() string { return "[rename-flags] " } +func (r *rename) ShortHelp() string { return "rename selected identifier" } +func (r *rename) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-based location (:line:column or :#position) of the thing to change + $ gopls rename helper/helper.go:8:6 Foo + $ gopls rename helper/helper.go:#53 Foo + +rename-flags: +`) + printFlagDefaults(f) +} + +// Run renames the specified identifier and either; +// - if -w is specified, updates the file(s) in place; +// - if -d is specified, prints out unified diffs of the changes; or +// - otherwise, prints the new versions to stdout. +func (r *rename) Run(ctx context.Context, args ...string) error { + if len(args) != 2 { + return tool.CommandLineErrorf("rename expects 2 arguments (position, new name)") + } + r.app.editFlags = &r.EditFlags + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + file, err := conn.openFile(ctx, from.URI()) + if err != nil { + return err + } + loc, err := file.spanLocation(from) + if err != nil { + return err + } + p := protocol.RenameParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + NewName: args[1], + } + edit, err := conn.Rename(ctx, &p) + if err != nil { + return err + } + return conn.client.applyWorkspaceEdit(edit) +} diff --git a/contribs/gnopls/internal/cmd/semantictokens.go b/contribs/gnopls/internal/cmd/semantictokens.go new file mode 100644 index 00000000000..77e8a03939c --- /dev/null +++ b/contribs/gnopls/internal/cmd/semantictokens.go @@ -0,0 +1,196 @@ +// 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 cmd + +import ( + "bytes" + "context" + "flag" + "fmt" + "log" + "os" + "unicode/utf8" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" +) + +// generate semantic tokens and interpolate them in the file + +// The output is the input file decorated with comments showing the +// syntactic tokens. The comments are stylized: +// /*,,[ is the length of the token in runes, is one +// of the supported semantic token types, and " } +func (c *semtok) ShortHelp() string { return "show semantic tokens for the specified file" } +func (c *semtok) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: show the semantic tokens for this file: + + $ gopls semtok internal/cmd/semtok.go +`) + printFlagDefaults(f) +} + +// Run performs the semtok on the files specified by args and prints the +// results to stdout in the format described above. +func (c *semtok) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return fmt.Errorf("expected one file name, got %d", len(args)) + } + // perhaps simpler if app had just had a FlagSet member + origOptions := c.app.options + c.app.options = func(opts *settings.Options) { + if origOptions != nil { + origOptions(opts) + } + opts.SemanticTokens = true + } + conn, err := c.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + uri := protocol.URIFromPath(args[0]) + file, err := conn.openFile(ctx, uri) + if err != nil { + return err + } + + lines := bytes.Split(file.mapper.Content, []byte{'\n'}) + p := &protocol.SemanticTokensRangeParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: uri, + }, + Range: protocol.Range{Start: protocol.Position{Line: 0, Character: 0}, + End: protocol.Position{ + Line: uint32(len(lines) - 1), + Character: uint32(len(lines[len(lines)-1]))}, + }, + } + resp, err := conn.semanticTokens(ctx, p) + if err != nil { + return err + } + return decorate(file, resp.Data) +} + +type mark struct { + line, offset int // 1-based, from RangeSpan + len int // bytes, not runes + typ string + mods []string +} + +// prefixes for semantic token comments +const ( + SemanticLeft = "/*⇐" + SemanticRight = "/*⇒" +) + +func markLine(m mark, lines [][]byte) { + l := lines[m.line-1] // mx is 1-based + length := utf8.RuneCount(l[m.offset-1 : m.offset-1+m.len]) + splitAt := m.offset - 1 + insert := "" + if m.typ == "namespace" && m.offset-1+m.len < len(l) && l[m.offset-1+m.len] == '"' { + // it is the last component of an import spec + // cannot put a comment inside a string + insert = fmt.Sprintf("%s%d,namespace,[]*/", SemanticLeft, length) + splitAt = m.offset + m.len + } else { + // be careful not to generate //* + spacer := "" + if splitAt-1 >= 0 && l[splitAt-1] == '/' { + spacer = " " + } + insert = fmt.Sprintf("%s%s%d,%s,%v*/", spacer, SemanticRight, length, m.typ, m.mods) + } + x := append([]byte(insert), l[splitAt:]...) + l = append(l[:splitAt], x...) + lines[m.line-1] = l +} + +func decorate(file *cmdFile, result []uint32) error { + marks := newMarks(file, result) + if len(marks) == 0 { + return nil + } + lines := bytes.Split(file.mapper.Content, []byte{'\n'}) + for i := len(marks) - 1; i >= 0; i-- { + mx := marks[i] + markLine(mx, lines) + } + os.Stdout.Write(bytes.Join(lines, []byte{'\n'})) + return nil +} + +func newMarks(file *cmdFile, d []uint32) []mark { + ans := []mark{} + // the following two loops could be merged, at the cost + // of making the logic slightly more complicated to understand + // first, convert from deltas to absolute, in LSP coordinates + lspLine := make([]uint32, len(d)/5) + lspChar := make([]uint32, len(d)/5) + var line, char uint32 + for i := 0; 5*i < len(d); i++ { + lspLine[i] = line + d[5*i+0] + if d[5*i+0] > 0 { + char = 0 + } + lspChar[i] = char + d[5*i+1] + char = lspChar[i] + line = lspLine[i] + } + // second, convert to gopls coordinates + for i := 0; 5*i < len(d); i++ { + pr := protocol.Range{ + Start: protocol.Position{ + Line: lspLine[i], + Character: lspChar[i], + }, + End: protocol.Position{ + Line: lspLine[i], + Character: lspChar[i] + d[5*i+2], + }, + } + spn, err := file.rangeSpan(pr) + if err != nil { + log.Fatal(err) + } + m := mark{ + line: spn.Start().Line(), + offset: spn.Start().Column(), + len: spn.End().Column() - spn.Start().Column(), + typ: protocol.SemType(int(d[5*i+3])), + mods: protocol.SemMods(int(d[5*i+4])), + } + ans = append(ans, m) + } + return ans +} diff --git a/contribs/gnopls/internal/cmd/serve.go b/contribs/gnopls/internal/cmd/serve.go new file mode 100644 index 00000000000..16f3b160a73 --- /dev/null +++ b/contribs/gnopls/internal/cmd/serve.go @@ -0,0 +1,146 @@ +// Copyright 2018 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 cmd + +import ( + "context" + "errors" + "flag" + "fmt" + "io" + "log" + "os" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/lsprpc" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/fakenet" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/tool" +) + +// Serve is a struct that exposes the configurable parts of the LSP server as +// flags, in the right form for tool.Main to consume. +type Serve struct { + Logfile string `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"` + Mode string `flag:"mode" help:"no effect"` + Port int `flag:"port" help:"port on which to run gopls for debugging purposes"` + Address string `flag:"listen" help:"address on which to listen for remote connections. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. Otherwise, TCP is used."` + IdleTimeout time.Duration `flag:"listen.timeout" help:"when used with -listen, shut down the server when there are no connected clients for this duration"` + Trace bool `flag:"rpc.trace" help:"print the full rpc trace in lsp inspector format"` + Debug string `flag:"debug" help:"serve debug information on the supplied address"` + + RemoteListenTimeout time.Duration `flag:"remote.listen.timeout" help:"when used with -remote=auto, the -listen.timeout value used to start the daemon"` + RemoteDebug string `flag:"remote.debug" help:"when used with -remote=auto, the -debug value used to start the daemon"` + RemoteLogfile string `flag:"remote.logfile" help:"when used with -remote=auto, the -logfile value used to start the daemon"` + + app *Application +} + +func (s *Serve) Name() string { return "serve" } +func (s *Serve) Parent() string { return s.app.Name() } +func (s *Serve) Usage() string { return "[server-flags]" } +func (s *Serve) ShortHelp() string { + return "run a server for Go code using the Language Server Protocol" +} +func (s *Serve) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` gopls [flags] [server-flags] + +The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as +a child of an editor process. + +server-flags: +`) + printFlagDefaults(f) +} + +func (s *Serve) remoteArgs(network, address string) []string { + args := []string{"serve", + "-listen", fmt.Sprintf(`%s;%s`, network, address), + } + if s.RemoteDebug != "" { + args = append(args, "-debug", s.RemoteDebug) + } + if s.RemoteListenTimeout != 0 { + args = append(args, "-listen.timeout", s.RemoteListenTimeout.String()) + } + if s.RemoteLogfile != "" { + args = append(args, "-logfile", s.RemoteLogfile) + } + return args +} + +// Run configures a server based on the flags, and then runs it. +// It blocks until the server shuts down. +func (s *Serve) Run(ctx context.Context, args ...string) error { + if len(args) > 0 { + return tool.CommandLineErrorf("server does not take arguments, got %v", args) + } + + di := debug.GetInstance(ctx) + isDaemon := s.Address != "" || s.Port != 0 + if di != nil { + closeLog, err := di.SetLogFile(s.Logfile, isDaemon) + if err != nil { + return err + } + defer closeLog() + di.ServerAddress = s.Address + di.Serve(ctx, s.Debug) + } + var ss jsonrpc2.StreamServer + if s.app.Remote != "" { + var err error + ss, err = lsprpc.NewForwarder(s.app.Remote, s.remoteArgs) + if err != nil { + return fmt.Errorf("creating forwarder: %w", err) + } + } else { + ss = lsprpc.NewStreamServer(cache.New(nil), isDaemon, s.app.options) + } + + var network, addr string + if s.Address != "" { + network, addr = lsprpc.ParseAddr(s.Address) + } + if s.Port != 0 { + network = "tcp" + // TODO(adonovan): should gopls ever be listening on network + // sockets, or only local ones? + // + // Ian says this was added in anticipation of + // something related to "VS Code remote" that turned + // out to be unnecessary. So I propose we limit it to + // localhost, if only so that we avoid the macOS + // firewall prompt. + // + // Hana says: "s.Address is for the remote access (LSP) + // and s.Port is for debugging purpose (according to + // the Server type documentation). I am not sure why the + // existing code here is mixing up and overwriting addr. + // For debugging endpoint, I think localhost makes perfect sense." + // + // TODO(adonovan): disentangle Address and Port, + // and use only localhost for the latter. + addr = fmt.Sprintf(":%v", s.Port) + } + if addr != "" { + log.Printf("Gopls daemon: listening on %s network, address %s...", network, addr) + defer log.Printf("Gopls daemon: exiting") + return jsonrpc2.ListenAndServe(ctx, network, addr, ss, s.IdleTimeout) + } + stream := jsonrpc2.NewHeaderStream(fakenet.NewConn("stdio", os.Stdin, os.Stdout)) + if s.Trace && di != nil { + stream = protocol.LoggingStream(stream, di.LogWriter) + } + conn := jsonrpc2.NewConn(stream) + err := ss.ServeStream(ctx, conn) + if errors.Is(err, io.EOF) { + return nil + } + return err +} diff --git a/contribs/gnopls/internal/cmd/signature.go b/contribs/gnopls/internal/cmd/signature.go new file mode 100644 index 00000000000..601cfaa13fa --- /dev/null +++ b/contribs/gnopls/internal/cmd/signature.go @@ -0,0 +1,87 @@ +// 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. + +package cmd + +import ( + "context" + "flag" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// signature implements the signature verb for gopls +type signature struct { + app *Application +} + +func (r *signature) Name() string { return "signature" } +func (r *signature) Parent() string { return r.app.Name() } +func (r *signature) Usage() string { return "" } +func (r *signature) ShortHelp() string { return "display selected identifier's signature" } +func (r *signature) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls signature helper/helper.go:8:6 + $ gopls signature helper/helper.go:#53 +`) + printFlagDefaults(f) +} + +func (r *signature) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("signature expects 1 argument (position)") + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + file, err := conn.openFile(ctx, from.URI()) + if err != nil { + return err + } + + loc, err := file.spanLocation(from) + if err != nil { + return err + } + + p := protocol.SignatureHelpParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + + s, err := conn.SignatureHelp(ctx, &p) + if err != nil { + return err + } + + if s == nil || len(s.Signatures) == 0 { + return tool.CommandLineErrorf("%v: not a function", from) + } + + // there is only ever one possible signature, + // see toProtocolSignatureHelp in lsp/signature_help.go + signature := s.Signatures[0] + fmt.Printf("%s\n", signature.Label) + switch x := signature.Documentation.Value.(type) { + case string: + if x != "" { + fmt.Printf("\n%s\n", x) + } + case protocol.MarkupContent: + if x.Value != "" { + fmt.Printf("\n%s\n", x.Value) + } + } + + return nil +} diff --git a/contribs/gnopls/internal/cmd/span.go b/contribs/gnopls/internal/cmd/span.go new file mode 100644 index 00000000000..4753d534350 --- /dev/null +++ b/contribs/gnopls/internal/cmd/span.go @@ -0,0 +1,238 @@ +// 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. + +package cmd + +// span and point represent positions and ranges in text files. + +import ( + "encoding/json" + "fmt" + "path" + "sort" + "strings" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// A span represents a range of text within a source file. The start +// and end points of a valid span may be hold either its byte offset, +// or its (line, column) pair, or both. Columns are measured in bytes. +// +// Spans are appropriate in user interfaces (e.g. command-line tools) +// and tests where a position is notated without access to the content +// of the file. +// +// Use protocol.Mapper to convert between span and other +// representations, such as go/token (also UTF-8) or the LSP protocol +// (UTF-16). The latter requires access to file contents. +// +// See overview comments at ../protocol/mapper.go. +type span struct { + v _span +} + +// point represents a single point within a file. +// In general this should only be used as part of a span, as on its own it +// does not carry enough information. +type point struct { + v _point +} + +// The span_/point_ types have public fields to support JSON encoding, +// but the span/point types hide these fields by defining methods that +// shadow them. (This is used by a few of the command-line tool +// subcommands, which emit spans and have a -json flag.) +// +// TODO(adonovan): simplify now that it's all internal to cmd. + +type _span struct { + URI protocol.DocumentURI `json:"uri"` + Start _point `json:"start"` + End _point `json:"end"` +} + +type _point struct { + Line int `json:"line"` // 1-based line number + Column int `json:"column"` // 1-based, UTF-8 codes (bytes) + Offset int `json:"offset"` // 0-based byte offset +} + +func newSpan(uri protocol.DocumentURI, start, end point) span { + s := span{v: _span{URI: uri, Start: start.v, End: end.v}} + s.v.clean() + return s +} + +func newPoint(line, col, offset int) point { + p := point{v: _point{Line: line, Column: col, Offset: offset}} + p.v.clean() + return p +} + +// sortSpans sorts spans into a stable but unspecified order. +func sortSpans(spans []span) { + sort.SliceStable(spans, func(i, j int) bool { + return compare(spans[i], spans[j]) < 0 + }) +} + +// compare implements a three-valued ordered comparison of Spans. +func compare(a, b span) int { + // This is a textual comparison. It does not perform path + // cleaning, case folding, resolution of symbolic links, + // testing for existence, or any I/O. + if cmp := strings.Compare(string(a.URI()), string(b.URI())); cmp != 0 { + return cmp + } + if cmp := comparePoint(a.v.Start, b.v.Start); cmp != 0 { + return cmp + } + return comparePoint(a.v.End, b.v.End) +} + +func comparePoint(a, b _point) int { + if !a.hasPosition() { + if a.Offset < b.Offset { + return -1 + } + if a.Offset > b.Offset { + return 1 + } + return 0 + } + if a.Line < b.Line { + return -1 + } + if a.Line > b.Line { + return 1 + } + if a.Column < b.Column { + return -1 + } + if a.Column > b.Column { + return 1 + } + return 0 +} + +func (s span) HasPosition() bool { return s.v.Start.hasPosition() } +func (s span) HasOffset() bool { return s.v.Start.hasOffset() } +func (s span) IsValid() bool { return s.v.Start.isValid() } +func (s span) IsPoint() bool { return s.v.Start == s.v.End } +func (s span) URI() protocol.DocumentURI { return s.v.URI } +func (s span) Start() point { return point{s.v.Start} } +func (s span) End() point { return point{s.v.End} } +func (s *span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) } +func (s *span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) } + +func (p point) HasPosition() bool { return p.v.hasPosition() } +func (p point) HasOffset() bool { return p.v.hasOffset() } +func (p point) IsValid() bool { return p.v.isValid() } +func (p *point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) } +func (p *point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) } +func (p point) Line() int { + if !p.v.hasPosition() { + panic(fmt.Errorf("position not set in %v", p.v)) + } + return p.v.Line +} +func (p point) Column() int { + if !p.v.hasPosition() { + panic(fmt.Errorf("position not set in %v", p.v)) + } + return p.v.Column +} +func (p point) Offset() int { + if !p.v.hasOffset() { + panic(fmt.Errorf("offset not set in %v", p.v)) + } + return p.v.Offset +} + +func (p _point) hasPosition() bool { return p.Line > 0 } +func (p _point) hasOffset() bool { return p.Offset >= 0 } +func (p _point) isValid() bool { return p.hasPosition() || p.hasOffset() } +func (p _point) isZero() bool { + return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0) +} + +func (s *_span) clean() { + //this presumes the points are already clean + if !s.End.isValid() || (s.End == _point{}) { + s.End = s.Start + } +} + +func (p *_point) clean() { + if p.Line < 0 { + p.Line = 0 + } + if p.Column <= 0 { + if p.Line > 0 { + p.Column = 1 + } else { + p.Column = 0 + } + } + if p.Offset == 0 && (p.Line > 1 || p.Column > 1) { + p.Offset = -1 + } +} + +// Format implements fmt.Formatter to print the Location in a standard form. +// The format produced is one that can be read back in using parseSpan. +// +// TODO(adonovan): this is esoteric, and the formatting options are +// never used outside of TestFormat. Replace with something simpler +// along the lines of MappedRange.String. +func (s span) Format(f fmt.State, c rune) { + fullForm := f.Flag('+') + preferOffset := f.Flag('#') + // we should always have a uri, simplify if it is file format + //TODO: make sure the end of the uri is unambiguous + uri := string(s.v.URI) + if c == 'f' { + uri = path.Base(uri) + } else if !fullForm { + uri = s.v.URI.Path() + } + fmt.Fprint(f, uri) + if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) { + return + } + // see which bits of start to write + printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition()) + printLine := s.HasPosition() && (fullForm || !printOffset) + printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1)) + fmt.Fprint(f, ":") + if printLine { + fmt.Fprintf(f, "%d", s.v.Start.Line) + } + if printColumn { + fmt.Fprintf(f, ":%d", s.v.Start.Column) + } + if printOffset { + fmt.Fprintf(f, "#%d", s.v.Start.Offset) + } + // start is written, do we need end? + if s.IsPoint() { + return + } + // we don't print the line if it did not change + printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line) + fmt.Fprint(f, "-") + if printLine { + fmt.Fprintf(f, "%d", s.v.End.Line) + } + if printColumn { + if printLine { + fmt.Fprint(f, ":") + } + fmt.Fprintf(f, "%d", s.v.End.Column) + } + if printOffset { + fmt.Fprintf(f, "#%d", s.v.End.Offset) + } +} diff --git a/contribs/gnopls/internal/cmd/spanformat_test.go b/contribs/gnopls/internal/cmd/spanformat_test.go new file mode 100644 index 00000000000..659d59ce2b3 --- /dev/null +++ b/contribs/gnopls/internal/cmd/spanformat_test.go @@ -0,0 +1,55 @@ +// 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. + +package cmd + +import ( + "fmt" + "path/filepath" + "strings" + "testing" +) + +func TestSpanFormat(t *testing.T) { + formats := []string{"%v", "%#v", "%+v"} + + // Element 0 is the input, and the elements 0-2 are the expected + // output in [%v %#v %+v] formats. Thus the first must be in + // canonical form (invariant under parseSpan + fmt.Sprint). + // The '#' form displays offsets; the '+' form outputs a URI. + // If len=4, element 0 is a noncanonical input and 1-3 are expected outputs. + for _, test := range [][]string{ + {"C:/file_a", "C:/file_a", "file:///C:/file_a:#0"}, + {"C:/file_b:1:2", "C:/file_b:1:2", "file:///C:/file_b:1:2"}, + {"C:/file_c:1000", "C:/file_c:1000", "file:///C:/file_c:1000:1"}, + {"C:/file_d:14:9", "C:/file_d:14:9", "file:///C:/file_d:14:9"}, + {"C:/file_e:1:2-7", "C:/file_e:1:2-7", "file:///C:/file_e:1:2-1:7"}, + {"C:/file_f:500-502", "C:/file_f:500-502", "file:///C:/file_f:500:1-502:1"}, + {"C:/file_g:3:7-8", "C:/file_g:3:7-8", "file:///C:/file_g:3:7-3:8"}, + {"C:/file_h:3:7-4:8", "C:/file_h:3:7-4:8", "file:///C:/file_h:3:7-4:8"}, + {"C:/file_i:#100", "C:/file_i:#100", "file:///C:/file_i:#100"}, + {"C:/file_j:#26-#28", "C:/file_j:#26-#28", "file:///C:/file_j:#26-0#28"}, // 0#28? + {"C:/file_h:3:7#26-4:8#37", // not canonical + "C:/file_h:3:7-4:8", "C:/file_h:#26-#37", "file:///C:/file_h:3:7#26-4:8#37"}} { + input := test[0] + spn := parseSpan(input) + wants := test[0:3] + if len(test) == 4 { + wants = test[1:4] + } + for i, format := range formats { + want := toPath(wants[i]) + if got := fmt.Sprintf(format, spn); got != want { + t.Errorf("Sprintf(%q, %q) = %q, want %q", format, input, got, want) + } + } + } +} + +func toPath(value string) string { + if strings.HasPrefix(value, "file://") { + return value + } + return filepath.FromSlash(value) +} diff --git a/contribs/gnopls/internal/cmd/stats.go b/contribs/gnopls/internal/cmd/stats.go new file mode 100644 index 00000000000..cc19a94fb84 --- /dev/null +++ b/contribs/gnopls/internal/cmd/stats.go @@ -0,0 +1,248 @@ +// 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 cmd + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "go/token" + "io/fs" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "time" + + "golang.org/x/tools/gopls/internal/filecache" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" + bugpkg "golang.org/x/tools/gopls/internal/util/bug" + versionpkg "golang.org/x/tools/gopls/internal/version" + "golang.org/x/tools/internal/event" +) + +type stats struct { + app *Application + + Anon bool `flag:"anon" help:"hide any fields that may contain user names, file names, or source code"` +} + +func (s *stats) Name() string { return "stats" } +func (r *stats) Parent() string { return r.app.Name() } +func (s *stats) Usage() string { return "" } +func (s *stats) ShortHelp() string { return "print workspace statistics" } + +func (s *stats) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Load the workspace for the current directory, and output a JSON summary of +workspace information relevant to performance. As a side effect, this command +populates the gopls file cache for the current workspace. + +By default, this command may include output that refers to the location or +content of user code. When the -anon flag is set, fields that may refer to user +code are hidden. + +Example: + $ gopls stats -anon +`) + printFlagDefaults(f) +} + +func (s *stats) Run(ctx context.Context, args ...string) error { + if s.app.Remote != "" { + // stats does not work with -remote. + // Other sessions on the daemon may interfere with results. + // Additionally, the type assertions in below only work if progress + // notifications bypass jsonrpc2 serialization. + return fmt.Errorf("the stats subcommand does not work with -remote") + } + + if !s.app.Verbose { + event.SetExporter(nil) // don't log errors to stderr + } + + stats := GoplsStats{ + GOOS: runtime.GOOS, + GOARCH: runtime.GOARCH, + GOPLSCACHE: os.Getenv("GOPLSCACHE"), + GoVersion: runtime.Version(), + GoplsVersion: versionpkg.Version(), + GOPACKAGESDRIVER: os.Getenv("GOPACKAGESDRIVER"), + } + + opts := s.app.options + s.app.options = func(o *settings.Options) { + if opts != nil { + opts(o) + } + o.VerboseWorkDoneProgress = true + } + + // do executes a timed section of the stats command. + do := func(name string, f func() error) (time.Duration, error) { + start := time.Now() + fmt.Fprintf(os.Stderr, "%-30s", name+"...") + if err := f(); err != nil { + return time.Since(start), err + } + d := time.Since(start) + fmt.Fprintf(os.Stderr, "done (%v)\n", d) + return d, nil + } + + var conn *connection + iwlDuration, err := do("Initializing workspace", func() (err error) { + conn, err = s.app.connect(ctx) + if err != nil { + return err + } + select { + case <-conn.client.iwlDone: + case <-ctx.Done(): + return ctx.Err() + } + return nil + }) + if err != nil { + return err + } + defer conn.terminate(ctx) + + stats.InitialWorkspaceLoadDuration = fmt.Sprint(iwlDuration) + + // Gather bug reports produced by any process using + // this executable and persisted in the cache. + do("Gathering bug reports", func() error { + stats.CacheDir, stats.BugReports = filecache.BugReports() + if stats.BugReports == nil { + stats.BugReports = []bugpkg.Bug{} // non-nil for JSON + } + return nil + }) + + if _, err := do("Querying memstats", func() error { + memStats, err := conn.executeCommand(ctx, &protocol.Command{ + Command: command.MemStats.String(), + }) + if err != nil { + return err + } + stats.MemStats = memStats.(command.MemStatsResult) + return nil + }); err != nil { + return err + } + + if _, err := do("Querying workspace stats", func() error { + wsStats, err := conn.executeCommand(ctx, &protocol.Command{ + Command: command.WorkspaceStats.String(), + }) + if err != nil { + return err + } + stats.WorkspaceStats = wsStats.(command.WorkspaceStatsResult) + return nil + }); err != nil { + return err + } + + if _, err := do("Collecting directory info", func() error { + var err error + stats.DirStats, err = findDirStats() + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + + // Filter JSON output to fields that are consistent with s.Anon. + okFields := make(map[string]interface{}) + { + v := reflect.ValueOf(stats) + t := v.Type() + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if !token.IsExported(f.Name) { + continue + } + vf := v.FieldByName(f.Name) + if s.Anon && f.Tag.Get("anon") != "ok" && !vf.IsZero() { + // Fields that can be served with -anon must be explicitly marked as OK. + // But, if it's zero value, it's ok to print. + continue + } + okFields[f.Name] = vf.Interface() + } + } + data, err := json.MarshalIndent(okFields, "", " ") + if err != nil { + return err + } + + os.Stdout.Write(data) + fmt.Println() + return nil +} + +// GoplsStats holds information extracted from a gopls session in the current +// workspace. +// +// Fields that should be printed with the -anon flag should be explicitly +// marked as `anon:"ok"`. Only fields that cannot refer to user files or code +// should be marked as such. +type GoplsStats struct { + GOOS, GOARCH string `anon:"ok"` + GOPLSCACHE string + GoVersion string `anon:"ok"` + GoplsVersion string `anon:"ok"` + GOPACKAGESDRIVER string + InitialWorkspaceLoadDuration string `anon:"ok"` // in time.Duration string form + CacheDir string + BugReports []bugpkg.Bug + MemStats command.MemStatsResult `anon:"ok"` + WorkspaceStats command.WorkspaceStatsResult `anon:"ok"` + DirStats dirStats `anon:"ok"` +} + +type dirStats struct { + Files int + TestdataFiles int + GoFiles int + ModFiles int + Dirs int +} + +// findDirStats collects information about the current directory and its +// subdirectories. +func findDirStats() (dirStats, error) { + var ds dirStats + filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + ds.Dirs++ + } else { + ds.Files++ + slashed := filepath.ToSlash(path) + switch { + case strings.Contains(slashed, "/testdata/") || strings.HasPrefix(slashed, "testdata/"): + ds.TestdataFiles++ + case strings.HasSuffix(path, ".go"): + ds.GoFiles++ + case strings.HasSuffix(path, ".mod"): + ds.ModFiles++ + } + } + return nil + }) + return ds, nil +} diff --git a/contribs/gnopls/internal/cmd/subcommands.go b/contribs/gnopls/internal/cmd/subcommands.go new file mode 100644 index 00000000000..e30c42b85f9 --- /dev/null +++ b/contribs/gnopls/internal/cmd/subcommands.go @@ -0,0 +1,59 @@ +// 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 cmd + +import ( + "context" + "flag" + "fmt" + "text/tabwriter" + + "golang.org/x/tools/internal/tool" +) + +// subcommands is a helper that may be embedded for commands that delegate to +// subcommands. +type subcommands []tool.Application + +func (s subcommands) DetailedHelp(f *flag.FlagSet) { + w := tabwriter.NewWriter(f.Output(), 0, 0, 2, ' ', 0) + defer w.Flush() + fmt.Fprint(w, "\nSubcommand:\n") + for _, c := range s { + fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.ShortHelp()) + } + printFlagDefaults(f) +} + +func (s subcommands) Usage() string { return " [arg]..." } + +func (s subcommands) Run(ctx context.Context, args ...string) error { + if len(args) == 0 { + return tool.CommandLineErrorf("must provide subcommand") + } + command, args := args[0], args[1:] + for _, c := range s { + if c.Name() == command { + s := flag.NewFlagSet(c.Name(), flag.ExitOnError) + return tool.Run(ctx, s, c, args) + } + } + return tool.CommandLineErrorf("unknown subcommand %v", command) +} + +func (s subcommands) Commands() []tool.Application { return s } + +// getSubcommands returns the subcommands of a given Application. +func getSubcommands(a tool.Application) []tool.Application { + // This interface is satisfied both by tool.Applications + // that embed subcommands, and by *cmd.Application. + type hasCommands interface { + Commands() []tool.Application + } + if sub, ok := a.(hasCommands); ok { + return sub.Commands() + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/symbols.go b/contribs/gnopls/internal/cmd/symbols.go new file mode 100644 index 00000000000..663a08f4be1 --- /dev/null +++ b/contribs/gnopls/internal/cmd/symbols.go @@ -0,0 +1,115 @@ +// 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. + +package cmd + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "sort" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/tool" +) + +// symbols implements the symbols verb for gopls +type symbols struct { + app *Application +} + +func (r *symbols) Name() string { return "symbols" } +func (r *symbols) Parent() string { return r.app.Name() } +func (r *symbols) Usage() string { return "" } +func (r *symbols) ShortHelp() string { return "display selected file's symbols" } +func (r *symbols) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + $ gopls symbols helper/helper.go +`) + printFlagDefaults(f) +} +func (r *symbols) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("symbols expects 1 argument (position)") + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := parseSpan(args[0]) + p := protocol.DocumentSymbolParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: from.URI(), + }, + } + symbols, err := conn.DocumentSymbol(ctx, &p) + if err != nil { + return err + } + for _, s := range symbols { + if m, ok := s.(map[string]interface{}); ok { + s, err = mapToSymbol(m) + if err != nil { + return err + } + } + switch t := s.(type) { + case protocol.DocumentSymbol: + printDocumentSymbol(t) + case protocol.SymbolInformation: + printSymbolInformation(t) + } + } + return nil +} + +func mapToSymbol(m map[string]interface{}) (interface{}, error) { + b, err := json.Marshal(m) + if err != nil { + return nil, err + } + + if _, ok := m["selectionRange"]; ok { + var s protocol.DocumentSymbol + if err := json.Unmarshal(b, &s); err != nil { + return nil, err + } + return s, nil + } + + var s protocol.SymbolInformation + if err := json.Unmarshal(b, &s); err != nil { + return nil, err + } + return s, nil +} + +func printDocumentSymbol(s protocol.DocumentSymbol) { + fmt.Printf("%s %s %s\n", s.Name, s.Kind, positionToString(s.SelectionRange)) + // Sort children for consistency + sort.Slice(s.Children, func(i, j int) bool { + return s.Children[i].Name < s.Children[j].Name + }) + for _, c := range s.Children { + fmt.Printf("\t%s %s %s\n", c.Name, c.Kind, positionToString(c.SelectionRange)) + } +} + +func printSymbolInformation(s protocol.SymbolInformation) { + fmt.Printf("%s %s %s\n", s.Name, s.Kind, positionToString(s.Location.Range)) +} + +func positionToString(r protocol.Range) string { + return fmt.Sprintf("%v:%v-%v:%v", + r.Start.Line+1, + r.Start.Character+1, + r.End.Line+1, + r.End.Character+1, + ) +} diff --git a/contribs/gnopls/internal/cmd/usage/api-json.hlp b/contribs/gnopls/internal/cmd/usage/api-json.hlp new file mode 100644 index 00000000000..304c43d3b47 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/api-json.hlp @@ -0,0 +1,8 @@ +print JSON describing gopls API + +Usage: + gopls [flags] api-json + +The api-json command prints a JSON value that describes +and documents all gopls' public interfaces. +Its schema is defined by golang.org/x/tools/gopls/internal/doc.API. diff --git a/contribs/gnopls/internal/cmd/usage/bug.hlp b/contribs/gnopls/internal/cmd/usage/bug.hlp new file mode 100644 index 00000000000..772d54d5f1b --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/bug.hlp @@ -0,0 +1,4 @@ +report a bug in gopls + +Usage: + gopls [flags] bug diff --git a/contribs/gnopls/internal/cmd/usage/call_hierarchy.hlp b/contribs/gnopls/internal/cmd/usage/call_hierarchy.hlp new file mode 100644 index 00000000000..07fccc8285e --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/call_hierarchy.hlp @@ -0,0 +1,10 @@ +display selected identifier's call hierarchy + +Usage: + gopls [flags] call_hierarchy + +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls call_hierarchy helper/helper.go:8:6 + $ gopls call_hierarchy helper/helper.go:#53 diff --git a/contribs/gnopls/internal/cmd/usage/check.hlp b/contribs/gnopls/internal/cmd/usage/check.hlp new file mode 100644 index 00000000000..eda1a25a191 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/check.hlp @@ -0,0 +1,8 @@ +show diagnostic results for the specified file + +Usage: + gopls [flags] check + +Example: show the diagnostic results of this file: + + $ gopls check internal/cmd/check.go diff --git a/contribs/gnopls/internal/cmd/usage/codeaction.hlp b/contribs/gnopls/internal/cmd/usage/codeaction.hlp new file mode 100644 index 00000000000..6d6923ef458 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/codeaction.hlp @@ -0,0 +1,85 @@ +list or execute code actions + +Usage: + gopls [flags] codeaction [codeaction-flags] filename[:line[:col]] + + +The codeaction command lists or executes code actions for the +specified file or range of a file. Each code action contains +either an edit to be directly applied to the file, or a command +to be executed by the server, which may have an effect such as: +- requesting that the client apply an edit; +- changing the state of the server; or +- requesting that the client open a document. + +The -kind and and -title flags filter the list of actions. + +The -kind flag specifies a comma-separated list of LSP CodeAction kinds. +Only actions of these kinds will be requested from the server. +Valid kinds include: + + gopls.doc.features + quickfix + refactor + refactor.extract + refactor.extract.function + refactor.extract.method + refactor.extract.toNewFile + refactor.extract.variable + refactor.inline + refactor.inline.call + refactor.rewrite + refactor.rewrite.changeQuote + refactor.rewrite.fillStruct + refactor.rewrite.fillSwitch + refactor.rewrite.invertIf + refactor.rewrite.joinLines + refactor.rewrite.removeUnusedParam + refactor.rewrite.splitLines + source + source.assembly + source.doc + source.fixAll + source.freesymbols + source.organizeImports + source.test + +Kinds are hierarchical, so "refactor" includes "refactor.inline". +(Note: actions of kind "source.test" are not returned unless explicitly +requested.) + +The -title flag specifies a regular expression that must match the +action's title. (Ideally kinds would be specific enough that this +isn't necessary; we really need to subdivide refactor.rewrite; see +gopls/internal/settings/codeactionkind.go.) + +The -exec flag causes the first matching code action to be executed. +Without the flag, the matching actions are merely listed. + +It is not currently possible to execute more than one action, +as that requires a way to detect and resolve conflicts. +TODO(adonovan): support it when golang/go#67049 is resolved. + +If executing an action causes the server to send a patch to the +client, the usual -write, -preserve, -diff, and -list flags govern how +the client deals with the patch. + +Example: execute the first "quick fix" in the specified file and show the diff: + + $ gopls codeaction -kind=quickfix -exec -diff ./gopls/main.go + +codeaction-flags: + -d,-diff + display diffs instead of edited file content + -exec + execute the first matching code action + -kind=string + comma-separated list of code action kinds to filter + -l,-list + display names of edited files + -preserve + with -write, make copies of original files + -title=string + regular expression to match title + -w,-write + write edited content to source files diff --git a/contribs/gnopls/internal/cmd/usage/codelens.hlp b/contribs/gnopls/internal/cmd/usage/codelens.hlp new file mode 100644 index 00000000000..5766d7fd189 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/codelens.hlp @@ -0,0 +1,35 @@ +List or execute code lenses for a file + +Usage: + gopls [flags] codelens [codelens-flags] file[:line[:col]] [title] + +The codelens command lists or executes code lenses for the specified +file, or line within a file. A code lens is a command associated with +a position in the code. + +With an optional title argment, only code lenses matching that +title are considered. + +By default, the codelens command lists the available lenses for the +specified file or line within a file, including the title and +title of the command. With the -exec flag, the first matching command +is executed, and its output is printed to stdout. + +Example: + + $ gopls codelens a_test.go # list code lenses in a file + $ gopls codelens a_test.go:10 # list code lenses on line 10 + $ gopls codelens a_test.go gopls.test # list gopls.test commands + $ gopls codelens -run a_test.go:10 gopls.test # run a specific test + +codelens-flags: + -d,-diff + display diffs instead of edited file content + -exec + execute the first matching code lens + -l,-list + display names of edited files + -preserve + with -write, make copies of original files + -w,-write + write edited content to source files diff --git a/contribs/gnopls/internal/cmd/usage/definition.hlp b/contribs/gnopls/internal/cmd/usage/definition.hlp new file mode 100644 index 00000000000..80825c3b049 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/definition.hlp @@ -0,0 +1,15 @@ +show declaration of selected identifier + +Usage: + gopls [flags] definition [definition-flags] + +Example: show the definition of the identifier at syntax at offset 44 in this file (flag.FlagSet): + + $ gopls definition internal/cmd/definition.go:44:47 + $ gopls definition internal/cmd/definition.go:#1270 + +definition-flags: + -json + emit output in JSON format + -markdown + support markdown in responses diff --git a/contribs/gnopls/internal/cmd/usage/execute.hlp b/contribs/gnopls/internal/cmd/usage/execute.hlp new file mode 100644 index 00000000000..b5a7b1cefbc --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/execute.hlp @@ -0,0 +1,28 @@ +Execute a gopls custom LSP command + +Usage: + gopls [flags] execute [flags] command argument... + +The execute command sends an LSP ExecuteCommand request to gopls, +with a set of optional JSON argument values. +Some commands return a result, also JSON. + +Gopls' command set is defined by the command.Interface type; see +https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol/command#Interface. +It is not a stable interface: commands may change or disappear without notice. + +Examples: + + $ gopls execute gopls.add_import '{"ImportPath": "fmt", "URI": "file:///hello.go"}' + $ gopls execute gopls.run_tests '{"URI": "file:///a_test.go", "Tests": ["Test"]}' + $ gopls execute gopls.list_known_packages '{"URI": "file:///hello.go"}' + +execute-flags: + -d,-diff + display diffs instead of edited file content + -l,-list + display names of edited files + -preserve + with -write, make copies of original files + -w,-write + write edited content to source files diff --git a/contribs/gnopls/internal/cmd/usage/fix.hlp b/contribs/gnopls/internal/cmd/usage/fix.hlp new file mode 100644 index 00000000000..b6819985a8b --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/fix.hlp @@ -0,0 +1,5 @@ +apply suggested fixes (obsolete) + +Usage: + gopls [flags] fix +No longer supported; use "gopls codeaction" instead. \ No newline at end of file diff --git a/contribs/gnopls/internal/cmd/usage/folding_ranges.hlp b/contribs/gnopls/internal/cmd/usage/folding_ranges.hlp new file mode 100644 index 00000000000..4af2da61501 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/folding_ranges.hlp @@ -0,0 +1,8 @@ +display selected file's folding ranges + +Usage: + gopls [flags] folding_ranges + +Example: + + $ gopls folding_ranges helper/helper.go diff --git a/contribs/gnopls/internal/cmd/usage/format.hlp b/contribs/gnopls/internal/cmd/usage/format.hlp new file mode 100644 index 00000000000..389532babf4 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/format.hlp @@ -0,0 +1,20 @@ +format the code according to the go standard + +Usage: + gopls [flags] format [format-flags] + +The arguments supplied may be simple file names, or ranges within files. + +Example: reformat this file: + + $ gopls format -w internal/cmd/check.go + +format-flags: + -d,-diff + display diffs instead of edited file content + -l,-list + display names of edited files + -preserve + with -write, make copies of original files + -w,-write + write edited content to source files diff --git a/contribs/gnopls/internal/cmd/usage/help.hlp b/contribs/gnopls/internal/cmd/usage/help.hlp new file mode 100644 index 00000000000..f0ff44a4d59 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/help.hlp @@ -0,0 +1,10 @@ +print usage information for subcommands + +Usage: + gopls [flags] help + + +Examples: +$ gopls help # main gopls help message +$ gopls help remote # help on 'remote' command +$ gopls help remote sessions # help on 'remote sessions' subcommand diff --git a/contribs/gnopls/internal/cmd/usage/highlight.hlp b/contribs/gnopls/internal/cmd/usage/highlight.hlp new file mode 100644 index 00000000000..e128eb7de56 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/highlight.hlp @@ -0,0 +1,10 @@ +display selected identifier's highlights + +Usage: + gopls [flags] highlight + +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls highlight helper/helper.go:8:6 + $ gopls highlight helper/helper.go:#53 diff --git a/contribs/gnopls/internal/cmd/usage/implementation.hlp b/contribs/gnopls/internal/cmd/usage/implementation.hlp new file mode 100644 index 00000000000..09414f1904a --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/implementation.hlp @@ -0,0 +1,10 @@ +display selected identifier's implementation + +Usage: + gopls [flags] implementation + +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls implementation helper/helper.go:8:6 + $ gopls implementation helper/helper.go:#53 diff --git a/contribs/gnopls/internal/cmd/usage/imports.hlp b/contribs/gnopls/internal/cmd/usage/imports.hlp new file mode 100644 index 00000000000..789c832f471 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/imports.hlp @@ -0,0 +1,18 @@ +updates import statements + +Usage: + gopls [flags] imports [imports-flags] + +Example: update imports statements in a file: + + $ gopls imports -w internal/cmd/check.go + +imports-flags: + -d,-diff + display diffs instead of edited file content + -l,-list + display names of edited files + -preserve + with -write, make copies of original files + -w,-write + write edited content to source files diff --git a/contribs/gnopls/internal/cmd/usage/inspect.hlp b/contribs/gnopls/internal/cmd/usage/inspect.hlp new file mode 100644 index 00000000000..3d0a0f3c4db --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/inspect.hlp @@ -0,0 +1,8 @@ +interact with the gopls daemon (deprecated: use 'remote') + +Usage: + gopls [flags] inspect [arg]... + +Subcommand: + sessions print information about current gopls sessions + debug start the debug server diff --git a/contribs/gnopls/internal/cmd/usage/licenses.hlp b/contribs/gnopls/internal/cmd/usage/licenses.hlp new file mode 100644 index 00000000000..ab60ebc2f18 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/licenses.hlp @@ -0,0 +1,4 @@ +print licenses of included software + +Usage: + gopls [flags] licenses diff --git a/contribs/gnopls/internal/cmd/usage/links.hlp b/contribs/gnopls/internal/cmd/usage/links.hlp new file mode 100644 index 00000000000..1550625961d --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/links.hlp @@ -0,0 +1,12 @@ +list links in a file + +Usage: + gopls [flags] links [links-flags] + +Example: list links contained within a file: + + $ gopls links internal/cmd/check.go + +links-flags: + -json + emit document links in JSON format diff --git a/contribs/gnopls/internal/cmd/usage/prepare_rename.hlp b/contribs/gnopls/internal/cmd/usage/prepare_rename.hlp new file mode 100644 index 00000000000..7f8a6f32db0 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/prepare_rename.hlp @@ -0,0 +1,10 @@ +test validity of a rename operation at location + +Usage: + gopls [flags] prepare_rename + +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls prepare_rename helper/helper.go:8:6 + $ gopls prepare_rename helper/helper.go:#53 diff --git a/contribs/gnopls/internal/cmd/usage/references.hlp b/contribs/gnopls/internal/cmd/usage/references.hlp new file mode 100644 index 00000000000..c55ef033708 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/references.hlp @@ -0,0 +1,14 @@ +display selected identifier's references + +Usage: + gopls [flags] references [references-flags] + +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls references helper/helper.go:8:6 + $ gopls references helper/helper.go:#53 + +references-flags: + -d,-declaration + include the declaration of the specified identifier in the results diff --git a/contribs/gnopls/internal/cmd/usage/remote.hlp b/contribs/gnopls/internal/cmd/usage/remote.hlp new file mode 100644 index 00000000000..dd6034f46a6 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/remote.hlp @@ -0,0 +1,8 @@ +interact with the gopls daemon + +Usage: + gopls [flags] remote [arg]... + +Subcommand: + sessions print information about current gopls sessions + debug start the debug server diff --git a/contribs/gnopls/internal/cmd/usage/rename.hlp b/contribs/gnopls/internal/cmd/usage/rename.hlp new file mode 100644 index 00000000000..7b6d7f96b55 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/rename.hlp @@ -0,0 +1,20 @@ +rename selected identifier + +Usage: + gopls [flags] rename [rename-flags] + +Example: + + $ # 1-based location (:line:column or :#position) of the thing to change + $ gopls rename helper/helper.go:8:6 Foo + $ gopls rename helper/helper.go:#53 Foo + +rename-flags: + -d,-diff + display diffs instead of edited file content + -l,-list + display names of edited files + -preserve + with -write, make copies of original files + -w,-write + write edited content to source files diff --git a/contribs/gnopls/internal/cmd/usage/semtok.hlp b/contribs/gnopls/internal/cmd/usage/semtok.hlp new file mode 100644 index 00000000000..e368212f255 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/semtok.hlp @@ -0,0 +1,8 @@ +show semantic tokens for the specified file + +Usage: + gopls [flags] semtok + +Example: show the semantic tokens for this file: + + $ gopls semtok internal/cmd/semtok.go diff --git a/contribs/gnopls/internal/cmd/usage/serve.hlp b/contribs/gnopls/internal/cmd/usage/serve.hlp new file mode 100644 index 00000000000..370cbce83df --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/serve.hlp @@ -0,0 +1,30 @@ +run a server for Go code using the Language Server Protocol + +Usage: + gopls [flags] serve [server-flags] + gopls [flags] [server-flags] + +The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as +a child of an editor process. + +server-flags: + -debug=string + serve debug information on the supplied address + -listen=string + address on which to listen for remote connections. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. Otherwise, TCP is used. + -listen.timeout=duration + when used with -listen, shut down the server when there are no connected clients for this duration + -logfile=string + filename to log to. if value is "auto", then logging to a default output file is enabled + -mode=string + no effect + -port=int + port on which to run gopls for debugging purposes + -remote.debug=string + when used with -remote=auto, the -debug value used to start the daemon + -remote.listen.timeout=duration + when used with -remote=auto, the -listen.timeout value used to start the daemon (default 1m0s) + -remote.logfile=string + when used with -remote=auto, the -logfile value used to start the daemon + -rpc.trace + print the full rpc trace in lsp inspector format diff --git a/contribs/gnopls/internal/cmd/usage/signature.hlp b/contribs/gnopls/internal/cmd/usage/signature.hlp new file mode 100644 index 00000000000..f9fd0bfb7ed --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/signature.hlp @@ -0,0 +1,10 @@ +display selected identifier's signature + +Usage: + gopls [flags] signature + +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls signature helper/helper.go:8:6 + $ gopls signature helper/helper.go:#53 diff --git a/contribs/gnopls/internal/cmd/usage/stats.hlp b/contribs/gnopls/internal/cmd/usage/stats.hlp new file mode 100644 index 00000000000..71cce07c008 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/stats.hlp @@ -0,0 +1,17 @@ +print workspace statistics + +Usage: + gopls [flags] stats + +Load the workspace for the current directory, and output a JSON summary of +workspace information relevant to performance. As a side effect, this command +populates the gopls file cache for the current workspace. + +By default, this command may include output that refers to the location or +content of user code. When the -anon flag is set, fields that may refer to user +code are hidden. + +Example: + $ gopls stats -anon + -anon + hide any fields that may contain user names, file names, or source code diff --git a/contribs/gnopls/internal/cmd/usage/symbols.hlp b/contribs/gnopls/internal/cmd/usage/symbols.hlp new file mode 100644 index 00000000000..2aa36aa8413 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/symbols.hlp @@ -0,0 +1,7 @@ +display selected file's symbols + +Usage: + gopls [flags] symbols + +Example: + $ gopls symbols helper/helper.go diff --git a/contribs/gnopls/internal/cmd/usage/usage-v.hlp b/contribs/gnopls/internal/cmd/usage/usage-v.hlp new file mode 100644 index 00000000000..21c124eef97 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/usage-v.hlp @@ -0,0 +1,89 @@ + +gopls is a Go language server. + +It is typically used with an editor to provide language features. When no +command is specified, gopls will default to the 'serve' command. The language +features can also be accessed via the gopls command-line interface. + +For documentation of all its features, see: + + https://github.com/golang/tools/blob/master/gopls/doc/features + +Usage: + gopls help [] + +Command: + +Main + serve run a server for Go code using the Language Server Protocol + version print the gopls version information + bug report a bug in gopls + help print usage information for subcommands + api-json print JSON describing gopls API + licenses print licenses of included software + +Features + call_hierarchy display selected identifier's call hierarchy + check show diagnostic results for the specified file + codeaction list or execute code actions + codelens List or execute code lenses for a file + definition show declaration of selected identifier + execute Execute a gopls custom LSP command + fix apply suggested fixes (obsolete) + folding_ranges display selected file's folding ranges + format format the code according to the go standard + highlight display selected identifier's highlights + implementation display selected identifier's implementation + imports updates import statements + remote interact with the gopls daemon + inspect interact with the gopls daemon (deprecated: use 'remote') + links list links in a file + prepare_rename test validity of a rename operation at location + references display selected identifier's references + rename rename selected identifier + semtok show semantic tokens for the specified file + signature display selected identifier's signature + stats print workspace statistics + symbols display selected file's symbols + workspace_symbol search symbols in workspace + +Internal Use Only + vulncheck run vulncheck analysis (internal-use only) + +flags: + -debug=string + serve debug information on the supplied address + -listen=string + address on which to listen for remote connections. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. Otherwise, TCP is used. + -listen.timeout=duration + when used with -listen, shut down the server when there are no connected clients for this duration + -logfile=string + filename to log to. if value is "auto", then logging to a default output file is enabled + -mode=string + no effect + -ocagent=string + the address of the ocagent (e.g. http://localhost:55678), or off (default "off") + -port=int + port on which to run gopls for debugging purposes + -profile.alloc=string + write alloc profile to this file + -profile.cpu=string + write CPU profile to this file + -profile.mem=string + write memory profile to this file + -profile.trace=string + write trace log to this file + -remote=string + forward all commands to a remote lsp specified by this flag. With no special prefix, this is assumed to be a TCP address. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. If 'auto', or prefixed by 'auto;', the remote address is automatically resolved based on the executing environment. + -remote.debug=string + when used with -remote=auto, the -debug value used to start the daemon + -remote.listen.timeout=duration + when used with -remote=auto, the -listen.timeout value used to start the daemon (default 1m0s) + -remote.logfile=string + when used with -remote=auto, the -logfile value used to start the daemon + -rpc.trace + print the full rpc trace in lsp inspector format + -v,-verbose + verbose output + -vv,-veryverbose + very verbose output diff --git a/contribs/gnopls/internal/cmd/usage/usage.hlp b/contribs/gnopls/internal/cmd/usage/usage.hlp new file mode 100644 index 00000000000..9ee0077ac95 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/usage.hlp @@ -0,0 +1,86 @@ + +gopls is a Go language server. + +It is typically used with an editor to provide language features. When no +command is specified, gopls will default to the 'serve' command. The language +features can also be accessed via the gopls command-line interface. + +For documentation of all its features, see: + + https://github.com/golang/tools/blob/master/gopls/doc/features + +Usage: + gopls help [] + +Command: + +Main + serve run a server for Go code using the Language Server Protocol + version print the gopls version information + bug report a bug in gopls + help print usage information for subcommands + api-json print JSON describing gopls API + licenses print licenses of included software + +Features + call_hierarchy display selected identifier's call hierarchy + check show diagnostic results for the specified file + codeaction list or execute code actions + codelens List or execute code lenses for a file + definition show declaration of selected identifier + execute Execute a gopls custom LSP command + fix apply suggested fixes (obsolete) + folding_ranges display selected file's folding ranges + format format the code according to the go standard + highlight display selected identifier's highlights + implementation display selected identifier's implementation + imports updates import statements + remote interact with the gopls daemon + inspect interact with the gopls daemon (deprecated: use 'remote') + links list links in a file + prepare_rename test validity of a rename operation at location + references display selected identifier's references + rename rename selected identifier + semtok show semantic tokens for the specified file + signature display selected identifier's signature + stats print workspace statistics + symbols display selected file's symbols + workspace_symbol search symbols in workspace + +flags: + -debug=string + serve debug information on the supplied address + -listen=string + address on which to listen for remote connections. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. Otherwise, TCP is used. + -listen.timeout=duration + when used with -listen, shut down the server when there are no connected clients for this duration + -logfile=string + filename to log to. if value is "auto", then logging to a default output file is enabled + -mode=string + no effect + -ocagent=string + the address of the ocagent (e.g. http://localhost:55678), or off (default "off") + -port=int + port on which to run gopls for debugging purposes + -profile.alloc=string + write alloc profile to this file + -profile.cpu=string + write CPU profile to this file + -profile.mem=string + write memory profile to this file + -profile.trace=string + write trace log to this file + -remote=string + forward all commands to a remote lsp specified by this flag. With no special prefix, this is assumed to be a TCP address. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. If 'auto', or prefixed by 'auto;', the remote address is automatically resolved based on the executing environment. + -remote.debug=string + when used with -remote=auto, the -debug value used to start the daemon + -remote.listen.timeout=duration + when used with -remote=auto, the -listen.timeout value used to start the daemon (default 1m0s) + -remote.logfile=string + when used with -remote=auto, the -logfile value used to start the daemon + -rpc.trace + print the full rpc trace in lsp inspector format + -v,-verbose + verbose output + -vv,-veryverbose + very verbose output diff --git a/contribs/gnopls/internal/cmd/usage/version.hlp b/contribs/gnopls/internal/cmd/usage/version.hlp new file mode 100644 index 00000000000..3a09ddedf65 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/version.hlp @@ -0,0 +1,6 @@ +print the gopls version information + +Usage: + gopls [flags] version + -json + outputs in json format. diff --git a/contribs/gnopls/internal/cmd/usage/vulncheck.hlp b/contribs/gnopls/internal/cmd/usage/vulncheck.hlp new file mode 100644 index 00000000000..7f2818dd40c --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/vulncheck.hlp @@ -0,0 +1,13 @@ +run vulncheck analysis (internal-use only) + +Usage: + gopls [flags] vulncheck + + WARNING: this command is for internal-use only. + + By default, the command outputs a JSON-encoded + golang.org/x/tools/gopls/internal/protocol/command.VulncheckResult + message. + Example: + $ gopls vulncheck + diff --git a/contribs/gnopls/internal/cmd/usage/workspace_symbol.hlp b/contribs/gnopls/internal/cmd/usage/workspace_symbol.hlp new file mode 100644 index 00000000000..ed22e989ee3 --- /dev/null +++ b/contribs/gnopls/internal/cmd/usage/workspace_symbol.hlp @@ -0,0 +1,13 @@ +search symbols in workspace + +Usage: + gopls [flags] workspace_symbol [workspace_symbol-flags] + +Example: + + $ gopls workspace_symbol -matcher fuzzy 'wsymbols' + +workspace_symbol-flags: + -matcher=string + specifies the type of matcher: fuzzy, fastfuzzy, casesensitive, or caseinsensitive. + The default is caseinsensitive. diff --git a/contribs/gnopls/internal/cmd/vulncheck.go b/contribs/gnopls/internal/cmd/vulncheck.go new file mode 100644 index 00000000000..7babf0d14d7 --- /dev/null +++ b/contribs/gnopls/internal/cmd/vulncheck.go @@ -0,0 +1,47 @@ +// 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 cmd + +import ( + "context" + "flag" + "fmt" + "os" + + "golang.org/x/tools/gopls/internal/vulncheck/scan" +) + +// vulncheck implements the vulncheck command. +// TODO(hakim): hide from the public. +type vulncheck struct { + app *Application +} + +func (v *vulncheck) Name() string { return "vulncheck" } +func (v *vulncheck) Parent() string { return v.app.Name() } +func (v *vulncheck) Usage() string { return "" } +func (v *vulncheck) ShortHelp() string { + return "run vulncheck analysis (internal-use only)" +} +func (v *vulncheck) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` + WARNING: this command is for internal-use only. + + By default, the command outputs a JSON-encoded + golang.org/x/tools/gopls/internal/protocol/command.VulncheckResult + message. + Example: + $ gopls vulncheck + +`) +} + +func (v *vulncheck) Run(ctx context.Context, args ...string) error { + if err := scan.Main(ctx, args...); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + return nil +} diff --git a/contribs/gnopls/internal/cmd/workspace_symbol.go b/contribs/gnopls/internal/cmd/workspace_symbol.go new file mode 100644 index 00000000000..aba33fa9d2a --- /dev/null +++ b/contribs/gnopls/internal/cmd/workspace_symbol.go @@ -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 cmd + +import ( + "context" + "flag" + "fmt" + "strings" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/tool" +) + +// workspaceSymbol implements the workspace_symbol verb for gopls. +type workspaceSymbol struct { + Matcher string `flag:"matcher" help:"specifies the type of matcher: fuzzy, fastfuzzy, casesensitive, or caseinsensitive.\nThe default is caseinsensitive."` + + app *Application +} + +func (r *workspaceSymbol) Name() string { return "workspace_symbol" } +func (r *workspaceSymbol) Parent() string { return r.app.Name() } +func (r *workspaceSymbol) Usage() string { return "[workspace_symbol-flags] " } +func (r *workspaceSymbol) ShortHelp() string { return "search symbols in workspace" } +func (r *workspaceSymbol) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ gopls workspace_symbol -matcher fuzzy 'wsymbols' + +workspace_symbol-flags: +`) + printFlagDefaults(f) +} + +func (r *workspaceSymbol) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("workspace_symbol expects 1 argument") + } + + opts := r.app.options + r.app.options = func(o *settings.Options) { + if opts != nil { + opts(o) + } + switch strings.ToLower(r.Matcher) { + case "fuzzy": + o.SymbolMatcher = settings.SymbolFuzzy + case "casesensitive": + o.SymbolMatcher = settings.SymbolCaseSensitive + case "fastfuzzy": + o.SymbolMatcher = settings.SymbolFastFuzzy + default: + o.SymbolMatcher = settings.SymbolCaseInsensitive + } + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + p := protocol.WorkspaceSymbolParams{ + Query: args[0], + } + + symbols, err := conn.Symbol(ctx, &p) + if err != nil { + return err + } + for _, s := range symbols { + f, err := conn.openFile(ctx, s.Location.URI) + if err != nil { + return err + } + span, err := f.locationSpan(s.Location) + if err != nil { + return err + } + fmt.Printf("%s %s %s\n", span, s.Name, s.Kind) + } + + return nil +} diff --git a/contribs/gnopls/internal/debug/info.go b/contribs/gnopls/internal/debug/info.go new file mode 100644 index 00000000000..b2824d86f38 --- /dev/null +++ b/contribs/gnopls/internal/debug/info.go @@ -0,0 +1,139 @@ +// 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. + +// Package debug exports debug information for gopls. +package debug + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "runtime" + "runtime/debug" + "strings" + + "golang.org/x/tools/gopls/internal/version" +) + +type PrintMode int + +const ( + PlainText = PrintMode(iota) + Markdown + HTML + JSON +) + +// ServerVersion is the format used by gopls to report its version to the +// client. This format is structured so that the client can parse it easily. +type ServerVersion struct { + *debug.BuildInfo + Version string +} + +// VersionInfo returns the build info for the gopls process. If it was not +// built in module mode, we return a GOPATH-specific message with the +// hardcoded version. +func VersionInfo() *ServerVersion { + if info, ok := debug.ReadBuildInfo(); ok { + return &ServerVersion{ + Version: version.Version(), + BuildInfo: info, + } + } + return &ServerVersion{ + Version: version.Version(), + BuildInfo: &debug.BuildInfo{ + Path: "gopls, built in GOPATH mode", + GoVersion: runtime.Version(), + }, + } +} + +// PrintServerInfo writes HTML debug info to w for the Instance. +func (i *Instance) PrintServerInfo(ctx context.Context, w io.Writer) { + workDir, _ := os.Getwd() + section(w, HTML, "Server Instance", func() { + fmt.Fprintf(w, "Start time: %v\n", i.StartTime) + fmt.Fprintf(w, "LogFile: %s\n", i.Logfile) + fmt.Fprintf(w, "pid: %d\n", os.Getpid()) + fmt.Fprintf(w, "Working directory: %s\n", workDir) + fmt.Fprintf(w, "Address: %s\n", i.ServerAddress) + fmt.Fprintf(w, "Debug address: %s\n", i.DebugAddress()) + }) + PrintVersionInfo(ctx, w, true, HTML) + section(w, HTML, "Command Line", func() { + fmt.Fprintf(w, "cmdline") + }) +} + +// PrintVersionInfo writes version information to w, using the output format +// specified by mode. verbose controls whether additional information is +// written, including section headers. +func PrintVersionInfo(_ context.Context, w io.Writer, verbose bool, mode PrintMode) error { + info := VersionInfo() + if mode == JSON { + return printVersionInfoJSON(w, info) + } + + if !verbose { + printBuildInfo(w, info, false, mode) + return nil + } + section(w, mode, "Build info", func() { + printBuildInfo(w, info, true, mode) + }) + return nil +} + +func printVersionInfoJSON(w io.Writer, info *ServerVersion) error { + js, err := json.MarshalIndent(info, "", "\t") + if err != nil { + return err + } + _, err = fmt.Fprint(w, string(js)) + return err +} + +func section(w io.Writer, mode PrintMode, title string, body func()) { + switch mode { + case PlainText: + fmt.Fprintln(w, title) + fmt.Fprintln(w, strings.Repeat("-", len(title))) + body() + case Markdown: + fmt.Fprintf(w, "#### %s\n\n```\n", title) + body() + fmt.Fprintf(w, "```\n") + case HTML: + fmt.Fprintf(w, "

%s

\n
\n", title)
+		body()
+		fmt.Fprint(w, "
\n") + } +} + +func printBuildInfo(w io.Writer, info *ServerVersion, verbose bool, mode PrintMode) { + fmt.Fprintf(w, "%v %v\n", info.Path, version.Version()) + if !verbose { + return + } + printModuleInfo(w, info.Main, mode) + for _, dep := range info.Deps { + printModuleInfo(w, *dep, mode) + } + fmt.Fprintf(w, "go: %v\n", info.GoVersion) +} + +func printModuleInfo(w io.Writer, m debug.Module, _ PrintMode) { + fmt.Fprintf(w, " %s@%s", m.Path, m.Version) + if m.Sum != "" { + fmt.Fprintf(w, " %s", m.Sum) + } + if m.Replace != nil { + fmt.Fprintf(w, " => %v", m.Replace.Path) + } + fmt.Fprintf(w, "\n") +} diff --git a/contribs/gnopls/internal/debug/info_test.go b/contribs/gnopls/internal/debug/info_test.go new file mode 100644 index 00000000000..7f24b696682 --- /dev/null +++ b/contribs/gnopls/internal/debug/info_test.go @@ -0,0 +1,50 @@ +// 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 debug exports debug information for gopls. +package debug + +import ( + "bytes" + "context" + "encoding/json" + "runtime" + "testing" + + "golang.org/x/tools/gopls/internal/version" +) + +func TestPrintVersionInfoJSON(t *testing.T) { + buf := new(bytes.Buffer) + if err := PrintVersionInfo(context.Background(), buf, true, JSON); err != nil { + t.Fatalf("PrintVersionInfo failed: %v", err) + } + res := buf.Bytes() + + var got ServerVersion + if err := json.Unmarshal(res, &got); err != nil { + t.Fatalf("unexpected output: %v\n%s", err, res) + } + if g, w := got.GoVersion, runtime.Version(); g != w { + t.Errorf("go version = %v, want %v", g, w) + } + if g, w := got.Version, version.Version(); g != w { + t.Errorf("gopls version = %v, want %v", g, w) + } + // Other fields of BuildInfo may not be available during test. +} + +func TestPrintVersionInfoPlainText(t *testing.T) { + buf := new(bytes.Buffer) + if err := PrintVersionInfo(context.Background(), buf, true, PlainText); err != nil { + t.Fatalf("PrintVersionInfo failed: %v", err) + } + res := buf.Bytes() + + // Other fields of BuildInfo may not be available during test. + wantGoplsVersion, wantGoVersion := version.Version(), runtime.Version() + if !bytes.Contains(res, []byte(wantGoplsVersion)) || !bytes.Contains(res, []byte(wantGoVersion)) { + t.Errorf("plaintext output = %q,\nwant (version: %v, go: %v)", res, wantGoplsVersion, wantGoVersion) + } +} diff --git a/contribs/gnopls/internal/debug/log/log.go b/contribs/gnopls/internal/debug/log/log.go new file mode 100644 index 00000000000..d015f9bfdd3 --- /dev/null +++ b/contribs/gnopls/internal/debug/log/log.go @@ -0,0 +1,43 @@ +// 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 log provides helper methods for exporting log events to the +// internal/event package. +package log + +import ( + "context" + "fmt" + + label1 "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/label" +) + +// Level parameterizes log severity. +type Level int + +const ( + _ Level = iota + Error + Warning + Info + Debug + Trace +) + +// Log exports a log event labeled with level l. +func (l Level) Log(ctx context.Context, msg string) { + event.Log(ctx, msg, label1.Level.Of(int(l))) +} + +// Logf formats and exports a log event labeled with level l. +func (l Level) Logf(ctx context.Context, format string, args ...interface{}) { + l.Log(ctx, fmt.Sprintf(format, args...)) +} + +// LabeledLevel extracts the labeled log l +func LabeledLevel(lm label.Map) Level { + return Level(label1.Level.Get(lm)) +} diff --git a/contribs/gnopls/internal/debug/metrics.go b/contribs/gnopls/internal/debug/metrics.go new file mode 100644 index 00000000000..d8bfe52f106 --- /dev/null +++ b/contribs/gnopls/internal/debug/metrics.go @@ -0,0 +1,58 @@ +// 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. + +package debug + +import ( + "golang.org/x/tools/internal/event/export/metric" + "golang.org/x/tools/internal/event/label" + "golang.org/x/tools/internal/jsonrpc2" +) + +var ( + // the distributions we use for histograms + bytesDistribution = []int64{1 << 10, 1 << 11, 1 << 12, 1 << 14, 1 << 16, 1 << 20} + millisecondsDistribution = []float64{0.1, 0.5, 1, 2, 5, 10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000} + + receivedBytes = metric.HistogramInt64{ + Name: "received_bytes", + Description: "Distribution of received bytes, by method.", + Keys: []label.Key{jsonrpc2.RPCDirection, jsonrpc2.Method}, + Buckets: bytesDistribution, + } + + sentBytes = metric.HistogramInt64{ + Name: "sent_bytes", + Description: "Distribution of sent bytes, by method.", + Keys: []label.Key{jsonrpc2.RPCDirection, jsonrpc2.Method}, + Buckets: bytesDistribution, + } + + latency = metric.HistogramFloat64{ + Name: "latency", + Description: "Distribution of latency in milliseconds, by method.", + Keys: []label.Key{jsonrpc2.RPCDirection, jsonrpc2.Method}, + Buckets: millisecondsDistribution, + } + + started = metric.Scalar{ + Name: "started", + Description: "Count of RPCs started by method.", + Keys: []label.Key{jsonrpc2.RPCDirection, jsonrpc2.Method}, + } + + completed = metric.Scalar{ + Name: "completed", + Description: "Count of RPCs completed by method and status.", + Keys: []label.Key{jsonrpc2.RPCDirection, jsonrpc2.Method, jsonrpc2.StatusCode}, + } +) + +func registerMetrics(m *metric.Config) { + receivedBytes.Record(m, jsonrpc2.ReceivedBytes) + sentBytes.Record(m, jsonrpc2.SentBytes) + latency.Record(m, jsonrpc2.Latency) + started.Count(m, jsonrpc2.Started) + completed.Count(m, jsonrpc2.Latency) +} diff --git a/contribs/gnopls/internal/debug/rpc.go b/contribs/gnopls/internal/debug/rpc.go new file mode 100644 index 00000000000..8a696f848d0 --- /dev/null +++ b/contribs/gnopls/internal/debug/rpc.go @@ -0,0 +1,239 @@ +// 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. + +package debug + +import ( + "context" + "fmt" + "html/template" + "net/http" + "sort" + "sync" + "time" + + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/core" + "golang.org/x/tools/internal/event/export" + "golang.org/x/tools/internal/event/label" + "golang.org/x/tools/internal/jsonrpc2" +) + +var RPCTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}RPC Information{{end}} +{{define "body"}} +

Inbound

+ {{template "rpcSection" .Inbound}} +

Outbound

+ {{template "rpcSection" .Outbound}} +{{end}} +{{define "rpcSection"}} + {{range .}}

+ {{.Method}} {{.Started}} traces ({{.InProgress}} in progress) +
+ Latency {{with .Latency}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}} + By bucket 0s {{range .Latency.Values}}{{if gt .Count 0}}{{.Count}} {{.Limit}} {{end}}{{end}} +
+ Received {{.Received}} (avg. {{.ReceivedMean}}) + Sent {{.Sent}} (avg. {{.SentMean}}) +
+ Result codes {{range .Codes}}{{.Key}}={{.Count}} {{end}} +

+ {{end}} +{{end}} +`)) + +type Rpcs struct { // exported for testing + mu sync.Mutex + Inbound []*rpcStats // stats for incoming lsp rpcs sorted by method name + Outbound []*rpcStats // stats for outgoing lsp rpcs sorted by method name +} + +type rpcStats struct { + Method string + Started int64 + Completed int64 + + Latency rpcTimeHistogram + Received byteUnits + Sent byteUnits + Codes []*rpcCodeBucket +} + +type rpcTimeHistogram struct { + Sum timeUnits + Count int64 + Min timeUnits + Max timeUnits + Values []rpcTimeBucket +} + +type rpcTimeBucket struct { + Limit timeUnits + Count int64 +} + +type rpcCodeBucket struct { + Key string + Count int64 +} + +func (r *Rpcs) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { + r.mu.Lock() + defer r.mu.Unlock() + switch { + case event.IsStart(ev): + if _, stats := r.getRPCSpan(ctx); stats != nil { + stats.Started++ + } + case event.IsEnd(ev): + span, stats := r.getRPCSpan(ctx) + if stats != nil { + endRPC(span, stats) + } + case event.IsMetric(ev): + sent := byteUnits(jsonrpc2.SentBytes.Get(lm)) + rec := byteUnits(jsonrpc2.ReceivedBytes.Get(lm)) + if sent != 0 || rec != 0 { + if _, stats := r.getRPCSpan(ctx); stats != nil { + stats.Sent += sent + stats.Received += rec + } + } + } + return ctx +} + +func endRPC(span *export.Span, stats *rpcStats) { + // update the basic counts + stats.Completed++ + + // get and record the status code + if status := getStatusCode(span); status != "" { + var b *rpcCodeBucket + for c, entry := range stats.Codes { + if entry.Key == status { + b = stats.Codes[c] + break + } + } + if b == nil { + b = &rpcCodeBucket{Key: status} + stats.Codes = append(stats.Codes, b) + sort.Slice(stats.Codes, func(i int, j int) bool { + return stats.Codes[i].Key < stats.Codes[j].Key + }) + } + b.Count++ + } + + // calculate latency if this was an rpc span + elapsedTime := span.Finish().At().Sub(span.Start().At()) + latencyMillis := timeUnits(elapsedTime) / timeUnits(time.Millisecond) + if stats.Latency.Count == 0 { + stats.Latency.Min = latencyMillis + stats.Latency.Max = latencyMillis + } else { + if stats.Latency.Min > latencyMillis { + stats.Latency.Min = latencyMillis + } + if stats.Latency.Max < latencyMillis { + stats.Latency.Max = latencyMillis + } + } + stats.Latency.Count++ + stats.Latency.Sum += latencyMillis + for i := range stats.Latency.Values { + if stats.Latency.Values[i].Limit > latencyMillis { + stats.Latency.Values[i].Count++ + break + } + } +} + +func (r *Rpcs) getRPCSpan(ctx context.Context) (*export.Span, *rpcStats) { + // get the span + span := export.GetSpan(ctx) + if span == nil { + return nil, nil + } + // use the span start event look up the correct stats block + // we do this because it prevents us matching a sub span + return span, r.getRPCStats(span.Start()) +} + +func (r *Rpcs) getRPCStats(lm label.Map) *rpcStats { + method := jsonrpc2.Method.Get(lm) + if method == "" { + return nil + } + set := &r.Inbound + if jsonrpc2.RPCDirection.Get(lm) != jsonrpc2.Inbound { + set = &r.Outbound + } + // get the record for this method + index := sort.Search(len(*set), func(i int) bool { + return (*set)[i].Method >= method + }) + + if index < len(*set) && (*set)[index].Method == method { + return (*set)[index] + } + + old := *set + *set = make([]*rpcStats, len(old)+1) + copy(*set, old[:index]) + copy((*set)[index+1:], old[index:]) + stats := &rpcStats{Method: method} + stats.Latency.Values = make([]rpcTimeBucket, len(millisecondsDistribution)) + for i, m := range millisecondsDistribution { + stats.Latency.Values[i].Limit = timeUnits(m) + } + (*set)[index] = stats + return stats +} + +func (s *rpcStats) InProgress() int64 { return s.Started - s.Completed } +func (s *rpcStats) SentMean() byteUnits { return s.Sent / byteUnits(s.Started) } +func (s *rpcStats) ReceivedMean() byteUnits { return s.Received / byteUnits(s.Started) } + +func (h *rpcTimeHistogram) Mean() timeUnits { return h.Sum / timeUnits(h.Count) } + +func getStatusCode(span *export.Span) string { + for _, ev := range span.Events() { + if status := jsonrpc2.StatusCode.Get(ev); status != "" { + return status + } + } + return "" +} + +func (r *Rpcs) getData(req *http.Request) interface{} { + return r +} + +func units(v float64, suffixes []string) string { + s := "" + for _, s = range suffixes { + n := v / 1000 + if n < 1 { + break + } + v = n + } + return fmt.Sprintf("%.2f%s", v, s) +} + +type timeUnits float64 + +func (v timeUnits) String() string { + v = v * 1000 * 1000 + return units(float64(v), []string{"ns", "μs", "ms", "s"}) +} + +type byteUnits float64 + +func (v byteUnits) String() string { + return units(float64(v), []string{"B", "KB", "MB", "GB", "TB"}) +} diff --git a/contribs/gnopls/internal/debug/serve.go b/contribs/gnopls/internal/debug/serve.go new file mode 100644 index 00000000000..058254b755b --- /dev/null +++ b/contribs/gnopls/internal/debug/serve.go @@ -0,0 +1,846 @@ +// 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. + +package debug + +import ( + "bytes" + "context" + "errors" + "fmt" + "html/template" + "io" + stdlog "log" + "net" + "net/http" + "net/http/pprof" + "os" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug/log" + label1 "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/core" + "golang.org/x/tools/internal/event/export" + "golang.org/x/tools/internal/event/export/metric" + "golang.org/x/tools/internal/event/export/ocagent" + "golang.org/x/tools/internal/event/export/prometheus" + "golang.org/x/tools/internal/event/keys" + "golang.org/x/tools/internal/event/label" +) + +type contextKeyType int + +const ( + instanceKey contextKeyType = iota + traceKey +) + +// An Instance holds all debug information associated with a gopls instance. +type Instance struct { + Logfile string + StartTime time.Time + ServerAddress string + OCAgentConfig string + + LogWriter io.Writer + + exporter event.Exporter + + ocagent *ocagent.Exporter + prometheus *prometheus.Exporter + rpcs *Rpcs + traces *traces + State *State + + serveMu sync.Mutex + debugAddress string + listenedDebugAddress string +} + +// State holds debugging information related to the server state. +type State struct { + mu sync.Mutex + clients []*Client + servers []*Server +} + +func (st *State) Bugs() []bug.Bug { + return bug.List() +} + +// Caches returns the set of Cache objects currently being served. +func (st *State) Caches() []*cache.Cache { + var caches []*cache.Cache + seen := make(map[string]struct{}) + for _, client := range st.Clients() { + cache := client.Session.Cache() + if _, found := seen[cache.ID()]; found { + continue + } + seen[cache.ID()] = struct{}{} + caches = append(caches, cache) + } + return caches +} + +// Cache returns the Cache that matches the supplied id. +func (st *State) Cache(id string) *cache.Cache { + for _, c := range st.Caches() { + if c.ID() == id { + return c + } + } + return nil +} + +// Analysis returns the global Analysis template value. +func (st *State) Analysis() (_ analysisTmpl) { return } + +type analysisTmpl struct{} + +func (analysisTmpl) AnalyzerRunTimes() []cache.LabelDuration { return cache.AnalyzerRunTimes() } + +// Sessions returns the set of Session objects currently being served. +func (st *State) Sessions() []*cache.Session { + var sessions []*cache.Session + for _, client := range st.Clients() { + sessions = append(sessions, client.Session) + } + return sessions +} + +// Session returns the Session that matches the supplied id. +func (st *State) Session(id string) *cache.Session { + for _, s := range st.Sessions() { + if s.ID() == id { + return s + } + } + return nil +} + +// Views returns the set of View objects currently being served. +func (st *State) Views() []*cache.View { + var views []*cache.View + for _, s := range st.Sessions() { + views = append(views, s.Views()...) + } + return views +} + +// View returns the View that matches the supplied id. +func (st *State) View(id string) *cache.View { + for _, v := range st.Views() { + if v.ID() == id { + return v + } + } + return nil +} + +// Clients returns the set of Clients currently being served. +func (st *State) Clients() []*Client { + st.mu.Lock() + defer st.mu.Unlock() + clients := make([]*Client, len(st.clients)) + copy(clients, st.clients) + return clients +} + +// Client returns the Client matching the supplied id. +func (st *State) Client(id string) *Client { + for _, c := range st.Clients() { + if c.Session.ID() == id { + return c + } + } + return nil +} + +// Servers returns the set of Servers the instance is currently connected to. +func (st *State) Servers() []*Server { + st.mu.Lock() + defer st.mu.Unlock() + servers := make([]*Server, len(st.servers)) + copy(servers, st.servers) + return servers +} + +// A Client is an incoming connection from a remote client. +type Client struct { + Session *cache.Session + DebugAddress string + Logfile string + GoplsPath string + ServerID string + Service protocol.Server +} + +// A Server is an outgoing connection to a remote LSP server. +type Server struct { + ID string + DebugAddress string + Logfile string + GoplsPath string + ClientID string +} + +// addClient adds a client to the set being served. +func (st *State) addClient(session *cache.Session) { + st.mu.Lock() + defer st.mu.Unlock() + st.clients = append(st.clients, &Client{Session: session}) +} + +// dropClient removes a client from the set being served. +func (st *State) dropClient(session *cache.Session) { + st.mu.Lock() + defer st.mu.Unlock() + for i, c := range st.clients { + if c.Session == session { + copy(st.clients[i:], st.clients[i+1:]) + st.clients[len(st.clients)-1] = nil + st.clients = st.clients[:len(st.clients)-1] + return + } + } +} + +// updateServer updates a server to the set being queried. In practice, there should +// be at most one remote server. +func (st *State) updateServer(server *Server) { + st.mu.Lock() + defer st.mu.Unlock() + for i, existing := range st.servers { + if existing.ID == server.ID { + // Replace, rather than mutate, to avoid a race. + newServers := make([]*Server, len(st.servers)) + copy(newServers, st.servers[:i]) + newServers[i] = server + copy(newServers[i+1:], st.servers[i+1:]) + st.servers = newServers + return + } + } + st.servers = append(st.servers, server) +} + +// dropServer drops a server from the set being queried. +func (st *State) dropServer(id string) { + st.mu.Lock() + defer st.mu.Unlock() + for i, s := range st.servers { + if s.ID == id { + copy(st.servers[i:], st.servers[i+1:]) + st.servers[len(st.servers)-1] = nil + st.servers = st.servers[:len(st.servers)-1] + return + } + } +} + +// an http.ResponseWriter that filters writes +type filterResponse struct { + w http.ResponseWriter + edit func([]byte) []byte +} + +func (c filterResponse) Header() http.Header { + return c.w.Header() +} + +func (c filterResponse) Write(buf []byte) (int, error) { + ans := c.edit(buf) + return c.w.Write(ans) +} + +func (c filterResponse) WriteHeader(n int) { + c.w.WriteHeader(n) +} + +// replace annoying nuls by spaces +func cmdline(w http.ResponseWriter, r *http.Request) { + fake := filterResponse{ + w: w, + edit: func(buf []byte) []byte { + return bytes.ReplaceAll(buf, []byte{0}, []byte{' '}) + }, + } + pprof.Cmdline(fake, r) +} + +func (i *Instance) getCache(r *http.Request) interface{} { + return i.State.Cache(path.Base(r.URL.Path)) +} + +func (i *Instance) getAnalysis(r *http.Request) interface{} { + return i.State.Analysis() +} + +func (i *Instance) getSession(r *http.Request) interface{} { + return i.State.Session(path.Base(r.URL.Path)) +} + +func (i *Instance) getClient(r *http.Request) interface{} { + return i.State.Client(path.Base(r.URL.Path)) +} + +func (i *Instance) getServer(r *http.Request) interface{} { + i.State.mu.Lock() + defer i.State.mu.Unlock() + id := path.Base(r.URL.Path) + for _, s := range i.State.servers { + if s.ID == id { + return s + } + } + return nil +} + +func (i *Instance) getFile(r *http.Request) interface{} { + identifier := path.Base(r.URL.Path) + sid := path.Base(path.Dir(r.URL.Path)) + s := i.State.Session(sid) + if s == nil { + return nil + } + for _, o := range s.Overlays() { + // TODO(adonovan): understand and document this comparison. + if o.Identity().Hash.String() == identifier { + return o + } + } + return nil +} + +func (i *Instance) getInfo(r *http.Request) interface{} { + buf := &bytes.Buffer{} + i.PrintServerInfo(r.Context(), buf) + return template.HTML(buf.String()) +} + +func (i *Instance) AddService(s protocol.Server, session *cache.Session) { + for _, c := range i.State.clients { + if c.Session == session { + c.Service = s + return + } + } + stdlog.Printf("unable to find a Client to add the protocol.Server to") +} + +func getMemory(_ *http.Request) interface{} { + var m runtime.MemStats + runtime.ReadMemStats(&m) + return m +} + +func init() { + event.SetExporter(makeGlobalExporter(os.Stderr)) +} + +func GetInstance(ctx context.Context) *Instance { + if ctx == nil { + return nil + } + v := ctx.Value(instanceKey) + if v == nil { + return nil + } + return v.(*Instance) +} + +// WithInstance creates debug instance ready for use using the supplied +// configuration and stores it in the returned context. +func WithInstance(ctx context.Context, agent string) context.Context { + i := &Instance{ + StartTime: time.Now(), + OCAgentConfig: agent, + } + i.LogWriter = os.Stderr + ocConfig := ocagent.Discover() + //TODO: we should not need to adjust the discovered configuration + ocConfig.Address = i.OCAgentConfig + i.ocagent = ocagent.Connect(ocConfig) + i.prometheus = prometheus.New() + i.rpcs = &Rpcs{} + i.traces = &traces{} + i.State = &State{} + i.exporter = makeInstanceExporter(i) + return context.WithValue(ctx, instanceKey, i) +} + +// SetLogFile sets the logfile for use with this instance. +func (i *Instance) SetLogFile(logfile string, isDaemon bool) (func(), error) { + // TODO: probably a better solution for deferring closure to the caller would + // be for the debug instance to itself be closed, but this fixes the + // immediate bug of logs not being captured. + closeLog := func() {} + if logfile != "" { + if logfile == "auto" { + if isDaemon { + logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-daemon-%d.log", os.Getpid())) + } else { + logfile = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.log", os.Getpid())) + } + } + f, err := os.Create(logfile) + if err != nil { + return nil, fmt.Errorf("unable to create log file: %w", err) + } + closeLog = func() { + defer f.Close() + } + stdlog.SetOutput(io.MultiWriter(os.Stderr, f)) + i.LogWriter = f + } + i.Logfile = logfile + return closeLog, nil +} + +// Serve starts and runs a debug server in the background on the given addr. +// It also logs the port the server starts on, to allow for :0 auto assigned +// ports. +func (i *Instance) Serve(ctx context.Context, addr string) (string, error) { + stdlog.SetFlags(stdlog.Lshortfile) + if addr == "" { + return "", nil + } + i.serveMu.Lock() + defer i.serveMu.Unlock() + + if i.listenedDebugAddress != "" { + // Already serving. Return the bound address. + return i.listenedDebugAddress, nil + } + + i.debugAddress = addr + listener, err := net.Listen("tcp", i.debugAddress) + if err != nil { + return "", err + } + i.listenedDebugAddress = listener.Addr().String() + + port := listener.Addr().(*net.TCPAddr).Port + if strings.HasSuffix(i.debugAddress, ":0") { + stdlog.Printf("debug server listening at http://localhost:%d", port) + } + event.Log(ctx, "Debug serving", label1.Port.Of(port)) + go func() { + mux := http.NewServeMux() + mux.HandleFunc("/", render(MainTmpl, func(*http.Request) interface{} { return i })) + mux.HandleFunc("/debug/", render(DebugTmpl, nil)) + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + if i.prometheus != nil { + mux.HandleFunc("/metrics/", i.prometheus.Serve) + } + if i.rpcs != nil { + mux.HandleFunc("/rpc/", render(RPCTmpl, i.rpcs.getData)) + } + if i.traces != nil { + mux.HandleFunc("/trace/", render(TraceTmpl, i.traces.getData)) + } + mux.HandleFunc("/analysis/", render(AnalysisTmpl, i.getAnalysis)) + mux.HandleFunc("/cache/", render(CacheTmpl, i.getCache)) + mux.HandleFunc("/session/", render(SessionTmpl, i.getSession)) + mux.HandleFunc("/client/", render(ClientTmpl, i.getClient)) + mux.HandleFunc("/server/", render(ServerTmpl, i.getServer)) + mux.HandleFunc("/file/", render(FileTmpl, i.getFile)) + mux.HandleFunc("/info", render(InfoTmpl, i.getInfo)) + mux.HandleFunc("/memory", render(MemoryTmpl, getMemory)) + + // Internal debugging helpers. + mux.HandleFunc("/gc", func(w http.ResponseWriter, r *http.Request) { + runtime.GC() + runtime.GC() + runtime.GC() + http.Redirect(w, r, "/memory", http.StatusTemporaryRedirect) + }) + mux.HandleFunc("/_makeabug", func(w http.ResponseWriter, r *http.Request) { + bug.Report("bug here") + http.Error(w, "made a bug", http.StatusOK) + }) + + if err := http.Serve(listener, mux); err != nil { + event.Error(ctx, "Debug server failed", err) + return + } + event.Log(ctx, "Debug server finished") + }() + return i.listenedDebugAddress, nil +} + +func (i *Instance) DebugAddress() string { + i.serveMu.Lock() + defer i.serveMu.Unlock() + return i.debugAddress +} + +func (i *Instance) ListenedDebugAddress() string { + i.serveMu.Lock() + defer i.serveMu.Unlock() + return i.listenedDebugAddress +} + +func makeGlobalExporter(stderr io.Writer) event.Exporter { + p := export.Printer{} + var pMu sync.Mutex + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + i := GetInstance(ctx) + + if event.IsLog(ev) { + // Don't log context cancellation errors. + if err := keys.Err.Get(ev); errors.Is(err, context.Canceled) { + return ctx + } + // Make sure any log messages without an instance go to stderr. + if i == nil { + pMu.Lock() + p.WriteEvent(stderr, ev, lm) + pMu.Unlock() + } + level := log.LabeledLevel(lm) + // Exclude trace logs from LSP logs. + if level < log.Trace { + ctx = protocol.LogEvent(ctx, ev, lm, messageType(level)) + } + } + if i == nil { + return ctx + } + return i.exporter(ctx, ev, lm) + } +} + +func messageType(l log.Level) protocol.MessageType { + switch l { + case log.Error: + return protocol.Error + case log.Warning: + return protocol.Warning + case log.Debug: + return protocol.Log + } + return protocol.Info +} + +func makeInstanceExporter(i *Instance) event.Exporter { + exporter := func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + if i.ocagent != nil { + ctx = i.ocagent.ProcessEvent(ctx, ev, lm) + } + if i.prometheus != nil { + ctx = i.prometheus.ProcessEvent(ctx, ev, lm) + } + if i.rpcs != nil { + ctx = i.rpcs.ProcessEvent(ctx, ev, lm) + } + if i.traces != nil { + ctx = i.traces.ProcessEvent(ctx, ev, lm) + } + if event.IsLog(ev) { + if s := cache.KeyCreateSession.Get(ev); s != nil { + i.State.addClient(s) + } + if sid := label1.NewServer.Get(ev); sid != "" { + i.State.updateServer(&Server{ + ID: sid, + Logfile: label1.Logfile.Get(ev), + DebugAddress: label1.DebugAddress.Get(ev), + GoplsPath: label1.GoplsPath.Get(ev), + ClientID: label1.ClientID.Get(ev), + }) + } + if s := cache.KeyShutdownSession.Get(ev); s != nil { + i.State.dropClient(s) + } + if sid := label1.EndServer.Get(ev); sid != "" { + i.State.dropServer(sid) + } + if s := cache.KeyUpdateSession.Get(ev); s != nil { + if c := i.State.Client(s.ID()); c != nil { + c.DebugAddress = label1.DebugAddress.Get(ev) + c.Logfile = label1.Logfile.Get(ev) + c.ServerID = label1.ServerID.Get(ev) + c.GoplsPath = label1.GoplsPath.Get(ev) + } + } + } + return ctx + } + // StdTrace must be above export.Spans below (by convention, export + // middleware applies its wrapped exporter last). + exporter = StdTrace(exporter) + metrics := metric.Config{} + registerMetrics(&metrics) + exporter = metrics.Exporter(exporter) + exporter = export.Spans(exporter) + exporter = export.Labels(exporter) + return exporter +} + +type dataFunc func(*http.Request) interface{} + +func render(tmpl *template.Template, fun dataFunc) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + var data interface{} + if fun != nil { + data = fun(r) + } + if err := tmpl.Execute(w, data); err != nil { + event.Error(context.Background(), "", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } +} + +func commas(s string) string { + for i := len(s); i > 3; { + i -= 3 + s = s[:i] + "," + s[i:] + } + return s +} + +func fuint64(v uint64) string { + return commas(strconv.FormatUint(v, 10)) +} + +func fuint32(v uint32) string { + return commas(strconv.FormatUint(uint64(v), 10)) +} + +func fcontent(v []byte) string { + return string(v) +} + +var BaseTemplate = template.Must(template.New("").Parse(` + + +{{template "title" .}} + +{{block "head" .}}{{end}} + + +Main +Info +Memory +Profiling +Metrics +RPC +Trace +Analysis +
+

{{template "title" .}}

+{{block "body" .}} +Unknown page +{{end}} + + + +{{define "cachelink"}}Cache {{.}}{{end}} +{{define "clientlink"}}Client {{.}}{{end}} +{{define "serverlink"}}Server {{.}}{{end}} +{{define "sessionlink"}}Session {{.}}{{end}} +`)).Funcs(template.FuncMap{ + "fuint64": fuint64, + "fuint32": fuint32, + "fcontent": fcontent, + "localAddress": func(s string) string { + // Try to translate loopback addresses to localhost, both for cosmetics and + // because unspecified ipv6 addresses can break links on Windows. + // + // TODO(rfindley): In the future, it would be better not to assume the + // server is running on localhost, and instead construct this address using + // the remote host. + host, port, err := net.SplitHostPort(s) + if err != nil { + return s + } + ip := net.ParseIP(host) + if ip == nil { + return s + } + if ip.IsLoopback() || ip.IsUnspecified() { + return "localhost:" + port + } + return s + }, + // TODO(rfindley): re-enable option inspection. + // "options": func(s *cache.Session) []sessionOption { + // return showOptions(s.Options()) + // }, +}) + +var MainTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Gopls server information{{end}} +{{define "body"}} +

Caches

+
    {{range .State.Caches}}
  • {{template "cachelink" .ID}}
  • {{end}}
+

Sessions

+
    {{range .State.Sessions}}
  • {{template "sessionlink" .ID}} from {{template "cachelink" .Cache.ID}}
  • {{end}}
+

Clients

+
    {{range .State.Clients}}
  • {{template "clientlink" .Session.ID}}
  • {{end}}
+

Servers

+
    {{range .State.Servers}}
  • {{template "serverlink" .ID}}
  • {{end}}
+

Bug reports

+
{{range .State.Bugs}}
{{.Key}}
{{.Description}}
{{end}}
+{{end}} +`)) + +var InfoTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Gopls version information{{end}} +{{define "body"}} +{{.}} +{{end}} +`)) + +var MemoryTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Gopls memory usage{{end}} +{{define "head"}}{{end}} +{{define "body"}} +
+

Stats

+ + + + + + + + + + + + + + + + +
Allocated bytes{{fuint64 .HeapAlloc}}
Total allocated bytes{{fuint64 .TotalAlloc}}
System bytes{{fuint64 .Sys}}
Heap system bytes{{fuint64 .HeapSys}}
Malloc calls{{fuint64 .Mallocs}}
Frees{{fuint64 .Frees}}
Idle heap bytes{{fuint64 .HeapIdle}}
In use bytes{{fuint64 .HeapInuse}}
Released to system bytes{{fuint64 .HeapReleased}}
Heap object count{{fuint64 .HeapObjects}}
Stack in use bytes{{fuint64 .StackInuse}}
Stack from system bytes{{fuint64 .StackSys}}
Bucket hash bytes{{fuint64 .BuckHashSys}}
GC metadata bytes{{fuint64 .GCSys}}
Off heap bytes{{fuint64 .OtherSys}}
+

By size

+ + +{{range .BySize}}{{end}} +
SizeMallocsFrees
{{fuint32 .Size}}{{fuint64 .Mallocs}}{{fuint64 .Frees}}
+{{end}} +`)) + +var DebugTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}GoPls Debug pages{{end}} +{{define "body"}} +Profiling +{{end}} +`)) + +var CacheTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Cache {{.ID}}{{end}} +{{define "body"}} +

memoize.Store entries

+
    {{range $k,$v := .MemStats}}
  • {{$k}} - {{$v}}
  • {{end}}
+

File stats

+

+{{- $stats := .FileStats -}} +Total: {{$stats.Total}}
+Largest: {{$stats.Largest}}
+Errors: {{$stats.Errs}}
+

+{{end}} +`)) + +var AnalysisTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Analysis{{end}} +{{define "body"}} +

Analyzer.Run times

+
    {{range .AnalyzerRunTimes}}
  • {{.Duration}} {{.Label}}
  • {{end}}
+{{end}} +`)) + +var ClientTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Client {{.Session.ID}}{{end}} +{{define "body"}} +Using session: {{template "sessionlink" .Session.ID}}
+{{if .DebugAddress}}Debug this client at: {{localAddress .DebugAddress}}
{{end}} +Logfile: {{.Logfile}}
+Gopls Path: {{.GoplsPath}}
+{{end}} +`)) + +var ServerTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Server {{.ID}}{{end}} +{{define "body"}} +{{if .DebugAddress}}Debug this server at: {{localAddress .DebugAddress}}
{{end}} +Logfile: {{.Logfile}}
+Gopls Path: {{.GoplsPath}}
+{{end}} +`)) + +var SessionTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Session {{.ID}}{{end}} +{{define "body"}} +From: {{template "cachelink" .Cache.ID}}
+

Views

+
    {{range .Views}} +{{- $envOverlay := .EnvOverlay -}} +
  • ID: {{.ID}}
    +Type: {{.Type}}
    +Root: {{.Root}}
    +{{- if $envOverlay}} +Env overlay: {{$envOverlay}})
    +{{end -}} +Folder: {{.Folder.Name}}:{{.Folder.Dir}}
  • +{{end}}
+

Overlays

+{{$session := .}} + +{{end}} +`)) + +var FileTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Overlay {{.Identity.Hash}}{{end}} +{{define "body"}} +{{with .}} + URI: {{.URI}}
+ Identifier: {{.Identity.Hash}}
+ Version: {{.Version}}
+ Kind: {{.Kind}}
+{{end}} +

Contents

+
{{fcontent .Content}}
+{{end}} +`)) diff --git a/contribs/gnopls/internal/debug/template_test.go b/contribs/gnopls/internal/debug/template_test.go new file mode 100644 index 00000000000..db940efc602 --- /dev/null +++ b/contribs/gnopls/internal/debug/template_test.go @@ -0,0 +1,156 @@ +// 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 debug_test + +// Provide 'static type checking' of the templates. This guards against changes in various +// gopls datastructures causing template execution to fail. The checking is done by +// the github.com/jba/templatecheck package. Before that is run, the test checks that +// its list of templates and their arguments corresponds to the arguments in +// calls to render(). The test assumes that all uses of templates are done through render(). + +import ( + "go/ast" + "html/template" + "os" + "runtime" + "sort" + "strings" + "testing" + + "github.com/jba/templatecheck" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/internal/testenv" +) + +var templates = map[string]struct { + tmpl *template.Template + data interface{} // a value of the needed type +}{ + "MainTmpl": {debug.MainTmpl, &debug.Instance{}}, + "DebugTmpl": {debug.DebugTmpl, nil}, + "RPCTmpl": {debug.RPCTmpl, &debug.Rpcs{}}, + "TraceTmpl": {debug.TraceTmpl, debug.TraceResults{}}, + "CacheTmpl": {debug.CacheTmpl, &cache.Cache{}}, + "SessionTmpl": {debug.SessionTmpl, &cache.Session{}}, + "ClientTmpl": {debug.ClientTmpl, &debug.Client{}}, + "ServerTmpl": {debug.ServerTmpl, &debug.Server{}}, + "FileTmpl": {debug.FileTmpl, *new(interface { + file.Handle + Kind() file.Kind // (overlay files only) + })}, + "InfoTmpl": {debug.InfoTmpl, "something"}, + "MemoryTmpl": {debug.MemoryTmpl, runtime.MemStats{}}, + "AnalysisTmpl": {debug.AnalysisTmpl, new(debug.State).Analysis()}, +} + +func TestTemplates(t *testing.T) { + testenv.NeedsGoPackages(t) + testenv.NeedsLocalXTools(t) + + cfg := &packages.Config{ + Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo, + } + cfg.Env = os.Environ() + cfg.Env = append(cfg.Env, + "GOPACKAGESDRIVER=off", + "GOWORK=off", // necessary for -mod=mod below + "GOFLAGS=-mod=mod", + ) + + pkgs, err := packages.Load(cfg, "golang.org/x/tools/gopls/internal/debug") + if err != nil { + t.Fatal(err) + } + if len(pkgs) != 1 { + t.Fatalf("expected a single package, but got %d", len(pkgs)) + } + p := pkgs[0] + if len(p.Errors) != 0 { + t.Fatalf("compiler error, e.g. %v", p.Errors[0]) + } + // find the calls to render in serve.go + tree := treeOf(p, "serve.go") + if tree == nil { + t.Fatalf("found no syntax tree for %s", "serve.go") + } + renders := callsOf(tree, "render") + if len(renders) == 0 { + t.Fatalf("found no calls to render") + } + var found = make(map[string]bool) + for _, r := range renders { + if len(r.Args) != 2 { + // template, func + t.Fatalf("got %d args, expected 2", len(r.Args)) + } + t0, ok := p.TypesInfo.Types[r.Args[0]] + if !ok || !t0.IsValue() || t0.Type.String() != "*html/template.Template" { + t.Fatalf("no type info for template") + } + if id, ok := r.Args[0].(*ast.Ident); !ok { + t.Errorf("expected *ast.Ident, got %T", r.Args[0]) + } else { + found[id.Name] = true + } + } + // make sure found and templates have the same templates + for k := range found { + if _, ok := templates[k]; !ok { + t.Errorf("code has template %s, but test does not", k) + } + } + for k := range templates { + if _, ok := found[k]; !ok { + t.Errorf("test has template %s, code does not", k) + } + } + // now check all the known templates, in alphabetic order, for determinacy + keys := []string{} + for k := range templates { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + v := templates[k] + // the FuncMap is an annoyance; should not be necessary + if err := templatecheck.CheckHTML(v.tmpl, v.data); err != nil { + t.Errorf("%s: %v", k, err) + continue + } + t.Logf("%s ok", k) + } +} + +func callsOf(tree *ast.File, name string) []*ast.CallExpr { + var ans []*ast.CallExpr + f := func(n ast.Node) bool { + x, ok := n.(*ast.CallExpr) + if !ok { + return true + } + if y, ok := x.Fun.(*ast.Ident); ok { + if y.Name == name { + ans = append(ans, x) + } + } + return true + } + ast.Inspect(tree, f) + return ans +} + +func treeOf(p *packages.Package, fname string) *ast.File { + for _, tree := range p.Syntax { + loc := tree.Package + pos := p.Fset.PositionFor(loc, false) + if strings.HasSuffix(pos.Filename, fname) { + return tree + } + } + return nil +} diff --git a/contribs/gnopls/internal/debug/trace.go b/contribs/gnopls/internal/debug/trace.go new file mode 100644 index 00000000000..9314a04d241 --- /dev/null +++ b/contribs/gnopls/internal/debug/trace.go @@ -0,0 +1,320 @@ +// 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. + +package debug + +import ( + "bytes" + "context" + "fmt" + "html/template" + "net/http" + "runtime/trace" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/core" + "golang.org/x/tools/internal/event/export" + "golang.org/x/tools/internal/event/label" +) + +// TraceTmpl extends BaseTemplate and renders a TraceResults, e.g. from getData(). +var TraceTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` +{{define "title"}}Trace Information{{end}} +{{define "body"}} + {{range .Traces}}{{.Name}} last: {{.Last.Duration}}, longest: {{.Longest.Duration}}
{{end}} + {{if .Selected}} +

{{.Selected.Name}}

+ {{if .Selected.Last}}

Last

    {{template "completeSpan" .Selected.Last}}
{{end}} + {{if .Selected.Longest}}

Longest

    {{template "completeSpan" .Selected.Longest}}
{{end}} + {{end}} + +

Recent spans (oldest first)

+

+ A finite number of recent span start/end times are shown below. + The nesting represents the children of a parent span (and the log events within a span). + A span may appear twice: chronologically at toplevel, and nested within its parent. +

+
    {{range .Recent}}{{template "spanStartEnd" .}}{{end}}
+{{end}} +{{define "spanStartEnd"}} + {{if .Start}} +
  • {{.Span.Header .Start}}
  • + {{else}} + {{template "completeSpan" .Span}} + {{end}} +{{end}} +{{define "completeSpan"}} +
  • {{.Header false}}
  • + {{if .Events}}
      {{range .Events}}
    • {{.Header}}
    • {{end}}
    {{end}} + {{if .ChildStartEnd}}
      {{range .ChildStartEnd}}{{template "spanStartEnd" .}}{{end}}
    {{end}} +{{end}} +`)) + +type traces struct { + mu sync.Mutex + sets map[string]*traceSet + unfinished map[export.SpanContext]*traceSpan + recent []spanStartEnd + recentEvictions int +} + +// A spanStartEnd records the start or end of a span. +// If Start, the span may be unfinished, so some fields (e.g. Finish) +// may be unset and others (e.g. Events) may be being actively populated. +type spanStartEnd struct { + Start bool + Span *traceSpan +} + +func (ev spanStartEnd) Time() time.Time { + if ev.Start { + return ev.Span.Start + } else { + return ev.Span.Finish + } +} + +// A TraceResults is the subject for the /trace HTML template. +type TraceResults struct { // exported for testing + Traces []*traceSet + Selected *traceSet + Recent []spanStartEnd +} + +// A traceSet holds two representative spans of a given span name. +type traceSet struct { + Name string + Last *traceSpan + Longest *traceSpan +} + +// A traceSpan holds information about a single span. +type traceSpan struct { + TraceID export.TraceID + SpanID export.SpanID + ParentID export.SpanID + Name string + Start time.Time + Finish time.Time // set at end + Duration time.Duration // set at end + Tags string + Events []traceEvent // set at end + ChildStartEnd []spanStartEnd // populated while active + + parent *traceSpan +} + +const timeFormat = "15:04:05.000" + +// Header renders the time, name, tags, and (if !start), +// duration of a span start or end event. +func (span *traceSpan) Header(start bool) string { + if start { + return fmt.Sprintf("%s start %s %s", + span.Start.Format(timeFormat), span.Name, span.Tags) + } else { + return fmt.Sprintf("%s end %s (+%s) %s", + span.Finish.Format(timeFormat), span.Name, span.Duration, span.Tags) + } +} + +type traceEvent struct { + Time time.Time + Offset time.Duration // relative to start of span + Tags string +} + +func (ev traceEvent) Header() string { + return fmt.Sprintf("%s event (+%s) %s", ev.Time.Format(timeFormat), ev.Offset, ev.Tags) +} + +func StdTrace(exporter event.Exporter) event.Exporter { + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + span := export.GetSpan(ctx) + if span == nil { + return exporter(ctx, ev, lm) + } + switch { + case event.IsStart(ev): + if span.ParentID.IsValid() { + region := trace.StartRegion(ctx, span.Name) + ctx = context.WithValue(ctx, traceKey, region) + } else { + var task *trace.Task + ctx, task = trace.NewTask(ctx, span.Name) + ctx = context.WithValue(ctx, traceKey, task) + } + // Log the start event as it may contain useful labels. + msg := formatEvent(ev, lm) + trace.Log(ctx, "start", msg) + case event.IsLog(ev): + category := "" + if event.IsError(ev) { + category = "error" + } + msg := formatEvent(ev, lm) + trace.Log(ctx, category, msg) + case event.IsEnd(ev): + if v := ctx.Value(traceKey); v != nil { + v.(interface{ End() }).End() + } + } + return exporter(ctx, ev, lm) + } +} + +func formatEvent(ev core.Event, lm label.Map) string { + buf := &bytes.Buffer{} + p := export.Printer{} + p.WriteEvent(buf, ev, lm) + return buf.String() +} + +func (t *traces) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { + span := export.GetSpan(ctx) + if span == nil { + return ctx + } + + switch { + case event.IsStart(ev): + // Just starting: add it to the unfinished map. + // Allocate before the critical section. + td := &traceSpan{ + TraceID: span.ID.TraceID, + SpanID: span.ID.SpanID, + ParentID: span.ParentID, + Name: span.Name, + Start: span.Start().At(), + Tags: renderLabels(span.Start()), + } + + t.mu.Lock() + defer t.mu.Unlock() + + t.addRecentLocked(td, true) // add start event + + if t.sets == nil { + t.sets = make(map[string]*traceSet) + t.unfinished = make(map[export.SpanContext]*traceSpan) + } + t.unfinished[span.ID] = td + + // Wire up parents if we have them. + if span.ParentID.IsValid() { + parentID := export.SpanContext{TraceID: span.ID.TraceID, SpanID: span.ParentID} + if parent, ok := t.unfinished[parentID]; ok { + td.parent = parent + parent.ChildStartEnd = append(parent.ChildStartEnd, spanStartEnd{true, td}) + } + } + + case event.IsEnd(ev): + // Finishing: must be already in the map. + // Allocate events before the critical section. + events := span.Events() + tdEvents := make([]traceEvent, len(events)) + for i, event := range events { + tdEvents[i] = traceEvent{ + Time: event.At(), + Tags: renderLabels(event), + } + } + + t.mu.Lock() + defer t.mu.Unlock() + td, found := t.unfinished[span.ID] + if !found { + return ctx // if this happens we are in a bad place + } + delete(t.unfinished, span.ID) + td.Finish = span.Finish().At() + td.Duration = span.Finish().At().Sub(span.Start().At()) + td.Events = tdEvents + t.addRecentLocked(td, false) // add end event + + set, ok := t.sets[span.Name] + if !ok { + set = &traceSet{Name: span.Name} + t.sets[span.Name] = set + } + set.Last = td + if set.Longest == nil || set.Last.Duration > set.Longest.Duration { + set.Longest = set.Last + } + if td.parent != nil { + td.parent.ChildStartEnd = append(td.parent.ChildStartEnd, spanStartEnd{false, td}) + } else { + fillOffsets(td, td.Start) + } + } + return ctx +} + +// addRecentLocked appends a start or end event to the "recent" log, +// evicting an old entry if necessary. +func (t *traces) addRecentLocked(span *traceSpan, start bool) { + t.recent = append(t.recent, spanStartEnd{Start: start, Span: span}) + + const maxRecent = 100 // number of log entries before eviction + for len(t.recent) > maxRecent { + t.recent[0] = spanStartEnd{} // aid GC + t.recent = t.recent[1:] + t.recentEvictions++ + + // Using a slice as a FIFO queue leads to unbounded growth + // as Go's GC cannot collect the ever-growing unused prefix. + // So, compact it periodically. + if t.recentEvictions%maxRecent == 0 { + t.recent = append([]spanStartEnd(nil), t.recent...) + } + } +} + +// getData returns the TraceResults rendered by TraceTmpl for the /trace[/name] endpoint. +func (t *traces) getData(req *http.Request) interface{} { + // TODO(adonovan): the HTTP request doesn't acquire the mutex + // for t or for each span! Audit and fix. + + // Sort last/longest sets by name. + traces := make([]*traceSet, 0, len(t.sets)) + for _, set := range t.sets { + traces = append(traces, set) + } + sort.Slice(traces, func(i, j int) bool { + return traces[i].Name < traces[j].Name + }) + + return TraceResults{ + Traces: traces, + Selected: t.sets[strings.TrimPrefix(req.URL.Path, "/trace/")], // may be nil + Recent: t.recent, + } +} + +func fillOffsets(td *traceSpan, start time.Time) { + for i := range td.Events { + td.Events[i].Offset = td.Events[i].Time.Sub(start) + } + for _, child := range td.ChildStartEnd { + if !child.Start { + fillOffsets(child.Span, start) + } + } +} + +func renderLabels(labels label.List) string { + buf := &bytes.Buffer{} + for index := 0; labels.Valid(index); index++ { + // The 'start' label duplicates the span name, so discard it. + if l := labels.Label(index); l.Valid() && l.Key().Name() != "start" { + fmt.Fprintf(buf, "%v ", l) + } + } + return buf.String() +} diff --git a/contribs/gnopls/internal/doc/api.go b/contribs/gnopls/internal/doc/api.go new file mode 100644 index 00000000000..a096f5ad63e --- /dev/null +++ b/contribs/gnopls/internal/doc/api.go @@ -0,0 +1,75 @@ +// 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. + +//go:generate go run ../../doc/generate + +// The doc package provides JSON metadata that documents gopls' public +// interfaces. +package doc + +import _ "embed" + +// JSON is a JSON encoding of value of type API. +// The 'gopls api-json' command prints it. +// +//go:embed api.json +var JSON string + +// API is a JSON-encodable representation of gopls' public interfaces. +// +// TODO(adonovan): document these data types. +type API struct { + Options map[string][]*Option + Lenses []*Lens + Analyzers []*Analyzer + Hints []*Hint +} + +type Option struct { + Name string + Type string // T = bool | string | int | enum | any | []T | map[T]T | time.Duration + Doc string + EnumKeys EnumKeys + EnumValues []EnumValue + Default string + Status string + Hierarchy string +} + +type EnumKeys struct { + ValueType string + Keys []EnumKey +} + +type EnumKey struct { + Name string // in JSON syntax (quoted) + Doc string + Default string +} + +type EnumValue struct { + Value string // in JSON syntax (quoted) + Doc string // doc comment; always starts with `Value` +} + +type Lens struct { + FileType string // e.g. "Go", "go.mod" + Lens string + Title string + Doc string + Default bool +} + +type Analyzer struct { + Name string + Doc string // from analysis.Analyzer.Doc ("title: summary\ndescription"; not Markdown) + URL string + Default bool +} + +type Hint struct { + Name string + Doc string + Default bool +} diff --git a/contribs/gnopls/internal/doc/api.json b/contribs/gnopls/internal/doc/api.json new file mode 100644 index 00000000000..d294ea0197d --- /dev/null +++ b/contribs/gnopls/internal/doc/api.json @@ -0,0 +1,1357 @@ +{ + "Options": { + "User": [ + { + "Name": "buildFlags", + "Type": "[]string", + "Doc": "buildFlags is the set of flags passed on to the build system when invoked.\nIt is applied to queries like `go list`, which is used when discovering files.\nThe most common use is to set `-tags`.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "[]", + "Status": "", + "Hierarchy": "build" + }, + { + "Name": "env", + "Type": "map[string]string", + "Doc": "env adds environment variables to external commands run by `gopls`, most notably `go list`.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "{}", + "Status": "", + "Hierarchy": "build" + }, + { + "Name": "directoryFilters", + "Type": "[]string", + "Doc": "directoryFilters can be used to exclude unwanted directories from the\nworkspace. By default, all directories are included. Filters are an\noperator, `+` to include and `-` to exclude, followed by a path prefix\nrelative to the workspace folder. They are evaluated in order, and\nthe last filter that applies to a path controls whether it is included.\nThe path prefix can be empty, so an initial `-` excludes everything.\n\nDirectoryFilters also supports the `**` operator to match 0 or more directories.\n\nExamples:\n\nExclude node_modules at current depth: `-node_modules`\n\nExclude node_modules at any depth: `-**/node_modules`\n\nInclude only project_a: `-` (exclude everything), `+project_a`\n\nInclude only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "[\"-**/node_modules\"]", + "Status": "", + "Hierarchy": "build" + }, + { + "Name": "templateExtensions", + "Type": "[]string", + "Doc": "templateExtensions gives the extensions of file names that are treated\nas template files. (The extension\nis the part of the file name after the final dot.)\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "[]", + "Status": "", + "Hierarchy": "build" + }, + { + "Name": "memoryMode", + "Type": "string", + "Doc": "obsolete, no effect\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "\"\"", + "Status": "experimental", + "Hierarchy": "build" + }, + { + "Name": "expandWorkspaceToModule", + "Type": "bool", + "Doc": "expandWorkspaceToModule determines which packages are considered\n\"workspace packages\" when the workspace is using modules.\n\nWorkspace packages affect the scope of workspace-wide operations. Notably,\ngopls diagnoses all packages considered to be part of the workspace after\nevery keystroke, so by setting \"ExpandWorkspaceToModule\" to false, and\nopening a nested workspace directory, you can reduce the amount of work\ngopls has to do to keep your workspace up to date.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "true", + "Status": "experimental", + "Hierarchy": "build" + }, + { + "Name": "allowImplicitNetworkAccess", + "Type": "bool", + "Doc": "allowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module\ndownloads rather than requiring user action. This option will eventually\nbe removed.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "false", + "Status": "experimental", + "Hierarchy": "build" + }, + { + "Name": "standaloneTags", + "Type": "[]string", + "Doc": "standaloneTags specifies a set of build constraints that identify\nindividual Go source files that make up the entire main package of an\nexecutable.\n\nA common example of standalone main files is the convention of using the\ndirective `//go:build ignore` to denote files that are not intended to be\nincluded in any package, for example because they are invoked directly by\nthe developer using `go run`.\n\nGopls considers a file to be a standalone main file if and only if it has\npackage name \"main\" and has a build directive of the exact form\n\"//go:build tag\" or \"// +build tag\", where tag is among the list of tags\nconfigured by this setting. Notably, if the build constraint is more\ncomplicated than a simple tag (such as the composite constraint\n`//go:build tag \u0026\u0026 go1.18`), the file is not considered to be a standalone\nmain file.\n\nThis setting is only supported when gopls is built with Go 1.16 or later.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "[\"ignore\"]", + "Status": "", + "Hierarchy": "build" + }, + { + "Name": "hoverKind", + "Type": "enum", + "Doc": "hoverKind controls the information that appears in the hover text.\nSingleLine and Structured are intended for use only by authors of editor plugins.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "\"FullDocumentation\"", + "Doc": "" + }, + { + "Value": "\"NoDocumentation\"", + "Doc": "" + }, + { + "Value": "\"SingleLine\"", + "Doc": "" + }, + { + "Value": "\"Structured\"", + "Doc": "`\"Structured\"` is an experimental setting that returns a structured hover format.\nThis format separates the signature from the documentation, so that the client\ncan do more manipulation of these fields.\n\nThis should only be used by clients that support this behavior.\n" + }, + { + "Value": "\"SynopsisDocumentation\"", + "Doc": "" + } + ], + "Default": "\"FullDocumentation\"", + "Status": "", + "Hierarchy": "ui.documentation" + }, + { + "Name": "linkTarget", + "Type": "string", + "Doc": "linkTarget is the base URL for links to Go package\ndocumentation returned by LSP operations such as Hover and\nDocumentLinks and in the CodeDescription field of each\nDiagnostic.\n\nIt might be one of:\n\n* `\"godoc.org\"`\n* `\"pkg.go.dev\"`\n\nIf company chooses to use its own `godoc.org`, its address can be used as well.\n\nModules matching the GOPRIVATE environment variable will not have\ndocumentation links in hover.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "\"pkg.go.dev\"", + "Status": "", + "Hierarchy": "ui.documentation" + }, + { + "Name": "linksInHover", + "Type": "enum", + "Doc": "linksInHover controls the presence of documentation links in hover markdown.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "false", + "Doc": "false: do not show links" + }, + { + "Value": "true", + "Doc": "true: show links to the `linkTarget` domain" + }, + { + "Value": "\"gopls\"", + "Doc": "`\"gopls\"`: show links to gopls' internal documentation viewer" + } + ], + "Default": "true", + "Status": "", + "Hierarchy": "ui.documentation" + }, + { + "Name": "usePlaceholders", + "Type": "bool", + "Doc": "placeholders enables placeholders for function parameters or struct\nfields in completion responses.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "false", + "Status": "", + "Hierarchy": "ui.completion" + }, + { + "Name": "completionBudget", + "Type": "time.Duration", + "Doc": "completionBudget is the soft latency goal for completion requests. Most\nrequests finish in a couple milliseconds, but in some cases deep\ncompletions can take much longer. As we use up our budget we\ndynamically reduce the search scope to ensure we return timely\nresults. Zero means unlimited.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "\"100ms\"", + "Status": "debug", + "Hierarchy": "ui.completion" + }, + { + "Name": "matcher", + "Type": "enum", + "Doc": "matcher sets the algorithm that is used when calculating completion\ncandidates.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "\"CaseInsensitive\"", + "Doc": "" + }, + { + "Value": "\"CaseSensitive\"", + "Doc": "" + }, + { + "Value": "\"Fuzzy\"", + "Doc": "" + } + ], + "Default": "\"Fuzzy\"", + "Status": "advanced", + "Hierarchy": "ui.completion" + }, + { + "Name": "experimentalPostfixCompletions", + "Type": "bool", + "Doc": "experimentalPostfixCompletions enables artificial method snippets\nsuch as \"someSlice.sort!\".\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "true", + "Status": "experimental", + "Hierarchy": "ui.completion" + }, + { + "Name": "completeFunctionCalls", + "Type": "bool", + "Doc": "completeFunctionCalls enables function call completion.\n\nWhen completing a statement, or when a function return type matches the\nexpected of the expression being completed, completion may suggest call\nexpressions (i.e. may include parentheses).\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "true", + "Status": "", + "Hierarchy": "ui.completion" + }, + { + "Name": "importShortcut", + "Type": "enum", + "Doc": "importShortcut specifies whether import statements should link to\ndocumentation or go to definitions.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "\"Both\"", + "Doc": "" + }, + { + "Value": "\"Definition\"", + "Doc": "" + }, + { + "Value": "\"Link\"", + "Doc": "" + } + ], + "Default": "\"Both\"", + "Status": "", + "Hierarchy": "ui.navigation" + }, + { + "Name": "symbolMatcher", + "Type": "enum", + "Doc": "symbolMatcher sets the algorithm that is used when finding workspace symbols.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "\"CaseInsensitive\"", + "Doc": "" + }, + { + "Value": "\"CaseSensitive\"", + "Doc": "" + }, + { + "Value": "\"FastFuzzy\"", + "Doc": "" + }, + { + "Value": "\"Fuzzy\"", + "Doc": "" + } + ], + "Default": "\"FastFuzzy\"", + "Status": "advanced", + "Hierarchy": "ui.navigation" + }, + { + "Name": "symbolStyle", + "Type": "enum", + "Doc": "symbolStyle controls how symbols are qualified in symbol responses.\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"symbolStyle\": \"Dynamic\",\n...\n}\n```\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "\"Dynamic\"", + "Doc": "`\"Dynamic\"` uses whichever qualifier results in the highest scoring\nmatch for the given symbol query. Here a \"qualifier\" is any \"/\" or \".\"\ndelimited suffix of the fully qualified symbol. i.e. \"to/pkg.Foo.Field\" or\njust \"Foo.Field\".\n" + }, + { + "Value": "\"Full\"", + "Doc": "`\"Full\"` is fully qualified symbols, i.e.\n\"path/to/pkg.Foo.Field\".\n" + }, + { + "Value": "\"Package\"", + "Doc": "`\"Package\"` is package qualified symbols i.e.\n\"pkg.Foo.Field\".\n" + } + ], + "Default": "\"Dynamic\"", + "Status": "advanced", + "Hierarchy": "ui.navigation" + }, + { + "Name": "symbolScope", + "Type": "enum", + "Doc": "symbolScope controls which packages are searched for workspace/symbol\nrequests. When the scope is \"workspace\", gopls searches only workspace\npackages. When the scope is \"all\", gopls searches all loaded packages,\nincluding dependencies and the standard library.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "\"all\"", + "Doc": "`\"all\"` matches symbols in any loaded package, including\ndependencies.\n" + }, + { + "Value": "\"workspace\"", + "Doc": "`\"workspace\"` matches symbols in workspace packages only.\n" + } + ], + "Default": "\"all\"", + "Status": "", + "Hierarchy": "ui.navigation" + }, + { + "Name": "analyses", + "Type": "map[string]bool", + "Doc": "analyses specify analyses that the user would like to enable or disable.\nA map of the names of analysis passes that should be enabled/disabled.\nA full list of analyzers that gopls uses can be found in\n[analyzers.md](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).\n\nExample Usage:\n\n```json5\n...\n\"analyses\": {\n \"unreachable\": false, // Disable the unreachable analyzer.\n \"unusedvariable\": true // Enable the unusedvariable analyzer.\n}\n...\n```\n", + "EnumKeys": { + "ValueType": "bool", + "Keys": [ + { + "Name": "\"appends\"", + "Doc": "check for missing values after append\n\nThis checker reports calls to append that pass\nno values to be appended to the slice.\n\n\ts := []string{\"a\", \"b\", \"c\"}\n\t_ = append(s)\n\nSuch calls are always no-ops and often indicate an\nunderlying mistake.", + "Default": "true" + }, + { + "Name": "\"asmdecl\"", + "Doc": "report mismatches between assembly files and Go declarations", + "Default": "true" + }, + { + "Name": "\"assign\"", + "Doc": "check for useless assignments\n\nThis checker reports assignments of the form x = x or a[i] = a[i].\nThese are almost always useless, and even when they aren't they are\nusually a mistake.", + "Default": "true" + }, + { + "Name": "\"atomic\"", + "Doc": "check for common mistakes using the sync/atomic package\n\nThe atomic checker looks for assignment statements of the form:\n\n\tx = atomic.AddUint64(\u0026x, 1)\n\nwhich are not atomic.", + "Default": "true" + }, + { + "Name": "\"atomicalign\"", + "Doc": "check for non-64-bits-aligned arguments to sync/atomic functions", + "Default": "true" + }, + { + "Name": "\"bools\"", + "Doc": "check for common mistakes involving boolean operators", + "Default": "true" + }, + { + "Name": "\"buildtag\"", + "Doc": "check //go:build and // +build directives", + "Default": "true" + }, + { + "Name": "\"cgocall\"", + "Doc": "detect some violations of the cgo pointer passing rules\n\nCheck for invalid cgo pointer passing.\nThis looks for code that uses cgo to call C code passing values\nwhose types are almost always invalid according to the cgo pointer\nsharing rules.\nSpecifically, it warns about attempts to pass a Go chan, map, func,\nor slice to C, either directly, or via a pointer, array, or struct.", + "Default": "true" + }, + { + "Name": "\"composites\"", + "Doc": "check for unkeyed composite literals\n\nThis analyzer reports a diagnostic for composite literals of struct\ntypes imported from another package that do not use the field-keyed\nsyntax. Such literals are fragile because the addition of a new field\n(even if unexported) to the struct will cause compilation to fail.\n\nAs an example,\n\n\terr = \u0026net.DNSConfigError{err}\n\nshould be replaced by:\n\n\terr = \u0026net.DNSConfigError{Err: err}\n", + "Default": "true" + }, + { + "Name": "\"copylocks\"", + "Doc": "check for locks erroneously passed by value\n\nInadvertently copying a value containing a lock, such as sync.Mutex or\nsync.WaitGroup, may cause both copies to malfunction. Generally such\nvalues should be referred to through a pointer.", + "Default": "true" + }, + { + "Name": "\"deepequalerrors\"", + "Doc": "check for calls of reflect.DeepEqual on error values\n\nThe deepequalerrors checker looks for calls of the form:\n\n reflect.DeepEqual(err1, err2)\n\nwhere err1 and err2 are errors. Using reflect.DeepEqual to compare\nerrors is discouraged.", + "Default": "true" + }, + { + "Name": "\"defers\"", + "Doc": "report common mistakes in defer statements\n\nThe defers analyzer reports a diagnostic when a defer statement would\nresult in a non-deferred call to time.Since, as experience has shown\nthat this is nearly always a mistake.\n\nFor example:\n\n\tstart := time.Now()\n\t...\n\tdefer recordLatency(time.Since(start)) // error: call to time.Since is not deferred\n\nThe correct code is:\n\n\tdefer func() { recordLatency(time.Since(start)) }()", + "Default": "true" + }, + { + "Name": "\"deprecated\"", + "Doc": "check for use of deprecated identifiers\n\nThe deprecated analyzer looks for deprecated symbols and package\nimports.\n\nSee https://go.dev/wiki/Deprecated to learn about Go's convention\nfor documenting and signaling deprecated identifiers.", + "Default": "true" + }, + { + "Name": "\"directive\"", + "Doc": "check Go toolchain directives such as //go:debug\n\nThis analyzer checks for problems with known Go toolchain directives\nin all Go source files in a package directory, even those excluded by\n//go:build constraints, and all non-Go source files too.\n\nFor //go:debug (see https://go.dev/doc/godebug), the analyzer checks\nthat the directives are placed only in Go source files, only above the\npackage comment, and only in package main or *_test.go files.\n\nSupport for other known directives may be added in the future.\n\nThis analyzer does not check //go:build, which is handled by the\nbuildtag analyzer.\n", + "Default": "true" + }, + { + "Name": "\"embed\"", + "Doc": "check //go:embed directive usage\n\nThis analyzer checks that the embed package is imported if //go:embed\ndirectives are present, providing a suggested fix to add the import if\nit is missing.\n\nThis analyzer also checks that //go:embed directives precede the\ndeclaration of a single variable.", + "Default": "true" + }, + { + "Name": "\"errorsas\"", + "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analysis reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.", + "Default": "true" + }, + { + "Name": "\"fillreturns\"", + "Doc": "suggest fixes for errors due to an incorrect number of return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\n\nwill turn into\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.", + "Default": "true" + }, + { + "Name": "\"framepointer\"", + "Doc": "report assembly that clobbers the frame pointer before saving it", + "Default": "true" + }, + { + "Name": "\"httpresponse\"", + "Doc": "check for mistakes using HTTP responses\n\nA common mistake when using the net/http package is to defer a function\ncall to close the http.Response Body before checking the error that\ndetermines whether the response is valid:\n\n\tresp, err := http.Head(url)\n\tdefer resp.Body.Close()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// (defer statement belongs here)\n\nThis checker helps uncover latent nil dereference bugs by reporting a\ndiagnostic for such mistakes.", + "Default": "true" + }, + { + "Name": "\"ifaceassert\"", + "Doc": "detect impossible interface-to-interface type assertions\n\nThis checker flags type assertions v.(T) and corresponding type-switch cases\nin which the static type V of v is an interface that cannot possibly implement\nthe target interface T. This occurs when V and T contain methods with the same\nname but different signatures. Example:\n\n\tvar v interface {\n\t\tRead()\n\t}\n\t_ = v.(io.Reader)\n\nThe Read method in v has a different signature than the Read method in\nio.Reader, so this assertion cannot succeed.", + "Default": "true" + }, + { + "Name": "\"infertypeargs\"", + "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n", + "Default": "true" + }, + { + "Name": "\"loopclosure\"", + "Doc": "check references to loop variables from within nested functions\n\nThis analyzer reports places where a function literal references the\niteration variable of an enclosing loop, and the loop calls the function\nin such a way (e.g. with go or defer) that it may outlive the loop\niteration and possibly observe the wrong value of the variable.\n\nNote: An iteration variable can only outlive a loop iteration in Go versions \u003c=1.21.\nIn Go 1.22 and later, the loop variable lifetimes changed to create a new\niteration variable per loop iteration. (See go.dev/issue/60078.)\n\nIn this example, all the deferred functions run after the loop has\ncompleted, so all observe the final value of v [\u003cgo1.22].\n\n\tfor _, v := range list {\n\t defer func() {\n\t use(v) // incorrect\n\t }()\n\t}\n\nOne fix is to create a new variable for each iteration of the loop:\n\n\tfor _, v := range list {\n\t v := v // new var per iteration\n\t defer func() {\n\t use(v) // ok\n\t }()\n\t}\n\nAfter Go version 1.22, the previous two for loops are equivalent\nand both are correct.\n\nThe next example uses a go statement and has a similar problem [\u003cgo1.22].\nIn addition, it has a data race because the loop updates v\nconcurrent with the goroutines accessing it.\n\n\tfor _, v := range elem {\n\t go func() {\n\t use(v) // incorrect, and a data race\n\t }()\n\t}\n\nA fix is the same as before. The checker also reports problems\nin goroutines started by golang.org/x/sync/errgroup.Group.\nA hard-to-spot variant of this form is common in parallel tests:\n\n\tfunc Test(t *testing.T) {\n\t for _, test := range tests {\n\t t.Run(test.name, func(t *testing.T) {\n\t t.Parallel()\n\t use(test) // incorrect, and a data race\n\t })\n\t }\n\t}\n\nThe t.Parallel() call causes the rest of the function to execute\nconcurrent with the loop [\u003cgo1.22].\n\nThe analyzer reports references only in the last statement,\nas it is not deep enough to understand the effects of subsequent\nstatements that might render the reference benign.\n(\"Last statement\" is defined recursively in compound\nstatements such as if, switch, and select.)\n\nSee: https://golang.org/doc/go_faq.html#closures_and_goroutines", + "Default": "true" + }, + { + "Name": "\"lostcancel\"", + "Doc": "check cancel func returned by context.WithCancel is called\n\nThe cancellation function returned by context.WithCancel, WithTimeout,\nand WithDeadline must be called or the new context will remain live\nuntil its parent context is cancelled.\n(The background context is never cancelled.)", + "Default": "true" + }, + { + "Name": "\"nilfunc\"", + "Doc": "check for useless comparisons between functions and nil\n\nA useless comparison is one like f == nil as opposed to f() == nil.", + "Default": "true" + }, + { + "Name": "\"nilness\"", + "Doc": "check for redundant or impossible nil comparisons\n\nThe nilness checker inspects the control-flow graph of each function in\na package and reports nil pointer dereferences, degenerate nil\npointers, and panics with nil values. A degenerate comparison is of the form\nx==nil or x!=nil where x is statically known to be nil or non-nil. These are\noften a mistake, especially in control flow related to errors. Panics with nil\nvalues are checked because they are not detectable by\n\n\tif r := recover(); r != nil {\n\nThis check reports conditions such as:\n\n\tif f == nil { // impossible condition (f is a function)\n\t}\n\nand:\n\n\tp := \u0026v\n\t...\n\tif p != nil { // tautological condition\n\t}\n\nand:\n\n\tif p == nil {\n\t\tprint(*p) // nil dereference\n\t}\n\nand:\n\n\tif p == nil {\n\t\tpanic(p)\n\t}\n\nSometimes the control flow may be quite complex, making bugs hard\nto spot. In the example below, the err.Error expression is\nguaranteed to panic because, after the first return, err must be\nnil. The intervening loop is just a distraction.\n\n\t...\n\terr := g.Wait()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpartialSuccess := false\n\tfor _, err := range errs {\n\t\tif err == nil {\n\t\t\tpartialSuccess = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif partialSuccess {\n\t\treportStatus(StatusMessage{\n\t\t\tCode: code.ERROR,\n\t\t\tDetail: err.Error(), // \"nil dereference in dynamic method call\"\n\t\t})\n\t\treturn nil\n\t}\n\n...", + "Default": "true" + }, + { + "Name": "\"nonewvars\"", + "Doc": "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\n\tz := 1\n\tz := 2\n\nwill turn into\n\n\tz := 1\n\tz = 2", + "Default": "true" + }, + { + "Name": "\"noresultvalues\"", + "Doc": "suggested fixes for unexpected return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\" or \"too many return values\".\nFor example:\n\n\tfunc z() { return nil }\n\nwill turn into\n\n\tfunc z() { return }", + "Default": "true" + }, + { + "Name": "\"printf\"", + "Doc": "check consistency of Printf format strings and arguments\n\nThe check applies to calls of the formatting functions such as\n[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of\nthose functions such as [log.Printf]. It reports a variety of\nmistakes such as syntax errors in the format string and mismatches\n(of number and type) between the verbs and their arguments.\n\nSee the documentation of the fmt package for the complete set of\nformat operators and their operand types.", + "Default": "true" + }, + { + "Name": "\"shadow\"", + "Doc": "check for possible unintended shadowing of variables\n\nThis analyzer check for shadowed variables.\nA shadowed variable is a variable declared in an inner scope\nwith the same name and type as a variable in an outer scope,\nand where the outer variable is mentioned after the inner one\nis declared.\n\n(This definition can be refined; the module generates too many\nfalse positives and is not yet enabled by default.)\n\nFor example:\n\n\tfunc BadRead(f *os.File, buf []byte) error {\n\t\tvar err error\n\t\tfor {\n\t\t\tn, err := f.Read(buf) // shadows the function variable 'err'\n\t\t\tif err != nil {\n\t\t\t\tbreak // causes return of wrong value\n\t\t\t}\n\t\t\tfoo(buf)\n\t\t}\n\t\treturn err\n\t}", + "Default": "false" + }, + { + "Name": "\"shift\"", + "Doc": "check for shifts that equal or exceed the width of the integer", + "Default": "true" + }, + { + "Name": "\"sigchanyzer\"", + "Doc": "check for unbuffered channel of os.Signal\n\nThis checker reports call expression of the form\n\n\tsignal.Notify(c \u003c-chan os.Signal, sig ...os.Signal),\n\nwhere c is an unbuffered channel, which can be at risk of missing the signal.", + "Default": "true" + }, + { + "Name": "\"simplifycompositelit\"", + "Doc": "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\n\t[]T{T{}, T{}}\n\nwill be simplified to:\n\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", + "Default": "true" + }, + { + "Name": "\"simplifyrange\"", + "Doc": "check for range statement simplifications\n\nA range of the form:\n\n\tfor x, _ = range v {...}\n\nwill be simplified to:\n\n\tfor x = range v {...}\n\nA range of the form:\n\n\tfor _ = range v {...}\n\nwill be simplified to:\n\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", + "Default": "true" + }, + { + "Name": "\"simplifyslice\"", + "Doc": "check for slice simplifications\n\nA slice expression of the form:\n\n\ts[a:len(s)]\n\nwill be simplified to:\n\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", + "Default": "true" + }, + { + "Name": "\"slog\"", + "Doc": "check for invalid structured logging calls\n\nThe slog checker looks for calls to functions from the log/slog\npackage that take alternating key-value pairs. It reports calls\nwhere an argument in a key position is neither a string nor a\nslog.Attr, and where a final key is missing its value.\nFor example,it would report\n\n\tslog.Warn(\"message\", 11, \"k\") // slog.Warn arg \"11\" should be a string or a slog.Attr\n\nand\n\n\tslog.Info(\"message\", \"k1\", v1, \"k2\") // call to slog.Info missing a final value", + "Default": "true" + }, + { + "Name": "\"sortslice\"", + "Doc": "check the argument type of sort.Slice\n\nsort.Slice requires an argument of a slice type. Check that\nthe interface{} value passed to sort.Slice is actually a slice.", + "Default": "true" + }, + { + "Name": "\"stdmethods\"", + "Doc": "check signature of methods of well-known interfaces\n\nSometimes a type may be intended to satisfy an interface but may fail to\ndo so because of a mistake in its method signature.\nFor example, the result of this WriteTo method should be (int64, error),\nnot error, to satisfy io.WriterTo:\n\n\ttype myWriterTo struct{...}\n\tfunc (myWriterTo) WriteTo(w io.Writer) error { ... }\n\nThis check ensures that each method whose name matches one of several\nwell-known interface methods from the standard library has the correct\nsignature for that interface.\n\nChecked method names include:\n\n\tFormat GobEncode GobDecode MarshalJSON MarshalXML\n\tPeek ReadByte ReadFrom ReadRune Scan Seek\n\tUnmarshalJSON UnreadByte UnreadRune WriteByte\n\tWriteTo", + "Default": "true" + }, + { + "Name": "\"stdversion\"", + "Doc": "report uses of too-new standard library symbols\n\nThe stdversion analyzer reports references to symbols in the standard\nlibrary that were introduced by a Go release higher than the one in\nforce in the referring file. (Recall that the file's Go version is\ndefined by the 'go' directive its module's go.mod file, or by a\n\"//go:build go1.X\" build tag at the top of the file.)\n\nThe analyzer does not report a diagnostic for a reference to a \"too\nnew\" field or method of a type that is itself \"too new\", as this may\nhave false positives, for example if fields or methods are accessed\nthrough a type alias that is guarded by a Go version constraint.\n", + "Default": "true" + }, + { + "Name": "\"stringintconv\"", + "Doc": "check for string(int) conversions\n\nThis checker flags conversions of the form string(x) where x is an integer\n(but not byte or rune) type. Such conversions are discouraged because they\nreturn the UTF-8 representation of the Unicode code point x, and not a decimal\nstring representation of x as one might expect. Furthermore, if x denotes an\ninvalid code point, the conversion cannot be statically rejected.\n\nFor conversions that intend on using the code point, consider replacing them\nwith string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the\nstring representation of the value in the desired base.", + "Default": "true" + }, + { + "Name": "\"structtag\"", + "Doc": "check that struct field tags conform to reflect.StructTag.Get\n\nAlso report certain struct tags (json, xml) used with unexported fields.", + "Default": "true" + }, + { + "Name": "\"stubmethods\"", + "Doc": "detect missing methods and fix with stub implementations\n\nThis analyzer detects type-checking errors due to missing methods\nin assignments from concrete types to interface types, and offers\na suggested fix that will create a set of stub methods so that\nthe concrete type satisfies the interface.\n\nFor example, this function will not compile because the value\nNegativeErr{} does not implement the \"error\" interface:\n\n\tfunc sqrt(x float64) (float64, error) {\n\t\tif x \u003c 0 {\n\t\t\treturn 0, NegativeErr{} // error: missing method\n\t\t}\n\t\t...\n\t}\n\n\ttype NegativeErr struct{}\n\nThis analyzer will suggest a fix to declare this method:\n\n\t// Error implements error.Error.\n\tfunc (NegativeErr) Error() string {\n\t\tpanic(\"unimplemented\")\n\t}\n\n(At least, it appears to behave that way, but technically it\ndoesn't use the SuggestedFix mechanism and the stub is created by\nlogic in gopls's golang.stub function.)", + "Default": "true" + }, + { + "Name": "\"testinggoroutine\"", + "Doc": "report calls to (*testing.T).Fatal from goroutines started by a test\n\nFunctions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and\nSkip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.\nThis checker detects calls to these functions that occur within a goroutine\nstarted by the test. For example:\n\n\tfunc TestFoo(t *testing.T) {\n\t go func() {\n\t t.Fatal(\"oops\") // error: (*T).Fatal called from non-test goroutine\n\t }()\n\t}", + "Default": "true" + }, + { + "Name": "\"tests\"", + "Doc": "check for common mistaken usages of tests and examples\n\nThe tests checker walks Test, Benchmark, Fuzzing and Example functions checking\nmalformed names, wrong signatures and examples documenting non-existent\nidentifiers.\n\nPlease see the documentation for package testing in golang.org/pkg/testing\nfor the conventions that are enforced for Tests, Benchmarks, and Examples.", + "Default": "true" + }, + { + "Name": "\"timeformat\"", + "Doc": "check for calls of (time.Time).Format or time.Parse with 2006-02-01\n\nThe timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm)\nformat. Internationally, \"yyyy-dd-mm\" does not occur in common calendar date\nstandards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.", + "Default": "true" + }, + { + "Name": "\"undeclaredname\"", + "Doc": "suggested fixes for \"undeclared name: \u003c\u003e\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: \u003c\u003e\". It will either insert a new statement,\nsuch as:\n\n\t\u003c\u003e :=\n\nor a new function declaration, such as:\n\n\tfunc \u003c\u003e(inferred parameters) {\n\t\tpanic(\"implement me!\")\n\t}", + "Default": "true" + }, + { + "Name": "\"unmarshal\"", + "Doc": "report passing non-pointer or non-interface values to unmarshal\n\nThe unmarshal analysis reports calls to functions such as json.Unmarshal\nin which the argument type is not a pointer or an interface.", + "Default": "true" + }, + { + "Name": "\"unreachable\"", + "Doc": "check for unreachable code\n\nThe unreachable analyzer finds statements that execution can never reach\nbecause they are preceded by an return statement, a call to panic, an\ninfinite loop, or similar constructs.", + "Default": "true" + }, + { + "Name": "\"unsafeptr\"", + "Doc": "check for invalid conversions of uintptr to unsafe.Pointer\n\nThe unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer\nto convert integers to pointers. A conversion from uintptr to\nunsafe.Pointer is invalid if it implies that there is a uintptr-typed\nword in memory that holds a pointer value, because that word will be\ninvisible to stack copying and to the garbage collector.", + "Default": "true" + }, + { + "Name": "\"unusedparams\"", + "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.", + "Default": "true" + }, + { + "Name": "\"unusedresult\"", + "Doc": "check for unused results of calls to some functions\n\nSome functions like fmt.Errorf return a result and have no side\neffects, so it is always a mistake to discard the result. Other\nfunctions may return an error that must not be ignored, or a cleanup\noperation that must be called. This analyzer reports calls to\nfunctions like these when the result of the call is ignored.\n\nThe set of functions may be controlled using flags.", + "Default": "true" + }, + { + "Name": "\"unusedvariable\"", + "Doc": "check for unused variables and suggest fixes", + "Default": "false" + }, + { + "Name": "\"unusedwrite\"", + "Doc": "checks for unused writes\n\nThe analyzer reports instances of writes to struct fields and\narrays that are never read. Specifically, when a struct object\nor an array is copied, its elements are copied implicitly by\nthe compiler, and any element write to this copy does nothing\nwith the original object.\n\nFor example:\n\n\ttype T struct { x int }\n\n\tfunc f(input []T) {\n\t\tfor i, v := range input { // v is a copy\n\t\t\tv.x = i // unused write to field x\n\t\t}\n\t}\n\nAnother example is about non-pointer receiver:\n\n\ttype T struct { x int }\n\n\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}", + "Default": "true" + }, + { + "Name": "\"useany\"", + "Doc": "check for constraints that could be simplified to \"any\"", + "Default": "false" + } + ] + }, + "EnumValues": null, + "Default": "{}", + "Status": "", + "Hierarchy": "ui.diagnostic" + }, + { + "Name": "staticcheck", + "Type": "bool", + "Doc": "staticcheck enables additional analyses from staticcheck.io.\nThese analyses are documented on\n[Staticcheck's website](https://staticcheck.io/docs/checks/).\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "false", + "Status": "experimental", + "Hierarchy": "ui.diagnostic" + }, + { + "Name": "annotations", + "Type": "map[enum]bool", + "Doc": "annotations specifies the various kinds of optimization diagnostics\nthat should be reported by the gc_details command.\n", + "EnumKeys": { + "ValueType": "bool", + "Keys": [ + { + "Name": "\"bounds\"", + "Doc": "`\"bounds\"` controls bounds checking diagnostics.\n", + "Default": "true" + }, + { + "Name": "\"escape\"", + "Doc": "`\"escape\"` controls diagnostics about escape choices.\n", + "Default": "true" + }, + { + "Name": "\"inline\"", + "Doc": "`\"inline\"` controls diagnostics about inlining choices.\n", + "Default": "true" + }, + { + "Name": "\"nil\"", + "Doc": "`\"nil\"` controls nil checks.\n", + "Default": "true" + } + ] + }, + "EnumValues": null, + "Default": "{\"bounds\":true,\"escape\":true,\"inline\":true,\"nil\":true}", + "Status": "experimental", + "Hierarchy": "ui.diagnostic" + }, + { + "Name": "vulncheck", + "Type": "enum", + "Doc": "vulncheck enables vulnerability scanning.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "\"Imports\"", + "Doc": "`\"Imports\"`: In Imports mode, `gopls` will report vulnerabilities that affect packages\ndirectly and indirectly used by the analyzed main module.\n" + }, + { + "Value": "\"Off\"", + "Doc": "`\"Off\"`: Disable vulnerability analysis.\n" + } + ], + "Default": "\"Off\"", + "Status": "experimental", + "Hierarchy": "ui.diagnostic" + }, + { + "Name": "diagnosticsDelay", + "Type": "time.Duration", + "Doc": "diagnosticsDelay controls the amount of time that gopls waits\nafter the most recent file modification before computing deep diagnostics.\nSimple diagnostics (parsing and type-checking) are always run immediately\non recently modified packages.\n\nThis option must be set to a valid duration string, for example `\"250ms\"`.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "\"1s\"", + "Status": "advanced", + "Hierarchy": "ui.diagnostic" + }, + { + "Name": "diagnosticsTrigger", + "Type": "enum", + "Doc": "diagnosticsTrigger controls when to run diagnostics.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": [ + { + "Value": "\"Edit\"", + "Doc": "`\"Edit\"`: Trigger diagnostics on file edit and save. (default)\n" + }, + { + "Value": "\"Save\"", + "Doc": "`\"Save\"`: Trigger diagnostics only on file save. Events like initial workspace load\nor configuration change will still trigger diagnostics.\n" + } + ], + "Default": "\"Edit\"", + "Status": "experimental", + "Hierarchy": "ui.diagnostic" + }, + { + "Name": "analysisProgressReporting", + "Type": "bool", + "Doc": "analysisProgressReporting controls whether gopls sends progress\nnotifications when construction of its index of analysis facts is taking a\nlong time. Cancelling these notifications will cancel the indexing task,\nthough it will restart after the next change in the workspace.\n\nWhen a package is opened for the first time and heavyweight analyses such as\nstaticcheck are enabled, it can take a while to construct the index of\nanalysis facts for all its dependencies. The index is cached in the\nfilesystem, so subsequent analysis should be faster.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "true", + "Status": "", + "Hierarchy": "ui.diagnostic" + }, + { + "Name": "hints", + "Type": "map[enum]bool", + "Doc": "hints specify inlay hints that users want to see. A full list of hints\nthat gopls uses can be found in\n[inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).\n", + "EnumKeys": { + "ValueType": "bool", + "Keys": [ + { + "Name": "\"assignVariableTypes\"", + "Doc": "`\"assignVariableTypes\"` controls inlay hints for variable types in assign statements:\n```go\n\ti/* int*/, j/* int*/ := 0, len(r)-1\n```\n", + "Default": "false" + }, + { + "Name": "\"compositeLiteralFields\"", + "Doc": "`\"compositeLiteralFields\"` inlay hints for composite literal field names:\n```go\n\t{/*in: */\"Hello, world\", /*want: */\"dlrow ,olleH\"}\n```\n", + "Default": "false" + }, + { + "Name": "\"compositeLiteralTypes\"", + "Doc": "`\"compositeLiteralTypes\"` controls inlay hints for composite literal types:\n```go\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}\n```\n", + "Default": "false" + }, + { + "Name": "\"constantValues\"", + "Doc": "`\"constantValues\"` controls inlay hints for constant values:\n```go\n\tconst (\n\t\tKindNone Kind = iota/* = 0*/\n\t\tKindPrint/* = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)\n```\n", + "Default": "false" + }, + { + "Name": "\"functionTypeParameters\"", + "Doc": "`\"functionTypeParameters\"` inlay hints for implicit type parameters on generic functions:\n```go\n\tmyFoo/*[int, string]*/(1, \"hello\")\n```\n", + "Default": "false" + }, + { + "Name": "\"parameterNames\"", + "Doc": "`\"parameterNames\"` controls inlay hints for parameter names:\n```go\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)\n```\n", + "Default": "false" + }, + { + "Name": "\"rangeVariableTypes\"", + "Doc": "`\"rangeVariableTypes\"` controls inlay hints for variable types in range statements:\n```go\n\tfor k/* int*/, v/* string*/ := range []string{} {\n\t\tfmt.Println(k, v)\n\t}\n```\n", + "Default": "false" + } + ] + }, + "EnumValues": null, + "Default": "{}", + "Status": "experimental", + "Hierarchy": "ui.inlayhint" + }, + { + "Name": "codelenses", + "Type": "map[enum]bool", + "Doc": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"codelenses\": {\n \"generate\": false, // Don't show the `go generate` lens.\n \"gc_details\": true // Show a code lens toggling the display of gc's choices.\n }\n...\n}\n```\n", + "EnumKeys": { + "ValueType": "bool", + "Keys": [ + { + "Name": "\"gc_details\"", + "Doc": "`\"gc_details\"`: Toggle display of Go compiler optimization decisions\n\nThis codelens source causes the `package` declaration of\neach file to be annotated with a command to toggle the\nstate of the per-session variable that controls whether\noptimization decisions from the Go compiler (formerly known\nas \"gc\") should be displayed as diagnostics.\n\nOptimization decisions include:\n- whether a variable escapes, and how escape is inferred;\n- whether a nil-pointer check is implied or eliminated;\n- whether a function can be inlined.\n\nTODO(adonovan): this source is off by default because the\nannotation is annoying and because VS Code has a separate\n\"Toggle gc details\" command. Replace it with a Code Action\n(\"Source action...\").\n", + "Default": "false" + }, + { + "Name": "\"generate\"", + "Doc": "`\"generate\"`: Run `go generate`\n\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n", + "Default": "true" + }, + { + "Name": "\"regenerate_cgo\"", + "Doc": "`\"regenerate_cgo\"`: Re-generate cgo declarations\n\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n", + "Default": "true" + }, + { + "Name": "\"run_govulncheck\"", + "Doc": "`\"run_govulncheck\"`: Run govulncheck\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run Govulncheck.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static\nanalysis tool that computes the set of functions reachable\nwithin your application, including dependencies;\nqueries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n", + "Default": "false" + }, + { + "Name": "\"test\"", + "Doc": "`\"test\"`: Run tests and benchmarks\n\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n", + "Default": "false" + }, + { + "Name": "\"tidy\"", + "Doc": "`\"tidy\"`: Tidy go.mod file\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n", + "Default": "true" + }, + { + "Name": "\"upgrade_dependency\"", + "Doc": "`\"upgrade_dependency\"`: Update dependencies\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n", + "Default": "true" + }, + { + "Name": "\"vendor\"", + "Doc": "`\"vendor\"`: Update vendor directory\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n", + "Default": "true" + } + ] + }, + "EnumValues": null, + "Default": "{\"gc_details\":false,\"generate\":true,\"regenerate_cgo\":true,\"run_govulncheck\":false,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}", + "Status": "", + "Hierarchy": "ui" + }, + { + "Name": "semanticTokens", + "Type": "bool", + "Doc": "semanticTokens controls whether the LSP server will send\nsemantic tokens to the client.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "false", + "Status": "experimental", + "Hierarchy": "ui" + }, + { + "Name": "noSemanticString", + "Type": "bool", + "Doc": "noSemanticString turns off the sending of the semantic token 'string'\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "false", + "Status": "experimental", + "Hierarchy": "ui" + }, + { + "Name": "noSemanticNumber", + "Type": "bool", + "Doc": "noSemanticNumber turns off the sending of the semantic token 'number'\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "false", + "Status": "experimental", + "Hierarchy": "ui" + }, + { + "Name": "local", + "Type": "string", + "Doc": "local is the equivalent of the `goimports -local` flag, which puts\nimports beginning with this string after third-party packages. It should\nbe the prefix of the import path whose imports should be grouped\nseparately.\n\nIt is used when tidying imports (during an LSP Organize\nImports request) or when inserting new ones (for example,\nduring completion); an LSP Formatting request merely sorts the\nexisting imports.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "\"\"", + "Status": "", + "Hierarchy": "formatting" + }, + { + "Name": "gofumpt", + "Type": "bool", + "Doc": "gofumpt indicates if we should run gofumpt formatting.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "false", + "Status": "", + "Hierarchy": "formatting" + }, + { + "Name": "verboseOutput", + "Type": "bool", + "Doc": "verboseOutput enables additional debug logging.\n", + "EnumKeys": { + "ValueType": "", + "Keys": null + }, + "EnumValues": null, + "Default": "false", + "Status": "debug", + "Hierarchy": "" + } + ] + }, + "Lenses": [ + { + "FileType": "Go", + "Lens": "gc_details", + "Title": "Toggle display of Go compiler optimization decisions", + "Doc": "\nThis codelens source causes the `package` declaration of\neach file to be annotated with a command to toggle the\nstate of the per-session variable that controls whether\noptimization decisions from the Go compiler (formerly known\nas \"gc\") should be displayed as diagnostics.\n\nOptimization decisions include:\n- whether a variable escapes, and how escape is inferred;\n- whether a nil-pointer check is implied or eliminated;\n- whether a function can be inlined.\n\nTODO(adonovan): this source is off by default because the\nannotation is annoying and because VS Code has a separate\n\"Toggle gc details\" command. Replace it with a Code Action\n(\"Source action...\").\n", + "Default": false + }, + { + "FileType": "Go", + "Lens": "generate", + "Title": "Run `go generate`", + "Doc": "\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n", + "Default": true + }, + { + "FileType": "Go", + "Lens": "regenerate_cgo", + "Title": "Re-generate cgo declarations", + "Doc": "\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n", + "Default": true + }, + { + "FileType": "Go", + "Lens": "test", + "Title": "Run tests and benchmarks", + "Doc": "\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n", + "Default": false + }, + { + "FileType": "go.mod", + "Lens": "run_govulncheck", + "Title": "Run govulncheck", + "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run Govulncheck.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static\nanalysis tool that computes the set of functions reachable\nwithin your application, including dependencies;\nqueries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n", + "Default": false + }, + { + "FileType": "go.mod", + "Lens": "tidy", + "Title": "Tidy go.mod file", + "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n", + "Default": true + }, + { + "FileType": "go.mod", + "Lens": "upgrade_dependency", + "Title": "Update dependencies", + "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n", + "Default": true + }, + { + "FileType": "go.mod", + "Lens": "vendor", + "Title": "Update vendor directory", + "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n", + "Default": true + } + ], + "Analyzers": [ + { + "Name": "appends", + "Doc": "check for missing values after append\n\nThis checker reports calls to append that pass\nno values to be appended to the slice.\n\n\ts := []string{\"a\", \"b\", \"c\"}\n\t_ = append(s)\n\nSuch calls are always no-ops and often indicate an\nunderlying mistake.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends", + "Default": true + }, + { + "Name": "asmdecl", + "Doc": "report mismatches between assembly files and Go declarations", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/asmdecl", + "Default": true + }, + { + "Name": "assign", + "Doc": "check for useless assignments\n\nThis checker reports assignments of the form x = x or a[i] = a[i].\nThese are almost always useless, and even when they aren't they are\nusually a mistake.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign", + "Default": true + }, + { + "Name": "atomic", + "Doc": "check for common mistakes using the sync/atomic package\n\nThe atomic checker looks for assignment statements of the form:\n\n\tx = atomic.AddUint64(\u0026x, 1)\n\nwhich are not atomic.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic", + "Default": true + }, + { + "Name": "atomicalign", + "Doc": "check for non-64-bits-aligned arguments to sync/atomic functions", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomicalign", + "Default": true + }, + { + "Name": "bools", + "Doc": "check for common mistakes involving boolean operators", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/bools", + "Default": true + }, + { + "Name": "buildtag", + "Doc": "check //go:build and // +build directives", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/buildtag", + "Default": true + }, + { + "Name": "cgocall", + "Doc": "detect some violations of the cgo pointer passing rules\n\nCheck for invalid cgo pointer passing.\nThis looks for code that uses cgo to call C code passing values\nwhose types are almost always invalid according to the cgo pointer\nsharing rules.\nSpecifically, it warns about attempts to pass a Go chan, map, func,\nor slice to C, either directly, or via a pointer, array, or struct.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall", + "Default": true + }, + { + "Name": "composites", + "Doc": "check for unkeyed composite literals\n\nThis analyzer reports a diagnostic for composite literals of struct\ntypes imported from another package that do not use the field-keyed\nsyntax. Such literals are fragile because the addition of a new field\n(even if unexported) to the struct will cause compilation to fail.\n\nAs an example,\n\n\terr = \u0026net.DNSConfigError{err}\n\nshould be replaced by:\n\n\terr = \u0026net.DNSConfigError{Err: err}\n", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/composite", + "Default": true + }, + { + "Name": "copylocks", + "Doc": "check for locks erroneously passed by value\n\nInadvertently copying a value containing a lock, such as sync.Mutex or\nsync.WaitGroup, may cause both copies to malfunction. Generally such\nvalues should be referred to through a pointer.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/copylock", + "Default": true + }, + { + "Name": "deepequalerrors", + "Doc": "check for calls of reflect.DeepEqual on error values\n\nThe deepequalerrors checker looks for calls of the form:\n\n reflect.DeepEqual(err1, err2)\n\nwhere err1 and err2 are errors. Using reflect.DeepEqual to compare\nerrors is discouraged.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/deepequalerrors", + "Default": true + }, + { + "Name": "defers", + "Doc": "report common mistakes in defer statements\n\nThe defers analyzer reports a diagnostic when a defer statement would\nresult in a non-deferred call to time.Since, as experience has shown\nthat this is nearly always a mistake.\n\nFor example:\n\n\tstart := time.Now()\n\t...\n\tdefer recordLatency(time.Since(start)) // error: call to time.Since is not deferred\n\nThe correct code is:\n\n\tdefer func() { recordLatency(time.Since(start)) }()", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers", + "Default": true + }, + { + "Name": "deprecated", + "Doc": "check for use of deprecated identifiers\n\nThe deprecated analyzer looks for deprecated symbols and package\nimports.\n\nSee https://go.dev/wiki/Deprecated to learn about Go's convention\nfor documenting and signaling deprecated identifiers.", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated", + "Default": true + }, + { + "Name": "directive", + "Doc": "check Go toolchain directives such as //go:debug\n\nThis analyzer checks for problems with known Go toolchain directives\nin all Go source files in a package directory, even those excluded by\n//go:build constraints, and all non-Go source files too.\n\nFor //go:debug (see https://go.dev/doc/godebug), the analyzer checks\nthat the directives are placed only in Go source files, only above the\npackage comment, and only in package main or *_test.go files.\n\nSupport for other known directives may be added in the future.\n\nThis analyzer does not check //go:build, which is handled by the\nbuildtag analyzer.\n", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/directive", + "Default": true + }, + { + "Name": "embed", + "Doc": "check //go:embed directive usage\n\nThis analyzer checks that the embed package is imported if //go:embed\ndirectives are present, providing a suggested fix to add the import if\nit is missing.\n\nThis analyzer also checks that //go:embed directives precede the\ndeclaration of a single variable.", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective", + "Default": true + }, + { + "Name": "errorsas", + "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analysis reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas", + "Default": true + }, + { + "Name": "fillreturns", + "Doc": "suggest fixes for errors due to an incorrect number of return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\n\nwill turn into\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns", + "Default": true + }, + { + "Name": "framepointer", + "Doc": "report assembly that clobbers the frame pointer before saving it", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer", + "Default": true + }, + { + "Name": "httpresponse", + "Doc": "check for mistakes using HTTP responses\n\nA common mistake when using the net/http package is to defer a function\ncall to close the http.Response Body before checking the error that\ndetermines whether the response is valid:\n\n\tresp, err := http.Head(url)\n\tdefer resp.Body.Close()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// (defer statement belongs here)\n\nThis checker helps uncover latent nil dereference bugs by reporting a\ndiagnostic for such mistakes.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/httpresponse", + "Default": true + }, + { + "Name": "ifaceassert", + "Doc": "detect impossible interface-to-interface type assertions\n\nThis checker flags type assertions v.(T) and corresponding type-switch cases\nin which the static type V of v is an interface that cannot possibly implement\nthe target interface T. This occurs when V and T contain methods with the same\nname but different signatures. Example:\n\n\tvar v interface {\n\t\tRead()\n\t}\n\t_ = v.(io.Reader)\n\nThe Read method in v has a different signature than the Read method in\nio.Reader, so this assertion cannot succeed.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert", + "Default": true + }, + { + "Name": "infertypeargs", + "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs", + "Default": true + }, + { + "Name": "loopclosure", + "Doc": "check references to loop variables from within nested functions\n\nThis analyzer reports places where a function literal references the\niteration variable of an enclosing loop, and the loop calls the function\nin such a way (e.g. with go or defer) that it may outlive the loop\niteration and possibly observe the wrong value of the variable.\n\nNote: An iteration variable can only outlive a loop iteration in Go versions \u003c=1.21.\nIn Go 1.22 and later, the loop variable lifetimes changed to create a new\niteration variable per loop iteration. (See go.dev/issue/60078.)\n\nIn this example, all the deferred functions run after the loop has\ncompleted, so all observe the final value of v [\u003cgo1.22].\n\n\tfor _, v := range list {\n\t defer func() {\n\t use(v) // incorrect\n\t }()\n\t}\n\nOne fix is to create a new variable for each iteration of the loop:\n\n\tfor _, v := range list {\n\t v := v // new var per iteration\n\t defer func() {\n\t use(v) // ok\n\t }()\n\t}\n\nAfter Go version 1.22, the previous two for loops are equivalent\nand both are correct.\n\nThe next example uses a go statement and has a similar problem [\u003cgo1.22].\nIn addition, it has a data race because the loop updates v\nconcurrent with the goroutines accessing it.\n\n\tfor _, v := range elem {\n\t go func() {\n\t use(v) // incorrect, and a data race\n\t }()\n\t}\n\nA fix is the same as before. The checker also reports problems\nin goroutines started by golang.org/x/sync/errgroup.Group.\nA hard-to-spot variant of this form is common in parallel tests:\n\n\tfunc Test(t *testing.T) {\n\t for _, test := range tests {\n\t t.Run(test.name, func(t *testing.T) {\n\t t.Parallel()\n\t use(test) // incorrect, and a data race\n\t })\n\t }\n\t}\n\nThe t.Parallel() call causes the rest of the function to execute\nconcurrent with the loop [\u003cgo1.22].\n\nThe analyzer reports references only in the last statement,\nas it is not deep enough to understand the effects of subsequent\nstatements that might render the reference benign.\n(\"Last statement\" is defined recursively in compound\nstatements such as if, switch, and select.)\n\nSee: https://golang.org/doc/go_faq.html#closures_and_goroutines", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/loopclosure", + "Default": true + }, + { + "Name": "lostcancel", + "Doc": "check cancel func returned by context.WithCancel is called\n\nThe cancellation function returned by context.WithCancel, WithTimeout,\nand WithDeadline must be called or the new context will remain live\nuntil its parent context is cancelled.\n(The background context is never cancelled.)", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel", + "Default": true + }, + { + "Name": "nilfunc", + "Doc": "check for useless comparisons between functions and nil\n\nA useless comparison is one like f == nil as opposed to f() == nil.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc", + "Default": true + }, + { + "Name": "nilness", + "Doc": "check for redundant or impossible nil comparisons\n\nThe nilness checker inspects the control-flow graph of each function in\na package and reports nil pointer dereferences, degenerate nil\npointers, and panics with nil values. A degenerate comparison is of the form\nx==nil or x!=nil where x is statically known to be nil or non-nil. These are\noften a mistake, especially in control flow related to errors. Panics with nil\nvalues are checked because they are not detectable by\n\n\tif r := recover(); r != nil {\n\nThis check reports conditions such as:\n\n\tif f == nil { // impossible condition (f is a function)\n\t}\n\nand:\n\n\tp := \u0026v\n\t...\n\tif p != nil { // tautological condition\n\t}\n\nand:\n\n\tif p == nil {\n\t\tprint(*p) // nil dereference\n\t}\n\nand:\n\n\tif p == nil {\n\t\tpanic(p)\n\t}\n\nSometimes the control flow may be quite complex, making bugs hard\nto spot. In the example below, the err.Error expression is\nguaranteed to panic because, after the first return, err must be\nnil. The intervening loop is just a distraction.\n\n\t...\n\terr := g.Wait()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpartialSuccess := false\n\tfor _, err := range errs {\n\t\tif err == nil {\n\t\t\tpartialSuccess = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif partialSuccess {\n\t\treportStatus(StatusMessage{\n\t\t\tCode: code.ERROR,\n\t\t\tDetail: err.Error(), // \"nil dereference in dynamic method call\"\n\t\t})\n\t\treturn nil\n\t}\n\n...", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilness", + "Default": true + }, + { + "Name": "nonewvars", + "Doc": "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\n\tz := 1\n\tz := 2\n\nwill turn into\n\n\tz := 1\n\tz = 2", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars", + "Default": true + }, + { + "Name": "noresultvalues", + "Doc": "suggested fixes for unexpected return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\" or \"too many return values\".\nFor example:\n\n\tfunc z() { return nil }\n\nwill turn into\n\n\tfunc z() { return }", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues", + "Default": true + }, + { + "Name": "printf", + "Doc": "check consistency of Printf format strings and arguments\n\nThe check applies to calls of the formatting functions such as\n[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of\nthose functions such as [log.Printf]. It reports a variety of\nmistakes such as syntax errors in the format string and mismatches\n(of number and type) between the verbs and their arguments.\n\nSee the documentation of the fmt package for the complete set of\nformat operators and their operand types.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf", + "Default": true + }, + { + "Name": "shadow", + "Doc": "check for possible unintended shadowing of variables\n\nThis analyzer check for shadowed variables.\nA shadowed variable is a variable declared in an inner scope\nwith the same name and type as a variable in an outer scope,\nand where the outer variable is mentioned after the inner one\nis declared.\n\n(This definition can be refined; the module generates too many\nfalse positives and is not yet enabled by default.)\n\nFor example:\n\n\tfunc BadRead(f *os.File, buf []byte) error {\n\t\tvar err error\n\t\tfor {\n\t\t\tn, err := f.Read(buf) // shadows the function variable 'err'\n\t\t\tif err != nil {\n\t\t\t\tbreak // causes return of wrong value\n\t\t\t}\n\t\t\tfoo(buf)\n\t\t}\n\t\treturn err\n\t}", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow", + "Default": false + }, + { + "Name": "shift", + "Doc": "check for shifts that equal or exceed the width of the integer", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shift", + "Default": true + }, + { + "Name": "sigchanyzer", + "Doc": "check for unbuffered channel of os.Signal\n\nThis checker reports call expression of the form\n\n\tsignal.Notify(c \u003c-chan os.Signal, sig ...os.Signal),\n\nwhere c is an unbuffered channel, which can be at risk of missing the signal.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer", + "Default": true + }, + { + "Name": "simplifycompositelit", + "Doc": "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\n\t[]T{T{}, T{}}\n\nwill be simplified to:\n\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit", + "Default": true + }, + { + "Name": "simplifyrange", + "Doc": "check for range statement simplifications\n\nA range of the form:\n\n\tfor x, _ = range v {...}\n\nwill be simplified to:\n\n\tfor x = range v {...}\n\nA range of the form:\n\n\tfor _ = range v {...}\n\nwill be simplified to:\n\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange", + "Default": true + }, + { + "Name": "simplifyslice", + "Doc": "check for slice simplifications\n\nA slice expression of the form:\n\n\ts[a:len(s)]\n\nwill be simplified to:\n\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice", + "Default": true + }, + { + "Name": "slog", + "Doc": "check for invalid structured logging calls\n\nThe slog checker looks for calls to functions from the log/slog\npackage that take alternating key-value pairs. It reports calls\nwhere an argument in a key position is neither a string nor a\nslog.Attr, and where a final key is missing its value.\nFor example,it would report\n\n\tslog.Warn(\"message\", 11, \"k\") // slog.Warn arg \"11\" should be a string or a slog.Attr\n\nand\n\n\tslog.Info(\"message\", \"k1\", v1, \"k2\") // call to slog.Info missing a final value", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog", + "Default": true + }, + { + "Name": "sortslice", + "Doc": "check the argument type of sort.Slice\n\nsort.Slice requires an argument of a slice type. Check that\nthe interface{} value passed to sort.Slice is actually a slice.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sortslice", + "Default": true + }, + { + "Name": "stdmethods", + "Doc": "check signature of methods of well-known interfaces\n\nSometimes a type may be intended to satisfy an interface but may fail to\ndo so because of a mistake in its method signature.\nFor example, the result of this WriteTo method should be (int64, error),\nnot error, to satisfy io.WriterTo:\n\n\ttype myWriterTo struct{...}\n\tfunc (myWriterTo) WriteTo(w io.Writer) error { ... }\n\nThis check ensures that each method whose name matches one of several\nwell-known interface methods from the standard library has the correct\nsignature for that interface.\n\nChecked method names include:\n\n\tFormat GobEncode GobDecode MarshalJSON MarshalXML\n\tPeek ReadByte ReadFrom ReadRune Scan Seek\n\tUnmarshalJSON UnreadByte UnreadRune WriteByte\n\tWriteTo", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdmethods", + "Default": true + }, + { + "Name": "stdversion", + "Doc": "report uses of too-new standard library symbols\n\nThe stdversion analyzer reports references to symbols in the standard\nlibrary that were introduced by a Go release higher than the one in\nforce in the referring file. (Recall that the file's Go version is\ndefined by the 'go' directive its module's go.mod file, or by a\n\"//go:build go1.X\" build tag at the top of the file.)\n\nThe analyzer does not report a diagnostic for a reference to a \"too\nnew\" field or method of a type that is itself \"too new\", as this may\nhave false positives, for example if fields or methods are accessed\nthrough a type alias that is guarded by a Go version constraint.\n", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion", + "Default": true + }, + { + "Name": "stringintconv", + "Doc": "check for string(int) conversions\n\nThis checker flags conversions of the form string(x) where x is an integer\n(but not byte or rune) type. Such conversions are discouraged because they\nreturn the UTF-8 representation of the Unicode code point x, and not a decimal\nstring representation of x as one might expect. Furthermore, if x denotes an\ninvalid code point, the conversion cannot be statically rejected.\n\nFor conversions that intend on using the code point, consider replacing them\nwith string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the\nstring representation of the value in the desired base.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv", + "Default": true + }, + { + "Name": "structtag", + "Doc": "check that struct field tags conform to reflect.StructTag.Get\n\nAlso report certain struct tags (json, xml) used with unexported fields.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/structtag", + "Default": true + }, + { + "Name": "stubmethods", + "Doc": "detect missing methods and fix with stub implementations\n\nThis analyzer detects type-checking errors due to missing methods\nin assignments from concrete types to interface types, and offers\na suggested fix that will create a set of stub methods so that\nthe concrete type satisfies the interface.\n\nFor example, this function will not compile because the value\nNegativeErr{} does not implement the \"error\" interface:\n\n\tfunc sqrt(x float64) (float64, error) {\n\t\tif x \u003c 0 {\n\t\t\treturn 0, NegativeErr{} // error: missing method\n\t\t}\n\t\t...\n\t}\n\n\ttype NegativeErr struct{}\n\nThis analyzer will suggest a fix to declare this method:\n\n\t// Error implements error.Error.\n\tfunc (NegativeErr) Error() string {\n\t\tpanic(\"unimplemented\")\n\t}\n\n(At least, it appears to behave that way, but technically it\ndoesn't use the SuggestedFix mechanism and the stub is created by\nlogic in gopls's golang.stub function.)", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods", + "Default": true + }, + { + "Name": "testinggoroutine", + "Doc": "report calls to (*testing.T).Fatal from goroutines started by a test\n\nFunctions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and\nSkip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.\nThis checker detects calls to these functions that occur within a goroutine\nstarted by the test. For example:\n\n\tfunc TestFoo(t *testing.T) {\n\t go func() {\n\t t.Fatal(\"oops\") // error: (*T).Fatal called from non-test goroutine\n\t }()\n\t}", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/testinggoroutine", + "Default": true + }, + { + "Name": "tests", + "Doc": "check for common mistaken usages of tests and examples\n\nThe tests checker walks Test, Benchmark, Fuzzing and Example functions checking\nmalformed names, wrong signatures and examples documenting non-existent\nidentifiers.\n\nPlease see the documentation for package testing in golang.org/pkg/testing\nfor the conventions that are enforced for Tests, Benchmarks, and Examples.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/tests", + "Default": true + }, + { + "Name": "timeformat", + "Doc": "check for calls of (time.Time).Format or time.Parse with 2006-02-01\n\nThe timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm)\nformat. Internationally, \"yyyy-dd-mm\" does not occur in common calendar date\nstandards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat", + "Default": true + }, + { + "Name": "undeclaredname", + "Doc": "suggested fixes for \"undeclared name: \u003c\u003e\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: \u003c\u003e\". It will either insert a new statement,\nsuch as:\n\n\t\u003c\u003e :=\n\nor a new function declaration, such as:\n\n\tfunc \u003c\u003e(inferred parameters) {\n\t\tpanic(\"implement me!\")\n\t}", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/undeclaredname", + "Default": true + }, + { + "Name": "unmarshal", + "Doc": "report passing non-pointer or non-interface values to unmarshal\n\nThe unmarshal analysis reports calls to functions such as json.Unmarshal\nin which the argument type is not a pointer or an interface.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unmarshal", + "Default": true + }, + { + "Name": "unreachable", + "Doc": "check for unreachable code\n\nThe unreachable analyzer finds statements that execution can never reach\nbecause they are preceded by an return statement, a call to panic, an\ninfinite loop, or similar constructs.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable", + "Default": true + }, + { + "Name": "unsafeptr", + "Doc": "check for invalid conversions of uintptr to unsafe.Pointer\n\nThe unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer\nto convert integers to pointers. A conversion from uintptr to\nunsafe.Pointer is invalid if it implies that there is a uintptr-typed\nword in memory that holds a pointer value, because that word will be\ninvisible to stack copying and to the garbage collector.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr", + "Default": true + }, + { + "Name": "unusedparams", + "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams", + "Default": true + }, + { + "Name": "unusedresult", + "Doc": "check for unused results of calls to some functions\n\nSome functions like fmt.Errorf return a result and have no side\neffects, so it is always a mistake to discard the result. Other\nfunctions may return an error that must not be ignored, or a cleanup\noperation that must be called. This analyzer reports calls to\nfunctions like these when the result of the call is ignored.\n\nThe set of functions may be controlled using flags.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult", + "Default": true + }, + { + "Name": "unusedvariable", + "Doc": "check for unused variables and suggest fixes", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedvariable", + "Default": false + }, + { + "Name": "unusedwrite", + "Doc": "checks for unused writes\n\nThe analyzer reports instances of writes to struct fields and\narrays that are never read. Specifically, when a struct object\nor an array is copied, its elements are copied implicitly by\nthe compiler, and any element write to this copy does nothing\nwith the original object.\n\nFor example:\n\n\ttype T struct { x int }\n\n\tfunc f(input []T) {\n\t\tfor i, v := range input { // v is a copy\n\t\t\tv.x = i // unused write to field x\n\t\t}\n\t}\n\nAnother example is about non-pointer receiver:\n\n\ttype T struct { x int }\n\n\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedwrite", + "Default": true + }, + { + "Name": "useany", + "Doc": "check for constraints that could be simplified to \"any\"", + "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/useany", + "Default": false + } + ], + "Hints": [ + { + "Name": "assignVariableTypes", + "Doc": "`\"assignVariableTypes\"` controls inlay hints for variable types in assign statements:\n```go\n\ti/* int*/, j/* int*/ := 0, len(r)-1\n```\n", + "Default": false + }, + { + "Name": "compositeLiteralFields", + "Doc": "`\"compositeLiteralFields\"` inlay hints for composite literal field names:\n```go\n\t{/*in: */\"Hello, world\", /*want: */\"dlrow ,olleH\"}\n```\n", + "Default": false + }, + { + "Name": "compositeLiteralTypes", + "Doc": "`\"compositeLiteralTypes\"` controls inlay hints for composite literal types:\n```go\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}\n```\n", + "Default": false + }, + { + "Name": "constantValues", + "Doc": "`\"constantValues\"` controls inlay hints for constant values:\n```go\n\tconst (\n\t\tKindNone Kind = iota/* = 0*/\n\t\tKindPrint/* = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)\n```\n", + "Default": false + }, + { + "Name": "functionTypeParameters", + "Doc": "`\"functionTypeParameters\"` inlay hints for implicit type parameters on generic functions:\n```go\n\tmyFoo/*[int, string]*/(1, \"hello\")\n```\n", + "Default": false + }, + { + "Name": "parameterNames", + "Doc": "`\"parameterNames\"` controls inlay hints for parameter names:\n```go\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)\n```\n", + "Default": false + }, + { + "Name": "rangeVariableTypes", + "Doc": "`\"rangeVariableTypes\"` controls inlay hints for variable types in range statements:\n```go\n\tfor k/* int*/, v/* string*/ := range []string{} {\n\t\tfmt.Println(k, v)\n\t}\n```\n", + "Default": false + } + ] +} \ No newline at end of file diff --git a/contribs/gnopls/internal/file/file.go b/contribs/gnopls/internal/file/file.go new file mode 100644 index 00000000000..5f8be06cf60 --- /dev/null +++ b/contribs/gnopls/internal/file/file.go @@ -0,0 +1,62 @@ +// 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. + +// The file package defines types used for working with LSP files. +package file + +import ( + "context" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// An Identity identifies the name and contents of a file. +// +// TODO(rfindley): Identity may not carry its weight. Consider instead just +// exposing Handle.Hash, and using an ad-hoc key type where necessary. +// Or perhaps if mod/work parsing is moved outside of the memoize cache, +// a notion of Identity simply isn't needed. +type Identity struct { + URI protocol.DocumentURI + Hash Hash // digest of file contents +} + +func (id Identity) String() string { + return fmt.Sprintf("%s%s", id.URI, id.Hash) +} + +// A FileHandle represents the URI, content, hash, and optional +// version of a file tracked by the LSP session. +// +// File content may be provided by the file system (for Saved files) +// or from an overlay, for open files with unsaved edits. +// A FileHandle may record an attempt to read a non-existent file, +// in which case Content returns an error. +type Handle interface { + // URI is the URI for this file handle. + URI() protocol.DocumentURI + // Identity returns an Identity for the file, even if there was an error + // reading it. + Identity() Identity + // SameContentsOnDisk reports whether the file has the same content on disk: + // it is false for files open on an editor with unsaved edits. + SameContentsOnDisk() bool + // Version returns the file version, as defined by the LSP client. + // For on-disk file handles, Version returns 0. + Version() int32 + // Content returns the contents of a file. + // If the file is not available, returns a nil slice and an error. + Content() ([]byte, error) +} + +// A Source maps URIs to Handles. +type Source interface { + // ReadFile returns the Handle for a given URI, either by reading the content + // of the file or by obtaining it from a cache. + // + // Invariant: ReadFile must only return an error in the case of context + // cancellation. If ctx.Err() is nil, the resulting error must also be nil. + ReadFile(ctx context.Context, uri protocol.DocumentURI) (Handle, error) +} diff --git a/contribs/gnopls/internal/file/hash.go b/contribs/gnopls/internal/file/hash.go new file mode 100644 index 00000000000..eb182536ab7 --- /dev/null +++ b/contribs/gnopls/internal/file/hash.go @@ -0,0 +1,33 @@ +// 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 file + +import ( + "crypto/sha256" + "fmt" +) + +// A Hash is a cryptographic digest of the contents of a file. +// (Although at 32B it is larger than a 16B string header, it is smaller +// and has better locality than the string header + 64B of hex digits.) +type Hash [sha256.Size]byte + +// HashOf returns the hash of some data. +func HashOf(data []byte) Hash { + return Hash(sha256.Sum256(data)) +} + +// String returns the digest as a string of hex digits. +func (h Hash) String() string { + return fmt.Sprintf("%64x", [sha256.Size]byte(h)) +} + +// XORWith updates *h to *h XOR h2. +func (h *Hash) XORWith(h2 Hash) { + // Small enough that we don't need crypto/subtle.XORBytes. + for i := range h { + h[i] ^= h2[i] + } +} diff --git a/contribs/gnopls/internal/file/kind.go b/contribs/gnopls/internal/file/kind.go new file mode 100644 index 00000000000..087a57f32d0 --- /dev/null +++ b/contribs/gnopls/internal/file/kind.go @@ -0,0 +1,68 @@ +// 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 file + +import ( + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// Kind describes the kind of the file in question. +// It can be one of Go,mod, Sum, or Tmpl. +type Kind int + +const ( + // UnknownKind is a file type we don't know about. + UnknownKind = Kind(iota) + + // Go is a Go source file. + Go + // Mod is a go.mod file. + Mod + // Sum is a go.sum file. + Sum + // Tmpl is a template file. + Tmpl + // Work is a go.work file. + Work +) + +func (k Kind) String() string { + switch k { + case Go: + return "go" + case Mod: + return "go.mod" + case Sum: + return "go.sum" + case Tmpl: + return "tmpl" + case Work: + return "go.work" + default: + return fmt.Sprintf("internal error: unknown file kind %d", k) + } +} + +// KindForLang returns the gopls file [Kind] associated with the given LSP +// LanguageKind string from protocol.TextDocumentItem.LanguageID, +// or UnknownKind if the language is not one recognized by gopls. +func KindForLang(langID protocol.LanguageKind) Kind { + switch langID { + case "go": + return Go + case "go.mod": + return Mod + case "go.sum": + return Sum + case "tmpl", "gotmpl": + return Tmpl + case "go.work": + return Work + default: + return UnknownKind + } +} diff --git a/contribs/gnopls/internal/file/modification.go b/contribs/gnopls/internal/file/modification.go new file mode 100644 index 00000000000..a53bb17898a --- /dev/null +++ b/contribs/gnopls/internal/file/modification.go @@ -0,0 +1,57 @@ +// 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 file + +import "golang.org/x/tools/gopls/internal/protocol" + +// Modification represents a modification to a file. +type Modification struct { + URI protocol.DocumentURI + Action Action + + // OnDisk is true if a watched file is changed on disk. + // If true, Version will be -1 and Text will be nil. + OnDisk bool + + // Version will be -1 and Text will be nil when they are not supplied, + // specifically on textDocument/didClose and for on-disk changes. + Version int32 + Text []byte + + // LanguageID is only sent from the language client on textDocument/didOpen. + LanguageID protocol.LanguageKind +} + +// An Action is a type of file state change. +type Action int + +const ( + UnknownAction = Action(iota) + Open + Change + Close + Save + Create + Delete +) + +func (a Action) String() string { + switch a { + case Open: + return "Open" + case Change: + return "Change" + case Close: + return "Close" + case Save: + return "Save" + case Create: + return "Create" + case Delete: + return "Delete" + default: + return "Unknown" + } +} diff --git a/contribs/gnopls/internal/filecache/filecache.go b/contribs/gnopls/internal/filecache/filecache.go new file mode 100644 index 00000000000..243e9547128 --- /dev/null +++ b/contribs/gnopls/internal/filecache/filecache.go @@ -0,0 +1,606 @@ +// 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. + +// The filecache package provides a file-based shared durable blob cache. +// +// The cache is a machine-global mapping from (kind string, key +// [32]byte) to []byte, where kind is an identifier describing the +// namespace or purpose (e.g. "analysis"), and key is a SHA-256 digest +// of the recipe of the value. (It need not be the digest of the value +// itself, so you can query the cache without knowing what value the +// recipe would produce.) +// +// The space budget of the cache can be controlled by [SetBudget]. +// Cache entries may be evicted at any time or in any order. +// Note that "du -sh $GOPLSCACHE" may report a disk usage +// figure that is rather larger (e.g. 50%) than the budget because +// it rounds up partial disk blocks. +// +// The Get and Set operations are concurrency-safe. +package filecache + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "log" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/lru" +) + +// Start causes the filecache to initialize and start garbage gollection. +// +// Start is automatically called by the first call to Get, but may be called +// explicitly to pre-initialize the cache. +func Start() { + go getCacheDir() +} + +// As an optimization, use a 100MB in-memory LRU cache in front of filecache +// operations. This reduces I/O for operations such as diagnostics or +// implementations that repeatedly access the same cache entries. +var memCache = lru.New[memKey, []byte](100 * 1e6) + +type memKey struct { + kind string + key [32]byte +} + +// Get retrieves from the cache and returns the value most recently +// supplied to Set(kind, key), possibly by another process. +// Get returns ErrNotFound if the value was not found. +// +// Callers should not modify the returned array. +func Get(kind string, key [32]byte) ([]byte, error) { + // First consult the read-through memory cache. + // Note that memory cache hits do not update the times + // used for LRU eviction of the file-based cache. + if value, ok := memCache.Get(memKey{kind, key}); ok { + return value, nil + } + + iolimit <- struct{}{} // acquire a token + defer func() { <-iolimit }() // release a token + + // Read the index file, which provides the name of the CAS file. + indexName, err := filename(kind, key) + if err != nil { + return nil, err + } + indexData, err := os.ReadFile(indexName) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, ErrNotFound + } + return nil, err + } + var valueHash [32]byte + if copy(valueHash[:], indexData) != len(valueHash) { + return nil, ErrNotFound // index entry has wrong length + } + + // Read the CAS file and check its contents match. + // + // This ensures integrity in all cases (corrupt or truncated + // file, short read, I/O error, wrong length, etc) except an + // engineered hash collision, which is infeasible. + casName, err := filename(casKind, valueHash) + if err != nil { + return nil, err + } + value, _ := os.ReadFile(casName) // ignore error + if sha256.Sum256(value) != valueHash { + return nil, ErrNotFound // CAS file is missing or has wrong contents + } + + // Update file times used by LRU eviction. + // + // Because this turns a read into a write operation, + // we follow the approach used in the go command's + // cache and update the access time only if the + // existing timestamp is older than one hour. + // + // (Traditionally the access time would be updated + // automatically, but for efficiency most POSIX systems have + // for many years set the noatime mount option to avoid every + // open or read operation entailing a metadata write.) + now := time.Now() + touch := func(filename string) { + st, err := os.Stat(filename) + if err == nil && now.Sub(st.ModTime()) > time.Hour { + os.Chtimes(filename, now, now) // ignore error + } + } + touch(indexName) + touch(casName) + + memCache.Set(memKey{kind, key}, value, len(value)) + + return value, nil +} + +// ErrNotFound is the distinguished error +// returned by Get when the key is not found. +var ErrNotFound = fmt.Errorf("not found") + +// Set updates the value in the cache. +func Set(kind string, key [32]byte, value []byte) error { + memCache.Set(memKey{kind, key}, value, len(value)) + + // Set the active event to wake up the GC. + select { + case active <- struct{}{}: + default: + } + + iolimit <- struct{}{} // acquire a token + defer func() { <-iolimit }() // release a token + + // First, add the value to the content- + // addressable store (CAS), if not present. + hash := sha256.Sum256(value) + casName, err := filename(casKind, hash) + if err != nil { + return err + } + // Does CAS file exist and have correct (complete) content? + // TODO(adonovan): opt: use mmap for this check. + if prev, _ := os.ReadFile(casName); !bytes.Equal(prev, value) { + if err := os.MkdirAll(filepath.Dir(casName), 0700); err != nil { + return err + } + // Avoiding O_TRUNC here is merely an optimization to avoid + // cache misses when two threads race to write the same file. + if err := writeFileNoTrunc(casName, value, 0600); err != nil { + os.Remove(casName) // ignore error + return err // e.g. disk full + } + } + + // Now write an index entry that refers to the CAS file. + indexName, err := filename(kind, key) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(indexName), 0700); err != nil { + return err + } + if err := writeFileNoTrunc(indexName, hash[:], 0600); err != nil { + os.Remove(indexName) // ignore error + return err // e.g. disk full + } + + return nil +} + +// The active 1-channel is a selectable resettable event +// indicating recent cache activity. +var active = make(chan struct{}, 1) + +// writeFileNoTrunc is like os.WriteFile but doesn't truncate until +// after the write, so that racing writes of the same data are idempotent. +func writeFileNoTrunc(filename string, data []byte, perm os.FileMode) error { + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, perm) + if err != nil { + return err + } + _, err = f.Write(data) + if err == nil { + err = f.Truncate(int64(len(data))) + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + return err +} + +// reserved kind strings +const ( + casKind = "cas" // content-addressable store files + bugKind = "bug" // gopls bug reports +) + +var iolimit = make(chan struct{}, 128) // counting semaphore to limit I/O concurrency in Set. + +var budget int64 = 1e9 // 1GB + +// SetBudget sets a soft limit on disk usage of regular files in the +// cache (in bytes) and returns the previous value. Supplying a +// negative value queries the current value without changing it. +// +// If two gopls processes have different budgets, the one with the +// lower budget will collect garbage more actively, but both will +// observe the effect. +// +// Even in the steady state, the storage usage reported by the 'du' +// command may exceed the budget by as much as a factor of 3 due to +// the overheads of directories and the effects of block quantization, +// which are especially pronounced for the small index files. +func SetBudget(new int64) (old int64) { + if new < 0 { + return atomic.LoadInt64(&budget) + } + return atomic.SwapInt64(&budget, new) +} + +// --- implementation ---- + +// filename returns the name of the cache file of the specified kind and key. +// +// A typical cache file has a name such as: +// +// $HOME/Library/Caches / gopls / VVVVVVVV / KK / KKKK...KKKK - kind +// +// The portions separated by spaces are as follows: +// - The user's preferred cache directory; the default value varies by OS. +// - The constant "gopls". +// - The "version", 32 bits of the digest of the gopls executable. +// - The first 8 bits of the key, to avoid huge directories. +// - The full 256 bits of the key. +// - The kind or purpose of this cache file (e.g. "analysis"). +// +// The kind establishes a namespace for the keys. It is represented as +// a suffix, not a segment, as this significantly reduces the number +// of directories created, and thus the storage overhead. +// +// Previous iterations of the design aimed for the invariant that once +// a file is written, its contents are never modified, though it may +// be atomically replaced or removed. However, not all platforms have +// an atomic rename operation (our first approach), and file locking +// (our second) is a notoriously fickle mechanism. +// +// The current design instead exploits a trick from the cache +// implementation used by the go command: writes of small files are in +// practice atomic (all or nothing) on all platforms. +// (See GOROOT/src/cmd/go/internal/cache/cache.go.) +// +// Russ Cox notes: "all file systems use an rwlock around every file +// system block, including data blocks, so any writes or reads within +// the same block are going to be handled atomically by the FS +// implementation without any need to request file locking explicitly. +// And since the files are so small, there's only one block. (A block +// is at minimum 512 bytes, usually much more.)" And: "all modern file +// systems protect against [partial writes due to power loss] with +// journals." +// +// We use a two-level scheme consisting of an index and a +// content-addressable store (CAS). A single cache entry consists of +// two files. The value of a cache entry is written into the file at +// filename("cas", sha256(value)). Since the value may be arbitrarily +// large, this write is not atomic. That means we must check the +// integrity of the contents read back from the CAS to make sure they +// hash to the expected key. If the CAS file is incomplete or +// inconsistent, we proceed as if it were missing. +// +// Once the CAS file has been written, we write a small fixed-size +// index file at filename(kind, key), using the values supplied by the +// caller. The index file contains the hash that identifies the value +// file in the CAS. (We could add extra metadata to this file, up to +// 512B, the minimum size of a disk block, if later desired, so long +// as the total size remains fixed.) Because the index file is small, +// concurrent writes to it are atomic in practice, even though this is +// not guaranteed by any OS. The fixed size ensures that readers can't +// see a palimpsest when a short new file overwrites a longer old one. +// +// New versions of gopls are free to reorganize the contents of the +// version directory as needs evolve. But all versions of gopls must +// in perpetuity treat the "gopls" directory in a common fashion. +// +// In particular, each gopls process attempts to garbage collect +// the entire gopls directory so that newer binaries can clean up +// after older ones: in the development cycle especially, new +// versions may be created frequently. +func filename(kind string, key [32]byte) (string, error) { + base := fmt.Sprintf("%x-%s", key, kind) + dir, err := getCacheDir() + if err != nil { + return "", err + } + // Keep the BugReports function consistent with this one. + return filepath.Join(dir, base[:2], base), nil +} + +// getCacheDir returns the persistent cache directory of all processes +// running this version of the gopls executable. +// +// It must incorporate the hash of the executable so that we needn't +// worry about incompatible changes to the file format or changes to +// the algorithm that produced the index. +func getCacheDir() (string, error) { + cacheDirOnce.Do(func() { + // Use user's preferred cache directory. + userDir := os.Getenv("GOPLSCACHE") + if userDir == "" { + var err error + userDir, err = os.UserCacheDir() + if err != nil { + userDir = os.TempDir() + } + } + goplsDir := filepath.Join(userDir, "gopls") + + // UserCacheDir may return a nonexistent directory + // (in which case we must create it, which may fail), + // or it may return a non-writable directory, in + // which case we should ideally respect the user's express + // wishes (e.g. XDG_CACHE_HOME) and not write somewhere else. + // Sadly UserCacheDir doesn't currently let us distinguish + // such intent from accidental misconfiguraton such as HOME=/ + // in a CI builder. So, we check whether the gopls subdirectory + // can be created (or already exists) and not fall back to /tmp. + // See also https://github.com/golang/go/issues/57638. + if os.MkdirAll(goplsDir, 0700) != nil { + goplsDir = filepath.Join(os.TempDir(), "gopls") + } + + // Start the garbage collector. + go gc(goplsDir) + + // Compute the hash of this executable (~20ms) and create a subdirectory. + hash, err := hashExecutable() + if err != nil { + cacheDirErr = fmt.Errorf("can't hash gopls executable: %v", err) + } + // Use only 32 bits of the digest to avoid unwieldy filenames. + // It's not an adversarial situation. + cacheDir = filepath.Join(goplsDir, fmt.Sprintf("%x", hash[:4])) + if err := os.MkdirAll(cacheDir, 0700); err != nil { + cacheDirErr = fmt.Errorf("can't create cache: %v", err) + } + }) + return cacheDir, cacheDirErr +} + +var ( + cacheDirOnce sync.Once + cacheDir string + cacheDirErr error +) + +func hashExecutable() (hash [32]byte, err error) { + exe, err := os.Executable() + if err != nil { + return hash, err + } + f, err := os.Open(exe) + if err != nil { + return hash, err + } + defer f.Close() + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return hash, fmt.Errorf("can't read executable: %w", err) + } + h.Sum(hash[:0]) + return hash, nil +} + +// gc runs forever, periodically deleting files from the gopls +// directory until the space budget is no longer exceeded, and also +// deleting files older than the maximum age, regardless of budget. +// +// One gopls process may delete garbage created by a different gopls +// process, possibly running a different version of gopls, possibly +// running concurrently. +func gc(goplsDir string) { + // period between collections + // + // Originally the period was always 1 minute, but this + // consumed 15% of a CPU core when idle (#61049). + // + // The reason for running collections even when idle is so + // that long lived gopls sessions eventually clean up the + // caches created by defunct executables. + const ( + minPeriod = 5 * time.Minute // when active + maxPeriod = 6 * time.Hour // when idle + ) + + // Sleep statDelay*batchSize between stats to smooth out I/O. + // + // The constants below were chosen using the following heuristics: + // - 1GB of filecache is on the order of ~100-200k files, in which case + // 100μs delay per file introduces 10-20s of additional walk time, + // less than the minPeriod. + // - Processing batches of stats at once is much more efficient than + // sleeping after every stat (due to OS optimizations). + const statDelay = 100 * time.Microsecond // average delay between stats, to smooth out I/O + const batchSize = 1000 // # of stats to process before sleeping + const maxAge = 5 * 24 * time.Hour // max time since last access before file is deleted + + // The macOS filesystem is strikingly slow, at least on some machines. + // /usr/bin/find achieves only about 25,000 stats per second + // at full speed (no pause between items), meaning a large + // cache may take several minutes to scan. + // + // (gopls' caches should never actually get this big in + // practice: the example mentioned above resulted from a bug + // that caused filecache to fail to delete any files.) + + const debug = false + + // Names of all directories found in first pass; nil thereafter. + dirs := make(map[string]bool) + + for { + // Wait unconditionally for the minimum period. + // We do this even on the first run so that tests + // don't (all) run the GC. + time.Sleep(minPeriod) + + // Enumerate all files in the cache. + type item struct { + path string + mtime time.Time + size int64 + } + var files []item + start := time.Now() + var total int64 // bytes + _ = filepath.Walk(goplsDir, func(path string, stat os.FileInfo, err error) error { + if err != nil { + return nil // ignore errors + } + if stat.IsDir() { + // Collect (potentially empty) directories. + if dirs != nil { + dirs[path] = true + } + } else { + // Unconditionally delete files we haven't used in ages. + age := time.Since(stat.ModTime()) + if age > maxAge { + if debug { + log.Printf("age: deleting stale file %s (%dB, age %v)", + path, stat.Size(), age) + } + os.Remove(path) // ignore error + } else { + files = append(files, item{path, stat.ModTime(), stat.Size()}) + total += stat.Size() + if debug && len(files)%1000 == 0 { + log.Printf("filecache: checked %d files in %v", len(files), time.Since(start)) + } + if len(files)%batchSize == 0 { + time.Sleep(batchSize * statDelay) + } + } + } + return nil + }) + + // Sort oldest files first. + sort.Slice(files, func(i, j int) bool { + return files[i].mtime.Before(files[j].mtime) + }) + + // Delete oldest files until we're under budget. + budget := atomic.LoadInt64(&budget) + for _, file := range files { + if total < budget { + break + } + if debug { + age := time.Since(file.mtime) + log.Printf("budget: deleting stale file %s (%dB, age %v)", + file.path, file.size, age) + } + os.Remove(file.path) // ignore error + total -= file.size + } + files = nil // release memory before sleep + + // Once only, delete all directories. + // This will succeed only for the empty ones, + // and ensures that stale directories (whose + // files have been deleted) are removed eventually. + // They don't take up much space but they do slow + // down the traversal. + // + // We do this after the sleep to minimize the + // race against Set, which may create a directory + // that is momentarily empty. + // + // (Test processes don't live that long, so + // this may not be reached on the CI builders.) + if dirs != nil { + dirnames := make([]string, 0, len(dirs)) + for dir := range dirs { + dirnames = append(dirnames, dir) + } + dirs = nil + + // Descending length order => children before parents. + sort.Slice(dirnames, func(i, j int) bool { + return len(dirnames[i]) > len(dirnames[j]) + }) + var deleted int + for _, dir := range dirnames { + if os.Remove(dir) == nil { // ignore error + deleted++ + } + } + if debug { + log.Printf("deleted %d empty directories", deleted) + } + } + + // Wait up to the max period, + // or for Set activity in this process. + select { + case <-active: + case <-time.After(maxPeriod): + } + } +} + +func init() { + // Register a handler to durably record this process's first + // assertion failure in the cache so that we can ask users to + // share this information via the stats command. + bug.Handle(func(bug bug.Bug) { + // Wait for cache init (bugs in tests happen early). + _, _ = getCacheDir() + + data, err := json.Marshal(bug) + if err != nil { + panic(fmt.Sprintf("error marshalling bug %+v: %v", bug, err)) + } + + key := sha256.Sum256(data) + _ = Set(bugKind, key, data) + }) +} + +// BugReports returns a new unordered array of the contents +// of all cached bug reports produced by this executable. +// It also returns the location of the cache directory +// used by this process (or "" on initialization error). +func BugReports() (string, []bug.Bug) { + // To test this logic, run: + // $ TEST_GOPLS_BUG=oops gopls bug # trigger a bug + // $ gopls stats # list the bugs + + dir, err := getCacheDir() + if err != nil { + return "", nil // ignore initialization errors + } + var result []bug.Bug + _ = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return nil // ignore readdir/stat errors + } + // Parse the key from each "XXXX-bug" cache file name. + if !info.IsDir() && strings.HasSuffix(path, bugKind) { + var key [32]byte + n, err := hex.Decode(key[:], []byte(filepath.Base(path)[:len(key)*2])) + if err != nil || n != len(key) { + return nil // ignore malformed file names + } + content, err := Get(bugKind, key) + if err == nil { // ignore read errors + var b bug.Bug + if err := json.Unmarshal(content, &b); err != nil { + log.Printf("error marshalling bug %q: %v", string(content), err) + } + result = append(result, b) + } + } + return nil + }) + return dir, result +} diff --git a/contribs/gnopls/internal/filecache/filecache_test.go b/contribs/gnopls/internal/filecache/filecache_test.go new file mode 100644 index 00000000000..3419db4b513 --- /dev/null +++ b/contribs/gnopls/internal/filecache/filecache_test.go @@ -0,0 +1,265 @@ +// 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 filecache_test + +// This file defines tests of the API of the filecache package. +// +// Some properties (e.g. garbage collection) cannot be exercised +// through the API, so this test does not attempt to do so. + +import ( + "bytes" + cryptorand "crypto/rand" + "fmt" + "log" + mathrand "math/rand" + "os" + "os/exec" + "strconv" + "strings" + "testing" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/gopls/internal/filecache" + "golang.org/x/tools/internal/testenv" +) + +func TestBasics(t *testing.T) { + const kind = "TestBasics" + key := uniqueKey() // never used before + value := []byte("hello") + + // Get of a never-seen key returns not found. + if _, err := filecache.Get(kind, key); err != filecache.ErrNotFound { + if strings.Contains(err.Error(), "operation not supported") || + strings.Contains(err.Error(), "not implemented") { + t.Skipf("skipping: %v", err) + } + t.Errorf("Get of random key returned err=%q, want not found", err) + } + + // Set of a never-seen key and a small value succeeds. + if err := filecache.Set(kind, key, value); err != nil { + t.Errorf("Set failed: %v", err) + } + + // Get of the key returns a copy of the value. + if got, err := filecache.Get(kind, key); err != nil { + t.Errorf("Get after Set failed: %v", err) + } else if string(got) != string(value) { + t.Errorf("Get after Set returned different value: got %q, want %q", got, value) + } + + // The kind is effectively part of the key. + if _, err := filecache.Get("different-kind", key); err != filecache.ErrNotFound { + t.Errorf("Get with wrong kind returned err=%q, want not found", err) + } +} + +// TestConcurrency exercises concurrent access to the same entry. +func TestConcurrency(t *testing.T) { + if os.Getenv("GO_BUILDER_NAME") == "plan9-arm" { + t.Skip(`skipping on plan9-arm builder due to golang/go#58748: failing with 'mount rpc error'`) + } + const kind = "TestConcurrency" + key := uniqueKey() + const N = 100 // concurrency level + + // Construct N distinct values, each larger + // than a typical 4KB OS file buffer page. + var values [N][8192]byte + for i := range values { + if _, err := mathrand.Read(values[i][:]); err != nil { + t.Fatalf("rand: %v", err) + } + } + + // get calls Get and verifies that the cache entry + // matches one of the values passed to Set. + get := func(mustBeFound bool) error { + got, err := filecache.Get(kind, key) + if err != nil { + if err == filecache.ErrNotFound && !mustBeFound { + return nil // not found + } + return err + } + for _, want := range values { + if bytes.Equal(want[:], got) { + return nil // a match + } + } + return fmt.Errorf("Get returned a value that was never Set") + } + + // Perform N concurrent calls to Set and Get. + // All sets must succeed. + // All gets must return nothing, or one of the Set values; + // there is no third possibility. + var group errgroup.Group + for i := range values { + i := i + group.Go(func() error { return filecache.Set(kind, key, values[i][:]) }) + group.Go(func() error { return get(false) }) + } + if err := group.Wait(); err != nil { + if strings.Contains(err.Error(), "operation not supported") || + strings.Contains(err.Error(), "not implemented") { + t.Skipf("skipping: %v", err) + } + t.Fatal(err) + } + + // A final Get must report one of the values that was Set. + if err := get(true); err != nil { + t.Fatalf("final Get failed: %v", err) + } +} + +const ( + testIPCKind = "TestIPC" + testIPCValueA = "hello" + testIPCValueB = "world" +) + +// TestIPC exercises interprocess communication through the cache. +// It calls Set(A) in the parent, { Get(A); Set(B) } in the child +// process, then Get(B) in the parent. +func TestIPC(t *testing.T) { + testenv.NeedsExec(t) + + keyA := uniqueKey() + keyB := uniqueKey() + value := []byte(testIPCValueA) + + // Set keyA. + if err := filecache.Set(testIPCKind, keyA, value); err != nil { + if strings.Contains(err.Error(), "operation not supported") { + t.Skipf("skipping: %v", err) + } + t.Fatalf("Set: %v", err) + } + + // Call ipcChild in a child process, + // passing it the keys in the environment + // (quoted, to avoid NUL termination of C strings). + // It will Get(A) then Set(B). + cmd := exec.Command(os.Args[0], os.Args[1:]...) + cmd.Env = append(os.Environ(), + "ENTRYPOINT=ipcChild", + fmt.Sprintf("KEYA=%q", keyA), + fmt.Sprintf("KEYB=%q", keyB)) + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + t.Fatal(err) + } + + // Verify keyB. + got, err := filecache.Get(testIPCKind, keyB) + if err != nil { + t.Fatal(err) + } + if string(got) != "world" { + t.Fatalf("Get(keyB) = %q, want %q", got, "world") + } +} + +// We define our own main function so that portions of +// some tests can run in a separate (child) process. +func TestMain(m *testing.M) { + switch os.Getenv("ENTRYPOINT") { + case "ipcChild": + ipcChild() + default: + os.Exit(m.Run()) + } +} + +// ipcChild is the portion of TestIPC that runs in a child process. +func ipcChild() { + getenv := func(name string) (key [32]byte) { + s, _ := strconv.Unquote(os.Getenv(name)) + copy(key[:], []byte(s)) + return + } + + // Verify key A. + got, err := filecache.Get(testIPCKind, getenv("KEYA")) + if err != nil || string(got) != testIPCValueA { + log.Fatalf("child: Get(key) = %q, %v; want %q", got, err, testIPCValueA) + } + + // Set key B. + if err := filecache.Set(testIPCKind, getenv("KEYB"), []byte(testIPCValueB)); err != nil { + log.Fatalf("child: Set(keyB) failed: %v", err) + } +} + +// uniqueKey returns a key that has never been used before. +func uniqueKey() (key [32]byte) { + if _, err := cryptorand.Read(key[:]); err != nil { + log.Fatalf("rand: %v", err) + } + return +} + +func BenchmarkUncontendedGet(b *testing.B) { + const kind = "BenchmarkUncontendedGet" + key := uniqueKey() + + var value [8192]byte + if _, err := mathrand.Read(value[:]); err != nil { + b.Fatalf("rand: %v", err) + } + if err := filecache.Set(kind, key, value[:]); err != nil { + b.Fatal(err) + } + b.ResetTimer() + b.SetBytes(int64(len(value))) + + var group errgroup.Group + group.SetLimit(50) + for i := 0; i < b.N; i++ { + group.Go(func() error { + _, err := filecache.Get(kind, key) + return err + }) + } + if err := group.Wait(); err != nil { + b.Fatal(err) + } +} + +// These two benchmarks are asymmetric: the one for Get imposes a +// modest bound on concurrency (50) whereas the one for Set imposes a +// much higher concurrency (1000) to test the implementation's +// self-imposed bound. + +func BenchmarkUncontendedSet(b *testing.B) { + const kind = "BenchmarkUncontendedSet" + key := uniqueKey() + var value [8192]byte + + const P = 1000 // parallelism + b.SetBytes(P * int64(len(value))) + + for i := 0; i < b.N; i++ { + // Perform P concurrent calls to Set. All must succeed. + var group errgroup.Group + for range [P]bool{} { + group.Go(func() error { + return filecache.Set(kind, key, value[:]) + }) + } + if err := group.Wait(); err != nil { + if strings.Contains(err.Error(), "operation not supported") || + strings.Contains(err.Error(), "not implemented") { + b.Skipf("skipping: %v", err) + } + b.Fatal(err) + } + } +} diff --git a/contribs/gnopls/internal/fuzzy/input.go b/contribs/gnopls/internal/fuzzy/input.go new file mode 100644 index 00000000000..c1038163f1a --- /dev/null +++ b/contribs/gnopls/internal/fuzzy/input.go @@ -0,0 +1,183 @@ +// 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. + +package fuzzy + +import ( + "unicode" +) + +// RuneRole specifies the role of a rune in the context of an input. +type RuneRole byte + +const ( + // RNone specifies a rune without any role in the input (i.e., whitespace/non-ASCII). + RNone RuneRole = iota + // RSep specifies a rune with the role of segment separator. + RSep + // RTail specifies a rune which is a lower-case tail in a word in the input. + RTail + // RUCTail specifies a rune which is an upper-case tail in a word in the input. + RUCTail + // RHead specifies a rune which is the first character in a word in the input. + RHead +) + +// RuneRoles detects the roles of each byte rune in an input string and stores it in the output +// slice. The rune role depends on the input type. Stops when it parsed all the runes in the string +// or when it filled the output. If output is nil, then it gets created. +func RuneRoles(candidate []byte, reuse []RuneRole) []RuneRole { + var output []RuneRole + if cap(reuse) < len(candidate) { + output = make([]RuneRole, 0, len(candidate)) + } else { + output = reuse[:0] + } + + prev, prev2 := rtNone, rtNone + for i := 0; i < len(candidate); i++ { + r := rune(candidate[i]) + + role := RNone + + curr := rtLower + if candidate[i] <= unicode.MaxASCII { + curr = runeType(rt[candidate[i]] - '0') + } + + if curr == rtLower { + if prev == rtNone || prev == rtPunct { + role = RHead + } else { + role = RTail + } + } else if curr == rtUpper { + role = RHead + + if prev == rtUpper { + // This and previous characters are both upper case. + + if i+1 == len(candidate) { + // This is last character, previous was also uppercase -> this is UCTail + // i.e., (current char is C): aBC / BC / ABC + role = RUCTail + } + } + } else if curr == rtPunct { + switch r { + case '.', ':': + role = RSep + } + } + if curr != rtLower { + if i > 1 && output[i-1] == RHead && prev2 == rtUpper && (output[i-2] == RHead || output[i-2] == RUCTail) { + // The previous two characters were uppercase. The current one is not a lower case, so the + // previous one can't be a HEAD. Make it a UCTail. + // i.e., (last char is current char - B must be a UCTail): ABC / ZABC / AB. + output[i-1] = RUCTail + } + } + + output = append(output, role) + prev2 = prev + prev = curr + } + return output +} + +type runeType byte + +const ( + rtNone runeType = iota + rtPunct + rtLower + rtUpper +) + +const rt = "00000000000000000000000000000000000000000000001122222222221000000333333333333333333333333330000002222222222222222222222222200000" + +// LastSegment returns the substring representing the last segment from the input, where each +// byte has an associated RuneRole in the roles slice. This makes sense only for inputs of Symbol +// or Filename type. +func LastSegment(input string, roles []RuneRole) string { + // Exclude ending separators. + end := len(input) - 1 + for end >= 0 && roles[end] == RSep { + end-- + } + if end < 0 { + return "" + } + + start := end - 1 + for start >= 0 && roles[start] != RSep { + start-- + } + + return input[start+1 : end+1] +} + +// fromChunks copies string chunks into the given buffer. +func fromChunks(chunks []string, buffer []byte) []byte { + ii := 0 + for _, chunk := range chunks { + for i := 0; i < len(chunk); i++ { + if ii >= cap(buffer) { + break + } + buffer[ii] = chunk[i] + ii++ + } + } + return buffer[:ii] +} + +// toLower transforms the input string to lower case, which is stored in the output byte slice. +// The lower casing considers only ASCII values - non ASCII values are left unmodified. +// Stops when parsed all input or when it filled the output slice. If output is nil, then it gets +// created. +func toLower(input []byte, reuse []byte) []byte { + output := reuse + if cap(reuse) < len(input) { + output = make([]byte, len(input)) + } + + for i := 0; i < len(input); i++ { + r := rune(input[i]) + if input[i] <= unicode.MaxASCII { + if 'A' <= r && r <= 'Z' { + r += 'a' - 'A' + } + } + output[i] = byte(r) + } + return output[:len(input)] +} + +// WordConsumer defines a consumer for a word delimited by the [start,end) byte offsets in an input +// (start is inclusive, end is exclusive). +type WordConsumer func(start, end int) + +// Words find word delimiters in an input based on its bytes' mappings to rune roles. The offset +// delimiters for each word are fed to the provided consumer function. +func Words(roles []RuneRole, consume WordConsumer) { + var wordStart int + for i, r := range roles { + switch r { + case RUCTail, RTail: + case RHead, RNone, RSep: + if i != wordStart { + consume(wordStart, i) + } + wordStart = i + if r != RHead { + // Skip this character. + wordStart = i + 1 + } + } + } + if wordStart != len(roles) { + consume(wordStart, len(roles)) + } +} diff --git a/contribs/gnopls/internal/fuzzy/input_test.go b/contribs/gnopls/internal/fuzzy/input_test.go new file mode 100644 index 00000000000..4f3239372aa --- /dev/null +++ b/contribs/gnopls/internal/fuzzy/input_test.go @@ -0,0 +1,141 @@ +// 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. + +package fuzzy_test + +import ( + "bytes" + "sort" + "testing" + + "golang.org/x/tools/gopls/internal/fuzzy" +) + +var rolesTests = []struct { + str string + want string +}{ + {str: "abc::def::goo", want: "Ccc//Ccc//Ccc"}, + {str: "proto::Message", want: "Ccccc//Ccccccc"}, + {str: "AbstractSWTFactory", want: "CcccccccCuuCcccccc"}, + {str: "Abs012", want: "Cccccc"}, + {str: "/", want: " "}, + {str: "fOO", want: "CCu"}, + {str: "fo_oo.o_oo", want: "Cc Cc/C Cc"}, +} + +func rolesString(roles []fuzzy.RuneRole) string { + var buf bytes.Buffer + for _, r := range roles { + buf.WriteByte(" /cuC"[int(r)]) + } + return buf.String() +} + +func TestRoles(t *testing.T) { + for _, tc := range rolesTests { + gotRoles := make([]fuzzy.RuneRole, len(tc.str)) + fuzzy.RuneRoles([]byte(tc.str), gotRoles) + got := rolesString(gotRoles) + if got != tc.want { + t.Errorf("roles(%s) = %v; want %v", tc.str, got, tc.want) + } + } +} + +var wordSplitTests = []struct { + input string + want []string +}{ + { + input: "foo bar baz", + want: []string{"foo", "bar", "baz"}, + }, + { + input: "fooBarBaz", + want: []string{"foo", "Bar", "Baz"}, + }, + { + input: "FOOBarBAZ", + want: []string{"FOO", "Bar", "BAZ"}, + }, + { + input: "foo123_bar2Baz3", + want: []string{"foo123", "bar2", "Baz3"}, + }, +} + +func TestWordSplit(t *testing.T) { + for _, tc := range wordSplitTests { + roles := fuzzy.RuneRoles([]byte(tc.input), nil) + + var got []string + consumer := func(i, j int) { + got = append(got, tc.input[i:j]) + } + fuzzy.Words(roles, consumer) + + if eq := diffStringLists(tc.want, got); !eq { + t.Errorf("input %v: (want %v -> got %v)", tc.input, tc.want, got) + } + } +} + +func diffStringLists(a, b []string) bool { + if len(a) != len(b) { + return false + } + sort.Strings(a) + sort.Strings(b) + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +var lastSegmentSplitTests = []struct { + str string + want string +}{ + { + str: "identifier", + want: "identifier", + }, + { + str: "two_words", + want: "two_words", + }, + { + str: "first::second", + want: "second", + }, + { + str: "foo.bar.FOOBar_buz123_test", + want: "FOOBar_buz123_test", + }, +} + +func TestLastSegment(t *testing.T) { + for _, tc := range lastSegmentSplitTests { + roles := fuzzy.RuneRoles([]byte(tc.str), nil) + + got := fuzzy.LastSegment(tc.str, roles) + + if got != tc.want { + t.Errorf("str %v: want %v; got %v", tc.str, tc.want, got) + } + } +} + +func BenchmarkRoles(b *testing.B) { + str := "AbstractSWTFactory" + out := make([]fuzzy.RuneRole, len(str)) + + for i := 0; i < b.N; i++ { + fuzzy.RuneRoles([]byte(str), out) + } + b.SetBytes(int64(len(str))) +} diff --git a/contribs/gnopls/internal/fuzzy/matcher.go b/contribs/gnopls/internal/fuzzy/matcher.go new file mode 100644 index 00000000000..29d1b36501e --- /dev/null +++ b/contribs/gnopls/internal/fuzzy/matcher.go @@ -0,0 +1,439 @@ +// 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. + +// Package fuzzy implements a fuzzy matching algorithm. +package fuzzy + +import ( + "bytes" + "fmt" +) + +const ( + // MaxInputSize is the maximum size of the input scored against the fuzzy matcher. Longer inputs + // will be truncated to this size. + MaxInputSize = 127 + // MaxPatternSize is the maximum size of the pattern used to construct the fuzzy matcher. Longer + // inputs are truncated to this size. + MaxPatternSize = 63 +) + +type scoreVal int + +func (s scoreVal) val() int { + return int(s) >> 1 +} + +func (s scoreVal) prevK() int { + return int(s) & 1 +} + +func score(val int, prevK int /*0 or 1*/) scoreVal { + return scoreVal(val<<1 + prevK) +} + +// Matcher implements a fuzzy matching algorithm for scoring candidates against a pattern. +// The matcher does not support parallel usage. +type Matcher struct { + pattern string + patternLower []byte // lower-case version of the pattern + patternShort []byte // first characters of the pattern + caseSensitive bool // set if the pattern is mix-cased + + patternRoles []RuneRole // the role of each character in the pattern + roles []RuneRole // the role of each character in the tested string + + scores [MaxInputSize + 1][MaxPatternSize + 1][2]scoreVal + + scoreScale float32 + + lastCandidateLen int // in bytes + lastCandidateMatched bool + + // Reusable buffers to avoid allocating for every candidate. + // - inputBuf stores the concatenated input chunks + // - lowerBuf stores the last candidate in lower-case + // - rolesBuf stores the calculated roles for each rune in the last + // candidate. + inputBuf [MaxInputSize]byte + lowerBuf [MaxInputSize]byte + rolesBuf [MaxInputSize]RuneRole +} + +func (m *Matcher) bestK(i, j int) int { + if m.scores[i][j][0].val() < m.scores[i][j][1].val() { + return 1 + } + return 0 +} + +// NewMatcher returns a new fuzzy matcher for scoring candidates against the provided pattern. +func NewMatcher(pattern string) *Matcher { + if len(pattern) > MaxPatternSize { + pattern = pattern[:MaxPatternSize] + } + + m := &Matcher{ + pattern: pattern, + patternLower: toLower([]byte(pattern), nil), + } + + for i, c := range m.patternLower { + if pattern[i] != c { + m.caseSensitive = true + break + } + } + + if len(pattern) > 3 { + m.patternShort = m.patternLower[:3] + } else { + m.patternShort = m.patternLower + } + + m.patternRoles = RuneRoles([]byte(pattern), nil) + + if len(pattern) > 0 { + maxCharScore := 4 + m.scoreScale = 1 / float32(maxCharScore*len(pattern)) + } + + return m +} + +// Score returns the score returned by matching the candidate to the pattern. +// This is not designed for parallel use. Multiple candidates must be scored sequentially. +// Returns a score between 0 and 1 (0 - no match, 1 - perfect match). +func (m *Matcher) Score(candidate string) float32 { + return m.ScoreChunks([]string{candidate}) +} + +func (m *Matcher) ScoreChunks(chunks []string) float32 { + candidate := fromChunks(chunks, m.inputBuf[:]) + if len(candidate) > MaxInputSize { + candidate = candidate[:MaxInputSize] + } + lower := toLower(candidate, m.lowerBuf[:]) + m.lastCandidateLen = len(candidate) + + if len(m.pattern) == 0 { + // Empty patterns perfectly match candidates. + return 1 + } + + if m.match(candidate, lower) { + sc := m.computeScore(candidate, lower) + if sc > minScore/2 && !m.poorMatch() { + m.lastCandidateMatched = true + if len(m.pattern) == len(candidate) { + // Perfect match. + return 1 + } + + if sc < 0 { + sc = 0 + } + normalizedScore := float32(sc) * m.scoreScale + if normalizedScore > 1 { + normalizedScore = 1 + } + + return normalizedScore + } + } + + m.lastCandidateMatched = false + return 0 +} + +const minScore = -10000 + +// MatchedRanges returns matches ranges for the last scored string as a flattened array of +// [begin, end) byte offset pairs. +func (m *Matcher) MatchedRanges() []int { + if len(m.pattern) == 0 || !m.lastCandidateMatched { + return nil + } + i, j := m.lastCandidateLen, len(m.pattern) + if m.scores[i][j][0].val() < minScore/2 && m.scores[i][j][1].val() < minScore/2 { + return nil + } + + var ret []int + k := m.bestK(i, j) + for i > 0 { + take := (k == 1) + k = m.scores[i][j][k].prevK() + if take { + if len(ret) == 0 || ret[len(ret)-1] != i { + ret = append(ret, i) + ret = append(ret, i-1) + } else { + ret[len(ret)-1] = i - 1 + } + j-- + } + i-- + } + // Reverse slice. + for i := 0; i < len(ret)/2; i++ { + ret[i], ret[len(ret)-1-i] = ret[len(ret)-1-i], ret[i] + } + return ret +} + +func (m *Matcher) match(candidate []byte, candidateLower []byte) bool { + i, j := 0, 0 + for ; i < len(candidateLower) && j < len(m.patternLower); i++ { + if candidateLower[i] == m.patternLower[j] { + j++ + } + } + if j != len(m.patternLower) { + return false + } + + // The input passes the simple test against pattern, so it is time to classify its characters. + // Character roles are used below to find the last segment. + m.roles = RuneRoles(candidate, m.rolesBuf[:]) + + return true +} + +func (m *Matcher) computeScore(candidate []byte, candidateLower []byte) int { + pattLen, candLen := len(m.pattern), len(candidate) + + for j := 0; j <= len(m.pattern); j++ { + m.scores[0][j][0] = minScore << 1 + m.scores[0][j][1] = minScore << 1 + } + m.scores[0][0][0] = score(0, 0) // Start with 0. + + segmentsLeft, lastSegStart := 1, 0 + for i := 0; i < candLen; i++ { + if m.roles[i] == RSep { + segmentsLeft++ + lastSegStart = i + 1 + } + } + + // A per-character bonus for a consecutive match. + consecutiveBonus := 2 + wordIdx := 0 // Word count within segment. + for i := 1; i <= candLen; i++ { + + role := m.roles[i-1] + isHead := role == RHead + + if isHead { + wordIdx++ + } else if role == RSep && segmentsLeft > 1 { + wordIdx = 0 + segmentsLeft-- + } + + var skipPenalty int + if i == 1 || (i-1) == lastSegStart { + // Skipping the start of first or last segment. + skipPenalty++ + } + + for j := 0; j <= pattLen; j++ { + // By default, we don't have a match. Fill in the skip data. + m.scores[i][j][1] = minScore << 1 + + // Compute the skip score. + k := 0 + if m.scores[i-1][j][0].val() < m.scores[i-1][j][1].val() { + k = 1 + } + + skipScore := m.scores[i-1][j][k].val() + // Do not penalize missing characters after the last matched segment. + if j != pattLen { + skipScore -= skipPenalty + } + m.scores[i][j][0] = score(skipScore, k) + + if j == 0 || candidateLower[i-1] != m.patternLower[j-1] { + // Not a match. + continue + } + pRole := m.patternRoles[j-1] + + if role == RTail && pRole == RHead { + if j > 1 { + // Not a match: a head in the pattern matches a tail character in the candidate. + continue + } + // Special treatment for the first character of the pattern. We allow + // matches in the middle of a word if they are long enough, at least + // min(3, pattern.length) characters. + if !bytes.HasPrefix(candidateLower[i-1:], m.patternShort) { + continue + } + } + + // Compute the char score. + var charScore int + // Bonus: the char is in the candidate's last segment. + if segmentsLeft <= 1 { + charScore++ + } + + // Bonus: exact case match between pattern and candidate. + if candidate[i-1] == m.pattern[j-1] || + // Bonus: candidate char is a head and pattern is all + // lowercase. There is no segmentation in an all lowercase + // pattern, so assume any char in pattern can be a head. Note + // that we are intentionally _not_ giving a bonus to a case + // insensitive match when the pattern is case sensitive. + role == RHead && !m.caseSensitive { + charScore++ + } + + // Penalty: pattern char is Head, candidate char is Tail. + if role == RTail && pRole == RHead { + charScore-- + } + // Penalty: first pattern character matched in the middle of a word. + if j == 1 && role == RTail { + charScore -= 4 + } + + // Third dimension encodes whether there is a gap between the previous match and the current + // one. + for k := 0; k < 2; k++ { + sc := m.scores[i-1][j-1][k].val() + charScore + + isConsecutive := k == 1 || i-1 == 0 || i-1 == lastSegStart + if isConsecutive { + // Bonus: a consecutive match. First character match also gets a bonus to + // ensure prefix final match score normalizes to 1.0. + // Logically, this is a part of charScore, but we have to compute it here because it + // only applies for consecutive matches (k == 1). + sc += consecutiveBonus + } + if k == 0 { + // Penalty: Matching inside a segment (and previous char wasn't matched). Penalize for the lack + // of alignment. + if role == RTail || role == RUCTail { + sc -= 3 + } + } + + if sc > m.scores[i][j][1].val() { + m.scores[i][j][1] = score(sc, k) + } + } + } + } + + result := m.scores[len(candidate)][len(m.pattern)][m.bestK(len(candidate), len(m.pattern))].val() + + return result +} + +// ScoreTable returns the score table computed for the provided candidate. Used only for debugging. +func (m *Matcher) ScoreTable(candidate string) string { + var buf bytes.Buffer + + var line1, line2, separator bytes.Buffer + line1.WriteString("\t") + line2.WriteString("\t") + for j := 0; j < len(m.pattern); j++ { + line1.WriteString(fmt.Sprintf("%c\t\t", m.pattern[j])) + separator.WriteString("----------------") + } + + buf.WriteString(line1.String()) + buf.WriteString("\n") + buf.WriteString(separator.String()) + buf.WriteString("\n") + + for i := 1; i <= len(candidate); i++ { + line1.Reset() + line2.Reset() + + line1.WriteString(fmt.Sprintf("%c\t", candidate[i-1])) + line2.WriteString("\t") + + for j := 1; j <= len(m.pattern); j++ { + line1.WriteString(fmt.Sprintf("M%6d(%c)\t", m.scores[i][j][0].val(), dir(m.scores[i][j][0].prevK()))) + line2.WriteString(fmt.Sprintf("H%6d(%c)\t", m.scores[i][j][1].val(), dir(m.scores[i][j][1].prevK()))) + } + buf.WriteString(line1.String()) + buf.WriteString("\n") + buf.WriteString(line2.String()) + buf.WriteString("\n") + buf.WriteString(separator.String()) + buf.WriteString("\n") + } + + return buf.String() +} + +func dir(prevK int) rune { + if prevK == 0 { + return 'M' + } + return 'H' +} + +func (m *Matcher) poorMatch() bool { + if len(m.pattern) < 2 { + return false + } + + i, j := m.lastCandidateLen, len(m.pattern) + k := m.bestK(i, j) + + var counter, len int + for i > 0 { + take := (k == 1) + k = m.scores[i][j][k].prevK() + if take { + len++ + if k == 0 && len < 3 && m.roles[i-1] == RTail { + // Short match in the middle of a word + counter++ + if counter > 1 { + return true + } + } + j-- + } else { + len = 0 + } + i-- + } + return false +} + +// BestMatch returns the name most similar to the +// pattern, using fuzzy matching, or the empty string. +func BestMatch(pattern string, names []string) string { + fuzz := NewMatcher(pattern) + best := "" + highScore := float32(0) // minimum score is 0 (no match) + for _, name := range names { + // TODO: Improve scoring algorithm. + score := fuzz.Score(name) + if score > highScore { + highScore = score + best = name + } else if score == 0 { + // Order matters in the fuzzy matching algorithm. If we find no match + // when matching the target to the identifier, try matching the identifier + // to the target. + revFuzz := NewMatcher(name) + revScore := revFuzz.Score(pattern) + if revScore > highScore { + highScore = revScore + best = name + } + } + } + return best +} diff --git a/contribs/gnopls/internal/fuzzy/matcher_test.go b/contribs/gnopls/internal/fuzzy/matcher_test.go new file mode 100644 index 00000000000..056da25d675 --- /dev/null +++ b/contribs/gnopls/internal/fuzzy/matcher_test.go @@ -0,0 +1,307 @@ +// 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. + +// Benchmark results: +// +// BenchmarkMatcher-12 1000000 1615 ns/op 30.95 MB/s 0 B/op 0 allocs/op +package fuzzy_test + +import ( + "bytes" + "fmt" + "math" + "testing" + + "golang.org/x/tools/gopls/internal/fuzzy" +) + +type comparator struct { + f func(val, ref float32) bool + descr string +} + +var ( + eq = comparator{ + f: func(val, ref float32) bool { + return val == ref + }, + descr: "==", + } + ge = comparator{ + f: func(val, ref float32) bool { + return val >= ref + }, + descr: ">=", + } + gt = comparator{ + f: func(val, ref float32) bool { + return val > ref + }, + descr: ">", + } +) + +func (c comparator) eval(val, ref float32) bool { + return c.f(val, ref) +} + +func (c comparator) String() string { + return c.descr +} + +type scoreTest struct { + candidate string + comparator + ref float32 +} + +var matcherTests = []struct { + pattern string + tests []scoreTest +}{ + { + pattern: "", + tests: []scoreTest{ + {"def", eq, 1}, + {"Ab stuff c", eq, 1}, + }, + }, + { + pattern: "abc", + tests: []scoreTest{ + {"def", eq, 0}, + {"abd", eq, 0}, + {"abc", ge, 0}, + {"Abc", ge, 0}, + {"Ab stuff c", ge, 0}, + }, + }, + { + pattern: "Abc", + tests: []scoreTest{ + {"def", eq, 0}, + {"abd", eq, 0}, + {"abc", ge, 0}, + {"Abc", ge, 0}, + {"Ab stuff c", ge, 0}, + }, + }, + { + pattern: "U", + tests: []scoreTest{ + {"ErrUnexpectedEOF", gt, 0}, + {"ErrUnexpectedEOF.Error", eq, 0}, + }, + }, +} + +func TestScore(t *testing.T) { + for _, tc := range matcherTests { + m := fuzzy.NewMatcher(tc.pattern) + for _, sct := range tc.tests { + score := m.Score(sct.candidate) + if !sct.comparator.eval(score, sct.ref) { + t.Errorf("m.Score(%q) = %.2g, want %s %v", sct.candidate, score, sct.comparator, sct.ref) + } + } + } +} + +var compareCandidatesTestCases = []struct { + pattern string + // In `[][]string{{"foo", "bar"}, {"baz"}}`, + // "foo" and "bar" must have same score, "baz" must be strictly higher scoring. + orderedCandidates [][]string +}{ + { + pattern: "Foo", + orderedCandidates: [][]string{ + {"Barfoo"}, + {"Faoo"}, + {"F_o_o"}, + {"FaoFooa", "BarFoo"}, + {"F__oo", "F_oo"}, + {"FooA", "FooBar", "Foo"}, + }, + }, + { + pattern: "U", + orderedCandidates: [][]string{ + {"ErrUnexpectedEOF.Error"}, + {"ErrUnexpectedEOF"}, + }, + }, + { + pattern: "N", + orderedCandidates: [][]string{ + {"name"}, + {"Name"}, + }, + }, +} + +func TestCompareCandidateScores(t *testing.T) { + for _, tc := range compareCandidatesTestCases { + m := fuzzy.NewMatcher(tc.pattern) + + var prevScore float32 + var prevCandGroup []string + for i, candGroup := range tc.orderedCandidates { + var groupScore float32 + for j, cand := range candGroup { + score := m.Score(cand) + if j > 0 && score != groupScore { + t.Fatalf("score %f of %q different than group", score, cand) + } + groupScore = score + } + + if i > 0 && prevScore >= groupScore { + t.Errorf("%s[=%v] is not scored higher than %s[=%v]", candGroup, groupScore, prevCandGroup, prevScore) + } + if groupScore < 0 || groupScore > 1 { + t.Errorf("%s score is %v; want value between [0, 1]", candGroup, groupScore) + } + prevScore = groupScore + prevCandGroup = candGroup + } + } +} + +var fuzzyMatcherTestCases = []struct { + p string + str string + want string +}{ + {p: "foo", str: "abc::foo", want: "abc::[foo]"}, + {p: "foo", str: "foo.foo", want: "foo.[foo]"}, + {p: "foo", str: "fo_oo.o_oo", want: "[fo]_oo.[o]_oo"}, + {p: "foo", str: "fo_oo.fo_oo", want: "fo_oo.[fo]_[o]o"}, + {p: "fo_o", str: "fo_oo.o_oo", want: "[f]o_oo.[o_o]o"}, + {p: "fOO", str: "fo_oo.o_oo", want: "[f]o_oo.[o]_[o]o"}, + {p: "tedit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"}, + {p: "TEdit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"}, + {p: "Tedit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"}, + {p: "Tedit", str: "foo.Textedit", want: "foo.[Te]xte[dit]"}, + {p: "TEdit", str: "foo.Textedit", want: ""}, + {p: "te", str: "foo.Textedit", want: "foo.[Te]xtedit"}, + {p: "ee", str: "foo.Textedit", want: ""}, // short middle of the word match + {p: "ex", str: "foo.Textedit", want: "foo.T[ex]tedit"}, + {p: "exdi", str: "foo.Textedit", want: ""}, // short middle of the word match + {p: "exdit", str: "foo.Textedit", want: ""}, // short middle of the word match + {p: "extdit", str: "foo.Textedit", want: "foo.T[ext]e[dit]"}, + {p: "e", str: "foo.Textedit", want: "foo.T[e]xtedit"}, + {p: "E", str: "foo.Textedit", want: "foo.T[e]xtedit"}, + {p: "ed", str: "foo.Textedit", want: "foo.Text[ed]it"}, + {p: "edt", str: "foo.Textedit", want: ""}, // short middle of the word match + {p: "edit", str: "foo.Textedit", want: "foo.Text[edit]"}, + {p: "edin", str: "foo.TexteditNum", want: "foo.Text[edi]t[N]um"}, + {p: "n", str: "node.GoNodeMax", want: "[n]ode.GoNodeMax"}, + {p: "N", str: "node.GoNodeMax", want: "[n]ode.GoNodeMax"}, + {p: "completio", str: "completion", want: "[completio]n"}, + {p: "completio", str: "completion.None", want: "[completio]n.None"}, +} + +func TestFuzzyMatcherRanges(t *testing.T) { + for _, tc := range fuzzyMatcherTestCases { + matcher := fuzzy.NewMatcher(tc.p) + score := matcher.Score(tc.str) + if tc.want == "" { + if score > 0 { + t.Errorf("Score(%s, %s) = %v; want: <= 0", tc.p, tc.str, score) + } + continue + } + if score < 0 { + t.Errorf("Score(%s, %s) = %v, want: > 0", tc.p, tc.str, score) + continue + } + got := highlightMatches(tc.str, matcher) + if tc.want != got { + t.Errorf("highlightMatches(%s, %s) = %v, want: %v", tc.p, tc.str, got, tc.want) + } + } +} + +var scoreTestCases = []struct { + p string + str string + want float64 +}{ + // Score precision up to five digits. Modify if changing the score, but make sure the new values + // are reasonable. + {p: "abc", str: "abc", want: 1}, + {p: "abc", str: "Abc", want: 1}, + {p: "abc", str: "Abcdef", want: 1}, + {p: "strc", str: "StrCat", want: 1}, + {p: "abc_def", str: "abc_def_xyz", want: 1}, + {p: "abcdef", str: "abc_def_xyz", want: 0.91667}, + {p: "abcxyz", str: "abc_def_xyz", want: 0.91667}, + {p: "sc", str: "StrCat", want: 0.75}, + {p: "abc", str: "AbstrBasicCtor", want: 0.83333}, + {p: "foo", str: "abc::foo", want: 0.91667}, + {p: "afoo", str: "abc::foo", want: 0.9375}, + {p: "abr", str: "abc::bar", want: 0.5}, + {p: "br", str: "abc::bar", want: 0.25}, + {p: "aar", str: "abc::bar", want: 0.41667}, + {p: "edin", str: "foo.TexteditNum", want: 0.125}, + {p: "ediu", str: "foo.TexteditNum", want: 0}, + // We want the next two items to have roughly similar scores. + {p: "up", str: "unique_ptr", want: 0.75}, + {p: "up", str: "upper_bound", want: 1}, +} + +func TestScores(t *testing.T) { + for _, tc := range scoreTestCases { + matcher := fuzzy.NewMatcher(tc.p) + got := math.Round(float64(matcher.Score(tc.str))*1e5) / 1e5 + if got != tc.want { + t.Errorf("Score(%s, %s) = %v, want: %v", tc.p, tc.str, got, tc.want) + } + } +} + +func highlightMatches(str string, matcher *fuzzy.Matcher) string { + matches := matcher.MatchedRanges() + + var buf bytes.Buffer + index := 0 + for i := 0; i < len(matches)-1; i += 2 { + s, e := matches[i], matches[i+1] + fmt.Fprintf(&buf, "%s[%s]", str[index:s], str[s:e]) + index = e + } + buf.WriteString(str[index:]) + return buf.String() +} + +func BenchmarkMatcher(b *testing.B) { + pattern := "Foo" + candidates := []string{ + "F_o_o", + "Barfoo", + "Faoo", + "F__oo", + "F_oo", + "FaoFooa", + "BarFoo", + "FooA", + "FooBar", + "Foo", + } + + matcher := fuzzy.NewMatcher(pattern) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, c := range candidates { + matcher.Score(c) + } + } + var numBytes int + for _, c := range candidates { + numBytes += len(c) + } + b.SetBytes(int64(numBytes)) +} diff --git a/contribs/gnopls/internal/fuzzy/self_test.go b/contribs/gnopls/internal/fuzzy/self_test.go new file mode 100644 index 00000000000..1c64f1953df --- /dev/null +++ b/contribs/gnopls/internal/fuzzy/self_test.go @@ -0,0 +1,39 @@ +// 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 fuzzy_test + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/fuzzy" +) + +func BenchmarkSelf_Matcher(b *testing.B) { + idents := collectIdentifiers(b) + patterns := generatePatterns() + + for i := 0; i < b.N; i++ { + for _, pattern := range patterns { + sm := NewMatcher(pattern) + for _, ident := range idents { + _ = sm.Score(ident) + } + } + } +} + +func BenchmarkSelf_SymbolMatcher(b *testing.B) { + idents := collectIdentifiers(b) + patterns := generatePatterns() + + for i := 0; i < b.N; i++ { + for _, pattern := range patterns { + sm := NewSymbolMatcher(pattern) + for _, ident := range idents { + _, _ = sm.Match([]string{ident}) + } + } + } +} diff --git a/contribs/gnopls/internal/fuzzy/symbol.go b/contribs/gnopls/internal/fuzzy/symbol.go new file mode 100644 index 00000000000..5fe2ce3e2a3 --- /dev/null +++ b/contribs/gnopls/internal/fuzzy/symbol.go @@ -0,0 +1,309 @@ +// 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 fuzzy + +import ( + "bytes" + "fmt" + "log" + "unicode" +) + +// SymbolMatcher implements a fuzzy matching algorithm optimized for Go symbols +// of the form: +// +// example.com/path/to/package.object.field +// +// Knowing that we are matching symbols like this allows us to make the +// following optimizations: +// - We can incorporate right-to-left relevance directly into the score +// calculation. +// - We can match from right to left, discarding leading bytes if the input is +// too long. +// - We just take the right-most match without losing too much precision. This +// allows us to use an O(n) algorithm. +// - We can operate directly on chunked strings; in many cases we will +// be storing the package path and/or package name separately from the +// symbol or identifiers, so doing this avoids allocating strings. +// - We can return the index of the right-most match, allowing us to trim +// irrelevant qualification. +type SymbolMatcher struct { + // Using buffers of length 256 is both a reasonable size for most qualified + // symbols, and makes it easy to avoid bounds checks by using uint8 indexes. + pattern [256]rune + patternLen uint8 + inputBuffer [256]rune // avoid allocating when considering chunks + roles [256]uint32 // which roles does a rune play (word start, etc.) + segments [256]uint8 // how many segments from the right is each rune +} + +// Rune roles. +const ( + segmentStart uint32 = 1 << iota // input rune starts a segment (i.e. follows '/' or '.') + wordStart // input rune starts a word, per camel-case naming rules + separator // input rune is a separator ('/' or '.') + upper // input rune is an upper case letter +) + +// NewSymbolMatcher creates a SymbolMatcher that may be used to match the given +// search pattern. +// +// Currently this matcher only accepts case-insensitive fuzzy patterns. +// +// An empty pattern matches no input. +func NewSymbolMatcher(pattern string) *SymbolMatcher { + m := &SymbolMatcher{} + for _, p := range pattern { + m.pattern[m.patternLen] = unicode.ToLower(p) + m.patternLen++ + if m.patternLen == 255 || int(m.patternLen) == len(pattern) { + // break at 255 so that we can represent patternLen with a uint8. + break + } + } + return m +} + +// Match searches for the right-most match of the search pattern within the +// symbol represented by concatenating the given chunks. +// +// If a match is found, the first result holds the absolute byte offset within +// all chunks for the start of the symbol. In other words, the index of the +// match within strings.Join(chunks, ""). +// +// The second return value will be the score of the match, which is always +// between 0 and 1, inclusive. A score of 0 indicates no match. +// +// If no match is found, Match returns (-1, 0). +func (m *SymbolMatcher) Match(chunks []string) (int, float64) { + // Explicit behavior for an empty pattern. + // + // As a minor optimization, this also avoids nilness checks later on, since + // the compiler can prove that m != nil. + if m.patternLen == 0 { + return -1, 0 + } + + // Matching implements a heavily optimized linear scoring algorithm on the + // input. This is not guaranteed to produce the highest score, but works well + // enough, particularly due to the right-to-left significance of qualified + // symbols. + // + // Matching proceeds in three passes through the input: + // - The first pass populates the input buffer and collects rune roles. + // - The second pass proceeds right-to-left to find the right-most match. + // - The third pass proceeds left-to-right from the start of the right-most + // match, to find the most *compact* match, and computes the score of this + // match. + // + // See below for more details of each pass, as well as the scoring algorithm. + + // First pass: populate the input buffer out of the provided chunks + // (lower-casing in the process), and collect rune roles. + // + // We could also check for a forward match here, but since we'd have to write + // the entire input anyway this has negligible impact on performance. + var ( + inputLen = uint8(0) + modifiers = wordStart | segmentStart + ) + +input: + for _, chunk := range chunks { + for _, r := range chunk { + if r == '.' || r == '/' { + modifiers |= separator + } + // optimization: avoid calls to unicode.ToLower, which can't be inlined. + l := r + if r <= unicode.MaxASCII { + if 'A' <= r && r <= 'Z' { + l = r + 'a' - 'A' + } + } else { + l = unicode.ToLower(r) + } + if l != r { + modifiers |= upper + + // If the current rune is capitalized *and the preceding rune was not*, + // mark this as a word start. This avoids spuriously high ranking of + // non-camelcase naming schemas, such as the + // yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE example of + // golang/go#60201. + if inputLen == 0 || m.roles[inputLen-1]&upper == 0 { + modifiers |= wordStart + } + } + m.inputBuffer[inputLen] = l + m.roles[inputLen] = modifiers + inputLen++ + if m.roles[inputLen-1]&separator != 0 { + modifiers = wordStart | segmentStart + } else { + modifiers = 0 + } + // TODO: we should prefer the right-most input if it overflows, rather + // than the left-most as we're doing here. + if inputLen == 255 { + break input + } + } + } + + // Second pass: find the right-most match, and count segments from the + // right. + var ( + pi = uint8(m.patternLen - 1) // pattern index + p = m.pattern[pi] // pattern rune + start = -1 // start offset of match + rseg = uint8(0) // effective "depth" from the right of the current rune in consideration + ) + const maxSeg = 3 // maximum number of segments from the right to count, for scoring purposes. + + for ii := inputLen - 1; ; ii-- { + r := m.inputBuffer[ii] + if rseg < maxSeg && m.roles[ii]&separator != 0 { + rseg++ + } + m.segments[ii] = rseg + if p == r { + if pi == 0 { + // TODO(rfindley): BUG: the docstring for Match says that it returns an + // absolute byte offset, but clearly it is returning a rune offset here. + start = int(ii) + break + } + pi-- + p = m.pattern[pi] + } + // Don't check ii >= 0 in the loop condition: ii is a uint8. + if ii == 0 { + break + } + } + + if start < 0 { + // no match: skip scoring + return -1, 0 + } + + // Third pass: find the shortest match and compute the score. + + // Score is the average score for each rune. + // + // A rune score is the multiple of: + // 1. The base score, which is 1.0 if the rune starts a segment, 0.9 if the + // rune starts a mid-segment word, else 0.6. + // + // Runes preceded by a matching rune are treated the same as the start + // of a mid-segment word (with a 0.9 score), so that sequential or exact + // matches are preferred. We call this a sequential bonus. + // + // For the final rune match, this sequential bonus is reduced to 0.8 if + // the next rune in the input is a mid-segment word, or 0.7 if the next + // rune in the input is not a word or segment start. This ensures that + // we favor whole-word or whole-segment matches over prefix matches. + // + // 2. 1.0 if the rune is part of the last segment, otherwise + // 1.0-0.1*, with a max segment count of 3. + // Notably 1.0-0.1*3 = 0.7 > 0.6, so that foo/_/_/_/_ (a match very + // early in a qualified symbol name) still scores higher than _f_o_o_ (a + // completely split match). + // + // This is a naive algorithm, but it is fast. There's lots of prior art here + // that could be leveraged. For example, we could explicitly consider + // rune distance, and exact matches of words or segments. + // + // Also note that this might not actually find the highest scoring match, as + // doing so could require a non-linear algorithm, depending on how the score + // is calculated. + + // debugging support + const debug = false // enable to log debugging information + var ( + runeScores []float64 + runeIdxs []int + ) + + pi = 0 + p = m.pattern[pi] + + const ( + segStartScore = 1.0 // base score of runes starting a segment + wordScore = 0.9 // base score of runes starting or continuing a word + noStreak = 0.6 + perSegment = 0.1 // we count at most 3 segments above + ) + + totScore := 0.0 + lastMatch := uint8(255) + for ii := uint8(start); ii < inputLen; ii++ { + r := m.inputBuffer[ii] + if r == p { + pi++ + finalRune := pi >= m.patternLen + p = m.pattern[pi] + + baseScore := noStreak + + // Calculate the sequence bonus based on preceding matches. + // + // We do this first as it is overridden by role scoring below. + if lastMatch == ii-1 { + baseScore = wordScore + // Reduce the sequence bonus for the final rune of the pattern based on + // whether it borders a new segment or word. + if finalRune { + switch { + case ii == inputLen-1 || m.roles[ii+1]&separator != 0: + // Full segment: no reduction + case m.roles[ii+1]&wordStart != 0: + baseScore = wordScore - 0.1 + default: + baseScore = wordScore - 0.2 + } + } + } + lastMatch = ii + + // Calculate the rune's role score. If the rune starts a segment or word, + // this overrides the sequence score, as the rune starts a new sequence. + switch { + case m.roles[ii]&segmentStart != 0: + baseScore = segStartScore + case m.roles[ii]&wordStart != 0: + baseScore = wordScore + } + + // Apply the segment-depth penalty (segments from the right). + runeScore := baseScore * (1.0 - float64(m.segments[ii])*perSegment) + if debug { + runeScores = append(runeScores, runeScore) + runeIdxs = append(runeIdxs, int(ii)) + } + totScore += runeScore + if finalRune { + break + } + } + } + + if debug { + // Format rune roles and scores in line: + // fo[o:.52].[b:1]a[r:.6] + var summary bytes.Buffer + last := 0 + for i, idx := range runeIdxs { + summary.WriteString(string(m.inputBuffer[last:idx])) // encode runes + fmt.Fprintf(&summary, "[%s:%.2g]", string(m.inputBuffer[idx]), runeScores[i]) + last = idx + 1 + } + summary.WriteString(string(m.inputBuffer[last:inputLen])) // encode runes + log.Println(summary.String()) + } + + return start, totScore / float64(m.patternLen) +} diff --git a/contribs/gnopls/internal/fuzzy/symbol_test.go b/contribs/gnopls/internal/fuzzy/symbol_test.go new file mode 100644 index 00000000000..99e2152cef3 --- /dev/null +++ b/contribs/gnopls/internal/fuzzy/symbol_test.go @@ -0,0 +1,253 @@ +// 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 fuzzy_test + +import ( + "go/ast" + "go/token" + "sort" + "testing" + + "golang.org/x/tools/go/packages" + . "golang.org/x/tools/gopls/internal/fuzzy" +) + +func TestSymbolMatchIndex(t *testing.T) { + tests := []struct { + pattern, input string + want int + }{ + {"test", "foo.TestFoo", 4}, + {"test", "test", 0}, + {"test", "Test", 0}, + {"test", "est", -1}, + {"t", "shortest", 7}, + {"", "foo", -1}, + {"", string([]rune{0}), -1}, // verify that we don't default to an empty pattern. + {"anything", "", -1}, + } + + for _, test := range tests { + matcher := NewSymbolMatcher(test.pattern) + if got, _ := matcher.Match([]string{test.input}); got != test.want { + t.Errorf("NewSymbolMatcher(%q).Match(%q) = %v, _, want %v, _", test.pattern, test.input, got, test.want) + } + } +} + +func TestSymbolRanking(t *testing.T) { + + // query -> symbols to match, in ascending order of score + queryRanks := map[string][]string{ + "test": { + "this.is.better.than.most", + "test.foo.bar", + "thebest", + "atest", + "test.foo", + "testage", + "tTest", + "foo.test", + }, + "parseside": { // golang/go#60201 + "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE", + "parseContext.parse_sidebyside", + }, + "cvb": { + "filecache_test.testIPCValueB", + "cover.Boundary", + }, + "dho": { + "gocommand.DebugHangingGoCommands", + "protocol.DocumentHighlightOptions", + }, + "flg": { + "completion.FALLTHROUGH", + "main.flagGoCmd", + }, + "fvi": { + "godoc.fileIndexVersion", + "macho.FlagSubsectionsViaSymbols", + }, + } + + for query, symbols := range queryRanks { + t.Run(query, func(t *testing.T) { + matcher := NewSymbolMatcher(query) + prev := 0.0 + for _, sym := range symbols { + _, score := matcher.Match([]string{sym}) + t.Logf("Match(%q) = %v", sym, score) + if score <= prev { + t.Errorf("Match(%q) = _, %v, want > %v", sym, score, prev) + } + prev = score + } + }) + } +} + +func TestMatcherSimilarities(t *testing.T) { + // This test compares the fuzzy matcher with the symbol matcher on a corpus + // of qualified identifiers extracted from x/tools. + // + // These two matchers are not expected to agree, but inspecting differences + // can be useful for finding interesting ranking edge cases. + t.Skip("unskip this test to compare matchers") + + idents := collectIdentifiers(t) + t.Logf("collected %d unique identifiers", len(idents)) + + // We can't use slices.MaxFunc because we want a custom + // scoring (not equivalence) function. + topMatch := func(score func(string) float64) string { + top := "" + topScore := 0.0 + for _, cand := range idents { + if s := score(cand); s > topScore { + top = cand + topScore = s + } + } + return top + } + + agreed := 0 + total := 0 + bad := 0 + patterns := generatePatterns() + for _, pattern := range patterns { + total++ + + fm := NewMatcher(pattern) + topFuzzy := topMatch(func(input string) float64 { + return float64(fm.Score(input)) + }) + sm := NewSymbolMatcher(pattern) + topSymbol := topMatch(func(input string) float64 { + _, score := sm.Match([]string{input}) + return score + }) + switch { + case topFuzzy == "" && topSymbol != "": + if false { + // The fuzzy matcher has a bug where it misses some matches; for this + // test we only care about the symbol matcher. + t.Logf("%q matched %q but no fuzzy match", pattern, topSymbol) + } + total-- + bad++ + case topFuzzy != "" && topSymbol == "": + t.Fatalf("%q matched %q but no symbol match", pattern, topFuzzy) + case topFuzzy == topSymbol: + agreed++ + default: + // Enable this log to see mismatches. + if false { + t.Logf("mismatch for %q: fuzzy: %q, symbol: %q", pattern, topFuzzy, topSymbol) + } + } + } + t.Logf("fuzzy matchers agreed on %d out of %d queries (%d bad)", agreed, total, bad) +} + +func collectIdentifiers(tb testing.TB) []string { + cfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedSyntax | packages.NeedFiles, + Tests: true, + } + pkgs, err := packages.Load(cfg, "golang.org/x/tools/...") + if err != nil { + tb.Fatal(err) + } + uniqueIdents := make(map[string]bool) + decls := 0 + for _, pkg := range pkgs { + for _, f := range pkg.Syntax { + for _, decl := range f.Decls { + decls++ + switch decl := decl.(type) { + case *ast.GenDecl: + for _, spec := range decl.Specs { + switch decl.Tok { + case token.IMPORT: + case token.TYPE: + name := spec.(*ast.TypeSpec).Name.Name + qualified := pkg.Name + "." + name + uniqueIdents[qualified] = true + case token.CONST, token.VAR: + for _, n := range spec.(*ast.ValueSpec).Names { + qualified := pkg.Name + "." + n.Name + uniqueIdents[qualified] = true + } + } + } + } + } + } + } + var idents []string + for k := range uniqueIdents { + idents = append(idents, k) + } + sort.Strings(idents) + return idents +} + +func generatePatterns() []string { + var patterns []string + for x := 'a'; x <= 'z'; x++ { + for y := 'a'; y <= 'z'; y++ { + for z := 'a'; z <= 'z'; z++ { + patterns = append(patterns, string(x)+string(y)+string(z)) + } + } + } + return patterns +} + +// Test that we strongly prefer exact matches. +// +// In golang/go#60027, we preferred "Runner" for the query "rune" over several +// results containing the word "rune" exactly. Following this observation, +// scoring was tweaked to more strongly emphasize sequential characters and +// exact matches. +func TestSymbolRanking_Issue60027(t *testing.T) { + matcher := NewSymbolMatcher("rune") + + // symbols to match, in ascending order of ranking. + symbols := []string{ + "Runner", + "singleRuneParam", + "Config.ifsRune", + "Parser.rune", + } + prev := 0.0 + for _, sym := range symbols { + _, score := matcher.Match([]string{sym}) + t.Logf("Match(%q) = %v", sym, score) + if score < prev { + t.Errorf("Match(%q) = _, %v, want > %v", sym, score, prev) + } + prev = score + } +} + +func TestChunkedMatch(t *testing.T) { + matcher := NewSymbolMatcher("test") + _, want := matcher.Match([]string{"test"}) + chunked := [][]string{ + {"", "test"}, + {"test", ""}, + {"te", "st"}, + } + + for _, chunks := range chunked { + offset, score := matcher.Match(chunks) + if offset != 0 || score != want { + t.Errorf("Match(%v) = %v, %v, want 0, 1.0", chunks, offset, score) + } + } +} diff --git a/contribs/gnopls/internal/golang/add_import.go b/contribs/gnopls/internal/golang/add_import.go new file mode 100644 index 00000000000..a43256a6a08 --- /dev/null +++ b/contribs/gnopls/internal/golang/add_import.go @@ -0,0 +1,29 @@ +// 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 golang + +import ( + "context" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/imports" +) + +// AddImport adds a single import statement to the given file +func AddImport(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, importPath string) ([]protocol.TextEdit, error) { + pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full) + if err != nil { + return nil, err + } + return ComputeOneImportFixEdits(snapshot, pgf, &imports.ImportFix{ + StmtInfo: imports.ImportInfo{ + ImportPath: importPath, + }, + FixType: imports.AddImport, + }) +} diff --git a/contribs/gnopls/internal/golang/assembly.go b/contribs/gnopls/internal/golang/assembly.go new file mode 100644 index 00000000000..63d8e82d9fd --- /dev/null +++ b/contribs/gnopls/internal/golang/assembly.go @@ -0,0 +1,133 @@ +// 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 golang + +// This file produces the "Browse GOARCH assembly of f" HTML report. +// +// See also: +// - ./codeaction.go - computes the symbol and offers the CodeAction command. +// - ../server/command.go - handles the command by opening a web page. +// - ../server/server.go - handles the HTTP request and calls this function. + +import ( + "bytes" + "context" + "fmt" + "html" + "path/filepath" + "regexp" + "strconv" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/internal/gocommand" +) + +// AssemblyHTML returns an HTML document containing an assembly listing of the selected function. +// +// TODO(adonovan): +// - display a "Compiling..." message as a cold build can be slow. +// - cross-link jumps and block labels, like github.com/aclements/objbrowse. +func AssemblyHTML(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, symbol string, web Web) ([]byte, error) { + // Compile the package with -S, and capture its stderr stream. + inv, cleanupInvocation, err := snapshot.GoCommandInvocation(false, &gocommand.Invocation{ + Verb: "build", + Args: []string{"-gcflags=-S", "."}, + WorkingDir: filepath.Dir(pkg.Metadata().CompiledGoFiles[0].Path()), + }) + if err != nil { + return nil, err // e.g. failed to write overlays (rare) + } + defer cleanupInvocation() + _, stderr, err, _ := snapshot.View().GoCommandRunner().RunRaw(ctx, *inv) + if err != nil { + return nil, err // e.g. won't compile + } + content := stderr.String() + + escape := html.EscapeString + + // Produce the report. + title := fmt.Sprintf("%s assembly for %s", + escape(snapshot.View().GOARCH()), + escape(symbol)) + var buf bytes.Buffer + buf.WriteString(` + + + + ` + escape(title) + ` + + + + +

    ` + title + `

    +

    + A Quick Guide to Go's Assembler +

    +

    + Experimental. Contributions welcome! +

    +

    + Click on a source line marker L1234 to navigate your editor there. + (VS Code users: please upvote #208093) +

    +

    + Reload the page to recompile. +

    +
    +`)
    +
    +	// insnRx matches an assembly instruction line.
    +	// Submatch groups are: (offset-hex-dec, file-line-column, instruction).
    +	insnRx := regexp.MustCompile(`^(\s+0x[0-9a-f ]+)\(([^)]*)\)\s+(.*)$`)
    +
    +	// Parse the functions of interest out of the listing.
    +	// Each function is of the form:
    +	//
    +	//     symbol STEXT k=v...
    +	//         0x0000 00000 (/file.go:123) NOP...
    +	//         ...
    +	//
    +	// Allow matches of symbol, symbol.func1, symbol.deferwrap, etc.
    +	on := false
    +	for _, line := range strings.Split(content, "\n") {
    +		// start of function symbol?
    +		if strings.Contains(line, " STEXT ") {
    +			on = strings.HasPrefix(line, symbol) &&
    +				(line[len(symbol)] == ' ' || line[len(symbol)] == '.')
    +		}
    +		if !on {
    +			continue // within uninteresting symbol
    +		}
    +
    +		// In lines of the form
    +		//   "\t0x0000 00000 (/file.go:123) NOP..."
    +		// replace the "(/file.go:123)" portion with an "L0123" source link.
    +		// Skip filenames of the form "".
    +		if parts := insnRx.FindStringSubmatch(line); parts != nil {
    +			link := "     " // if unknown
    +			if file, linenum, ok := cutLast(parts[2], ":"); ok && !strings.HasPrefix(file, "<") {
    +				if linenum, err := strconv.Atoi(linenum); err == nil {
    +					text := fmt.Sprintf("L%04d", linenum)
    +					link = sourceLink(text, web.SrcURL(file, linenum, 1))
    +				}
    +			}
    +			fmt.Fprintf(&buf, "%s\t%s\t%s", escape(parts[1]), link, escape(parts[3]))
    +		} else {
    +			buf.WriteString(escape(line))
    +		}
    +		buf.WriteByte('\n')
    +	}
    +	return buf.Bytes(), nil
    +}
    +
    +// cutLast is the "last" analogue of [strings.Cut].
    +func cutLast(s, sep string) (before, after string, ok bool) {
    +	if i := strings.LastIndex(s, sep); i >= 0 {
    +		return s[:i], s[i+len(sep):], true
    +	}
    +	return s, "", false
    +}
    diff --git a/contribs/gnopls/internal/golang/call_hierarchy.go b/contribs/gnopls/internal/golang/call_hierarchy.go
    new file mode 100644
    index 00000000000..04dc9deeb5d
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/call_hierarchy.go
    @@ -0,0 +1,315 @@
    +// 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 golang
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +	"go/ast"
    +	"go/token"
    +	"go/types"
    +	"path/filepath"
    +
    +	"golang.org/x/tools/go/ast/astutil"
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/internal/event"
    +)
    +
    +// PrepareCallHierarchy returns an array of CallHierarchyItem for a file and the position within the file.
    +func PrepareCallHierarchy(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.CallHierarchyItem, error) {
    +	ctx, done := event.Start(ctx, "golang.PrepareCallHierarchy")
    +	defer done()
    +
    +	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil {
    +		return nil, err
    +	}
    +	pos, err := pgf.PositionPos(pp)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	_, obj, _ := referencedObject(pkg, pgf, pos)
    +	if obj == nil {
    +		return nil, nil
    +	}
    +
    +	if _, ok := obj.Type().Underlying().(*types.Signature); !ok {
    +		return nil, nil
    +	}
    +
    +	declLoc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), adjustedObjEnd(obj))
    +	if err != nil {
    +		return nil, err
    +	}
    +	rng := declLoc.Range
    +
    +	callHierarchyItem := protocol.CallHierarchyItem{
    +		Name:           obj.Name(),
    +		Kind:           protocol.Function,
    +		Tags:           []protocol.SymbolTag{},
    +		Detail:         fmt.Sprintf("%s • %s", obj.Pkg().Path(), filepath.Base(declLoc.URI.Path())),
    +		URI:            declLoc.URI,
    +		Range:          rng,
    +		SelectionRange: rng,
    +	}
    +	return []protocol.CallHierarchyItem{callHierarchyItem}, nil
    +}
    +
    +// IncomingCalls returns an array of CallHierarchyIncomingCall for a file and the position within the file.
    +func IncomingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pos protocol.Position) ([]protocol.CallHierarchyIncomingCall, error) {
    +	ctx, done := event.Start(ctx, "golang.IncomingCalls")
    +	defer done()
    +
    +	refs, err := references(ctx, snapshot, fh, pos, false)
    +	if err != nil {
    +		if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
    +			return nil, nil
    +		}
    +		return nil, err
    +	}
    +
    +	// Group references by their enclosing function declaration.
    +	incomingCalls := make(map[protocol.Location]*protocol.CallHierarchyIncomingCall)
    +	for _, ref := range refs {
    +		callItem, err := enclosingNodeCallItem(ctx, snapshot, ref.pkgPath, ref.location)
    +		if err != nil {
    +			event.Error(ctx, fmt.Sprintf("error getting enclosing node for %q", ref.pkgPath), err)
    +			continue
    +		}
    +		loc := protocol.Location{
    +			URI:   callItem.URI,
    +			Range: callItem.Range,
    +		}
    +		call, ok := incomingCalls[loc]
    +		if !ok {
    +			call = &protocol.CallHierarchyIncomingCall{From: callItem}
    +			incomingCalls[loc] = call
    +		}
    +		call.FromRanges = append(call.FromRanges, ref.location.Range)
    +	}
    +
    +	// Flatten the map of pointers into a slice of values.
    +	incomingCallItems := make([]protocol.CallHierarchyIncomingCall, 0, len(incomingCalls))
    +	for _, callItem := range incomingCalls {
    +		incomingCallItems = append(incomingCallItems, *callItem)
    +	}
    +	return incomingCallItems, nil
    +}
    +
    +// enclosingNodeCallItem creates a CallHierarchyItem representing the function call at loc.
    +func enclosingNodeCallItem(ctx context.Context, snapshot *cache.Snapshot, pkgPath PackagePath, loc protocol.Location) (protocol.CallHierarchyItem, error) {
    +	// Parse the file containing the reference.
    +	fh, err := snapshot.ReadFile(ctx, loc.URI)
    +	if err != nil {
    +		return protocol.CallHierarchyItem{}, err
    +	}
    +	// TODO(adonovan): opt: before parsing, trim the bodies of functions
    +	// that don't contain the reference, using either a scanner-based
    +	// implementation such as https://go.dev/play/p/KUrObH1YkX8
    +	// (~31% speedup), or a byte-oriented implementation (2x speedup).
    +	pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +	if err != nil {
    +		return protocol.CallHierarchyItem{}, err
    +	}
    +	start, end, err := pgf.RangePos(loc.Range)
    +	if err != nil {
    +		return protocol.CallHierarchyItem{}, err
    +	}
    +
    +	// Find the enclosing named function, if any.
    +	//
    +	// It is tempting to treat anonymous functions as nodes in the
    +	// call hierarchy, and historically we used to do that,
    +	// poorly; see #64451. However, it is impossible to track
    +	// references to anonymous functions without much deeper
    +	// analysis. Local analysis is tractable, but ultimately it
    +	// can only detect calls from the outer function to the inner
    +	// function.
    +	//
    +	// It is simpler and clearer to treat the top-level named
    +	// function and all its nested functions as one entity, and it
    +	// allows users to recursively expand the tree where, before,
    +	// the chain would be broken by each lambda.
    +	//
    +	// If the selection is in a global var initializer,
    +	// default to the file's package declaration.
    +	path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
    +	var (
    +		name = pgf.File.Name.Name
    +		kind = protocol.Package
    +	)
    +	start, end = pgf.File.Name.Pos(), pgf.File.Name.End()
    +	for _, node := range path {
    +		switch node := node.(type) {
    +		case *ast.FuncDecl:
    +			name = node.Name.Name
    +			start, end = node.Name.Pos(), node.Name.End()
    +			kind = protocol.Function
    +
    +		case *ast.FuncLit:
    +			// If the call comes from a FuncLit with
    +			// no enclosing FuncDecl, then use the
    +			// FuncLit's extent.
    +			name = "func"
    +			start, end = node.Pos(), node.Type.End() // signature, sans body
    +			kind = protocol.Function
    +
    +		case *ast.ValueSpec:
    +			// If the call comes from a var (or,
    +			// theoretically, const) initializer outside
    +			// any function, then use the ValueSpec.Names span.
    +			name = "init"
    +			start, end = node.Names[0].Pos(), node.Names[len(node.Names)-1].End()
    +			kind = protocol.Variable
    +		}
    +	}
    +
    +	rng, err := pgf.PosRange(start, end)
    +	if err != nil {
    +		return protocol.CallHierarchyItem{}, err
    +	}
    +
    +	return protocol.CallHierarchyItem{
    +		Name:           name,
    +		Kind:           kind,
    +		Tags:           []protocol.SymbolTag{},
    +		Detail:         fmt.Sprintf("%s • %s", pkgPath, filepath.Base(fh.URI().Path())),
    +		URI:            loc.URI,
    +		Range:          rng,
    +		SelectionRange: rng,
    +	}, nil
    +}
    +
    +// OutgoingCalls returns an array of CallHierarchyOutgoingCall for a file and the position within the file.
    +func OutgoingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.CallHierarchyOutgoingCall, error) {
    +	ctx, done := event.Start(ctx, "golang.OutgoingCalls")
    +	defer done()
    +
    +	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil {
    +		return nil, err
    +	}
    +	pos, err := pgf.PositionPos(pp)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	_, obj, _ := referencedObject(pkg, pgf, pos)
    +	if obj == nil {
    +		return nil, nil
    +	}
    +
    +	if _, ok := obj.Type().Underlying().(*types.Signature); !ok {
    +		return nil, nil
    +	}
    +
    +	if isBuiltin(obj) {
    +		return nil, nil // built-ins have no position
    +	}
    +
    +	declFile := pkg.FileSet().File(obj.Pos())
    +	if declFile == nil {
    +		return nil, bug.Errorf("file not found for %d", obj.Pos())
    +	}
    +
    +	uri := protocol.URIFromPath(declFile.Name())
    +	offset, err := safetoken.Offset(declFile, obj.Pos())
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	declPkg, declPGF, err := NarrowestPackageForFile(ctx, snapshot, uri)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	declPos, err := safetoken.Pos(declPGF.Tok, offset)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	declNode, _, _ := findDeclInfo([]*ast.File{declPGF.File}, declPos)
    +	if declNode == nil {
    +		// TODO(rfindley): why don't we return an error here, or even bug.Errorf?
    +		return nil, nil
    +		// return nil, bug.Errorf("failed to find declaration for object %s.%s", obj.Pkg().Path(), obj.Name())
    +	}
    +
    +	type callRange struct {
    +		start, end token.Pos
    +	}
    +	callRanges := []callRange{}
    +	ast.Inspect(declNode, func(n ast.Node) bool {
    +		if call, ok := n.(*ast.CallExpr); ok {
    +			var start, end token.Pos
    +			switch n := call.Fun.(type) {
    +			case *ast.SelectorExpr:
    +				start, end = n.Sel.NamePos, call.Lparen
    +			case *ast.Ident:
    +				start, end = n.NamePos, call.Lparen
    +			case *ast.FuncLit:
    +				// while we don't add the function literal as an 'outgoing' call
    +				// we still want to traverse into it
    +				return true
    +			default:
    +				// ignore any other kind of call expressions
    +				// for ex: direct function literal calls since that's not an 'outgoing' call
    +				return false
    +			}
    +			callRanges = append(callRanges, callRange{start: start, end: end})
    +		}
    +		return true
    +	})
    +
    +	outgoingCalls := map[token.Pos]*protocol.CallHierarchyOutgoingCall{}
    +	for _, callRange := range callRanges {
    +		_, obj, _ := referencedObject(declPkg, declPGF, callRange.start)
    +		if obj == nil {
    +			continue
    +		}
    +		if isBuiltin(obj) {
    +			continue // built-ins have no position
    +		}
    +
    +		outgoingCall, ok := outgoingCalls[obj.Pos()]
    +		if !ok {
    +			loc, err := mapPosition(ctx, declPkg.FileSet(), snapshot, obj.Pos(), obj.Pos()+token.Pos(len(obj.Name())))
    +			if err != nil {
    +				return nil, err
    +			}
    +			outgoingCall = &protocol.CallHierarchyOutgoingCall{
    +				To: protocol.CallHierarchyItem{
    +					Name:           obj.Name(),
    +					Kind:           protocol.Function,
    +					Tags:           []protocol.SymbolTag{},
    +					Detail:         fmt.Sprintf("%s • %s", obj.Pkg().Path(), filepath.Base(loc.URI.Path())),
    +					URI:            loc.URI,
    +					Range:          loc.Range,
    +					SelectionRange: loc.Range,
    +				},
    +			}
    +			outgoingCalls[obj.Pos()] = outgoingCall
    +		}
    +
    +		rng, err := declPGF.PosRange(callRange.start, callRange.end)
    +		if err != nil {
    +			return nil, err
    +		}
    +		outgoingCall.FromRanges = append(outgoingCall.FromRanges, rng)
    +	}
    +
    +	outgoingCallItems := make([]protocol.CallHierarchyOutgoingCall, 0, len(outgoingCalls))
    +	for _, callItem := range outgoingCalls {
    +		outgoingCallItems = append(outgoingCallItems, *callItem)
    +	}
    +	return outgoingCallItems, nil
    +}
    diff --git a/contribs/gnopls/internal/golang/change_quote.go b/contribs/gnopls/internal/golang/change_quote.go
    new file mode 100644
    index 00000000000..6fa56d42615
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/change_quote.go
    @@ -0,0 +1,79 @@
    +// 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 golang
    +
    +import (
    +	"go/ast"
    +	"go/token"
    +	"strconv"
    +	"strings"
    +
    +	"golang.org/x/tools/go/ast/astutil"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/settings"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/internal/diff"
    +)
    +
    +// convertStringLiteral reports whether we can convert between raw and interpreted
    +// string literals in the [start, end) range, along with a CodeAction containing the edits.
    +//
    +// Only the following conditions are true, the action in result is valid
    +//   - [start, end) is enclosed by a string literal
    +//   - if the string is interpreted string, need check whether the convert is allowed
    +func convertStringLiteral(pgf *parsego.File, fh file.Handle, startPos, endPos token.Pos) (protocol.CodeAction, bool) {
    +	path, _ := astutil.PathEnclosingInterval(pgf.File, startPos, endPos)
    +	lit, ok := path[0].(*ast.BasicLit)
    +	if !ok || lit.Kind != token.STRING {
    +		return protocol.CodeAction{}, false
    +	}
    +
    +	str, err := strconv.Unquote(lit.Value)
    +	if err != nil {
    +		return protocol.CodeAction{}, false
    +	}
    +
    +	interpreted := lit.Value[0] == '"'
    +	// Not all "..." strings can be represented as `...` strings.
    +	if interpreted && !strconv.CanBackquote(strings.ReplaceAll(str, "\n", "")) {
    +		return protocol.CodeAction{}, false
    +	}
    +
    +	var (
    +		title   string
    +		newText string
    +	)
    +	if interpreted {
    +		title = "Convert to raw string literal"
    +		newText = "`" + str + "`"
    +	} else {
    +		title = "Convert to interpreted string literal"
    +		newText = strconv.Quote(str)
    +	}
    +
    +	start, end, err := safetoken.Offsets(pgf.Tok, lit.Pos(), lit.End())
    +	if err != nil {
    +		bug.Reportf("failed to get string literal offset by token.Pos:%v", err)
    +		return protocol.CodeAction{}, false
    +	}
    +	edits := []diff.Edit{{
    +		Start: start,
    +		End:   end,
    +		New:   newText,
    +	}}
    +	textedits, err := protocol.EditsFromDiffEdits(pgf.Mapper, edits)
    +	if err != nil {
    +		bug.Reportf("failed to convert diff.Edit to protocol.TextEdit:%v", err)
    +		return protocol.CodeAction{}, false
    +	}
    +	return protocol.CodeAction{
    +		Title: title,
    +		Kind:  settings.RefactorRewriteChangeQuote,
    +		Edit:  protocol.NewWorkspaceEdit(protocol.DocumentChangeEdit(fh, textedits)),
    +	}, true
    +}
    diff --git a/contribs/gnopls/internal/golang/change_signature.go b/contribs/gnopls/internal/golang/change_signature.go
    new file mode 100644
    index 00000000000..72cbe4c2d90
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/change_signature.go
    @@ -0,0 +1,617 @@
    +// 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 golang
    +
    +import (
    +	"bytes"
    +	"context"
    +	"fmt"
    +	"go/ast"
    +	"go/format"
    +	"go/parser"
    +	"go/token"
    +	"go/types"
    +	"regexp"
    +
    +	"golang.org/x/tools/go/ast/astutil"
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/imports"
    +	internalastutil "golang.org/x/tools/internal/astutil"
    +	"golang.org/x/tools/internal/diff"
    +	"golang.org/x/tools/internal/refactor/inline"
    +	"golang.org/x/tools/internal/tokeninternal"
    +	"golang.org/x/tools/internal/typesinternal"
    +	"golang.org/x/tools/internal/versions"
    +)
    +
    +// RemoveUnusedParameter computes a refactoring to remove the parameter
    +// indicated by the given range, which must be contained within an unused
    +// parameter name or field.
    +//
    +// This operation is a work in progress. Remaining TODO:
    +//   - Handle function assignment correctly.
    +//   - Improve the extra newlines in output.
    +//   - Stream type checking via ForEachPackage.
    +//   - Avoid unnecessary additional type checking.
    +func RemoveUnusedParameter(ctx context.Context, fh file.Handle, rng protocol.Range, snapshot *cache.Snapshot) ([]protocol.DocumentChange, error) {
    +	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// Changes to our heuristics for whether we can remove a parameter must also
    +	// be reflected in the canRemoveParameter helper.
    +	if perrors, terrors := pkg.ParseErrors(), pkg.TypeErrors(); len(perrors) > 0 || len(terrors) > 0 {
    +		var sample string
    +		if len(perrors) > 0 {
    +			sample = perrors[0].Error()
    +		} else {
    +			sample = terrors[0].Error()
    +		}
    +		return nil, fmt.Errorf("can't change signatures for packages with parse or type errors: (e.g. %s)", sample)
    +	}
    +
    +	info, err := findParam(pgf, rng)
    +	if err != nil {
    +		return nil, err // e.g. invalid range
    +	}
    +	if info.field == nil {
    +		return nil, fmt.Errorf("failed to find field")
    +	}
    +
    +	// Create the new declaration, which is a copy of the original decl with the
    +	// unnecessary parameter removed.
    +	newDecl := internalastutil.CloneNode(info.decl)
    +	if info.name != nil {
    +		names := remove(newDecl.Type.Params.List[info.fieldIndex].Names, info.nameIndex)
    +		newDecl.Type.Params.List[info.fieldIndex].Names = names
    +	}
    +	if len(newDecl.Type.Params.List[info.fieldIndex].Names) == 0 {
    +		// Unnamed, or final name was removed: in either case, remove the field.
    +		newDecl.Type.Params.List = remove(newDecl.Type.Params.List, info.fieldIndex)
    +	}
    +
    +	// Compute inputs into building a wrapper function around the modified
    +	// signature.
    +	var (
    +		params   = internalastutil.CloneNode(info.decl.Type.Params) // "_" names will be modified
    +		args     []ast.Expr                                         // arguments to delegate
    +		variadic = false                                            // whether the signature is variadic
    +	)
    +	{
    +		allNames := make(map[string]bool) // for renaming blanks
    +		for _, fld := range params.List {
    +			for _, n := range fld.Names {
    +				if n.Name != "_" {
    +					allNames[n.Name] = true
    +				}
    +			}
    +		}
    +		blanks := 0
    +		for i, fld := range params.List {
    +			for j, n := range fld.Names {
    +				if i == info.fieldIndex && j == info.nameIndex {
    +					continue
    +				}
    +				if n.Name == "_" {
    +					// Create names for blank (_) parameters so the delegating wrapper
    +					// can refer to them.
    +					for {
    +						newName := fmt.Sprintf("blank%d", blanks)
    +						blanks++
    +						if !allNames[newName] {
    +							n.Name = newName
    +							break
    +						}
    +					}
    +				}
    +				args = append(args, &ast.Ident{Name: n.Name})
    +				if i == len(params.List)-1 {
    +					_, variadic = fld.Type.(*ast.Ellipsis)
    +				}
    +			}
    +		}
    +	}
    +
    +	// Rewrite all referring calls.
    +	newContent, err := rewriteCalls(ctx, signatureRewrite{
    +		snapshot: snapshot,
    +		pkg:      pkg,
    +		pgf:      pgf,
    +		origDecl: info.decl,
    +		newDecl:  newDecl,
    +		params:   params,
    +		callArgs: args,
    +		variadic: variadic,
    +	})
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// Finally, rewrite the original declaration. We do this after inlining all
    +	// calls, as there may be calls in the same file as the declaration. But none
    +	// of the inlining should have changed the location of the original
    +	// declaration.
    +	{
    +		idx := findDecl(pgf.File, info.decl)
    +		if idx < 0 {
    +			return nil, bug.Errorf("didn't find original decl")
    +		}
    +
    +		src, ok := newContent[pgf.URI]
    +		if !ok {
    +			src = pgf.Src
    +		}
    +		fset := tokeninternal.FileSetFor(pgf.Tok)
    +		src, err := rewriteSignature(fset, idx, src, newDecl)
    +		if err != nil {
    +			return nil, err
    +		}
    +		newContent[pgf.URI] = src
    +	}
    +
    +	// Translate the resulting state into document changes.
    +	var changes []protocol.DocumentChange
    +	for uri, after := range newContent {
    +		fh, err := snapshot.ReadFile(ctx, uri)
    +		if err != nil {
    +			return nil, err
    +		}
    +		before, err := fh.Content()
    +		if err != nil {
    +			return nil, err
    +		}
    +		edits := diff.Bytes(before, after)
    +		mapper := protocol.NewMapper(uri, before)
    +		textedits, err := protocol.EditsFromDiffEdits(mapper, edits)
    +		if err != nil {
    +			return nil, fmt.Errorf("computing edits for %s: %v", uri, err)
    +		}
    +		change := protocol.DocumentChangeEdit(fh, textedits)
    +		changes = append(changes, change)
    +	}
    +	return changes, nil
    +}
    +
    +// rewriteSignature rewrites the signature of the declIdx'th declaration in src
    +// to use the signature of newDecl (described by fset).
    +//
    +// TODO(rfindley): I think this operation could be generalized, for example by
    +// using a concept of a 'nodepath' to correlate nodes between two related
    +// files.
    +//
    +// Note that with its current application, rewriteSignature is expected to
    +// succeed. Separate bug.Errorf calls are used below (rather than one call at
    +// the callsite) in order to have greater precision.
    +func rewriteSignature(fset *token.FileSet, declIdx int, src0 []byte, newDecl *ast.FuncDecl) ([]byte, error) {
    +	// Parse the new file0 content, to locate the original params.
    +	file0, err := parser.ParseFile(fset, "", src0, parser.ParseComments|parser.SkipObjectResolution)
    +	if err != nil {
    +		return nil, bug.Errorf("re-parsing declaring file failed: %v", err)
    +	}
    +	decl0, _ := file0.Decls[declIdx].(*ast.FuncDecl)
    +	// Inlining shouldn't have changed the location of any declarations, but do
    +	// a sanity check.
    +	if decl0 == nil || decl0.Name.Name != newDecl.Name.Name {
    +		return nil, bug.Errorf("inlining affected declaration order: found %v, not func %s", decl0, newDecl.Name.Name)
    +	}
    +	opening0, closing0, err := safetoken.Offsets(fset.File(decl0.Pos()), decl0.Type.Params.Opening, decl0.Type.Params.Closing)
    +	if err != nil {
    +		return nil, bug.Errorf("can't find params: %v", err)
    +	}
    +
    +	// Format the modified signature and apply a textual replacement. This
    +	// minimizes comment disruption.
    +	formattedType := FormatNode(fset, newDecl.Type)
    +	expr, err := parser.ParseExprFrom(fset, "", []byte(formattedType), 0)
    +	if err != nil {
    +		return nil, bug.Errorf("parsing modified signature: %v", err)
    +	}
    +	newType := expr.(*ast.FuncType)
    +	opening1, closing1, err := safetoken.Offsets(fset.File(newType.Pos()), newType.Params.Opening, newType.Params.Closing)
    +	if err != nil {
    +		return nil, bug.Errorf("param offsets: %v", err)
    +	}
    +	newParams := formattedType[opening1 : closing1+1]
    +
    +	// Splice.
    +	var buf bytes.Buffer
    +	buf.Write(src0[:opening0])
    +	buf.WriteString(newParams)
    +	buf.Write(src0[closing0+1:])
    +	newSrc := buf.Bytes()
    +	if len(file0.Imports) > 0 {
    +		formatted, err := imports.Process("output", newSrc, nil)
    +		if err != nil {
    +			return nil, bug.Errorf("imports.Process failed: %v", err)
    +		}
    +		newSrc = formatted
    +	}
    +	return newSrc, nil
    +}
    +
    +// paramInfo records information about a param identified by a position.
    +type paramInfo struct {
    +	decl       *ast.FuncDecl // enclosing func decl (non-nil)
    +	fieldIndex int           // index of Field in Decl.Type.Params, or -1
    +	field      *ast.Field    // enclosing field of Decl, or nil if range not among parameters
    +	nameIndex  int           // index of Name in Field.Names, or nil
    +	name       *ast.Ident    // indicated name (either enclosing, or Field.Names[0] if len(Field.Names) == 1)
    +}
    +
    +// findParam finds the parameter information spanned by the given range.
    +func findParam(pgf *parsego.File, rng protocol.Range) (*paramInfo, error) {
    +	start, end, err := pgf.RangePos(rng)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
    +	var (
    +		id    *ast.Ident
    +		field *ast.Field
    +		decl  *ast.FuncDecl
    +	)
    +	// Find the outermost enclosing node of each kind, whether or not they match
    +	// the semantics described in the docstring.
    +	for _, n := range path {
    +		switch n := n.(type) {
    +		case *ast.Ident:
    +			id = n
    +		case *ast.Field:
    +			field = n
    +		case *ast.FuncDecl:
    +			decl = n
    +		}
    +	}
    +	// Check the conditions described in the docstring.
    +	if decl == nil {
    +		return nil, fmt.Errorf("range is not within a function declaration")
    +	}
    +	info := ¶mInfo{
    +		fieldIndex: -1,
    +		nameIndex:  -1,
    +		decl:       decl,
    +	}
    +	for fi, f := range decl.Type.Params.List {
    +		if f == field {
    +			info.fieldIndex = fi
    +			info.field = f
    +			for ni, n := range f.Names {
    +				if n == id {
    +					info.nameIndex = ni
    +					info.name = n
    +					break
    +				}
    +			}
    +			if info.name == nil && len(info.field.Names) == 1 {
    +				info.nameIndex = 0
    +				info.name = info.field.Names[0]
    +			}
    +			break
    +		}
    +	}
    +	return info, nil
    +}
    +
    +// signatureRewrite defines a rewritten function signature.
    +//
    +// See rewriteCalls for more details.
    +type signatureRewrite struct {
    +	snapshot          *cache.Snapshot
    +	pkg               *cache.Package
    +	pgf               *parsego.File
    +	origDecl, newDecl *ast.FuncDecl
    +	params            *ast.FieldList
    +	callArgs          []ast.Expr
    +	variadic          bool
    +}
    +
    +// rewriteCalls returns the document changes required to rewrite the
    +// signature of origDecl to that of newDecl.
    +//
    +// This is a rather complicated factoring of the rewrite operation, but is able
    +// to describe arbitrary rewrites. Specifically, rewriteCalls creates a
    +// synthetic copy of pkg, where the original function declaration is changed to
    +// be a trivial wrapper around the new declaration. params and callArgs are
    +// used to perform this delegation: params must have the same type as origDecl,
    +// but may have renamed parameters (such as is required for delegating blank
    +// parameters). callArgs are the arguments of the delegated call (i.e. using
    +// params).
    +//
    +// For example, consider removing the unused 'b' parameter below, rewriting
    +//
    +//	func Foo(a, b, c, _ int) int {
    +//	  return a+c
    +//	}
    +//
    +// To
    +//
    +//	func Foo(a, c, _ int) int {
    +//	  return a+c
    +//	}
    +//
    +// In this case, rewriteCalls is parameterized as follows:
    +//   - origDecl is the original declaration
    +//   - newDecl is the new declaration, which is a copy of origDecl less the 'b'
    +//     parameter.
    +//   - params is a new parameter list (a, b, c, blank0 int) to be used for the
    +//     new wrapper.
    +//   - callArgs is the argument list (a, c, blank0), to be used to call the new
    +//     delegate.
    +//
    +// rewriting is expressed this way so that rewriteCalls can own the details
    +// of *how* this rewriting is performed. For example, as of writing it names
    +// the synthetic delegate G_o_p_l_s_foo, but the caller need not know this.
    +//
    +// By passing an entirely new declaration, rewriteCalls may be used for
    +// signature refactorings that may affect the function body, such as removing
    +// or adding return values.
    +func rewriteCalls(ctx context.Context, rw signatureRewrite) (map[protocol.DocumentURI][]byte, error) {
    +	// tag is a unique prefix that is added to the delegated declaration.
    +	//
    +	// It must have a ~0% probability of causing collisions with existing names.
    +	const tag = "G_o_p_l_s_"
    +
    +	var (
    +		modifiedSrc  []byte
    +		modifiedFile *ast.File
    +		modifiedDecl *ast.FuncDecl
    +	)
    +	{
    +		delegate := internalastutil.CloneNode(rw.newDecl) // clone before modifying
    +		delegate.Name.Name = tag + delegate.Name.Name
    +		if obj := rw.pkg.Types().Scope().Lookup(delegate.Name.Name); obj != nil {
    +			return nil, fmt.Errorf("synthetic name %q conflicts with an existing declaration", delegate.Name.Name)
    +		}
    +
    +		wrapper := internalastutil.CloneNode(rw.origDecl)
    +		wrapper.Type.Params = rw.params
    +
    +		// Get the receiver name, creating it if necessary.
    +		var recv string // nonempty => call is a method call with receiver recv
    +		if wrapper.Recv.NumFields() > 0 {
    +			if len(wrapper.Recv.List[0].Names) > 0 {
    +				recv = wrapper.Recv.List[0].Names[0].Name
    +			} else {
    +				// Create unique name for the temporary receiver, which will be inlined away.
    +				//
    +				// We use the lexical scope of the original function to avoid conflicts
    +				// with (e.g.) named result variables. However, since the parameter syntax
    +				// may have been modified/renamed from the original function, we must
    +				// reject those names too.
    +				usedParams := make(map[string]bool)
    +				for _, fld := range wrapper.Type.Params.List {
    +					for _, name := range fld.Names {
    +						usedParams[name.Name] = true
    +					}
    +				}
    +				scope := rw.pkg.TypesInfo().Scopes[rw.origDecl.Type]
    +				if scope == nil {
    +					return nil, bug.Errorf("missing function scope for %v", rw.origDecl.Name.Name)
    +				}
    +				for i := 0; ; i++ {
    +					recv = fmt.Sprintf("r%d", i)
    +					_, obj := scope.LookupParent(recv, token.NoPos)
    +					if obj == nil && !usedParams[recv] {
    +						break
    +					}
    +				}
    +				wrapper.Recv.List[0].Names = []*ast.Ident{{Name: recv}}
    +			}
    +		}
    +
    +		name := &ast.Ident{Name: delegate.Name.Name}
    +		var fun ast.Expr = name
    +		if recv != "" {
    +			fun = &ast.SelectorExpr{
    +				X:   &ast.Ident{Name: recv},
    +				Sel: name,
    +			}
    +		}
    +		call := &ast.CallExpr{
    +			Fun:  fun,
    +			Args: rw.callArgs,
    +		}
    +		if rw.variadic {
    +			call.Ellipsis = 1 // must not be token.NoPos
    +		}
    +
    +		var stmt ast.Stmt
    +		if delegate.Type.Results.NumFields() > 0 {
    +			stmt = &ast.ReturnStmt{
    +				Results: []ast.Expr{call},
    +			}
    +		} else {
    +			stmt = &ast.ExprStmt{
    +				X: call,
    +			}
    +		}
    +		wrapper.Body = &ast.BlockStmt{
    +			List: []ast.Stmt{stmt},
    +		}
    +
    +		fset := tokeninternal.FileSetFor(rw.pgf.Tok)
    +		var err error
    +		modifiedSrc, err = replaceFileDecl(rw.pgf, rw.origDecl, delegate)
    +		if err != nil {
    +			return nil, err
    +		}
    +		// TODO(rfindley): we can probably get away with one fewer parse operations
    +		// by returning the modified AST from replaceDecl. Investigate if that is
    +		// accurate.
    +		modifiedSrc = append(modifiedSrc, []byte("\n\n"+FormatNode(fset, wrapper))...)
    +		modifiedFile, err = parser.ParseFile(rw.pkg.FileSet(), rw.pgf.URI.Path(), modifiedSrc, parser.ParseComments|parser.SkipObjectResolution)
    +		if err != nil {
    +			return nil, err
    +		}
    +		modifiedDecl = modifiedFile.Decls[len(modifiedFile.Decls)-1].(*ast.FuncDecl)
    +	}
    +
    +	// Type check pkg again with the modified file, to compute the synthetic
    +	// callee.
    +	logf := logger(ctx, "change signature", rw.snapshot.Options().VerboseOutput)
    +	pkg2, info, err := reTypeCheck(logf, rw.pkg, map[protocol.DocumentURI]*ast.File{rw.pgf.URI: modifiedFile}, false)
    +	if err != nil {
    +		return nil, err
    +	}
    +	calleeInfo, err := inline.AnalyzeCallee(logf, rw.pkg.FileSet(), pkg2, info, modifiedDecl, modifiedSrc)
    +	if err != nil {
    +		return nil, fmt.Errorf("analyzing callee: %v", err)
    +	}
    +
    +	post := func(got []byte) []byte { return bytes.ReplaceAll(got, []byte(tag), nil) }
    +	return inlineAllCalls(ctx, logf, rw.snapshot, rw.pkg, rw.pgf, rw.origDecl, calleeInfo, post)
    +}
    +
    +// reTypeCheck re-type checks orig with new file contents defined by fileMask.
    +//
    +// It expects that any newly added imports are already present in the
    +// transitive imports of orig.
    +//
    +// If expectErrors is true, reTypeCheck allows errors in the new package.
    +// TODO(rfindley): perhaps this should be a filter to specify which errors are
    +// acceptable.
    +func reTypeCheck(logf func(string, ...any), orig *cache.Package, fileMask map[protocol.DocumentURI]*ast.File, expectErrors bool) (*types.Package, *types.Info, error) {
    +	pkg := types.NewPackage(string(orig.Metadata().PkgPath), string(orig.Metadata().Name))
    +	info := &types.Info{
    +		Types:      make(map[ast.Expr]types.TypeAndValue),
    +		Defs:       make(map[*ast.Ident]types.Object),
    +		Uses:       make(map[*ast.Ident]types.Object),
    +		Implicits:  make(map[ast.Node]types.Object),
    +		Selections: make(map[*ast.SelectorExpr]*types.Selection),
    +		Scopes:     make(map[ast.Node]*types.Scope),
    +		Instances:  make(map[*ast.Ident]types.Instance),
    +	}
    +	versions.InitFileVersions(info)
    +	{
    +		var files []*ast.File
    +		for _, pgf := range orig.CompiledGoFiles() {
    +			if mask, ok := fileMask[pgf.URI]; ok {
    +				files = append(files, mask)
    +			} else {
    +				files = append(files, pgf.File)
    +			}
    +		}
    +
    +		// Implement a BFS for imports in the transitive package graph.
    +		//
    +		// Note that this only works if any newly added imports are expected to be
    +		// present among transitive imports. In general we cannot assume this to
    +		// be the case, but in the special case of removing a parameter it works
    +		// because any parameter types must be present in export data.
    +		var importer func(importPath string) (*types.Package, error)
    +		{
    +			var (
    +				importsByPath = make(map[string]*types.Package) // cached imports
    +				toSearch      = []*types.Package{orig.Types()}  // packages to search
    +				searched      = make(map[string]bool)           // path -> (false, if present in toSearch; true, if already searched)
    +			)
    +			importer = func(path string) (*types.Package, error) {
    +				if p, ok := importsByPath[path]; ok {
    +					return p, nil
    +				}
    +				for len(toSearch) > 0 {
    +					pkg := toSearch[0]
    +					toSearch = toSearch[1:]
    +					searched[pkg.Path()] = true
    +					for _, p := range pkg.Imports() {
    +						// TODO(rfindley): this is incorrect: p.Path() is a package path,
    +						// whereas path is an import path. We can fix this by reporting any
    +						// newly added imports from inlining, or by using the ImporterFrom
    +						// interface and package metadata.
    +						//
    +						// TODO(rfindley): can't the inliner also be wrong here? It's
    +						// possible that an import path means different things depending on
    +						// the location.
    +						importsByPath[p.Path()] = p
    +						if _, ok := searched[p.Path()]; !ok {
    +							searched[p.Path()] = false
    +							toSearch = append(toSearch, p)
    +						}
    +					}
    +					if p, ok := importsByPath[path]; ok {
    +						return p, nil
    +					}
    +				}
    +				return nil, fmt.Errorf("missing import")
    +			}
    +		}
    +		cfg := &types.Config{
    +			Sizes:    orig.Metadata().TypesSizes,
    +			Importer: ImporterFunc(importer),
    +		}
    +
    +		// Copied from cache/check.go.
    +		// TODO(rfindley): factor this out and fix goVersionRx.
    +		// Set Go dialect.
    +		if module := orig.Metadata().Module; module != nil && module.GoVersion != "" {
    +			goVersion := "go" + module.GoVersion
    +			// types.NewChecker panics if GoVersion is invalid.
    +			// An unparsable mod file should probably stop us
    +			// before we get here, but double check just in case.
    +			if goVersionRx.MatchString(goVersion) {
    +				cfg.GoVersion = goVersion
    +			}
    +		}
    +		if expectErrors {
    +			cfg.Error = func(err error) {
    +				logf("re-type checking: expected error: %v", err)
    +			}
    +		}
    +		typesinternal.SetUsesCgo(cfg)
    +		checker := types.NewChecker(cfg, orig.FileSet(), pkg, info)
    +		if err := checker.Files(files); err != nil && !expectErrors {
    +			return nil, nil, fmt.Errorf("type checking rewritten package: %v", err)
    +		}
    +	}
    +	return pkg, info, nil
    +}
    +
    +// TODO(golang/go#63472): this looks wrong with the new Go version syntax.
    +var goVersionRx = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
    +
    +func remove[T any](s []T, i int) []T {
    +	return append(s[:i], s[i+1:]...)
    +}
    +
    +// replaceFileDecl replaces old with new in the file described by pgf.
    +//
    +// TODO(rfindley): generalize, and combine with rewriteSignature.
    +func replaceFileDecl(pgf *parsego.File, old, new ast.Decl) ([]byte, error) {
    +	i := findDecl(pgf.File, old)
    +	if i == -1 {
    +		return nil, bug.Errorf("didn't find old declaration")
    +	}
    +	start, end, err := safetoken.Offsets(pgf.Tok, old.Pos(), old.End())
    +	if err != nil {
    +		return nil, err
    +	}
    +	var out bytes.Buffer
    +	out.Write(pgf.Src[:start])
    +	fset := tokeninternal.FileSetFor(pgf.Tok)
    +	if err := format.Node(&out, fset, new); err != nil {
    +		return nil, bug.Errorf("formatting new node: %v", err)
    +	}
    +	out.Write(pgf.Src[end:])
    +	return out.Bytes(), nil
    +}
    +
    +// findDecl finds the index of decl in file.Decls.
    +//
    +// TODO: use slices.Index when it is available.
    +func findDecl(file *ast.File, decl ast.Decl) int {
    +	for i, d := range file.Decls {
    +		if d == decl {
    +			return i
    +		}
    +	}
    +	return -1
    +}
    diff --git a/contribs/gnopls/internal/golang/code_lens.go b/contribs/gnopls/internal/golang/code_lens.go
    new file mode 100644
    index 00000000000..f0a5500b57f
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/code_lens.go
    @@ -0,0 +1,216 @@
    +// 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 golang
    +
    +import (
    +	"context"
    +	"go/ast"
    +	"go/token"
    +	"go/types"
    +	"regexp"
    +	"strings"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/protocol/command"
    +	"golang.org/x/tools/gopls/internal/settings"
    +)
    +
    +// CodeLensSources returns the supported sources of code lenses for Go files.
    +func CodeLensSources() map[settings.CodeLensSource]cache.CodeLensSourceFunc {
    +	return map[settings.CodeLensSource]cache.CodeLensSourceFunc{
    +		settings.CodeLensGenerate:      goGenerateCodeLens,    // commands: Generate
    +		settings.CodeLensTest:          runTestCodeLens,       // commands: Test
    +		settings.CodeLensRegenerateCgo: regenerateCgoLens,     // commands: RegenerateCgo
    +		settings.CodeLensGCDetails:     toggleDetailsCodeLens, // commands: GCDetails
    +	}
    +}
    +
    +var (
    +	testRe      = regexp.MustCompile(`^Test([^a-z]|$)`) // TestFoo or Test but not Testable
    +	benchmarkRe = regexp.MustCompile(`^Benchmark([^a-z]|$)`)
    +)
    +
    +func runTestCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) {
    +	var codeLens []protocol.CodeLens
    +
    +	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil {
    +		return nil, err
    +	}
    +	testFuncs, benchFuncs, err := testsAndBenchmarks(pkg.TypesInfo(), pgf)
    +	if err != nil {
    +		return nil, err
    +	}
    +	puri := fh.URI()
    +	for _, fn := range testFuncs {
    +		cmd := command.NewTestCommand("run test", puri, []string{fn.name}, nil)
    +		rng := protocol.Range{Start: fn.rng.Start, End: fn.rng.Start}
    +		codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
    +	}
    +
    +	for _, fn := range benchFuncs {
    +		cmd := command.NewTestCommand("run benchmark", puri, nil, []string{fn.name})
    +		rng := protocol.Range{Start: fn.rng.Start, End: fn.rng.Start}
    +		codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
    +	}
    +
    +	if len(benchFuncs) > 0 {
    +		pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +		if err != nil {
    +			return nil, err
    +		}
    +		// add a code lens to the top of the file which runs all benchmarks in the file
    +		rng, err := pgf.PosRange(pgf.File.Package, pgf.File.Package)
    +		if err != nil {
    +			return nil, err
    +		}
    +		var benches []string
    +		for _, fn := range benchFuncs {
    +			benches = append(benches, fn.name)
    +		}
    +		cmd := command.NewTestCommand("run file benchmarks", puri, nil, benches)
    +		codeLens = append(codeLens, protocol.CodeLens{Range: rng, Command: cmd})
    +	}
    +	return codeLens, nil
    +}
    +
    +type testFunc struct {
    +	name string
    +	rng  protocol.Range // of *ast.FuncDecl
    +}
    +
    +// testsAndBenchmarks returns all Test and Benchmark functions in the
    +// specified file.
    +func testsAndBenchmarks(info *types.Info, pgf *parsego.File) (tests, benchmarks []testFunc, _ error) {
    +	if !strings.HasSuffix(pgf.URI.Path(), "_test.go") {
    +		return nil, nil, nil // empty
    +	}
    +
    +	for _, d := range pgf.File.Decls {
    +		fn, ok := d.(*ast.FuncDecl)
    +		if !ok {
    +			continue
    +		}
    +
    +		rng, err := pgf.NodeRange(fn)
    +		if err != nil {
    +			return nil, nil, err
    +		}
    +
    +		if matchTestFunc(fn, info, testRe, "T") {
    +			tests = append(tests, testFunc{fn.Name.Name, rng})
    +		} else if matchTestFunc(fn, info, benchmarkRe, "B") {
    +			benchmarks = append(benchmarks, testFunc{fn.Name.Name, rng})
    +		}
    +	}
    +	return
    +}
    +
    +func matchTestFunc(fn *ast.FuncDecl, info *types.Info, nameRe *regexp.Regexp, paramID string) bool {
    +	// Make sure that the function name matches a test function.
    +	if !nameRe.MatchString(fn.Name.Name) {
    +		return false
    +	}
    +	obj, ok := info.ObjectOf(fn.Name).(*types.Func)
    +	if !ok {
    +		return false
    +	}
    +	sig := obj.Signature()
    +	// Test functions should have only one parameter.
    +	if sig.Params().Len() != 1 {
    +		return false
    +	}
    +
    +	// Check the type of the only parameter
    +	// (We don't Unalias or use typesinternal.ReceiverNamed
    +	// in the two checks below because "go test" can't see
    +	// through aliases when enumerating Test* functions;
    +	// it's syntactic.)
    +	paramTyp, ok := sig.Params().At(0).Type().(*types.Pointer)
    +	if !ok {
    +		return false
    +	}
    +	named, ok := paramTyp.Elem().(*types.Named)
    +	if !ok {
    +		return false
    +	}
    +	namedObj := named.Obj()
    +	if namedObj.Pkg().Path() != "testing" {
    +		return false
    +	}
    +	return namedObj.Id() == paramID
    +}
    +
    +func goGenerateCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) {
    +	pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +	if err != nil {
    +		return nil, err
    +	}
    +	const ggDirective = "//go:generate"
    +	for _, c := range pgf.File.Comments {
    +		for _, l := range c.List {
    +			if !strings.HasPrefix(l.Text, ggDirective) {
    +				continue
    +			}
    +			rng, err := pgf.PosRange(l.Pos(), l.Pos()+token.Pos(len(ggDirective)))
    +			if err != nil {
    +				return nil, err
    +			}
    +			dir := fh.URI().Dir()
    +			nonRecursiveCmd := command.NewGenerateCommand("run go generate", command.GenerateArgs{Dir: dir, Recursive: false})
    +			recursiveCmd := command.NewGenerateCommand("run go generate ./...", command.GenerateArgs{Dir: dir, Recursive: true})
    +			return []protocol.CodeLens{
    +				{Range: rng, Command: recursiveCmd},
    +				{Range: rng, Command: nonRecursiveCmd},
    +			}, nil
    +
    +		}
    +	}
    +	return nil, nil
    +}
    +
    +func regenerateCgoLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) {
    +	pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +	if err != nil {
    +		return nil, err
    +	}
    +	var c *ast.ImportSpec
    +	for _, imp := range pgf.File.Imports {
    +		if imp.Path.Value == `"C"` {
    +			c = imp
    +		}
    +	}
    +	if c == nil {
    +		return nil, nil
    +	}
    +	rng, err := pgf.NodeRange(c)
    +	if err != nil {
    +		return nil, err
    +	}
    +	puri := fh.URI()
    +	cmd := command.NewRegenerateCgoCommand("regenerate cgo definitions", command.URIArg{URI: puri})
    +	return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
    +}
    +
    +func toggleDetailsCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) {
    +	pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +	if err != nil {
    +		return nil, err
    +	}
    +	if !pgf.File.Package.IsValid() {
    +		// Without a package name we have nowhere to put the codelens, so give up.
    +		return nil, nil
    +	}
    +	rng, err := pgf.PosRange(pgf.File.Package, pgf.File.Package)
    +	if err != nil {
    +		return nil, err
    +	}
    +	puri := fh.URI()
    +	cmd := command.NewGCDetailsCommand("Toggle gc annotation details", puri)
    +	return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil
    +}
    diff --git a/contribs/gnopls/internal/golang/codeaction.go b/contribs/gnopls/internal/golang/codeaction.go
    new file mode 100644
    index 00000000000..b245b96eec6
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/codeaction.go
    @@ -0,0 +1,655 @@
    +// 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 golang
    +
    +import (
    +	"context"
    +	"encoding/json"
    +	"fmt"
    +	"go/ast"
    +	"go/types"
    +	"slices"
    +	"strings"
    +
    +	"golang.org/x/tools/go/ast/astutil"
    +	"golang.org/x/tools/gopls/internal/analysis/fillstruct"
    +	"golang.org/x/tools/gopls/internal/analysis/fillswitch"
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/label"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/protocol/command"
    +	"golang.org/x/tools/gopls/internal/settings"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/internal/event"
    +	"golang.org/x/tools/internal/imports"
    +	"golang.org/x/tools/internal/typesinternal"
    +)
    +
    +// CodeActions returns all enabled code actions (edits and other
    +// commands) available for the selected range.
    +//
    +// Depending on how the request was triggered, fewer actions may be
    +// offered, e.g. to avoid UI distractions after mere cursor motion.
    +//
    +// See ../protocol/codeactionkind.go for some code action theory.
    +func CodeActions(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range, diagnostics []protocol.Diagnostic, enabled func(protocol.CodeActionKind) bool, trigger protocol.CodeActionTriggerKind) (actions []protocol.CodeAction, _ error) {
    +
    +	// code actions that require only a parse tree
    +
    +	pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +	if err != nil {
    +		return nil, err
    +	}
    +	start, end, err := pgf.RangePos(rng)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// add adds a code action to the result.
    +	add := func(cmd *protocol.Command, kind protocol.CodeActionKind) {
    +		action := newCodeAction(cmd.Title, kind, cmd, nil, snapshot.Options())
    +		actions = append(actions, action)
    +	}
    +
    +	// Note: don't forget to update the allow-list in Server.CodeAction
    +	// when adding new query operations like GoTest and GoDoc that
    +	// are permitted even in generated source files.
    +
    +	// Only compute quick fixes if there are any diagnostics to fix.
    +	wantQuickFixes := len(diagnostics) > 0 && enabled(protocol.QuickFix)
    +	if wantQuickFixes || enabled(protocol.SourceOrganizeImports) {
    +		// Process any missing imports and pair them with the diagnostics they fix.
    +		importEdits, importEditsPerFix, err := allImportsFixes(ctx, snapshot, pgf)
    +		if err != nil {
    +			event.Error(ctx, "imports fixes", err, label.File.Of(fh.URI().Path()))
    +			importEdits = nil
    +			importEditsPerFix = nil
    +		}
    +
    +		// Separate this into a set of codeActions per diagnostic, where
    +		// each action is the addition, removal, or renaming of one import.
    +		if wantQuickFixes {
    +			for _, importFix := range importEditsPerFix {
    +				fixed := fixedByImportFix(importFix.fix, diagnostics)
    +				if len(fixed) == 0 {
    +					continue
    +				}
    +				actions = append(actions, protocol.CodeAction{
    +					Title: importFixTitle(importFix.fix),
    +					Kind:  protocol.QuickFix,
    +					Edit: protocol.NewWorkspaceEdit(
    +						protocol.DocumentChangeEdit(fh, importFix.edits)),
    +					Diagnostics: fixed,
    +				})
    +			}
    +		}
    +
    +		// Send all of the import edits as one code action if the file is
    +		// being organized.
    +		if len(importEdits) > 0 && enabled(protocol.SourceOrganizeImports) {
    +			actions = append(actions, protocol.CodeAction{
    +				Title: "Organize Imports",
    +				Kind:  protocol.SourceOrganizeImports,
    +				Edit: protocol.NewWorkspaceEdit(
    +					protocol.DocumentChangeEdit(fh, importEdits)),
    +			})
    +		}
    +	}
    +
    +	// refactor.extract.*
    +	{
    +		extractions, err := getExtractCodeActions(enabled, pgf, rng, snapshot.Options())
    +		if err != nil {
    +			return nil, err
    +		}
    +		actions = append(actions, extractions...)
    +	}
    +
    +	if kind := settings.GoFreeSymbols; enabled(kind) && rng.End != rng.Start {
    +		loc := protocol.Location{URI: pgf.URI, Range: rng}
    +		cmd := command.NewFreeSymbolsCommand("Browse free symbols", snapshot.View().ID(), loc)
    +		// For implementation, see commandHandler.FreeSymbols.
    +		add(cmd, kind)
    +	}
    +
    +	if kind := settings.GoplsDocFeatures; enabled(kind) {
    +		// TODO(adonovan): after the docs are published in gopls/v0.17.0,
    +		// use the gopls release tag instead of master.
    +		cmd := command.NewClientOpenURLCommand(
    +			"Browse gopls feature documentation",
    +			"https://github.com/golang/tools/blob/master/gopls/doc/features/README.md")
    +		add(cmd, kind)
    +	}
    +
    +	// code actions that require type information
    +	//
    +	// In order to keep the fast path (in particular,
    +	// VS Code's request for just source.organizeImports
    +	// immediately after a save) fast, avoid type checking
    +	// if no enabled code actions need it.
    +	//
    +	// TODO(adonovan): design some kind of registration mechanism
    +	// that avoids the need to keep this list up to date.
    +	if !slices.ContainsFunc([]protocol.CodeActionKind{
    +		settings.RefactorRewriteRemoveUnusedParam,
    +		settings.RefactorRewriteChangeQuote,
    +		settings.RefactorRewriteInvertIf,
    +		settings.RefactorRewriteSplitLines,
    +		settings.RefactorRewriteJoinLines,
    +		settings.RefactorRewriteFillStruct,
    +		settings.RefactorRewriteFillSwitch,
    +		settings.RefactorInlineCall,
    +		settings.GoTest,
    +		settings.GoDoc,
    +		settings.GoAssembly,
    +	}, enabled) {
    +		return actions, nil
    +	}
    +
    +	// NB: update pgf, since it may not be a parse cache hit (esp. on 386).
    +	// And update start, end, since they may have changed too.
    +	// A framework would really make this cleaner.
    +	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil {
    +		return nil, err
    +	}
    +	start, end, err = pgf.RangePos(rng)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// refactor.rewrite.*
    +	{
    +		rewrites, err := getRewriteCodeActions(enabled, ctx, pkg, snapshot, pgf, fh, rng, snapshot.Options())
    +		if err != nil {
    +			return nil, err
    +		}
    +		actions = append(actions, rewrites...)
    +	}
    +
    +	// To avoid distraction (e.g. VS Code lightbulb), offer "inline"
    +	// only after a selection or explicit menu operation.
    +	if trigger != protocol.CodeActionAutomatic || rng.Start != rng.End {
    +		rewrites, err := getInlineCodeActions(enabled, pkg, pgf, rng, snapshot.Options())
    +		if err != nil {
    +			return nil, err
    +		}
    +		actions = append(actions, rewrites...)
    +	}
    +
    +	if enabled(settings.GoTest) {
    +		fixes, err := getGoTestCodeActions(pkg, pgf, rng)
    +		if err != nil {
    +			return nil, err
    +		}
    +		actions = append(actions, fixes...)
    +	}
    +
    +	if kind := settings.GoDoc; enabled(kind) {
    +		// "Browse documentation for ..."
    +		_, _, title := DocFragment(pkg, pgf, start, end)
    +		loc := protocol.Location{URI: pgf.URI, Range: rng}
    +		cmd := command.NewDocCommand(title, loc)
    +		add(cmd, kind)
    +	}
    +
    +	if enabled(settings.GoAssembly) {
    +		fixes, err := getGoAssemblyAction(snapshot.View(), pkg, pgf, rng)
    +		if err != nil {
    +			return nil, err
    +		}
    +		actions = append(actions, fixes...)
    +	}
    +	return actions, nil
    +}
    +
    +func supportsResolveEdits(options *settings.Options) bool {
    +	return options.CodeActionResolveOptions != nil && slices.Contains(options.CodeActionResolveOptions, "edit")
    +}
    +
    +func importFixTitle(fix *imports.ImportFix) string {
    +	var str string
    +	switch fix.FixType {
    +	case imports.AddImport:
    +		str = fmt.Sprintf("Add import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
    +	case imports.DeleteImport:
    +		str = fmt.Sprintf("Delete import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
    +	case imports.SetImportName:
    +		str = fmt.Sprintf("Rename import: %s %q", fix.StmtInfo.Name, fix.StmtInfo.ImportPath)
    +	}
    +	return str
    +}
    +
    +// fixedByImportFix filters the provided slice of diagnostics to those that
    +// would be fixed by the provided imports fix.
    +func fixedByImportFix(fix *imports.ImportFix, diagnostics []protocol.Diagnostic) []protocol.Diagnostic {
    +	var results []protocol.Diagnostic
    +	for _, diagnostic := range diagnostics {
    +		switch {
    +		// "undeclared name: X" may be an unresolved import.
    +		case strings.HasPrefix(diagnostic.Message, "undeclared name: "):
    +			ident := strings.TrimPrefix(diagnostic.Message, "undeclared name: ")
    +			if ident == fix.IdentName {
    +				results = append(results, diagnostic)
    +			}
    +		// "undefined: X" may be an unresolved import at Go 1.20+.
    +		case strings.HasPrefix(diagnostic.Message, "undefined: "):
    +			ident := strings.TrimPrefix(diagnostic.Message, "undefined: ")
    +			if ident == fix.IdentName {
    +				results = append(results, diagnostic)
    +			}
    +		// "could not import: X" may be an invalid import.
    +		case strings.HasPrefix(diagnostic.Message, "could not import: "):
    +			ident := strings.TrimPrefix(diagnostic.Message, "could not import: ")
    +			if ident == fix.IdentName {
    +				results = append(results, diagnostic)
    +			}
    +		// "X imported but not used" is an unused import.
    +		// "X imported but not used as Y" is an unused import.
    +		case strings.Contains(diagnostic.Message, " imported but not used"):
    +			idx := strings.Index(diagnostic.Message, " imported but not used")
    +			importPath := diagnostic.Message[:idx]
    +			if importPath == fmt.Sprintf("%q", fix.StmtInfo.ImportPath) {
    +				results = append(results, diagnostic)
    +			}
    +		}
    +	}
    +	return results
    +}
    +
    +// getExtractCodeActions returns any refactor.extract code actions for the selection.
    +func getExtractCodeActions(enabled func(protocol.CodeActionKind) bool, pgf *parsego.File, rng protocol.Range, options *settings.Options) ([]protocol.CodeAction, error) {
    +	start, end, err := pgf.RangePos(rng)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	var actions []protocol.CodeAction
    +	add := func(cmd *protocol.Command, kind protocol.CodeActionKind) {
    +		action := newCodeAction(cmd.Title, kind, cmd, nil, options)
    +		actions = append(actions, action)
    +	}
    +
    +	// extract function or method
    +	if enabled(settings.RefactorExtractFunction) || enabled(settings.RefactorExtractMethod) {
    +		if _, ok, methodOk, _ := canExtractFunction(pgf.Tok, start, end, pgf.Src, pgf.File); ok {
    +			// extract function
    +			if kind := settings.RefactorExtractFunction; enabled(kind) {
    +				cmd := command.NewApplyFixCommand("Extract function", command.ApplyFixArgs{
    +					Fix:          fixExtractFunction,
    +					URI:          pgf.URI,
    +					Range:        rng,
    +					ResolveEdits: supportsResolveEdits(options),
    +				})
    +				add(cmd, kind)
    +			}
    +
    +			// extract method
    +			if kind := settings.RefactorExtractMethod; methodOk && enabled(kind) {
    +				cmd := command.NewApplyFixCommand("Extract method", command.ApplyFixArgs{
    +					Fix:          fixExtractMethod,
    +					URI:          pgf.URI,
    +					Range:        rng,
    +					ResolveEdits: supportsResolveEdits(options),
    +				})
    +				add(cmd, kind)
    +			}
    +		}
    +	}
    +
    +	// extract variable
    +	if kind := settings.RefactorExtractVariable; enabled(kind) {
    +		if _, _, ok, _ := canExtractVariable(start, end, pgf.File); ok {
    +			cmd := command.NewApplyFixCommand("Extract variable", command.ApplyFixArgs{
    +				Fix:          fixExtractVariable,
    +				URI:          pgf.URI,
    +				Range:        rng,
    +				ResolveEdits: supportsResolveEdits(options),
    +			})
    +			add(cmd, kind)
    +		}
    +	}
    +
    +	// extract to new file
    +	if kind := settings.RefactorExtractToNewFile; enabled(kind) {
    +		if canExtractToNewFile(pgf, start, end) {
    +			cmd := command.NewExtractToNewFileCommand(
    +				"Extract declarations to new file",
    +				protocol.Location{URI: pgf.URI, Range: rng},
    +			)
    +			add(cmd, kind)
    +		}
    +	}
    +
    +	return actions, nil
    +}
    +
    +func newCodeAction(title string, kind protocol.CodeActionKind, cmd *protocol.Command, diagnostics []protocol.Diagnostic, options *settings.Options) protocol.CodeAction {
    +	action := protocol.CodeAction{
    +		Title:       title,
    +		Kind:        kind,
    +		Diagnostics: diagnostics,
    +	}
    +	if !supportsResolveEdits(options) {
    +		action.Command = cmd
    +	} else {
    +		data, err := json.Marshal(cmd)
    +		if err != nil {
    +			panic("unable to marshal")
    +		}
    +		msg := json.RawMessage(data)
    +		action.Data = &msg
    +	}
    +	return action
    +}
    +
    +func getRewriteCodeActions(enabled func(protocol.CodeActionKind) bool, ctx context.Context, pkg *cache.Package, snapshot *cache.Snapshot, pgf *parsego.File, fh file.Handle, rng protocol.Range, options *settings.Options) (_ []protocol.CodeAction, rerr error) {
    +	// golang/go#61693: code actions were refactored to run outside of the
    +	// analysis framework, but as a result they lost their panic recovery.
    +	//
    +	// These code actions should never fail, but put back the panic recovery as a
    +	// defensive measure.
    +	defer func() {
    +		if r := recover(); r != nil {
    +			rerr = bug.Errorf("refactor.rewrite code actions panicked: %v", r)
    +		}
    +	}()
    +
    +	var actions []protocol.CodeAction
    +	add := func(cmd *protocol.Command, kind protocol.CodeActionKind) {
    +		action := newCodeAction(cmd.Title, kind, cmd, nil, options)
    +		actions = append(actions, action)
    +	}
    +
    +	// remove unused param
    +	if kind := settings.RefactorRewriteRemoveUnusedParam; enabled(kind) && canRemoveParameter(pkg, pgf, rng) {
    +		cmd := command.NewChangeSignatureCommand("Refactor: remove unused parameter", command.ChangeSignatureArgs{
    +			RemoveParameter: protocol.Location{
    +				URI:   pgf.URI,
    +				Range: rng,
    +			},
    +			ResolveEdits: supportsResolveEdits(options),
    +		})
    +		add(cmd, kind)
    +	}
    +
    +	start, end, err := pgf.RangePos(rng)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// change quote
    +	if enabled(settings.RefactorRewriteChangeQuote) {
    +		if action, ok := convertStringLiteral(pgf, fh, start, end); ok {
    +			actions = append(actions, action)
    +		}
    +	}
    +
    +	// invert if condition
    +	if kind := settings.RefactorRewriteInvertIf; enabled(kind) {
    +		if _, ok, _ := canInvertIfCondition(pgf.File, start, end); ok {
    +			cmd := command.NewApplyFixCommand("Invert 'if' condition", command.ApplyFixArgs{
    +				Fix:          fixInvertIfCondition,
    +				URI:          pgf.URI,
    +				Range:        rng,
    +				ResolveEdits: supportsResolveEdits(options),
    +			})
    +			add(cmd, kind)
    +		}
    +	}
    +
    +	// split lines
    +	if kind := settings.RefactorRewriteSplitLines; enabled(kind) {
    +		if msg, ok, _ := canSplitLines(pgf.File, pkg.FileSet(), start, end); ok {
    +			cmd := command.NewApplyFixCommand(msg, command.ApplyFixArgs{
    +				Fix:          fixSplitLines,
    +				URI:          pgf.URI,
    +				Range:        rng,
    +				ResolveEdits: supportsResolveEdits(options),
    +			})
    +			add(cmd, kind)
    +		}
    +	}
    +
    +	// join lines
    +	if kind := settings.RefactorRewriteJoinLines; enabled(kind) {
    +		if msg, ok, _ := canJoinLines(pgf.File, pkg.FileSet(), start, end); ok {
    +			cmd := command.NewApplyFixCommand(msg, command.ApplyFixArgs{
    +				Fix:          fixJoinLines,
    +				URI:          pgf.URI,
    +				Range:        rng,
    +				ResolveEdits: supportsResolveEdits(options),
    +			})
    +			add(cmd, kind)
    +		}
    +	}
    +
    +	// fill struct
    +	//
    +	// fillstruct.Diagnose is a lazy analyzer: all it gives us is
    +	// the (start, end, message) of each SuggestedFix; the actual
    +	// edit is computed only later by ApplyFix, which calls fillstruct.SuggestedFix.
    +	if kind := settings.RefactorRewriteFillStruct; enabled(kind) {
    +		for _, diag := range fillstruct.Diagnose(pgf.File, start, end, pkg.Types(), pkg.TypesInfo()) {
    +			rng, err := pgf.Mapper.PosRange(pgf.Tok, diag.Pos, diag.End)
    +			if err != nil {
    +				return nil, err
    +			}
    +			for _, fix := range diag.SuggestedFixes {
    +				cmd := command.NewApplyFixCommand(fix.Message, command.ApplyFixArgs{
    +					Fix:          diag.Category,
    +					URI:          pgf.URI,
    +					Range:        rng,
    +					ResolveEdits: supportsResolveEdits(options),
    +				})
    +				add(cmd, kind)
    +			}
    +		}
    +	}
    +
    +	// fill switch
    +	if kind := settings.RefactorRewriteFillSwitch; enabled(kind) {
    +		for _, diag := range fillswitch.Diagnose(pgf.File, start, end, pkg.Types(), pkg.TypesInfo()) {
    +			changes, err := suggestedFixToDocumentChange(ctx, snapshot, pkg.FileSet(), &diag.SuggestedFixes[0])
    +			if err != nil {
    +				return nil, err
    +			}
    +			actions = append(actions, protocol.CodeAction{
    +				Title: diag.Message,
    +				Kind:  kind,
    +				Edit:  protocol.NewWorkspaceEdit(changes...),
    +			})
    +		}
    +	}
    +
    +	return actions, nil
    +}
    +
    +// canRemoveParameter reports whether we can remove the function parameter
    +// indicated by the given [start, end) range.
    +//
    +// This is true if:
    +//   - there are no parse or type errors, and
    +//   - [start, end) is contained within an unused field or parameter name
    +//   - ... of a non-method function declaration.
    +//
    +// (Note that the unusedparam analyzer also computes this property, but
    +// much more precisely, allowing it to report its findings as diagnostics.)
    +func canRemoveParameter(pkg *cache.Package, pgf *parsego.File, rng protocol.Range) bool {
    +	if perrors, terrors := pkg.ParseErrors(), pkg.TypeErrors(); len(perrors) > 0 || len(terrors) > 0 {
    +		return false // can't remove parameters from packages with errors
    +	}
    +	info, err := findParam(pgf, rng)
    +	if err != nil {
    +		return false // e.g. invalid range
    +	}
    +	if info.field == nil {
    +		return false // range does not span a parameter
    +	}
    +	if info.decl.Body == nil {
    +		return false // external function
    +	}
    +	if len(info.field.Names) == 0 {
    +		return true // no names => field is unused
    +	}
    +	if info.name == nil {
    +		return false // no name is indicated
    +	}
    +	if info.name.Name == "_" {
    +		return true // trivially unused
    +	}
    +
    +	obj := pkg.TypesInfo().Defs[info.name]
    +	if obj == nil {
    +		return false // something went wrong
    +	}
    +
    +	used := false
    +	ast.Inspect(info.decl.Body, func(node ast.Node) bool {
    +		if n, ok := node.(*ast.Ident); ok && pkg.TypesInfo().Uses[n] == obj {
    +			used = true
    +		}
    +		return !used // keep going until we find a use
    +	})
    +	return !used
    +}
    +
    +// getInlineCodeActions returns refactor.inline actions available at the specified range.
    +func getInlineCodeActions(enabled func(protocol.CodeActionKind) bool, pkg *cache.Package, pgf *parsego.File, rng protocol.Range, options *settings.Options) ([]protocol.CodeAction, error) {
    +	var actions []protocol.CodeAction
    +	add := func(cmd *protocol.Command, kind protocol.CodeActionKind) {
    +		action := newCodeAction(cmd.Title, kind, cmd, nil, options)
    +		actions = append(actions, action)
    +	}
    +
    +	// inline call
    +	if kind := settings.RefactorInlineCall; enabled(kind) {
    +		start, end, err := pgf.RangePos(rng)
    +		if err != nil {
    +			return nil, err
    +		}
    +
    +		// If range is within call expression, offer to inline the call.
    +		if _, fn, err := enclosingStaticCall(pkg, pgf, start, end); err == nil {
    +			cmd := command.NewApplyFixCommand(fmt.Sprintf("Inline call to %s", fn.Name()), command.ApplyFixArgs{
    +				Fix:          fixInlineCall,
    +				URI:          pgf.URI,
    +				Range:        rng,
    +				ResolveEdits: supportsResolveEdits(options),
    +			})
    +			add(cmd, kind)
    +		}
    +	}
    +
    +	return actions, nil
    +}
    +
    +// getGoTestCodeActions returns any "run this test/benchmark" code actions for the selection.
    +func getGoTestCodeActions(pkg *cache.Package, pgf *parsego.File, rng protocol.Range) ([]protocol.CodeAction, error) {
    +	testFuncs, benchFuncs, err := testsAndBenchmarks(pkg.TypesInfo(), pgf)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	var tests, benchmarks []string
    +	for _, fn := range testFuncs {
    +		if protocol.Intersect(fn.rng, rng) {
    +			tests = append(tests, fn.name)
    +		}
    +	}
    +	for _, fn := range benchFuncs {
    +		if protocol.Intersect(fn.rng, rng) {
    +			benchmarks = append(benchmarks, fn.name)
    +		}
    +	}
    +
    +	if len(tests) == 0 && len(benchmarks) == 0 {
    +		return nil, nil
    +	}
    +
    +	cmd := command.NewTestCommand("Run tests and benchmarks", pgf.URI, tests, benchmarks)
    +	return []protocol.CodeAction{{
    +		Title:   cmd.Title,
    +		Kind:    settings.GoTest,
    +		Command: cmd,
    +	}}, nil
    +}
    +
    +// getGoAssemblyAction returns any "Browse assembly for f" code actions for the selection.
    +func getGoAssemblyAction(view *cache.View, pkg *cache.Package, pgf *parsego.File, rng protocol.Range) ([]protocol.CodeAction, error) {
    +	start, end, err := pgf.RangePos(rng)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// Find the enclosing toplevel function or method,
    +	// and compute its symbol name (e.g. "pkgpath.(T).method").
    +	// The report will show this method and all its nested
    +	// functions (FuncLit, defers, etc).
    +	//
    +	// TODO(adonovan): this is no good for generics, since they
    +	// will always be uninstantiated when they enclose the cursor.
    +	// Instead, we need to query the func symbol under the cursor,
    +	// rather than the enclosing function. It may be an explicitly
    +	// or implicitly instantiated generic, and it may be defined
    +	// in another package, though we would still need to compile
    +	// the current package to see its assembly. The challenge,
    +	// however, is that computing the linker name for a generic
    +	// symbol is quite tricky. Talk with the compiler team for
    +	// ideas.
    +	//
    +	// TODO(adonovan): think about a smoother UX for jumping
    +	// directly to (say) a lambda of interest.
    +	// Perhaps we could scroll to STEXT for the innermost
    +	// enclosing nested function?
    +	var actions []protocol.CodeAction
    +	path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
    +	if len(path) >= 2 { // [... FuncDecl File]
    +		if decl, ok := path[len(path)-2].(*ast.FuncDecl); ok {
    +			if fn, ok := pkg.TypesInfo().Defs[decl.Name].(*types.Func); ok {
    +				sig := fn.Signature()
    +
    +				// Compute the linker symbol of the enclosing function.
    +				var sym strings.Builder
    +				if fn.Pkg().Name() == "main" {
    +					sym.WriteString("main")
    +				} else {
    +					sym.WriteString(fn.Pkg().Path())
    +				}
    +				sym.WriteString(".")
    +				if sig.Recv() != nil {
    +					if isPtr, named := typesinternal.ReceiverNamed(sig.Recv()); named != nil {
    +						sym.WriteString("(")
    +						if isPtr {
    +							sym.WriteString("*")
    +						}
    +						sym.WriteString(named.Obj().Name())
    +						sym.WriteString(").")
    +					}
    +				}
    +				sym.WriteString(fn.Name())
    +
    +				if fn.Name() != "_" && // blank functions are not compiled
    +					(fn.Name() != "init" || sig.Recv() != nil) && // init functions aren't linker functions
    +					sig.TypeParams() == nil && sig.RecvTypeParams() == nil { // generic => no assembly
    +					cmd := command.NewAssemblyCommand(
    +						fmt.Sprintf("Browse %s assembly for %s", view.GOARCH(), decl.Name),
    +						view.ID(),
    +						string(pkg.Metadata().ID),
    +						sym.String())
    +					// For handler, see commandHandler.Assembly.
    +					actions = append(actions, protocol.CodeAction{
    +						Title:   cmd.Title,
    +						Kind:    settings.GoAssembly,
    +						Command: cmd,
    +					})
    +				}
    +			}
    +		}
    +	}
    +	return actions, nil
    +}
    diff --git a/contribs/gnopls/internal/golang/comment.go b/contribs/gnopls/internal/golang/comment.go
    new file mode 100644
    index 00000000000..3a0d8153665
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/comment.go
    @@ -0,0 +1,201 @@
    +// 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 golang
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +	"go/ast"
    +	"go/doc/comment"
    +	"go/token"
    +	"go/types"
    +	"strings"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/settings"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +)
    +
    +var errNoCommentReference = errors.New("no comment reference found")
    +
    +// CommentToMarkdown converts comment text to formatted markdown.
    +// The comment was prepared by DocReader,
    +// so it is known not to have leading, trailing blank lines
    +// nor to have trailing spaces at the end of lines.
    +// The comment markers have already been removed.
    +func CommentToMarkdown(text string, options *settings.Options) string {
    +	var p comment.Parser
    +	doc := p.Parse(text)
    +	var pr comment.Printer
    +	// The default produces {#Hdr-...} tags for headings.
    +	// vscode displays thems, which is undesirable.
    +	// The godoc for comment.Printer says the tags
    +	// avoid a security problem.
    +	pr.HeadingID = func(*comment.Heading) string { return "" }
    +	pr.DocLinkURL = func(link *comment.DocLink) string {
    +		msg := fmt.Sprintf("https://%s/%s", options.LinkTarget, link.ImportPath)
    +		if link.Name != "" {
    +			msg += "#"
    +			if link.Recv != "" {
    +				msg += link.Recv + "."
    +			}
    +			msg += link.Name
    +		}
    +		return msg
    +	}
    +	easy := pr.Markdown(doc)
    +	return string(easy)
    +}
    +
    +// docLinkDefinition finds the definition of the doc link in comments at pos.
    +// If there is no reference at pos, returns errNoCommentReference.
    +func docLinkDefinition(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, pos token.Pos) ([]protocol.Location, error) {
    +	obj, _, err := parseDocLink(pkg, pgf, pos)
    +	if err != nil {
    +		return nil, err
    +	}
    +	loc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), adjustedObjEnd(obj))
    +	if err != nil {
    +		return nil, err
    +	}
    +	return []protocol.Location{loc}, nil
    +}
    +
    +// parseDocLink parses a doc link in a comment such as [fmt.Println]
    +// and returns the symbol at pos, along with the link's start position.
    +func parseDocLink(pkg *cache.Package, pgf *parsego.File, pos token.Pos) (types.Object, protocol.Range, error) {
    +	var comment *ast.Comment
    +	for _, cg := range pgf.File.Comments {
    +		for _, c := range cg.List {
    +			if c.Pos() <= pos && pos <= c.End() {
    +				comment = c
    +				break
    +			}
    +		}
    +		if comment != nil {
    +			break
    +		}
    +	}
    +	if comment == nil {
    +		return nil, protocol.Range{}, errNoCommentReference
    +	}
    +
    +	// The canonical parsing algorithm is defined by go/doc/comment, but
    +	// unfortunately its API provides no way to reliably reconstruct the
    +	// position of each doc link from the parsed result.
    +	line := safetoken.Line(pgf.Tok, pos)
    +	var start, end token.Pos
    +	if pgf.Tok.LineStart(line) > comment.Pos() {
    +		start = pgf.Tok.LineStart(line)
    +	} else {
    +		start = comment.Pos()
    +	}
    +	if line < pgf.Tok.LineCount() && pgf.Tok.LineStart(line+1) < comment.End() {
    +		end = pgf.Tok.LineStart(line + 1)
    +	} else {
    +		end = comment.End()
    +	}
    +
    +	offsetStart, offsetEnd, err := safetoken.Offsets(pgf.Tok, start, end)
    +	if err != nil {
    +		return nil, protocol.Range{}, err
    +	}
    +
    +	text := string(pgf.Src[offsetStart:offsetEnd])
    +	lineOffset := int(pos - start)
    +
    +	for _, idx := range docLinkRegex.FindAllStringSubmatchIndex(text, -1) {
    +		// The [idx[2], idx[3]) identifies the first submatch,
    +		// which is the reference name in the doc link (sans '*').
    +		// e.g. The "[fmt.Println]" reference name is "fmt.Println".
    +		if !(idx[2] <= lineOffset && lineOffset < idx[3]) {
    +			continue
    +		}
    +		p := lineOffset - idx[2]
    +		name := text[idx[2]:idx[3]]
    +		i := strings.LastIndexByte(name, '.')
    +		for i != -1 {
    +			if p > i {
    +				break
    +			}
    +			name = name[:i]
    +			i = strings.LastIndexByte(name, '.')
    +		}
    +		obj := lookupDocLinkSymbol(pkg, pgf, name)
    +		if obj == nil {
    +			return nil, protocol.Range{}, errNoCommentReference
    +		}
    +		namePos := start + token.Pos(idx[2]+i+1)
    +		rng, err := pgf.PosRange(namePos, namePos+token.Pos(len(obj.Name())))
    +		if err != nil {
    +			return nil, protocol.Range{}, err
    +		}
    +		return obj, rng, nil
    +	}
    +
    +	return nil, protocol.Range{}, errNoCommentReference
    +}
    +
    +// lookupDocLinkSymbol returns the symbol denoted by a doc link such
    +// as "fmt.Println" or "bytes.Buffer.Write" in the specified file.
    +func lookupDocLinkSymbol(pkg *cache.Package, pgf *parsego.File, name string) types.Object {
    +	scope := pkg.Types().Scope()
    +
    +	prefix, suffix, _ := strings.Cut(name, ".")
    +
    +	// Try treating the prefix as a package name,
    +	// allowing for non-renaming and renaming imports.
    +	fileScope := pkg.TypesInfo().Scopes[pgf.File]
    +	pkgname, ok := fileScope.Lookup(prefix).(*types.PkgName) // ok => prefix is imported name
    +	if !ok {
    +		// Handle renaming import, e.g.
    +		// [path.Join] after import pathpkg "path".
    +		// (Should we look at all files of the package?)
    +		for _, imp := range pgf.File.Imports {
    +			pkgname2 := pkg.TypesInfo().PkgNameOf(imp)
    +			if pkgname2 != nil && pkgname2.Imported().Name() == prefix {
    +				pkgname = pkgname2
    +				break
    +			}
    +		}
    +	}
    +	if pkgname != nil {
    +		scope = pkgname.Imported().Scope()
    +		if suffix == "" {
    +			return pkgname // not really a valid doc link
    +		}
    +		name = suffix
    +	}
    +
    +	// TODO(adonovan): try searching the forward closure for packages
    +	// that define the symbol but are not directly imported;
    +	// see https://github.com/golang/go/issues/61677
    +
    +	// Type.Method?
    +	recv, method, ok := strings.Cut(name, ".")
    +	if ok {
    +		obj, ok := scope.Lookup(recv).(*types.TypeName)
    +		if !ok {
    +			return nil
    +		}
    +		t, ok := obj.Type().(*types.Named)
    +		if !ok {
    +			return nil
    +		}
    +		for i := 0; i < t.NumMethods(); i++ {
    +			m := t.Method(i)
    +			if m.Name() == method {
    +				return m
    +			}
    +		}
    +		return nil
    +	}
    +
    +	// package-level symbol
    +	return scope.Lookup(name)
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/builtin.go b/contribs/gnopls/internal/golang/completion/builtin.go
    new file mode 100644
    index 00000000000..68f773e09ae
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/builtin.go
    @@ -0,0 +1,147 @@
    +// 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 completion
    +
    +import (
    +	"context"
    +	"go/ast"
    +	"go/types"
    +)
    +
    +// builtinArgKind determines the expected object kind for a builtin
    +// argument. It attempts to use the AST hints from builtin.go where
    +// possible.
    +func (c *completer) builtinArgKind(ctx context.Context, obj types.Object, call *ast.CallExpr) objKind {
    +	builtin, err := c.snapshot.BuiltinFile(ctx)
    +	if err != nil {
    +		return 0
    +	}
    +	exprIdx := exprAtPos(c.pos, call.Args)
    +
    +	builtinObj := builtin.File.Scope.Lookup(obj.Name())
    +	if builtinObj == nil {
    +		return 0
    +	}
    +	decl, ok := builtinObj.Decl.(*ast.FuncDecl)
    +	if !ok || exprIdx >= len(decl.Type.Params.List) {
    +		return 0
    +	}
    +
    +	switch ptyp := decl.Type.Params.List[exprIdx].Type.(type) {
    +	case *ast.ChanType:
    +		return kindChan
    +	case *ast.ArrayType:
    +		return kindSlice
    +	case *ast.MapType:
    +		return kindMap
    +	case *ast.Ident:
    +		switch ptyp.Name {
    +		case "Type":
    +			switch obj.Name() {
    +			case "make":
    +				return kindChan | kindSlice | kindMap
    +			case "len":
    +				return kindSlice | kindMap | kindArray | kindString | kindChan
    +			case "cap":
    +				return kindSlice | kindArray | kindChan
    +			}
    +		}
    +	}
    +
    +	return 0
    +}
    +
    +// builtinArgType infers the type of an argument to a builtin
    +// function. parentInf is the inferred type info for the builtin
    +// call's parent node.
    +func (c *completer) builtinArgType(obj types.Object, call *ast.CallExpr, parentInf candidateInference) candidateInference {
    +	var (
    +		exprIdx = exprAtPos(c.pos, call.Args)
    +
    +		// Propagate certain properties from our parent's inference.
    +		inf = candidateInference{
    +			typeName:  parentInf.typeName,
    +			modifiers: parentInf.modifiers,
    +		}
    +	)
    +
    +	switch obj.Name() {
    +	case "append":
    +		if exprIdx <= 0 {
    +			// Infer first append() arg type as apparent return type of
    +			// append().
    +			inf.objType = parentInf.objType
    +			if parentInf.variadic {
    +				inf.objType = types.NewSlice(inf.objType)
    +			}
    +			break
    +		}
    +
    +		// For non-initial append() args, infer slice type from the first
    +		// append() arg, or from parent context.
    +		if len(call.Args) > 0 {
    +			inf.objType = c.pkg.TypesInfo().TypeOf(call.Args[0])
    +		}
    +		if inf.objType == nil {
    +			inf.objType = parentInf.objType
    +		}
    +		if inf.objType == nil {
    +			break
    +		}
    +
    +		inf.objType = deslice(inf.objType)
    +
    +		// Check if we are completing the variadic append() param.
    +		inf.variadic = exprIdx == 1 && len(call.Args) <= 2
    +
    +		// Penalize the first append() argument as a candidate. You
    +		// don't normally append a slice to itself.
    +		if sliceChain := objChain(c.pkg.TypesInfo(), call.Args[0]); len(sliceChain) > 0 {
    +			inf.penalized = append(inf.penalized, penalizedObj{objChain: sliceChain, penalty: 0.9})
    +		}
    +	case "delete":
    +		if exprIdx > 0 && len(call.Args) > 0 {
    +			// Try to fill in expected type of map key.
    +			firstArgType := c.pkg.TypesInfo().TypeOf(call.Args[0])
    +			if firstArgType != nil {
    +				if mt, ok := firstArgType.Underlying().(*types.Map); ok {
    +					inf.objType = mt.Key()
    +				}
    +			}
    +		}
    +	case "copy":
    +		var t1, t2 types.Type
    +		if len(call.Args) > 0 {
    +			t1 = c.pkg.TypesInfo().TypeOf(call.Args[0])
    +			if len(call.Args) > 1 {
    +				t2 = c.pkg.TypesInfo().TypeOf(call.Args[1])
    +			}
    +		}
    +
    +		// Fill in expected type of either arg if the other is already present.
    +		if exprIdx == 1 && t1 != nil {
    +			inf.objType = t1
    +		} else if exprIdx == 0 && t2 != nil {
    +			inf.objType = t2
    +		}
    +	case "new":
    +		inf.typeName.wantTypeName = true
    +		if parentInf.objType != nil {
    +			// Expected type for "new" is the de-pointered parent type.
    +			if ptr, ok := parentInf.objType.Underlying().(*types.Pointer); ok {
    +				inf.objType = ptr.Elem()
    +			}
    +		}
    +	case "make":
    +		if exprIdx == 0 {
    +			inf.typeName.wantTypeName = true
    +			inf.objType = parentInf.objType
    +		} else {
    +			inf.objType = types.Typ[types.UntypedInt]
    +		}
    +	}
    +
    +	return inf
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/completion.go b/contribs/gnopls/internal/golang/completion/completion.go
    new file mode 100644
    index 00000000000..3fdc2a8c62a
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/completion.go
    @@ -0,0 +1,3385 @@
    +// Copyright 2018 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 completion provides core functionality for code completion in Go
    +// editors and tools.
    +package completion
    +
    +import (
    +	"context"
    +	"fmt"
    +	"go/ast"
    +	"go/build"
    +	"go/constant"
    +	"go/parser"
    +	"go/printer"
    +	"go/scanner"
    +	"go/token"
    +	"go/types"
    +	"math"
    +	"slices"
    +	"sort"
    +	"strconv"
    +	"strings"
    +	"sync"
    +	"sync/atomic"
    +	"time"
    +	"unicode"
    +
    +	"golang.org/x/sync/errgroup"
    +	"golang.org/x/tools/go/ast/astutil"
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/metadata"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/fuzzy"
    +	"golang.org/x/tools/gopls/internal/golang"
    +	"golang.org/x/tools/gopls/internal/golang/completion/snippet"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/settings"
    +	goplsastutil "golang.org/x/tools/gopls/internal/util/astutil"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/gopls/internal/util/typesutil"
    +	"golang.org/x/tools/internal/aliases"
    +	"golang.org/x/tools/internal/event"
    +	"golang.org/x/tools/internal/imports"
    +	"golang.org/x/tools/internal/stdlib"
    +	"golang.org/x/tools/internal/typeparams"
    +	"golang.org/x/tools/internal/typesinternal"
    +	"golang.org/x/tools/internal/versions"
    +)
    +
    +// A CompletionItem represents a possible completion suggested by the algorithm.
    +type CompletionItem struct {
    +
    +	// Invariant: CompletionItem does not refer to syntax or types.
    +
    +	// Label is the primary text the user sees for this completion item.
    +	Label string
    +
    +	// Detail is supplemental information to present to the user.
    +	// This often contains the type or return type of the completion item.
    +	Detail string
    +
    +	// InsertText is the text to insert if this item is selected.
    +	// Any of the prefix that has already been typed is not trimmed.
    +	// The insert text does not contain snippets.
    +	InsertText string
    +
    +	Kind       protocol.CompletionItemKind
    +	Tags       []protocol.CompletionItemTag
    +	Deprecated bool // Deprecated, prefer Tags if available
    +
    +	// An optional array of additional TextEdits that are applied when
    +	// selecting this completion.
    +	//
    +	// Additional text edits should be used to change text unrelated to the current cursor position
    +	// (for example adding an import statement at the top of the file if the completion item will
    +	// insert an unqualified type).
    +	AdditionalTextEdits []protocol.TextEdit
    +
    +	// Depth is how many levels were searched to find this completion.
    +	// For example when completing "foo<>", "fooBar" is depth 0, and
    +	// "fooBar.Baz" is depth 1.
    +	Depth int
    +
    +	// Score is the internal relevance score.
    +	// A higher score indicates that this completion item is more relevant.
    +	Score float64
    +
    +	// snippet is the LSP snippet for the completion item. The LSP
    +	// specification contains details about LSP snippets. For example, a
    +	// snippet for a function with the following signature:
    +	//
    +	//     func foo(a, b, c int)
    +	//
    +	// would be:
    +	//
    +	//     foo(${1:a int}, ${2: b int}, ${3: c int})
    +	//
    +	// If Placeholders is false in the CompletionOptions, the above
    +	// snippet would instead be:
    +	//
    +	//     foo(${1:})
    +	snippet *snippet.Builder
    +
    +	// Documentation is the documentation for the completion item.
    +	Documentation string
    +
    +	// isSlice reports whether the underlying type of the object
    +	// from which this candidate was derived is a slice.
    +	// (Used to complete append() calls.)
    +	isSlice bool
    +}
    +
    +// completionOptions holds completion specific configuration.
    +type completionOptions struct {
    +	unimported            bool
    +	documentation         bool
    +	fullDocumentation     bool
    +	placeholders          bool
    +	snippets              bool
    +	postfix               bool
    +	matcher               settings.Matcher
    +	budget                time.Duration
    +	completeFunctionCalls bool
    +}
    +
    +// Snippet is a convenience returns the snippet if available, otherwise
    +// the InsertText.
    +// used for an item, depending on if the callee wants placeholders or not.
    +func (i *CompletionItem) Snippet() string {
    +	if i.snippet != nil {
    +		return i.snippet.String()
    +	}
    +	return i.InsertText
    +}
    +
    +// Scoring constants are used for weighting the relevance of different candidates.
    +const (
    +	// stdScore is the base score for all completion items.
    +	stdScore float64 = 1.0
    +
    +	// highScore indicates a very relevant completion item.
    +	highScore float64 = 10.0
    +
    +	// lowScore indicates an irrelevant or not useful completion item.
    +	lowScore float64 = 0.01
    +)
    +
    +// matcher matches a candidate's label against the user input. The
    +// returned score reflects the quality of the match. A score of zero
    +// indicates no match, and a score of one means a perfect match.
    +type matcher interface {
    +	Score(candidateLabel string) (score float32)
    +}
    +
    +// prefixMatcher implements case sensitive prefix matching.
    +type prefixMatcher string
    +
    +func (pm prefixMatcher) Score(candidateLabel string) float32 {
    +	if strings.HasPrefix(candidateLabel, string(pm)) {
    +		return 1
    +	}
    +	return -1
    +}
    +
    +// insensitivePrefixMatcher implements case insensitive prefix matching.
    +type insensitivePrefixMatcher string
    +
    +func (ipm insensitivePrefixMatcher) Score(candidateLabel string) float32 {
    +	if strings.HasPrefix(strings.ToLower(candidateLabel), string(ipm)) {
    +		return 1
    +	}
    +	return -1
    +}
    +
    +// completer contains the necessary information for a single completion request.
    +type completer struct {
    +	snapshot *cache.Snapshot
    +	pkg      *cache.Package
    +	qf       types.Qualifier          // for qualifying typed expressions
    +	mq       golang.MetadataQualifier // for syntactic qualifying
    +	opts     *completionOptions
    +
    +	// completionContext contains information about the trigger for this
    +	// completion request.
    +	completionContext completionContext
    +
    +	// fh is a handle to the file associated with this completion request.
    +	fh file.Handle
    +
    +	// filename is the name of the file associated with this completion request.
    +	filename string
    +
    +	// file is the AST of the file associated with this completion request.
    +	file *ast.File
    +
    +	// goversion is the version of Go in force in the file, as
    +	// defined by x/tools/internal/versions. Empty if unknown.
    +	// Since go1.22 it should always be known.
    +	goversion string
    +
    +	// (tokFile, pos) is the position at which the request was triggered.
    +	tokFile *token.File
    +	pos     token.Pos
    +
    +	// path is the path of AST nodes enclosing the position.
    +	path []ast.Node
    +
    +	// seen is the map that ensures we do not return duplicate results.
    +	seen map[types.Object]bool
    +
    +	// items is the list of completion items returned.
    +	items []CompletionItem
    +
    +	// completionCallbacks is a list of callbacks to collect completions that
    +	// require expensive operations. This includes operations where we search
    +	// through the entire module cache.
    +	completionCallbacks []func(context.Context, *imports.Options) error
    +
    +	// surrounding describes the identifier surrounding the position.
    +	surrounding *Selection
    +
    +	// inference contains information we've inferred about ideal
    +	// candidates such as the candidate's type.
    +	inference candidateInference
    +
    +	// enclosingFunc contains information about the function enclosing
    +	// the position.
    +	enclosingFunc *funcInfo
    +
    +	// enclosingCompositeLiteral contains information about the composite literal
    +	// enclosing the position.
    +	enclosingCompositeLiteral *compLitInfo
    +
    +	// deepState contains the current state of our deep completion search.
    +	deepState deepCompletionState
    +
    +	// matcher matches the candidates against the surrounding prefix.
    +	matcher matcher
    +
    +	// methodSetCache caches the types.NewMethodSet call, which is relatively
    +	// expensive and can be called many times for the same type while searching
    +	// for deep completions.
    +	methodSetCache map[methodSetKey]*types.MethodSet
    +
    +	// tooNewSymbolsCache is a cache of
    +	// [typesinternal.TooNewStdSymbols], recording for each std
    +	// package which of its exported symbols are too new for
    +	// the version of Go in force in the completion file.
    +	// (The value is the minimum version in the form "go1.%d".)
    +	tooNewSymbolsCache map[*types.Package]map[types.Object]string
    +
    +	// mapper converts the positions in the file from which the completion originated.
    +	mapper *protocol.Mapper
    +
    +	// startTime is when we started processing this completion request. It does
    +	// not include any time the request spent in the queue.
    +	//
    +	// Note: in CL 503016, startTime move to *after* type checking, but it was
    +	// subsequently determined that it was better to keep setting it *before*
    +	// type checking, so that the completion budget best approximates the user
    +	// experience. See golang/go#62665 for more details.
    +	startTime time.Time
    +
    +	// scopes contains all scopes defined by nodes in our path,
    +	// including nil values for nodes that don't defined a scope. It
    +	// also includes our package scope and the universal scope at the
    +	// end.
    +	scopes []*types.Scope
    +}
    +
    +// tooNew reports whether obj is a standard library symbol that is too
    +// new for the specified Go version.
    +func (c *completer) tooNew(obj types.Object) bool {
    +	pkg := obj.Pkg()
    +	if pkg == nil {
    +		return false // unsafe.Pointer or error.Error
    +	}
    +	disallowed, ok := c.tooNewSymbolsCache[pkg]
    +	if !ok {
    +		disallowed = typesinternal.TooNewStdSymbols(pkg, c.goversion)
    +		c.tooNewSymbolsCache[pkg] = disallowed
    +	}
    +	return disallowed[obj] != ""
    +}
    +
    +// funcInfo holds info about a function object.
    +type funcInfo struct {
    +	// sig is the function declaration enclosing the position.
    +	sig *types.Signature
    +
    +	// body is the function's body.
    +	body *ast.BlockStmt
    +}
    +
    +type compLitInfo struct {
    +	// cl is the *ast.CompositeLit enclosing the position.
    +	cl *ast.CompositeLit
    +
    +	// clType is the type of cl.
    +	clType types.Type
    +
    +	// kv is the *ast.KeyValueExpr enclosing the position, if any.
    +	kv *ast.KeyValueExpr
    +
    +	// inKey is true if we are certain the position is in the key side
    +	// of a key-value pair.
    +	inKey bool
    +
    +	// maybeInFieldName is true if inKey is false and it is possible
    +	// we are completing a struct field name. For example,
    +	// "SomeStruct{<>}" will be inKey=false, but maybeInFieldName=true
    +	// because we _could_ be completing a field name.
    +	maybeInFieldName bool
    +}
    +
    +type importInfo struct {
    +	importPath string
    +	name       string
    +}
    +
    +type methodSetKey struct {
    +	typ         types.Type
    +	addressable bool
    +}
    +
    +type completionContext struct {
    +	// triggerCharacter is the character used to trigger completion at current
    +	// position, if any.
    +	triggerCharacter string
    +
    +	// triggerKind is information about how a completion was triggered.
    +	triggerKind protocol.CompletionTriggerKind
    +
    +	// commentCompletion is true if we are completing a comment.
    +	commentCompletion bool
    +
    +	// packageCompletion is true if we are completing a package name.
    +	packageCompletion bool
    +}
    +
    +// A Selection represents the cursor position and surrounding identifier.
    +type Selection struct {
    +	content            string
    +	tokFile            *token.File
    +	start, end, cursor token.Pos // relative to rng.TokFile
    +	mapper             *protocol.Mapper
    +}
    +
    +// Range returns the surrounding identifier's protocol.Range.
    +func (p Selection) Range() (protocol.Range, error) {
    +	return p.mapper.PosRange(p.tokFile, p.start, p.end)
    +}
    +
    +// PrefixRange returns the protocol.Range of the prefix of the selection.
    +func (p Selection) PrefixRange() (protocol.Range, error) {
    +	return p.mapper.PosRange(p.tokFile, p.start, p.cursor)
    +}
    +
    +func (p Selection) Prefix() string {
    +	return p.content[:p.cursor-p.start]
    +}
    +
    +func (p Selection) Suffix() string {
    +	return p.content[p.cursor-p.start:]
    +}
    +
    +func (c *completer) setSurrounding(ident *ast.Ident) {
    +	if c.surrounding != nil {
    +		return
    +	}
    +	if !(ident.Pos() <= c.pos && c.pos <= ident.End()) {
    +		return
    +	}
    +
    +	c.surrounding = &Selection{
    +		content: ident.Name,
    +		cursor:  c.pos,
    +		// Overwrite the prefix only.
    +		tokFile: c.tokFile,
    +		start:   ident.Pos(),
    +		end:     ident.End(),
    +		mapper:  c.mapper,
    +	}
    +
    +	c.setMatcherFromPrefix(c.surrounding.Prefix())
    +}
    +
    +func (c *completer) setMatcherFromPrefix(prefix string) {
    +	switch c.opts.matcher {
    +	case settings.Fuzzy:
    +		c.matcher = fuzzy.NewMatcher(prefix)
    +	case settings.CaseSensitive:
    +		c.matcher = prefixMatcher(prefix)
    +	default:
    +		c.matcher = insensitivePrefixMatcher(strings.ToLower(prefix))
    +	}
    +}
    +
    +func (c *completer) getSurrounding() *Selection {
    +	if c.surrounding == nil {
    +		c.surrounding = &Selection{
    +			content: "",
    +			cursor:  c.pos,
    +			tokFile: c.tokFile,
    +			start:   c.pos,
    +			end:     c.pos,
    +			mapper:  c.mapper,
    +		}
    +	}
    +	return c.surrounding
    +}
    +
    +// candidate represents a completion candidate.
    +type candidate struct {
    +	// obj is the types.Object to complete to.
    +	// TODO(adonovan): eliminate dependence on go/types throughout this struct.
    +	// See comment in (*completer).selector for explanation.
    +	obj types.Object
    +
    +	// score is used to rank candidates.
    +	score float64
    +
    +	// name is the deep object name path, e.g. "foo.bar"
    +	name string
    +
    +	// detail is additional information about this item. If not specified,
    +	// defaults to type string for the object.
    +	detail string
    +
    +	// path holds the path from the search root (excluding the candidate
    +	// itself) for a deep candidate.
    +	path []types.Object
    +
    +	// pathInvokeMask is a bit mask tracking whether each entry in path
    +	// should be formatted with "()" (i.e. whether it is a function
    +	// invocation).
    +	pathInvokeMask uint16
    +
    +	// mods contains modifications that should be applied to the
    +	// candidate when inserted. For example, "foo" may be inserted as
    +	// "*foo" or "foo()".
    +	mods []typeModKind
    +
    +	// addressable is true if a pointer can be taken to the candidate.
    +	addressable bool
    +
    +	// convertTo is a type that this candidate should be cast to. For
    +	// example, if convertTo is float64, "foo" should be formatted as
    +	// "float64(foo)".
    +	convertTo types.Type
    +
    +	// imp is the import that needs to be added to this package in order
    +	// for this candidate to be valid. nil if no import needed.
    +	imp *importInfo
    +}
    +
    +func (c candidate) hasMod(mod typeModKind) bool {
    +	for _, m := range c.mods {
    +		if m == mod {
    +			return true
    +		}
    +	}
    +	return false
    +}
    +
    +// Completion returns a list of possible candidates for completion, given a
    +// a file and a position.
    +//
    +// The selection is computed based on the preceding identifier and can be used by
    +// the client to score the quality of the completion. For instance, some clients
    +// may tolerate imperfect matches as valid completion results, since users may make typos.
    +func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, protoPos protocol.Position, protoContext protocol.CompletionContext) ([]CompletionItem, *Selection, error) {
    +	ctx, done := event.Start(ctx, "completion.Completion")
    +	defer done()
    +
    +	startTime := time.Now()
    +
    +	pkg, pgf, err := golang.NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil || pgf.File.Package == token.NoPos {
    +		// If we can't parse this file or find position for the package
    +		// keyword, it may be missing a package declaration. Try offering
    +		// suggestions for the package declaration.
    +		// Note that this would be the case even if the keyword 'package' is
    +		// present but no package name exists.
    +		items, surrounding, innerErr := packageClauseCompletions(ctx, snapshot, fh, protoPos)
    +		if innerErr != nil {
    +			// return the error for GetParsedFile since it's more relevant in this situation.
    +			return nil, nil, fmt.Errorf("getting file %s for Completion: %v (package completions: %v)", fh.URI(), err, innerErr)
    +		}
    +		return items, surrounding, nil
    +	}
    +
    +	pos, err := pgf.PositionPos(protoPos)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +	// Completion is based on what precedes the cursor.
    +	// Find the path to the position before pos.
    +	path, _ := astutil.PathEnclosingInterval(pgf.File, pos-1, pos-1)
    +	if path == nil {
    +		return nil, nil, fmt.Errorf("cannot find node enclosing position")
    +	}
    +
    +	// Check if completion at this position is valid. If not, return early.
    +	switch n := path[0].(type) {
    +	case *ast.BasicLit:
    +		// Skip completion inside literals except for ImportSpec
    +		if len(path) > 1 {
    +			if _, ok := path[1].(*ast.ImportSpec); ok {
    +				break
    +			}
    +		}
    +		return nil, nil, nil
    +	case *ast.CallExpr:
    +		if n.Ellipsis.IsValid() && pos > n.Ellipsis && pos <= n.Ellipsis+token.Pos(len("...")) {
    +			// Don't offer completions inside or directly after "...". For
    +			// example, don't offer completions at "<>" in "foo(bar...<>").
    +			return nil, nil, nil
    +		}
    +	case *ast.Ident:
    +		// Don't offer completions for (most) defining identifiers.
    +		if obj, ok := pkg.TypesInfo().Defs[n]; ok {
    +			if v, ok := obj.(*types.Var); ok && v.IsField() && v.Embedded() {
    +				// Allow completion of anonymous fields, since they may reference type
    +				// names.
    +			} else if pgf.File.Name == n {
    +				// Allow package name completion.
    +			} else {
    +				// Check if we have special completion for this definition, such as
    +				// test function name completion.
    +				ans, sel := definition(path, obj, pgf)
    +				if ans != nil {
    +					sort.Slice(ans, func(i, j int) bool {
    +						return ans[i].Score > ans[j].Score
    +					})
    +					return ans, sel, nil
    +				}
    +
    +				return nil, nil, nil // No completions.
    +			}
    +		}
    +	}
    +
    +	// Collect all surrounding scopes, innermost first.
    +	scopes := golang.CollectScopes(pkg.TypesInfo(), path, pos)
    +	scopes = append(scopes, pkg.Types().Scope(), types.Universe)
    +
    +	var goversion string // "" => no version check
    +	// Prior go1.22, the behavior of FileVersion is not useful to us.
    +	if slices.Contains(build.Default.ReleaseTags, "go1.22") {
    +		goversion = versions.FileVersion(pkg.TypesInfo(), pgf.File) // may be ""
    +	}
    +
    +	opts := snapshot.Options()
    +	c := &completer{
    +		pkg:      pkg,
    +		snapshot: snapshot,
    +		qf:       typesutil.FileQualifier(pgf.File, pkg.Types(), pkg.TypesInfo()),
    +		mq:       golang.MetadataQualifierForFile(snapshot, pgf.File, pkg.Metadata()),
    +		completionContext: completionContext{
    +			triggerCharacter: protoContext.TriggerCharacter,
    +			triggerKind:      protoContext.TriggerKind,
    +		},
    +		fh:                        fh,
    +		filename:                  fh.URI().Path(),
    +		tokFile:                   pgf.Tok,
    +		file:                      pgf.File,
    +		goversion:                 goversion,
    +		path:                      path,
    +		pos:                       pos,
    +		seen:                      make(map[types.Object]bool),
    +		enclosingFunc:             enclosingFunction(path, pkg.TypesInfo()),
    +		enclosingCompositeLiteral: enclosingCompositeLiteral(path, pos, pkg.TypesInfo()),
    +		deepState: deepCompletionState{
    +			enabled: opts.DeepCompletion,
    +		},
    +		opts: &completionOptions{
    +			matcher:               opts.Matcher,
    +			unimported:            opts.CompleteUnimported,
    +			documentation:         opts.CompletionDocumentation && opts.HoverKind != settings.NoDocumentation,
    +			fullDocumentation:     opts.HoverKind == settings.FullDocumentation,
    +			placeholders:          opts.UsePlaceholders,
    +			budget:                opts.CompletionBudget,
    +			snippets:              opts.InsertTextFormat == protocol.SnippetTextFormat,
    +			postfix:               opts.ExperimentalPostfixCompletions,
    +			completeFunctionCalls: opts.CompleteFunctionCalls,
    +		},
    +		// default to a matcher that always matches
    +		matcher:            prefixMatcher(""),
    +		methodSetCache:     make(map[methodSetKey]*types.MethodSet),
    +		tooNewSymbolsCache: make(map[*types.Package]map[types.Object]string),
    +		mapper:             pgf.Mapper,
    +		startTime:          startTime,
    +		scopes:             scopes,
    +	}
    +
    +	ctx, cancel := context.WithCancel(ctx)
    +	defer cancel()
    +
    +	// Compute the deadline for this operation. Deadline is relative to the
    +	// search operation, not the entire completion RPC, as the work up until this
    +	// point depends significantly on how long it took to type-check, which in
    +	// turn depends on the timing of the request relative to other operations on
    +	// the snapshot. Including that work in the budget leads to inconsistent
    +	// results (and realistically, if type-checking took 200ms already, the user
    +	// is unlikely to be significantly more bothered by e.g. another 100ms of
    +	// search).
    +	//
    +	// Don't overload the context with this deadline, as we don't want to
    +	// conflate user cancellation (=fail the operation) with our time limit
    +	// (=stop searching and succeed with partial results).
    +	var deadline *time.Time
    +	if c.opts.budget > 0 {
    +		d := startTime.Add(c.opts.budget)
    +		deadline = &d
    +	}
    +
    +	if surrounding := c.containingIdent(pgf.Src); surrounding != nil {
    +		c.setSurrounding(surrounding)
    +	}
    +
    +	c.inference = expectedCandidate(ctx, c)
    +
    +	err = c.collectCompletions(ctx)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +
    +	// Deep search collected candidates and their members for more candidates.
    +	c.deepSearch(ctx, 1, deadline)
    +
    +	// At this point we have a sufficiently complete set of results, and want to
    +	// return as close to the completion budget as possible. Previously, we
    +	// avoided cancelling the context because it could result in partial results
    +	// for e.g. struct fields. At this point, we have a minimal valid set of
    +	// candidates, and so truncating due to context cancellation is acceptable.
    +	if c.opts.budget > 0 {
    +		timeoutDuration := time.Until(c.startTime.Add(c.opts.budget))
    +		ctx, cancel = context.WithTimeout(ctx, timeoutDuration)
    +		defer cancel()
    +	}
    +
    +	for _, callback := range c.completionCallbacks {
    +		if deadline == nil || time.Now().Before(*deadline) {
    +			if err := c.snapshot.RunProcessEnvFunc(ctx, callback); err != nil {
    +				return nil, nil, err
    +			}
    +		}
    +	}
    +
    +	// Search candidates populated by expensive operations like
    +	// unimportedMembers etc. for more completion items.
    +	c.deepSearch(ctx, 0, deadline)
    +
    +	// Statement candidates offer an entire statement in certain contexts, as
    +	// opposed to a single object. Add statement candidates last because they
    +	// depend on other candidates having already been collected.
    +	c.addStatementCandidates()
    +
    +	c.sortItems()
    +	return c.items, c.getSurrounding(), nil
    +}
    +
    +// collectCompletions adds possible completion candidates to either the deep
    +// search queue or completion items directly for different completion contexts.
    +func (c *completer) collectCompletions(ctx context.Context) error {
    +	// Inside import blocks, return completions for unimported packages.
    +	for _, importSpec := range c.file.Imports {
    +		if !(importSpec.Path.Pos() <= c.pos && c.pos <= importSpec.Path.End()) {
    +			continue
    +		}
    +		return c.populateImportCompletions(importSpec)
    +	}
    +
    +	// Inside comments, offer completions for the name of the relevant symbol.
    +	for _, comment := range c.file.Comments {
    +		if comment.Pos() < c.pos && c.pos <= comment.End() {
    +			c.populateCommentCompletions(comment)
    +			return nil
    +		}
    +	}
    +
    +	// Struct literals are handled entirely separately.
    +	if c.wantStructFieldCompletions() {
    +		// If we are definitely completing a struct field name, deep completions
    +		// don't make sense.
    +		if c.enclosingCompositeLiteral.inKey {
    +			c.deepState.enabled = false
    +		}
    +		return c.structLiteralFieldName(ctx)
    +	}
    +
    +	if lt := c.wantLabelCompletion(); lt != labelNone {
    +		c.labels(lt)
    +		return nil
    +	}
    +
    +	if c.emptySwitchStmt() {
    +		// Empty switch statements only admit "default" and "case" keywords.
    +		c.addKeywordItems(map[string]bool{}, highScore, CASE, DEFAULT)
    +		return nil
    +	}
    +
    +	switch n := c.path[0].(type) {
    +	case *ast.Ident:
    +		if c.file.Name == n {
    +			return c.packageNameCompletions(ctx, c.fh.URI(), n)
    +		} else if sel, ok := c.path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
    +			// Is this the Sel part of a selector?
    +			return c.selector(ctx, sel)
    +		}
    +		return c.lexical(ctx)
    +	// The function name hasn't been typed yet, but the parens are there:
    +	//   recv.‸(arg)
    +	case *ast.TypeAssertExpr:
    +		// Create a fake selector expression.
    +		//
    +		// The name "_" is the convention used by go/parser to represent phantom
    +		// selectors.
    +		sel := &ast.Ident{NamePos: n.X.End() + token.Pos(len(".")), Name: "_"}
    +		return c.selector(ctx, &ast.SelectorExpr{X: n.X, Sel: sel})
    +	case *ast.SelectorExpr:
    +		return c.selector(ctx, n)
    +	// At the file scope, only keywords are allowed.
    +	case *ast.BadDecl, *ast.File:
    +		c.addKeywordCompletions()
    +	default:
    +		// fallback to lexical completions
    +		return c.lexical(ctx)
    +	}
    +
    +	return nil
    +}
    +
    +// containingIdent returns the *ast.Ident containing pos, if any. It
    +// synthesizes an *ast.Ident to allow completion in the face of
    +// certain syntax errors.
    +func (c *completer) containingIdent(src []byte) *ast.Ident {
    +	// In the normal case, our leaf AST node is the identifier being completed.
    +	if ident, ok := c.path[0].(*ast.Ident); ok {
    +		return ident
    +	}
    +
    +	pos, tkn, lit := c.scanToken(src)
    +	if !pos.IsValid() {
    +		return nil
    +	}
    +
    +	fakeIdent := &ast.Ident{Name: lit, NamePos: pos}
    +
    +	if _, isBadDecl := c.path[0].(*ast.BadDecl); isBadDecl {
    +		// You don't get *ast.Idents at the file level, so look for bad
    +		// decls and use the manually extracted token.
    +		return fakeIdent
    +	} else if c.emptySwitchStmt() {
    +		// Only keywords are allowed in empty switch statements.
    +		// *ast.Idents are not parsed, so we must use the manually
    +		// extracted token.
    +		return fakeIdent
    +	} else if tkn.IsKeyword() {
    +		// Otherwise, manually extract the prefix if our containing token
    +		// is a keyword. This improves completion after an "accidental
    +		// keyword", e.g. completing to "variance" in "someFunc(var<>)".
    +		return fakeIdent
    +	}
    +
    +	return nil
    +}
    +
    +// scanToken scans pgh's contents for the token containing pos.
    +func (c *completer) scanToken(contents []byte) (token.Pos, token.Token, string) {
    +	tok := c.pkg.FileSet().File(c.pos)
    +
    +	var s scanner.Scanner
    +	s.Init(tok, contents, nil, 0)
    +	for {
    +		tknPos, tkn, lit := s.Scan()
    +		if tkn == token.EOF || tknPos >= c.pos {
    +			return token.NoPos, token.ILLEGAL, ""
    +		}
    +
    +		if len(lit) > 0 && tknPos <= c.pos && c.pos <= tknPos+token.Pos(len(lit)) {
    +			return tknPos, tkn, lit
    +		}
    +	}
    +}
    +
    +func (c *completer) sortItems() {
    +	sort.SliceStable(c.items, func(i, j int) bool {
    +		// Sort by score first.
    +		if c.items[i].Score != c.items[j].Score {
    +			return c.items[i].Score > c.items[j].Score
    +		}
    +
    +		// Then sort by label so order stays consistent. This also has the
    +		// effect of preferring shorter candidates.
    +		return c.items[i].Label < c.items[j].Label
    +	})
    +}
    +
    +// emptySwitchStmt reports whether pos is in an empty switch or select
    +// statement.
    +func (c *completer) emptySwitchStmt() bool {
    +	block, ok := c.path[0].(*ast.BlockStmt)
    +	if !ok || len(block.List) > 0 || len(c.path) == 1 {
    +		return false
    +	}
    +
    +	switch c.path[1].(type) {
    +	case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt:
    +		return true
    +	default:
    +		return false
    +	}
    +}
    +
    +// populateImportCompletions yields completions for an import path around the cursor.
    +//
    +// Completions are suggested at the directory depth of the given import path so
    +// that we don't overwhelm the user with a large list of possibilities. As an
    +// example, a completion for the prefix "golang" results in "golang.org/".
    +// Completions for "golang.org/" yield its subdirectories
    +// (i.e. "golang.org/x/"). The user is meant to accept completion suggestions
    +// until they reach a complete import path.
    +func (c *completer) populateImportCompletions(searchImport *ast.ImportSpec) error {
    +	if !strings.HasPrefix(searchImport.Path.Value, `"`) {
    +		return nil
    +	}
    +
    +	// deepSearch is not valuable for import completions.
    +	c.deepState.enabled = false
    +
    +	importPath := searchImport.Path.Value
    +
    +	// Extract the text between the quotes (if any) in an import spec.
    +	// prefix is the part of import path before the cursor.
    +	prefixEnd := c.pos - searchImport.Path.Pos()
    +	prefix := strings.Trim(importPath[:prefixEnd], `"`)
    +
    +	// The number of directories in the import path gives us the depth at
    +	// which to search.
    +	depth := len(strings.Split(prefix, "/")) - 1
    +
    +	content := importPath
    +	start, end := searchImport.Path.Pos(), searchImport.Path.End()
    +	namePrefix, nameSuffix := `"`, `"`
    +	// If a starting quote is present, adjust surrounding to either after the
    +	// cursor or after the first slash (/), except if cursor is at the starting
    +	// quote. Otherwise we provide a completion including the starting quote.
    +	if strings.HasPrefix(importPath, `"`) && c.pos > searchImport.Path.Pos() {
    +		content = content[1:]
    +		start++
    +		if depth > 0 {
    +			// Adjust textEdit start to replacement range. For ex: if current
    +			// path was "golang.or/x/to<>ols/internal/", where <> is the cursor
    +			// position, start of the replacement range would be after
    +			// "golang.org/x/".
    +			path := strings.SplitAfter(prefix, "/")
    +			numChars := len(strings.Join(path[:len(path)-1], ""))
    +			content = content[numChars:]
    +			start += token.Pos(numChars)
    +		}
    +		namePrefix = ""
    +	}
    +
    +	// We won't provide an ending quote if one is already present, except if
    +	// cursor is after the ending quote but still in import spec. This is
    +	// because cursor has to be in our textEdit range.
    +	if strings.HasSuffix(importPath, `"`) && c.pos < searchImport.Path.End() {
    +		end--
    +		content = content[:len(content)-1]
    +		nameSuffix = ""
    +	}
    +
    +	c.surrounding = &Selection{
    +		content: content,
    +		cursor:  c.pos,
    +		tokFile: c.tokFile,
    +		start:   start,
    +		end:     end,
    +		mapper:  c.mapper,
    +	}
    +
    +	seenImports := make(map[string]struct{})
    +	for _, importSpec := range c.file.Imports {
    +		if importSpec.Path.Value == importPath {
    +			continue
    +		}
    +		seenImportPath, err := strconv.Unquote(importSpec.Path.Value)
    +		if err != nil {
    +			return err
    +		}
    +		seenImports[seenImportPath] = struct{}{}
    +	}
    +
    +	var mu sync.Mutex // guard c.items locally, since searchImports is called in parallel
    +	seen := make(map[string]struct{})
    +	searchImports := func(pkg imports.ImportFix) {
    +		path := pkg.StmtInfo.ImportPath
    +		if _, ok := seenImports[path]; ok {
    +			return
    +		}
    +
    +		// Any package path containing fewer directories than the search
    +		// prefix is not a match.
    +		pkgDirList := strings.Split(path, "/")
    +		if len(pkgDirList) < depth+1 {
    +			return
    +		}
    +		pkgToConsider := strings.Join(pkgDirList[:depth+1], "/")
    +
    +		name := pkgDirList[depth]
    +		// if we're adding an opening quote to completion too, set name to full
    +		// package path since we'll need to overwrite that range.
    +		if namePrefix == `"` {
    +			name = pkgToConsider
    +		}
    +
    +		score := pkg.Relevance
    +		if len(pkgDirList)-1 == depth {
    +			score *= highScore
    +		} else {
    +			// For incomplete package paths, add a terminal slash to indicate that the
    +			// user should keep triggering completions.
    +			name += "/"
    +			pkgToConsider += "/"
    +		}
    +
    +		if _, ok := seen[pkgToConsider]; ok {
    +			return
    +		}
    +		seen[pkgToConsider] = struct{}{}
    +
    +		mu.Lock()
    +		defer mu.Unlock()
    +
    +		name = namePrefix + name + nameSuffix
    +		obj := types.NewPkgName(0, nil, name, types.NewPackage(pkgToConsider, name))
    +		c.deepState.enqueue(candidate{
    +			obj:    obj,
    +			detail: strconv.Quote(pkgToConsider),
    +			score:  score,
    +		})
    +	}
    +
    +	c.completionCallbacks = append(c.completionCallbacks, func(ctx context.Context, opts *imports.Options) error {
    +		return imports.GetImportPaths(ctx, searchImports, prefix, c.filename, c.pkg.Types().Name(), opts.Env)
    +	})
    +	return nil
    +}
    +
    +// populateCommentCompletions yields completions for comments preceding or in declarations.
    +func (c *completer) populateCommentCompletions(comment *ast.CommentGroup) {
    +	// If the completion was triggered by a period, ignore it. These types of
    +	// completions will not be useful in comments.
    +	if c.completionContext.triggerCharacter == "." {
    +		return
    +	}
    +
    +	// Using the comment position find the line after
    +	file := c.pkg.FileSet().File(comment.End())
    +	if file == nil {
    +		return
    +	}
    +
    +	// Deep completion doesn't work properly in comments since we don't
    +	// have a type object to complete further.
    +	c.deepState.enabled = false
    +	c.completionContext.commentCompletion = true
    +
    +	// Documentation isn't useful in comments, since it might end up being the
    +	// comment itself.
    +	c.opts.documentation = false
    +
    +	commentLine := safetoken.Line(file, comment.End())
    +
    +	// comment is valid, set surrounding as word boundaries around cursor
    +	c.setSurroundingForComment(comment)
    +
    +	// Using the next line pos, grab and parse the exported symbol on that line
    +	for _, n := range c.file.Decls {
    +		declLine := safetoken.Line(file, n.Pos())
    +		// if the comment is not in, directly above or on the same line as a declaration
    +		if declLine != commentLine && declLine != commentLine+1 &&
    +			!(n.Pos() <= comment.Pos() && comment.End() <= n.End()) {
    +			continue
    +		}
    +		switch node := n.(type) {
    +		// handle const, vars, and types
    +		case *ast.GenDecl:
    +			for _, spec := range node.Specs {
    +				switch spec := spec.(type) {
    +				case *ast.ValueSpec:
    +					for _, name := range spec.Names {
    +						if name.String() == "_" {
    +							continue
    +						}
    +						obj := c.pkg.TypesInfo().ObjectOf(name)
    +						c.deepState.enqueue(candidate{obj: obj, score: stdScore})
    +					}
    +				case *ast.TypeSpec:
    +					// add TypeSpec fields to completion
    +					switch typeNode := spec.Type.(type) {
    +					case *ast.StructType:
    +						c.addFieldItems(typeNode.Fields)
    +					case *ast.FuncType:
    +						c.addFieldItems(typeNode.Params)
    +						c.addFieldItems(typeNode.Results)
    +					case *ast.InterfaceType:
    +						c.addFieldItems(typeNode.Methods)
    +					}
    +
    +					if spec.Name.String() == "_" {
    +						continue
    +					}
    +
    +					obj := c.pkg.TypesInfo().ObjectOf(spec.Name)
    +					// Type name should get a higher score than fields but not highScore by default
    +					// since field near a comment cursor gets a highScore
    +					score := stdScore * 1.1
    +					// If type declaration is on the line after comment, give it a highScore.
    +					if declLine == commentLine+1 {
    +						score = highScore
    +					}
    +
    +					c.deepState.enqueue(candidate{obj: obj, score: score})
    +				}
    +			}
    +		// handle functions
    +		case *ast.FuncDecl:
    +			c.addFieldItems(node.Recv)
    +			c.addFieldItems(node.Type.Params)
    +			c.addFieldItems(node.Type.Results)
    +
    +			// collect receiver struct fields
    +			if node.Recv != nil {
    +				sig := c.pkg.TypesInfo().Defs[node.Name].(*types.Func).Signature()
    +				_, named := typesinternal.ReceiverNamed(sig.Recv()) // may be nil if ill-typed
    +				if named != nil {
    +					if recvStruct, ok := named.Underlying().(*types.Struct); ok {
    +						for i := 0; i < recvStruct.NumFields(); i++ {
    +							field := recvStruct.Field(i)
    +							c.deepState.enqueue(candidate{obj: field, score: lowScore})
    +						}
    +					}
    +				}
    +			}
    +
    +			if node.Name.String() == "_" {
    +				continue
    +			}
    +
    +			obj := c.pkg.TypesInfo().ObjectOf(node.Name)
    +			if obj == nil || obj.Pkg() != nil && obj.Pkg() != c.pkg.Types() {
    +				continue
    +			}
    +
    +			c.deepState.enqueue(candidate{obj: obj, score: highScore})
    +		}
    +	}
    +}
    +
    +// sets word boundaries surrounding a cursor for a comment
    +func (c *completer) setSurroundingForComment(comments *ast.CommentGroup) {
    +	var cursorComment *ast.Comment
    +	for _, comment := range comments.List {
    +		if c.pos >= comment.Pos() && c.pos <= comment.End() {
    +			cursorComment = comment
    +			break
    +		}
    +	}
    +	// if cursor isn't in the comment
    +	if cursorComment == nil {
    +		return
    +	}
    +
    +	// index of cursor in comment text
    +	cursorOffset := int(c.pos - cursorComment.Pos())
    +	start, end := cursorOffset, cursorOffset
    +	for start > 0 && isValidIdentifierChar(cursorComment.Text[start-1]) {
    +		start--
    +	}
    +	for end < len(cursorComment.Text) && isValidIdentifierChar(cursorComment.Text[end]) {
    +		end++
    +	}
    +
    +	c.surrounding = &Selection{
    +		content: cursorComment.Text[start:end],
    +		cursor:  c.pos,
    +		tokFile: c.tokFile,
    +		start:   token.Pos(int(cursorComment.Slash) + start),
    +		end:     token.Pos(int(cursorComment.Slash) + end),
    +		mapper:  c.mapper,
    +	}
    +	c.setMatcherFromPrefix(c.surrounding.Prefix())
    +}
    +
    +// isValidIdentifierChar returns true if a byte is a valid go identifier
    +// character, i.e. unicode letter or digit or underscore.
    +func isValidIdentifierChar(char byte) bool {
    +	charRune := rune(char)
    +	return unicode.In(charRune, unicode.Letter, unicode.Digit) || char == '_'
    +}
    +
    +// adds struct fields, interface methods, function declaration fields to completion
    +func (c *completer) addFieldItems(fields *ast.FieldList) {
    +	if fields == nil {
    +		return
    +	}
    +
    +	cursor := c.surrounding.cursor
    +	for _, field := range fields.List {
    +		for _, name := range field.Names {
    +			if name.String() == "_" {
    +				continue
    +			}
    +			obj := c.pkg.TypesInfo().ObjectOf(name)
    +			if obj == nil {
    +				continue
    +			}
    +
    +			// if we're in a field comment/doc, score that field as more relevant
    +			score := stdScore
    +			if field.Comment != nil && field.Comment.Pos() <= cursor && cursor <= field.Comment.End() {
    +				score = highScore
    +			} else if field.Doc != nil && field.Doc.Pos() <= cursor && cursor <= field.Doc.End() {
    +				score = highScore
    +			}
    +
    +			c.deepState.enqueue(candidate{obj: obj, score: score})
    +		}
    +	}
    +}
    +
    +func (c *completer) wantStructFieldCompletions() bool {
    +	clInfo := c.enclosingCompositeLiteral
    +	if clInfo == nil {
    +		return false
    +	}
    +	return is[*types.Struct](clInfo.clType) && (clInfo.inKey || clInfo.maybeInFieldName)
    +}
    +
    +func (c *completer) wantTypeName() bool {
    +	return !c.completionContext.commentCompletion && c.inference.typeName.wantTypeName
    +}
    +
    +// See https://golang.org/issue/36001. Unimported completions are expensive.
    +const (
    +	maxUnimportedPackageNames = 5
    +	unimportedMemberTarget    = 100
    +)
    +
    +// selector finds completions for the specified selector expression.
    +func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
    +	c.inference.objChain = objChain(c.pkg.TypesInfo(), sel.X)
    +
    +	// True selector?
    +	if tv, ok := c.pkg.TypesInfo().Types[sel.X]; ok {
    +		c.methodsAndFields(tv.Type, tv.Addressable(), nil, c.deepState.enqueue)
    +		c.addPostfixSnippetCandidates(ctx, sel)
    +		return nil
    +	}
    +
    +	id, ok := sel.X.(*ast.Ident)
    +	if !ok {
    +		return nil
    +	}
    +
    +	// Treat sel as a qualified identifier.
    +	var filter func(*metadata.Package) bool
    +	needImport := false
    +	if pkgName, ok := c.pkg.TypesInfo().Uses[id].(*types.PkgName); ok {
    +		// Qualified identifier with import declaration.
    +		imp := pkgName.Imported()
    +
    +		// Known direct dependency? Expand using type information.
    +		if _, ok := c.pkg.Metadata().DepsByPkgPath[golang.PackagePath(imp.Path())]; ok {
    +			c.packageMembers(imp, stdScore, nil, c.deepState.enqueue)
    +			return nil
    +		}
    +
    +		// Imported declaration with missing type information.
    +		// Fall through to shallow completion of unimported package members.
    +		// Match candidate packages by path.
    +		filter = func(mp *metadata.Package) bool {
    +			return strings.TrimPrefix(string(mp.PkgPath), "vendor/") == imp.Path()
    +		}
    +	} else {
    +		// Qualified identifier without import declaration.
    +		// Match candidate packages by name.
    +		filter = func(mp *metadata.Package) bool {
    +			return string(mp.Name) == id.Name
    +		}
    +		needImport = true
    +	}
    +
    +	// Search unimported packages.
    +	if !c.opts.unimported {
    +		return nil // feature disabled
    +	}
    +
    +	// -- completion of symbols in unimported packages --
    +
    +	// The deep completion algorithm is exceedingly complex and
    +	// deeply coupled to the now obsolete notions that all
    +	// token.Pos values can be interpreted by as a single FileSet
    +	// belonging to the Snapshot and that all types.Object values
    +	// are canonicalized by a single types.Importer mapping.
    +	// These invariants are no longer true now that gopls uses
    +	// an incremental approach, parsing and type-checking each
    +	// package separately.
    +	//
    +	// Consequently, completion of symbols defined in packages that
    +	// are not currently imported by the query file cannot use the
    +	// deep completion machinery which is based on type information.
    +	// Instead it must use only syntax information from a quick
    +	// parse of top-level declarations (but not function bodies).
    +	//
    +	// TODO(adonovan): rewrite the deep completion machinery to
    +	// not assume global Pos/Object realms and then use export
    +	// data instead of the quick parse approach taken here.
    +
    +	// First, we search among packages in the forward transitive
    +	// closure of the workspace.
    +	// We'll use a fast parse to extract package members
    +	// from those that match the name/path criterion.
    +	all, err := c.snapshot.AllMetadata(ctx)
    +	if err != nil {
    +		return err
    +	}
    +	known := make(map[golang.PackagePath]*metadata.Package)
    +	for _, mp := range all {
    +		if mp.Name == "main" {
    +			continue // not importable
    +		}
    +		if mp.IsIntermediateTestVariant() {
    +			continue
    +		}
    +		// The only test variant we admit is "p [p.test]"
    +		// when we are completing within "p_test [p.test]",
    +		// as in that case we would like to offer completions
    +		// of the test variants' additional symbols.
    +		if mp.ForTest != "" && c.pkg.Metadata().PkgPath != mp.ForTest+"_test" {
    +			continue
    +		}
    +		if !filter(mp) {
    +			continue
    +		}
    +		// Prefer previous entry unless this one is its test variant.
    +		if mp.ForTest != "" || known[mp.PkgPath] == nil {
    +			known[mp.PkgPath] = mp
    +		}
    +	}
    +
    +	paths := make([]string, 0, len(known))
    +	for path := range known {
    +		paths = append(paths, string(path))
    +	}
    +
    +	// Rank import paths as goimports would.
    +	var relevances map[string]float64
    +	if len(paths) > 0 {
    +		if err := c.snapshot.RunProcessEnvFunc(ctx, func(ctx context.Context, opts *imports.Options) error {
    +			var err error
    +			relevances, err = imports.ScoreImportPaths(ctx, opts.Env, paths)
    +			return err
    +		}); err != nil {
    +			return err
    +		}
    +		sort.Slice(paths, func(i, j int) bool {
    +			return relevances[paths[i]] > relevances[paths[j]]
    +		})
    +	}
    +
    +	// quickParse does a quick parse of a single file of package m,
    +	// extracts exported package members and adds candidates to c.items.
    +	// TODO(rfindley): synchronizing access to c here does not feel right.
    +	// Consider adding a concurrency-safe API for completer.
    +	var cMu sync.Mutex // guards c.items and c.matcher
    +	var enough int32   // atomic bool
    +	quickParse := func(uri protocol.DocumentURI, mp *metadata.Package, tooNew map[string]bool) error {
    +		if atomic.LoadInt32(&enough) != 0 {
    +			return nil
    +		}
    +
    +		fh, err := c.snapshot.ReadFile(ctx, uri)
    +		if err != nil {
    +			return err
    +		}
    +		content, err := fh.Content()
    +		if err != nil {
    +			return err
    +		}
    +		path := string(mp.PkgPath)
    +		forEachPackageMember(content, func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl) {
    +			if atomic.LoadInt32(&enough) != 0 {
    +				return
    +			}
    +
    +			if !id.IsExported() {
    +				return
    +			}
    +
    +			if tooNew[id.Name] {
    +				return // symbol too new for requesting file's Go's version
    +			}
    +
    +			cMu.Lock()
    +			score := c.matcher.Score(id.Name)
    +			cMu.Unlock()
    +
    +			if sel.Sel.Name != "_" && score == 0 {
    +				return // not a match; avoid constructing the completion item below
    +			}
    +
    +			// The only detail is the kind and package: `var (from "example.com/foo")`
    +			// TODO(adonovan): pretty-print FuncDecl.FuncType or TypeSpec.Type?
    +			// TODO(adonovan): should this score consider the actual c.matcher.Score
    +			// of the item? How does this compare with the deepState.enqueue path?
    +			item := CompletionItem{
    +				Label:      id.Name,
    +				Detail:     fmt.Sprintf("%s (from %q)", strings.ToLower(tok.String()), mp.PkgPath),
    +				InsertText: id.Name,
    +				Score:      float64(score) * unimportedScore(relevances[path]),
    +			}
    +			switch tok {
    +			case token.FUNC:
    +				item.Kind = protocol.FunctionCompletion
    +			case token.VAR:
    +				item.Kind = protocol.VariableCompletion
    +			case token.CONST:
    +				item.Kind = protocol.ConstantCompletion
    +			case token.TYPE:
    +				// Without types, we can't distinguish Class from Interface.
    +				item.Kind = protocol.ClassCompletion
    +			}
    +
    +			if needImport {
    +				imp := &importInfo{importPath: path}
    +				if imports.ImportPathToAssumedName(path) != string(mp.Name) {
    +					imp.name = string(mp.Name)
    +				}
    +				item.AdditionalTextEdits, _ = c.importEdits(imp)
    +			}
    +
    +			// For functions, add a parameter snippet.
    +			if fn != nil {
    +				paramList := func(list *ast.FieldList) []string {
    +					var params []string
    +					if list != nil {
    +						var cfg printer.Config // slight overkill
    +						param := func(name string, typ ast.Expr) {
    +							var buf strings.Builder
    +							buf.WriteString(name)
    +							buf.WriteByte(' ')
    +							cfg.Fprint(&buf, token.NewFileSet(), typ)
    +							params = append(params, buf.String())
    +						}
    +
    +						for _, field := range list.List {
    +							if field.Names != nil {
    +								for _, name := range field.Names {
    +									param(name.Name, field.Type)
    +								}
    +							} else {
    +								param("_", field.Type)
    +							}
    +						}
    +					}
    +					return params
    +				}
    +
    +				// Ideally we would eliminate the suffix of type
    +				// parameters that are redundant with inference
    +				// from the argument types (#51783), but it's
    +				// quite fiddly to do using syntax alone.
    +				// (See inferableTypeParams in format.go.)
    +				tparams := paramList(fn.Type.TypeParams)
    +				params := paramList(fn.Type.Params)
    +				var sn snippet.Builder
    +				c.functionCallSnippet(id.Name, tparams, params, &sn)
    +				item.snippet = &sn
    +			}
    +
    +			cMu.Lock()
    +			c.items = append(c.items, item)
    +			if len(c.items) >= unimportedMemberTarget {
    +				atomic.StoreInt32(&enough, 1)
    +			}
    +			cMu.Unlock()
    +		})
    +		return nil
    +	}
    +
    +	goversion := c.pkg.TypesInfo().FileVersions[c.file]
    +
    +	// Extract the package-level candidates using a quick parse.
    +	var g errgroup.Group
    +	for _, path := range paths {
    +		mp := known[golang.PackagePath(path)]
    +
    +		// For standard packages, build a filter of symbols that
    +		// are too new for the requesting file's Go version.
    +		var tooNew map[string]bool
    +		if syms, ok := stdlib.PackageSymbols[path]; ok && goversion != "" {
    +			tooNew = make(map[string]bool)
    +			for _, sym := range syms {
    +				if versions.Before(goversion, sym.Version.String()) {
    +					tooNew[sym.Name] = true
    +				}
    +			}
    +		}
    +
    +		for _, uri := range mp.CompiledGoFiles {
    +			uri := uri
    +			g.Go(func() error {
    +				return quickParse(uri, mp, tooNew)
    +			})
    +		}
    +	}
    +	if err := g.Wait(); err != nil {
    +		return err
    +	}
    +
    +	// In addition, we search in the module cache using goimports.
    +	ctx, cancel := context.WithCancel(ctx)
    +	var mu sync.Mutex
    +	add := func(pkgExport imports.PackageExport) {
    +		if ignoreUnimportedCompletion(pkgExport.Fix) {
    +			return
    +		}
    +
    +		mu.Lock()
    +		defer mu.Unlock()
    +		// TODO(adonovan): what if the actual package has a vendor/ prefix?
    +		if _, ok := known[golang.PackagePath(pkgExport.Fix.StmtInfo.ImportPath)]; ok {
    +			return // We got this one above.
    +		}
    +
    +		// Continue with untyped proposals.
    +		pkg := types.NewPackage(pkgExport.Fix.StmtInfo.ImportPath, pkgExport.Fix.IdentName)
    +		for _, symbol := range pkgExport.Exports {
    +			if goversion != "" && versions.Before(goversion, symbol.Version.String()) {
    +				continue // symbol too new for this file
    +			}
    +			score := unimportedScore(pkgExport.Fix.Relevance)
    +			c.deepState.enqueue(candidate{
    +				obj:   types.NewVar(0, pkg, symbol.Name, nil),
    +				score: score,
    +				imp: &importInfo{
    +					importPath: pkgExport.Fix.StmtInfo.ImportPath,
    +					name:       pkgExport.Fix.StmtInfo.Name,
    +				},
    +			})
    +		}
    +		if len(c.items) >= unimportedMemberTarget {
    +			cancel()
    +		}
    +	}
    +
    +	c.completionCallbacks = append(c.completionCallbacks, func(ctx context.Context, opts *imports.Options) error {
    +		defer cancel()
    +		return imports.GetPackageExports(ctx, add, id.Name, c.filename, c.pkg.Types().Name(), opts.Env)
    +	})
    +	return nil
    +}
    +
    +// unimportedScore returns a score for an unimported package that is generally
    +// lower than other candidates.
    +func unimportedScore(relevance float64) float64 {
    +	return (stdScore + .1*relevance) / 2
    +}
    +
    +func (c *completer) packageMembers(pkg *types.Package, score float64, imp *importInfo, cb func(candidate)) {
    +	scope := pkg.Scope()
    +	for _, name := range scope.Names() {
    +		obj := scope.Lookup(name)
    +		if c.tooNew(obj) {
    +			continue // std symbol too new for file's Go version
    +		}
    +		cb(candidate{
    +			obj:         obj,
    +			score:       score,
    +			imp:         imp,
    +			addressable: isVar(obj),
    +		})
    +	}
    +}
    +
    +// ignoreUnimportedCompletion reports whether an unimported completion
    +// resulting in the given import should be ignored.
    +func ignoreUnimportedCompletion(fix *imports.ImportFix) bool {
    +	// golang/go#60062: don't add unimported completion to golang.org/toolchain.
    +	return fix != nil && strings.HasPrefix(fix.StmtInfo.ImportPath, "golang.org/toolchain")
    +}
    +
    +func (c *completer) methodsAndFields(typ types.Type, addressable bool, imp *importInfo, cb func(candidate)) {
    +	mset := c.methodSetCache[methodSetKey{typ, addressable}]
    +	if mset == nil {
    +		if addressable && !types.IsInterface(typ) && !isPointer(typ) {
    +			// Add methods of *T, which includes methods with receiver T.
    +			mset = types.NewMethodSet(types.NewPointer(typ))
    +		} else {
    +			// Add methods of T.
    +			mset = types.NewMethodSet(typ)
    +		}
    +		c.methodSetCache[methodSetKey{typ, addressable}] = mset
    +	}
    +
    +	if isStarTestingDotF(typ) && addressable {
    +		// is that a sufficient test? (or is more care needed?)
    +		if c.fuzz(mset, imp, cb) {
    +			return
    +		}
    +	}
    +
    +	for i := 0; i < mset.Len(); i++ {
    +		obj := mset.At(i).Obj()
    +		// to the other side of the cb() queue?
    +		if c.tooNew(obj) {
    +			continue // std method too new for file's Go version
    +		}
    +		cb(candidate{
    +			obj:         mset.At(i).Obj(),
    +			score:       stdScore,
    +			imp:         imp,
    +			addressable: addressable || isPointer(typ),
    +		})
    +	}
    +
    +	// Add fields of T.
    +	eachField(typ, func(v *types.Var) {
    +		if c.tooNew(v) {
    +			return // std field too new for file's Go version
    +		}
    +		cb(candidate{
    +			obj:         v,
    +			score:       stdScore - 0.01,
    +			imp:         imp,
    +			addressable: addressable || isPointer(typ),
    +		})
    +	})
    +}
    +
    +// isStarTestingDotF reports whether typ is *testing.F.
    +func isStarTestingDotF(typ types.Type) bool {
    +	// No Unalias, since go test doesn't consider
    +	// types when enumeratinf test funcs, only syntax.
    +	ptr, _ := typ.(*types.Pointer)
    +	if ptr == nil {
    +		return false
    +	}
    +	named, _ := ptr.Elem().(*types.Named)
    +	if named == nil {
    +		return false
    +	}
    +	obj := named.Obj()
    +	// obj.Pkg is nil for the error type.
    +	return obj != nil && obj.Pkg() != nil && obj.Pkg().Path() == "testing" && obj.Name() == "F"
    +}
    +
    +// lexical finds completions in the lexical environment.
    +func (c *completer) lexical(ctx context.Context) error {
    +	var (
    +		builtinIota = types.Universe.Lookup("iota")
    +		builtinNil  = types.Universe.Lookup("nil")
    +
    +		// TODO(rfindley): only allow "comparable" where it is valid (in constraint
    +		// position or embedded in interface declarations).
    +		// builtinComparable = types.Universe.Lookup("comparable")
    +	)
    +
    +	// Track seen variables to avoid showing completions for shadowed variables.
    +	// This works since we look at scopes from innermost to outermost.
    +	seen := make(map[string]struct{})
    +
    +	// Process scopes innermost first.
    +	for i, scope := range c.scopes {
    +		if scope == nil {
    +			continue
    +		}
    +
    +	Names:
    +		for _, name := range scope.Names() {
    +			declScope, obj := scope.LookupParent(name, c.pos)
    +			if declScope != scope {
    +				continue // Name was declared in some enclosing scope, or not at all.
    +			}
    +
    +			// If obj's type is invalid, find the AST node that defines the lexical block
    +			// containing the declaration of obj. Don't resolve types for packages.
    +			if !isPkgName(obj) && !typeIsValid(obj.Type()) {
    +				// Match the scope to its ast.Node. If the scope is the package scope,
    +				// use the *ast.File as the starting node.
    +				var node ast.Node
    +				if i < len(c.path) {
    +					node = c.path[i]
    +				} else if i == len(c.path) { // use the *ast.File for package scope
    +					node = c.path[i-1]
    +				}
    +				if node != nil {
    +					if resolved := resolveInvalid(c.pkg.FileSet(), obj, node, c.pkg.TypesInfo()); resolved != nil {
    +						obj = resolved
    +					}
    +				}
    +			}
    +
    +			// Don't use LHS of decl in RHS.
    +			for _, ident := range enclosingDeclLHS(c.path) {
    +				if obj.Pos() == ident.Pos() {
    +					continue Names
    +				}
    +			}
    +
    +			// Don't suggest "iota" outside of const decls.
    +			if obj == builtinIota && !c.inConstDecl() {
    +				continue
    +			}
    +
    +			// Rank outer scopes lower than inner.
    +			score := stdScore * math.Pow(.99, float64(i))
    +
    +			// Dowrank "nil" a bit so it is ranked below more interesting candidates.
    +			if obj == builtinNil {
    +				score /= 2
    +			}
    +
    +			// If we haven't already added a candidate for an object with this name.
    +			if _, ok := seen[obj.Name()]; !ok {
    +				seen[obj.Name()] = struct{}{}
    +				c.deepState.enqueue(candidate{
    +					obj:         obj,
    +					score:       score,
    +					addressable: isVar(obj),
    +				})
    +			}
    +		}
    +	}
    +
    +	if c.inference.objType != nil {
    +		if named, ok := aliases.Unalias(typesinternal.Unpointer(c.inference.objType)).(*types.Named); ok {
    +			// If we expected a named type, check the type's package for
    +			// completion items. This is useful when the current file hasn't
    +			// imported the type's package yet.
    +
    +			if named.Obj() != nil && named.Obj().Pkg() != nil {
    +				pkg := named.Obj().Pkg()
    +
    +				// Make sure the package name isn't already in use by another
    +				// object, and that this file doesn't import the package yet.
    +				// TODO(adonovan): what if pkg.Path has vendor/ prefix?
    +				if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.Types() && !alreadyImports(c.file, golang.ImportPath(pkg.Path())) {
    +					seen[pkg.Name()] = struct{}{}
    +					obj := types.NewPkgName(0, nil, pkg.Name(), pkg)
    +					imp := &importInfo{
    +						importPath: pkg.Path(),
    +					}
    +					if imports.ImportPathToAssumedName(pkg.Path()) != pkg.Name() {
    +						imp.name = pkg.Name()
    +					}
    +					c.deepState.enqueue(candidate{
    +						obj:   obj,
    +						score: stdScore,
    +						imp:   imp,
    +					})
    +				}
    +			}
    +		}
    +	}
    +
    +	if c.opts.unimported {
    +		if err := c.unimportedPackages(ctx, seen); err != nil {
    +			return err
    +		}
    +	}
    +
    +	if c.inference.typeName.isTypeParam {
    +		// If we are completing a type param, offer each structural type.
    +		// This ensures we suggest "[]int" and "[]float64" for a constraint
    +		// with type union "[]int | []float64".
    +		if t, ok := c.inference.objType.(*types.Interface); ok {
    +			if terms, err := typeparams.InterfaceTermSet(t); err == nil {
    +				for _, term := range terms {
    +					c.injectType(ctx, term.Type())
    +				}
    +			}
    +		}
    +	} else {
    +		c.injectType(ctx, c.inference.objType)
    +	}
    +
    +	// Add keyword completion items appropriate in the current context.
    +	c.addKeywordCompletions()
    +
    +	return nil
    +}
    +
    +// injectType manufactures candidates based on the given type. This is
    +// intended for types not discoverable via lexical search, such as
    +// composite and/or generic types. For example, if the type is "[]int",
    +// this method makes sure you get candidates "[]int{}" and "[]int"
    +// (the latter applies when completing a type name).
    +func (c *completer) injectType(ctx context.Context, t types.Type) {
    +	if t == nil {
    +		return
    +	}
    +
    +	t = typesinternal.Unpointer(t)
    +
    +	// If we have an expected type and it is _not_ a named type, handle
    +	// it specially. Non-named types like "[]int" will never be
    +	// considered via a lexical search, so we need to directly inject
    +	// them. Also allow generic types since lexical search does not
    +	// infer instantiated versions of them.
    +	if named, ok := aliases.Unalias(t).(*types.Named); !ok || named.TypeParams().Len() > 0 {
    +		// If our expected type is "[]int", this will add a literal
    +		// candidate of "[]int{}".
    +		c.literal(ctx, t, nil)
    +
    +		if _, isBasic := t.(*types.Basic); !isBasic {
    +			// If we expect a non-basic type name (e.g. "[]int"), hack up
    +			// a named type whose name is literally "[]int". This allows
    +			// us to reuse our object based completion machinery.
    +			fakeNamedType := candidate{
    +				obj:   types.NewTypeName(token.NoPos, nil, types.TypeString(t, c.qf), t),
    +				score: stdScore,
    +			}
    +			// Make sure the type name matches before considering
    +			// candidate. This cuts down on useless candidates.
    +			if c.matchingTypeName(&fakeNamedType) {
    +				c.deepState.enqueue(fakeNamedType)
    +			}
    +		}
    +	}
    +}
    +
    +func (c *completer) unimportedPackages(ctx context.Context, seen map[string]struct{}) error {
    +	var prefix string
    +	if c.surrounding != nil {
    +		prefix = c.surrounding.Prefix()
    +	}
    +
    +	// Don't suggest unimported packages if we have absolutely nothing
    +	// to go on.
    +	if prefix == "" {
    +		return nil
    +	}
    +
    +	count := 0
    +
    +	// Search the forward transitive closure of the workspace.
    +	all, err := c.snapshot.AllMetadata(ctx)
    +	if err != nil {
    +		return err
    +	}
    +	pkgNameByPath := make(map[golang.PackagePath]string)
    +	var paths []string // actually PackagePaths
    +	for _, mp := range all {
    +		if mp.ForTest != "" {
    +			continue // skip all test variants
    +		}
    +		if mp.Name == "main" {
    +			continue // main is non-importable
    +		}
    +		if !strings.HasPrefix(string(mp.Name), prefix) {
    +			continue // not a match
    +		}
    +		paths = append(paths, string(mp.PkgPath))
    +		pkgNameByPath[mp.PkgPath] = string(mp.Name)
    +	}
    +
    +	// Rank candidates using goimports' algorithm.
    +	var relevances map[string]float64
    +	if len(paths) != 0 {
    +		if err := c.snapshot.RunProcessEnvFunc(ctx, func(ctx context.Context, opts *imports.Options) error {
    +			var err error
    +			relevances, err = imports.ScoreImportPaths(ctx, opts.Env, paths)
    +			return err
    +		}); err != nil {
    +			return err
    +		}
    +	}
    +	sort.Slice(paths, func(i, j int) bool {
    +		if relevances[paths[i]] != relevances[paths[j]] {
    +			return relevances[paths[i]] > relevances[paths[j]]
    +		}
    +
    +		// Fall back to lexical sort to keep truncated set of candidates
    +		// in a consistent order.
    +		return paths[i] < paths[j]
    +	})
    +
    +	for _, path := range paths {
    +		name := pkgNameByPath[golang.PackagePath(path)]
    +		if _, ok := seen[name]; ok {
    +			continue
    +		}
    +		imp := &importInfo{
    +			importPath: path,
    +		}
    +		if imports.ImportPathToAssumedName(path) != name {
    +			imp.name = name
    +		}
    +		if count >= maxUnimportedPackageNames {
    +			return nil
    +		}
    +		c.deepState.enqueue(candidate{
    +			// Pass an empty *types.Package to disable deep completions.
    +			obj:   types.NewPkgName(0, nil, name, types.NewPackage(path, name)),
    +			score: unimportedScore(relevances[path]),
    +			imp:   imp,
    +		})
    +		count++
    +	}
    +
    +	var mu sync.Mutex
    +	add := func(pkg imports.ImportFix) {
    +		if ignoreUnimportedCompletion(&pkg) {
    +			return
    +		}
    +		mu.Lock()
    +		defer mu.Unlock()
    +		if _, ok := seen[pkg.IdentName]; ok {
    +			return
    +		}
    +		if _, ok := relevances[pkg.StmtInfo.ImportPath]; ok {
    +			return
    +		}
    +
    +		if count >= maxUnimportedPackageNames {
    +			return
    +		}
    +
    +		// Do not add the unimported packages to seen, since we can have
    +		// multiple packages of the same name as completion suggestions, since
    +		// only one will be chosen.
    +		obj := types.NewPkgName(0, nil, pkg.IdentName, types.NewPackage(pkg.StmtInfo.ImportPath, pkg.IdentName))
    +		c.deepState.enqueue(candidate{
    +			obj:   obj,
    +			score: unimportedScore(pkg.Relevance),
    +			imp: &importInfo{
    +				importPath: pkg.StmtInfo.ImportPath,
    +				name:       pkg.StmtInfo.Name,
    +			},
    +		})
    +		count++
    +	}
    +
    +	c.completionCallbacks = append(c.completionCallbacks, func(ctx context.Context, opts *imports.Options) error {
    +		return imports.GetAllCandidates(ctx, add, prefix, c.filename, c.pkg.Types().Name(), opts.Env)
    +	})
    +
    +	return nil
    +}
    +
    +// alreadyImports reports whether f has an import with the specified path.
    +func alreadyImports(f *ast.File, path golang.ImportPath) bool {
    +	for _, s := range f.Imports {
    +		if metadata.UnquoteImportPath(s) == path {
    +			return true
    +		}
    +	}
    +	return false
    +}
    +
    +func (c *completer) inConstDecl() bool {
    +	for _, n := range c.path {
    +		if decl, ok := n.(*ast.GenDecl); ok && decl.Tok == token.CONST {
    +			return true
    +		}
    +	}
    +	return false
    +}
    +
    +// structLiteralFieldName finds completions for struct field names inside a struct literal.
    +func (c *completer) structLiteralFieldName(ctx context.Context) error {
    +	clInfo := c.enclosingCompositeLiteral
    +
    +	// Mark fields of the composite literal that have already been set,
    +	// except for the current field.
    +	addedFields := make(map[*types.Var]bool)
    +	for _, el := range clInfo.cl.Elts {
    +		if kvExpr, ok := el.(*ast.KeyValueExpr); ok {
    +			if clInfo.kv == kvExpr {
    +				continue
    +			}
    +
    +			if key, ok := kvExpr.Key.(*ast.Ident); ok {
    +				if used, ok := c.pkg.TypesInfo().Uses[key]; ok {
    +					if usedVar, ok := used.(*types.Var); ok {
    +						addedFields[usedVar] = true
    +					}
    +				}
    +			}
    +		}
    +	}
    +
    +	// Add struct fields.
    +	if t, ok := aliases.Unalias(clInfo.clType).(*types.Struct); ok {
    +		const deltaScore = 0.0001
    +		for i := 0; i < t.NumFields(); i++ {
    +			field := t.Field(i)
    +			if !addedFields[field] {
    +				c.deepState.enqueue(candidate{
    +					obj:   field,
    +					score: highScore - float64(i)*deltaScore,
    +				})
    +			}
    +		}
    +
    +		// Fall through and add lexical completions if we aren't
    +		// certain we are in the key part of a key-value pair.
    +		if !clInfo.maybeInFieldName {
    +			return nil
    +		}
    +	}
    +
    +	return c.lexical(ctx)
    +}
    +
    +// enclosingCompositeLiteral returns information about the composite literal enclosing the
    +// position.
    +func enclosingCompositeLiteral(path []ast.Node, pos token.Pos, info *types.Info) *compLitInfo {
    +	for _, n := range path {
    +		switch n := n.(type) {
    +		case *ast.CompositeLit:
    +			// The enclosing node will be a composite literal if the user has just
    +			// opened the curly brace (e.g. &x{<>) or the completion request is triggered
    +			// from an already completed composite literal expression (e.g. &x{foo: 1, <>})
    +			//
    +			// The position is not part of the composite literal unless it falls within the
    +			// curly braces (e.g. "foo.Foo<>Struct{}").
    +			if !(n.Lbrace < pos && pos <= n.Rbrace) {
    +				// Keep searching since we may yet be inside a composite literal.
    +				// For example "Foo{B: Ba<>{}}".
    +				break
    +			}
    +
    +			tv, ok := info.Types[n]
    +			if !ok {
    +				return nil
    +			}
    +
    +			clInfo := compLitInfo{
    +				cl:     n,
    +				clType: typesinternal.Unpointer(tv.Type).Underlying(),
    +			}
    +
    +			var (
    +				expr    ast.Expr
    +				hasKeys bool
    +			)
    +			for _, el := range n.Elts {
    +				// Remember the expression that the position falls in, if any.
    +				if el.Pos() <= pos && pos <= el.End() {
    +					expr = el
    +				}
    +
    +				if kv, ok := el.(*ast.KeyValueExpr); ok {
    +					hasKeys = true
    +					// If expr == el then we know the position falls in this expression,
    +					// so also record kv as the enclosing *ast.KeyValueExpr.
    +					if expr == el {
    +						clInfo.kv = kv
    +						break
    +					}
    +				}
    +			}
    +
    +			if clInfo.kv != nil {
    +				// If in a *ast.KeyValueExpr, we know we are in the key if the position
    +				// is to the left of the colon (e.g. "Foo{F<>: V}".
    +				clInfo.inKey = pos <= clInfo.kv.Colon
    +			} else if hasKeys {
    +				// If we aren't in a *ast.KeyValueExpr but the composite literal has
    +				// other *ast.KeyValueExprs, we must be on the key side of a new
    +				// *ast.KeyValueExpr (e.g. "Foo{F: V, <>}").
    +				clInfo.inKey = true
    +			} else {
    +				switch clInfo.clType.(type) {
    +				case *types.Struct:
    +					if len(n.Elts) == 0 {
    +						// If the struct literal is empty, next could be a struct field
    +						// name or an expression (e.g. "Foo{<>}" could become "Foo{F:}"
    +						// or "Foo{someVar}").
    +						clInfo.maybeInFieldName = true
    +					} else if len(n.Elts) == 1 {
    +						// If there is one expression and the position is in that expression
    +						// and the expression is an identifier, we may be writing a field
    +						// name or an expression (e.g. "Foo{F<>}").
    +						_, clInfo.maybeInFieldName = expr.(*ast.Ident)
    +					}
    +				case *types.Map:
    +					// If we aren't in a *ast.KeyValueExpr we must be adding a new key
    +					// to the map.
    +					clInfo.inKey = true
    +				}
    +			}
    +
    +			return &clInfo
    +		default:
    +			if breaksExpectedTypeInference(n, pos) {
    +				return nil
    +			}
    +		}
    +	}
    +
    +	return nil
    +}
    +
    +// enclosingFunction returns the signature and body of the function
    +// enclosing the given position.
    +func enclosingFunction(path []ast.Node, info *types.Info) *funcInfo {
    +	for _, node := range path {
    +		switch t := node.(type) {
    +		case *ast.FuncDecl:
    +			if obj, ok := info.Defs[t.Name]; ok {
    +				return &funcInfo{
    +					sig:  obj.Type().(*types.Signature),
    +					body: t.Body,
    +				}
    +			}
    +		case *ast.FuncLit:
    +			if typ, ok := info.Types[t]; ok {
    +				if sig, _ := typ.Type.(*types.Signature); sig == nil {
    +					// golang/go#49397: it should not be possible, but we somehow arrived
    +					// here with a non-signature type, most likely due to AST mangling
    +					// such that node.Type is not a FuncType.
    +					return nil
    +				}
    +				return &funcInfo{
    +					sig:  typ.Type.(*types.Signature),
    +					body: t.Body,
    +				}
    +			}
    +		}
    +	}
    +	return nil
    +}
    +
    +func (c *completer) expectedCompositeLiteralType() types.Type {
    +	clInfo := c.enclosingCompositeLiteral
    +	switch t := clInfo.clType.(type) {
    +	case *types.Slice:
    +		if clInfo.inKey {
    +			return types.Typ[types.UntypedInt]
    +		}
    +		return t.Elem()
    +	case *types.Array:
    +		if clInfo.inKey {
    +			return types.Typ[types.UntypedInt]
    +		}
    +		return t.Elem()
    +	case *types.Map:
    +		if clInfo.inKey {
    +			return t.Key()
    +		}
    +		return t.Elem()
    +	case *types.Struct:
    +		// If we are completing a key (i.e. field name), there is no expected type.
    +		if clInfo.inKey {
    +			return nil
    +		}
    +
    +		// If we are in a key-value pair, but not in the key, then we must be on the
    +		// value side. The expected type of the value will be determined from the key.
    +		if clInfo.kv != nil {
    +			if key, ok := clInfo.kv.Key.(*ast.Ident); ok {
    +				for i := 0; i < t.NumFields(); i++ {
    +					if field := t.Field(i); field.Name() == key.Name {
    +						return field.Type()
    +					}
    +				}
    +			}
    +		} else {
    +			// If we aren't in a key-value pair and aren't in the key, we must be using
    +			// implicit field names.
    +
    +			// The order of the literal fields must match the order in the struct definition.
    +			// Find the element that the position belongs to and suggest that field's type.
    +			if i := exprAtPos(c.pos, clInfo.cl.Elts); i < t.NumFields() {
    +				return t.Field(i).Type()
    +			}
    +		}
    +	}
    +	return nil
    +}
    +
    +// typeMod represents an operator that changes the expected type.
    +type typeMod struct {
    +	mod      typeModKind
    +	arrayLen int64
    +}
    +
    +type typeModKind int
    +
    +const (
    +	dereference   typeModKind = iota // pointer indirection: "*"
    +	reference                        // adds level of pointer: "&" for values, "*" for type names
    +	chanRead                         // channel read operator: "<-"
    +	sliceType                        // make a slice type: "[]" in "[]int"
    +	arrayType                        // make an array type: "[2]" in "[2]int"
    +	invoke                           // make a function call: "()" in "foo()"
    +	takeSlice                        // take slice of array: "[:]" in "foo[:]"
    +	takeDotDotDot                    // turn slice into variadic args: "..." in "foo..."
    +	index                            // index into slice/array: "[0]" in "foo[0]"
    +)
    +
    +type objKind int
    +
    +const (
    +	kindAny   objKind = 0
    +	kindArray objKind = 1 << iota
    +	kindSlice
    +	kindChan
    +	kindMap
    +	kindStruct
    +	kindString
    +	kindInt
    +	kindBool
    +	kindBytes
    +	kindPtr
    +	kindInterface
    +	kindFloat
    +	kindComplex
    +	kindError
    +	kindStringer
    +	kindFunc
    +	kindRange0Func
    +	kindRange1Func
    +	kindRange2Func
    +)
    +
    +// penalizedObj represents an object that should be disfavored as a
    +// completion candidate.
    +type penalizedObj struct {
    +	// objChain is the full "chain", e.g. "foo.bar().baz" becomes
    +	// []types.Object{foo, bar, baz}.
    +	objChain []types.Object
    +	// penalty is score penalty in the range (0, 1).
    +	penalty float64
    +}
    +
    +// candidateInference holds information we have inferred about a type that can be
    +// used at the current position.
    +type candidateInference struct {
    +	// objType is the desired type of an object used at the query position.
    +	objType types.Type
    +
    +	// objKind is a mask of expected kinds of types such as "map", "slice", etc.
    +	objKind objKind
    +
    +	// variadic is true if we are completing the initial variadic
    +	// parameter. For example:
    +	//   append([]T{}, <>)      // objType=T variadic=true
    +	//   append([]T{}, T{}, <>) // objType=T variadic=false
    +	variadic bool
    +
    +	// modifiers are prefixes such as "*", "&" or "<-" that influence how
    +	// a candidate type relates to the expected type.
    +	modifiers []typeMod
    +
    +	// convertibleTo is a type our candidate type must be convertible to.
    +	convertibleTo types.Type
    +
    +	// typeName holds information about the expected type name at
    +	// position, if any.
    +	typeName typeNameInference
    +
    +	// assignees are the types that would receive a function call's
    +	// results at the position. For example:
    +	//
    +	// foo := 123
    +	// foo, bar := <>
    +	//
    +	// at "<>", the assignees are [int, ].
    +	assignees []types.Type
    +
    +	// variadicAssignees is true if we could be completing an inner
    +	// function call that fills out an outer function call's variadic
    +	// params. For example:
    +	//
    +	// func foo(int, ...string) {}
    +	//
    +	// foo(<>)         // variadicAssignees=true
    +	// foo(bar<>)      // variadicAssignees=true
    +	// foo(bar, baz<>) // variadicAssignees=false
    +	variadicAssignees bool
    +
    +	// penalized holds expressions that should be disfavored as
    +	// candidates. For example, it tracks expressions already used in a
    +	// switch statement's other cases. Each expression is tracked using
    +	// its entire object "chain" allowing differentiation between
    +	// "a.foo" and "b.foo" when "a" and "b" are the same type.
    +	penalized []penalizedObj
    +
    +	// objChain contains the chain of objects representing the
    +	// surrounding *ast.SelectorExpr. For example, if we are completing
    +	// "foo.bar.ba<>", objChain will contain []types.Object{foo, bar}.
    +	objChain []types.Object
    +}
    +
    +// typeNameInference holds information about the expected type name at
    +// position.
    +type typeNameInference struct {
    +	// wantTypeName is true if we expect the name of a type.
    +	wantTypeName bool
    +
    +	// modifiers are prefixes such as "*", "&" or "<-" that influence how
    +	// a candidate type relates to the expected type.
    +	modifiers []typeMod
    +
    +	// assertableFrom is a type that must be assertable to our candidate type.
    +	assertableFrom types.Type
    +
    +	// wantComparable is true if we want a comparable type.
    +	wantComparable bool
    +
    +	// seenTypeSwitchCases tracks types that have already been used by
    +	// the containing type switch.
    +	seenTypeSwitchCases []types.Type
    +
    +	// compLitType is true if we are completing a composite literal type
    +	// name, e.g "foo<>{}".
    +	compLitType bool
    +
    +	// isTypeParam is true if we are completing a type instantiation parameter
    +	isTypeParam bool
    +}
    +
    +// expectedCandidate returns information about the expected candidate
    +// for an expression at the query position.
    +func expectedCandidate(ctx context.Context, c *completer) (inf candidateInference) {
    +	inf.typeName = expectTypeName(c)
    +
    +	if c.enclosingCompositeLiteral != nil {
    +		inf.objType = c.expectedCompositeLiteralType()
    +	}
    +
    +Nodes:
    +	for i, node := range c.path {
    +		switch node := node.(type) {
    +		case *ast.BinaryExpr:
    +			// Determine if query position comes from left or right of op.
    +			e := node.X
    +			if c.pos < node.OpPos {
    +				e = node.Y
    +			}
    +			if tv, ok := c.pkg.TypesInfo().Types[e]; ok {
    +				switch node.Op {
    +				case token.LAND, token.LOR:
    +					// Don't infer "bool" type for "&&" or "||". Often you want
    +					// to compose a boolean expression from non-boolean
    +					// candidates.
    +				default:
    +					inf.objType = tv.Type
    +				}
    +				break Nodes
    +			}
    +		case *ast.AssignStmt:
    +			// Only rank completions if you are on the right side of the token.
    +			if c.pos > node.TokPos {
    +				i := exprAtPos(c.pos, node.Rhs)
    +				if i >= len(node.Lhs) {
    +					i = len(node.Lhs) - 1
    +				}
    +				if tv, ok := c.pkg.TypesInfo().Types[node.Lhs[i]]; ok {
    +					inf.objType = tv.Type
    +				}
    +
    +				// If we have a single expression on the RHS, record the LHS
    +				// assignees so we can favor multi-return function calls with
    +				// matching result values.
    +				if len(node.Rhs) <= 1 {
    +					for _, lhs := range node.Lhs {
    +						inf.assignees = append(inf.assignees, c.pkg.TypesInfo().TypeOf(lhs))
    +					}
    +				} else {
    +					// Otherwise, record our single assignee, even if its type is
    +					// not available. We use this info to downrank functions
    +					// with the wrong number of result values.
    +					inf.assignees = append(inf.assignees, c.pkg.TypesInfo().TypeOf(node.Lhs[i]))
    +				}
    +			}
    +			return inf
    +		case *ast.ValueSpec:
    +			if node.Type != nil && c.pos > node.Type.End() {
    +				inf.objType = c.pkg.TypesInfo().TypeOf(node.Type)
    +			}
    +			return inf
    +		case *ast.CallExpr:
    +			// Only consider CallExpr args if position falls between parens.
    +			if node.Lparen < c.pos && c.pos <= node.Rparen {
    +				// For type conversions like "int64(foo)" we can only infer our
    +				// desired type is convertible to int64.
    +				if typ := typeConversion(node, c.pkg.TypesInfo()); typ != nil {
    +					inf.convertibleTo = typ
    +					break Nodes
    +				}
    +
    +				sig, _ := c.pkg.TypesInfo().Types[node.Fun].Type.(*types.Signature)
    +
    +				if sig != nil && sig.TypeParams().Len() > 0 {
    +					// If we are completing a generic func call, re-check the call expression.
    +					// This allows type param inference to work in cases like:
    +					//
    +					// func foo[T any](T) {}
    +					// foo[int](<>) // <- get "int" completions instead of "T"
    +					//
    +					// TODO: remove this after https://go.dev/issue/52503
    +					info := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
    +					types.CheckExpr(c.pkg.FileSet(), c.pkg.Types(), node.Fun.Pos(), node.Fun, info)
    +					sig, _ = info.Types[node.Fun].Type.(*types.Signature)
    +				}
    +
    +				if sig != nil {
    +					inf = c.expectedCallParamType(inf, node, sig)
    +				}
    +
    +				if funIdent, ok := node.Fun.(*ast.Ident); ok {
    +					obj := c.pkg.TypesInfo().ObjectOf(funIdent)
    +
    +					if obj != nil && obj.Parent() == types.Universe {
    +						// Defer call to builtinArgType so we can provide it the
    +						// inferred type from its parent node.
    +						defer func() {
    +							inf = c.builtinArgType(obj, node, inf)
    +							inf.objKind = c.builtinArgKind(ctx, obj, node)
    +						}()
    +
    +						// The expected type of builtin arguments like append() is
    +						// the expected type of the builtin call itself. For
    +						// example:
    +						//
    +						// var foo []int = append(<>)
    +						//
    +						// To find the expected type at <> we "skip" the append()
    +						// node and get the expected type one level up, which is
    +						// []int.
    +						continue Nodes
    +					}
    +				}
    +
    +				return inf
    +			}
    +		case *ast.ReturnStmt:
    +			if c.enclosingFunc != nil {
    +				sig := c.enclosingFunc.sig
    +				// Find signature result that corresponds to our return statement.
    +				if resultIdx := exprAtPos(c.pos, node.Results); resultIdx < len(node.Results) {
    +					if resultIdx < sig.Results().Len() {
    +						inf.objType = sig.Results().At(resultIdx).Type()
    +					}
    +				}
    +			}
    +			return inf
    +		case *ast.CaseClause:
    +			if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, node).(*ast.SwitchStmt); ok {
    +				if tv, ok := c.pkg.TypesInfo().Types[swtch.Tag]; ok {
    +					inf.objType = tv.Type
    +
    +					// Record which objects have already been used in the case
    +					// statements so we don't suggest them again.
    +					for _, cc := range swtch.Body.List {
    +						for _, caseExpr := range cc.(*ast.CaseClause).List {
    +							// Don't record the expression we are currently completing.
    +							if caseExpr.Pos() < c.pos && c.pos <= caseExpr.End() {
    +								continue
    +							}
    +
    +							if objs := objChain(c.pkg.TypesInfo(), caseExpr); len(objs) > 0 {
    +								inf.penalized = append(inf.penalized, penalizedObj{objChain: objs, penalty: 0.1})
    +							}
    +						}
    +					}
    +				}
    +			}
    +			return inf
    +		case *ast.SliceExpr:
    +			// Make sure position falls within the brackets (e.g. "foo[a:<>]").
    +			if node.Lbrack < c.pos && c.pos <= node.Rbrack {
    +				inf.objType = types.Typ[types.UntypedInt]
    +			}
    +			return inf
    +		case *ast.IndexExpr:
    +			// Make sure position falls within the brackets (e.g. "foo[<>]").
    +			if node.Lbrack < c.pos && c.pos <= node.Rbrack {
    +				if tv, ok := c.pkg.TypesInfo().Types[node.X]; ok {
    +					switch t := tv.Type.Underlying().(type) {
    +					case *types.Map:
    +						inf.objType = t.Key()
    +					case *types.Slice, *types.Array:
    +						inf.objType = types.Typ[types.UntypedInt]
    +					}
    +
    +					if ct := expectedConstraint(tv.Type, 0); ct != nil {
    +						inf.objType = ct
    +						inf.typeName.wantTypeName = true
    +						inf.typeName.isTypeParam = true
    +					}
    +				}
    +			}
    +			return inf
    +		case *ast.IndexListExpr:
    +			if node.Lbrack < c.pos && c.pos <= node.Rbrack {
    +				if tv, ok := c.pkg.TypesInfo().Types[node.X]; ok {
    +					if ct := expectedConstraint(tv.Type, exprAtPos(c.pos, node.Indices)); ct != nil {
    +						inf.objType = ct
    +						inf.typeName.wantTypeName = true
    +						inf.typeName.isTypeParam = true
    +					}
    +				}
    +			}
    +			return inf
    +		case *ast.SendStmt:
    +			// Make sure we are on right side of arrow (e.g. "foo <- <>").
    +			if c.pos > node.Arrow+1 {
    +				if tv, ok := c.pkg.TypesInfo().Types[node.Chan]; ok {
    +					if ch, ok := tv.Type.Underlying().(*types.Chan); ok {
    +						inf.objType = ch.Elem()
    +					}
    +				}
    +			}
    +			return inf
    +		case *ast.RangeStmt:
    +			if goplsastutil.NodeContains(node.X, c.pos) {
    +				inf.objKind |= kindSlice | kindArray | kindMap | kindString
    +				if node.Key == nil && node.Value == nil {
    +					inf.objKind |= kindRange0Func | kindRange1Func | kindRange2Func
    +				} else if node.Value == nil {
    +					inf.objKind |= kindChan | kindRange1Func | kindRange2Func
    +				} else {
    +					inf.objKind |= kindRange2Func
    +				}
    +			}
    +			return inf
    +		case *ast.StarExpr:
    +			inf.modifiers = append(inf.modifiers, typeMod{mod: dereference})
    +		case *ast.UnaryExpr:
    +			switch node.Op {
    +			case token.AND:
    +				inf.modifiers = append(inf.modifiers, typeMod{mod: reference})
    +			case token.ARROW:
    +				inf.modifiers = append(inf.modifiers, typeMod{mod: chanRead})
    +			}
    +		case *ast.DeferStmt, *ast.GoStmt:
    +			inf.objKind |= kindFunc
    +			return inf
    +		default:
    +			if breaksExpectedTypeInference(node, c.pos) {
    +				return inf
    +			}
    +		}
    +	}
    +
    +	return inf
    +}
    +
    +func (c *completer) expectedCallParamType(inf candidateInference, node *ast.CallExpr, sig *types.Signature) candidateInference {
    +	numParams := sig.Params().Len()
    +	if numParams == 0 {
    +		return inf
    +	}
    +
    +	exprIdx := exprAtPos(c.pos, node.Args)
    +
    +	// If we have one or zero arg expressions, we may be
    +	// completing to a function call that returns multiple
    +	// values, in turn getting passed in to the surrounding
    +	// call. Record the assignees so we can favor function
    +	// calls that return matching values.
    +	if len(node.Args) <= 1 && exprIdx == 0 {
    +		for i := 0; i < sig.Params().Len(); i++ {
    +			inf.assignees = append(inf.assignees, sig.Params().At(i).Type())
    +		}
    +
    +		// Record that we may be completing into variadic parameters.
    +		inf.variadicAssignees = sig.Variadic()
    +	}
    +
    +	// Make sure not to run past the end of expected parameters.
    +	if exprIdx >= numParams {
    +		inf.objType = sig.Params().At(numParams - 1).Type()
    +	} else {
    +		inf.objType = sig.Params().At(exprIdx).Type()
    +	}
    +
    +	if sig.Variadic() && exprIdx >= (numParams-1) {
    +		// If we are completing a variadic param, deslice the variadic type.
    +		inf.objType = deslice(inf.objType)
    +		// Record whether we are completing the initial variadic param.
    +		inf.variadic = exprIdx == numParams-1 && len(node.Args) <= numParams
    +
    +		// Check if we can infer object kind from printf verb.
    +		inf.objKind |= printfArgKind(c.pkg.TypesInfo(), node, exprIdx)
    +	}
    +
    +	// If our expected type is an uninstantiated generic type param,
    +	// swap to the constraint which will do a decent job filtering
    +	// candidates.
    +	if tp, _ := inf.objType.(*types.TypeParam); tp != nil {
    +		inf.objType = tp.Constraint()
    +	}
    +
    +	return inf
    +}
    +
    +func expectedConstraint(t types.Type, idx int) types.Type {
    +	var tp *types.TypeParamList
    +	if named, _ := t.(*types.Named); named != nil {
    +		tp = named.TypeParams()
    +	} else if sig, _ := t.Underlying().(*types.Signature); sig != nil {
    +		tp = sig.TypeParams()
    +	}
    +	if tp == nil || idx >= tp.Len() {
    +		return nil
    +	}
    +	return tp.At(idx).Constraint()
    +}
    +
    +// objChain decomposes e into a chain of objects if possible. For
    +// example, "foo.bar().baz" will yield []types.Object{foo, bar, baz}.
    +// If any part can't be turned into an object, return nil.
    +func objChain(info *types.Info, e ast.Expr) []types.Object {
    +	var objs []types.Object
    +
    +	for e != nil {
    +		switch n := e.(type) {
    +		case *ast.Ident:
    +			obj := info.ObjectOf(n)
    +			if obj == nil {
    +				return nil
    +			}
    +			objs = append(objs, obj)
    +			e = nil
    +		case *ast.SelectorExpr:
    +			obj := info.ObjectOf(n.Sel)
    +			if obj == nil {
    +				return nil
    +			}
    +			objs = append(objs, obj)
    +			e = n.X
    +		case *ast.CallExpr:
    +			if len(n.Args) > 0 {
    +				return nil
    +			}
    +			e = n.Fun
    +		default:
    +			return nil
    +		}
    +	}
    +
    +	// Reverse order so the layout matches the syntactic order.
    +	for i := 0; i < len(objs)/2; i++ {
    +		objs[i], objs[len(objs)-1-i] = objs[len(objs)-1-i], objs[i]
    +	}
    +
    +	return objs
    +}
    +
    +// applyTypeModifiers applies the list of type modifiers to a type.
    +// It returns nil if the modifiers could not be applied.
    +func (ci candidateInference) applyTypeModifiers(typ types.Type, addressable bool) types.Type {
    +	for _, mod := range ci.modifiers {
    +		switch mod.mod {
    +		case dereference:
    +			// For every "*" indirection operator, remove a pointer layer
    +			// from candidate type.
    +			if ptr, ok := typ.Underlying().(*types.Pointer); ok {
    +				typ = ptr.Elem()
    +			} else {
    +				return nil
    +			}
    +		case reference:
    +			// For every "&" address operator, add another pointer layer to
    +			// candidate type, if the candidate is addressable.
    +			if addressable {
    +				typ = types.NewPointer(typ)
    +			} else {
    +				return nil
    +			}
    +		case chanRead:
    +			// For every "<-" operator, remove a layer of channelness.
    +			if ch, ok := typ.(*types.Chan); ok {
    +				typ = ch.Elem()
    +			} else {
    +				return nil
    +			}
    +		}
    +	}
    +
    +	return typ
    +}
    +
    +// applyTypeNameModifiers applies the list of type modifiers to a type name.
    +func (ci candidateInference) applyTypeNameModifiers(typ types.Type) types.Type {
    +	for _, mod := range ci.typeName.modifiers {
    +		switch mod.mod {
    +		case reference:
    +			typ = types.NewPointer(typ)
    +		case arrayType:
    +			typ = types.NewArray(typ, mod.arrayLen)
    +		case sliceType:
    +			typ = types.NewSlice(typ)
    +		}
    +	}
    +	return typ
    +}
    +
    +// matchesVariadic returns true if we are completing a variadic
    +// parameter and candType is a compatible slice type.
    +func (ci candidateInference) matchesVariadic(candType types.Type) bool {
    +	return ci.variadic && ci.objType != nil && assignableTo(candType, types.NewSlice(ci.objType))
    +}
    +
    +// findSwitchStmt returns an *ast.CaseClause's corresponding *ast.SwitchStmt or
    +// *ast.TypeSwitchStmt. path should start from the case clause's first ancestor.
    +func findSwitchStmt(path []ast.Node, pos token.Pos, c *ast.CaseClause) ast.Stmt {
    +	// Make sure position falls within a "case <>:" clause.
    +	if exprAtPos(pos, c.List) >= len(c.List) {
    +		return nil
    +	}
    +	// A case clause is always nested within a block statement in a switch statement.
    +	if len(path) < 2 {
    +		return nil
    +	}
    +	if _, ok := path[0].(*ast.BlockStmt); !ok {
    +		return nil
    +	}
    +	switch s := path[1].(type) {
    +	case *ast.SwitchStmt:
    +		return s
    +	case *ast.TypeSwitchStmt:
    +		return s
    +	default:
    +		return nil
    +	}
    +}
    +
    +// breaksExpectedTypeInference reports if an expression node's type is unrelated
    +// to its child expression node types. For example, "Foo{Bar: x.Baz(<>)}" should
    +// expect a function argument, not a composite literal value.
    +func breaksExpectedTypeInference(n ast.Node, pos token.Pos) bool {
    +	switch n := n.(type) {
    +	case *ast.CompositeLit:
    +		// Doesn't break inference if pos is in type name.
    +		// For example: "Foo<>{Bar: 123}"
    +		return n.Type == nil || !goplsastutil.NodeContains(n.Type, pos)
    +	case *ast.CallExpr:
    +		// Doesn't break inference if pos is in func name.
    +		// For example: "Foo<>(123)"
    +		return !goplsastutil.NodeContains(n.Fun, pos)
    +	case *ast.FuncLit, *ast.IndexExpr, *ast.SliceExpr:
    +		return true
    +	default:
    +		return false
    +	}
    +}
    +
    +// expectTypeName returns information about the expected type name at position.
    +func expectTypeName(c *completer) typeNameInference {
    +	var inf typeNameInference
    +
    +Nodes:
    +	for i, p := range c.path {
    +		switch n := p.(type) {
    +		case *ast.FieldList:
    +			// Expect a type name if pos is in a FieldList. This applies to
    +			// FuncType params/results, FuncDecl receiver, StructType, and
    +			// InterfaceType. We don't need to worry about the field name
    +			// because completion bails out early if pos is in an *ast.Ident
    +			// that defines an object.
    +			inf.wantTypeName = true
    +			break Nodes
    +		case *ast.CaseClause:
    +			// Expect type names in type switch case clauses.
    +			if swtch, ok := findSwitchStmt(c.path[i+1:], c.pos, n).(*ast.TypeSwitchStmt); ok {
    +				// The case clause types must be assertable from the type switch parameter.
    +				ast.Inspect(swtch.Assign, func(n ast.Node) bool {
    +					if ta, ok := n.(*ast.TypeAssertExpr); ok {
    +						inf.assertableFrom = c.pkg.TypesInfo().TypeOf(ta.X)
    +						return false
    +					}
    +					return true
    +				})
    +				inf.wantTypeName = true
    +
    +				// Track the types that have already been used in this
    +				// switch's case statements so we don't recommend them.
    +				for _, e := range swtch.Body.List {
    +					for _, typeExpr := range e.(*ast.CaseClause).List {
    +						// Skip if type expression contains pos. We don't want to
    +						// count it as already used if the user is completing it.
    +						if typeExpr.Pos() < c.pos && c.pos <= typeExpr.End() {
    +							continue
    +						}
    +
    +						if t := c.pkg.TypesInfo().TypeOf(typeExpr); t != nil {
    +							inf.seenTypeSwitchCases = append(inf.seenTypeSwitchCases, t)
    +						}
    +					}
    +				}
    +
    +				break Nodes
    +			}
    +			return typeNameInference{}
    +		case *ast.TypeAssertExpr:
    +			// Expect type names in type assert expressions.
    +			if n.Lparen < c.pos && c.pos <= n.Rparen {
    +				// The type in parens must be assertable from the expression type.
    +				inf.assertableFrom = c.pkg.TypesInfo().TypeOf(n.X)
    +				inf.wantTypeName = true
    +				break Nodes
    +			}
    +			return typeNameInference{}
    +		case *ast.StarExpr:
    +			inf.modifiers = append(inf.modifiers, typeMod{mod: reference})
    +		case *ast.CompositeLit:
    +			// We want a type name if position is in the "Type" part of a
    +			// composite literal (e.g. "Foo<>{}").
    +			if n.Type != nil && n.Type.Pos() <= c.pos && c.pos <= n.Type.End() {
    +				inf.wantTypeName = true
    +				inf.compLitType = true
    +
    +				if i < len(c.path)-1 {
    +					// Track preceding "&" operator. Technically it applies to
    +					// the composite literal and not the type name, but if
    +					// affects our type completion nonetheless.
    +					if u, ok := c.path[i+1].(*ast.UnaryExpr); ok && u.Op == token.AND {
    +						inf.modifiers = append(inf.modifiers, typeMod{mod: reference})
    +					}
    +				}
    +			}
    +			break Nodes
    +		case *ast.ArrayType:
    +			// If we are inside the "Elt" part of an array type, we want a type name.
    +			if n.Elt.Pos() <= c.pos && c.pos <= n.Elt.End() {
    +				inf.wantTypeName = true
    +				if n.Len == nil {
    +					// No "Len" expression means a slice type.
    +					inf.modifiers = append(inf.modifiers, typeMod{mod: sliceType})
    +				} else {
    +					// Try to get the array type using the constant value of "Len".
    +					tv, ok := c.pkg.TypesInfo().Types[n.Len]
    +					if ok && tv.Value != nil && tv.Value.Kind() == constant.Int {
    +						if arrayLen, ok := constant.Int64Val(tv.Value); ok {
    +							inf.modifiers = append(inf.modifiers, typeMod{mod: arrayType, arrayLen: arrayLen})
    +						}
    +					}
    +				}
    +
    +				// ArrayTypes can be nested, so keep going if our parent is an
    +				// ArrayType.
    +				if i < len(c.path)-1 {
    +					if _, ok := c.path[i+1].(*ast.ArrayType); ok {
    +						continue Nodes
    +					}
    +				}
    +
    +				break Nodes
    +			}
    +		case *ast.MapType:
    +			inf.wantTypeName = true
    +			if n.Key != nil {
    +				inf.wantComparable = goplsastutil.NodeContains(n.Key, c.pos)
    +			} else {
    +				// If the key is empty, assume we are completing the key if
    +				// pos is directly after the "map[".
    +				inf.wantComparable = c.pos == n.Pos()+token.Pos(len("map["))
    +			}
    +			break Nodes
    +		case *ast.ValueSpec:
    +			inf.wantTypeName = n.Type != nil && goplsastutil.NodeContains(n.Type, c.pos)
    +			break Nodes
    +		case *ast.TypeSpec:
    +			inf.wantTypeName = goplsastutil.NodeContains(n.Type, c.pos)
    +		default:
    +			if breaksExpectedTypeInference(p, c.pos) {
    +				return typeNameInference{}
    +			}
    +		}
    +	}
    +
    +	return inf
    +}
    +
    +func (c *completer) fakeObj(T types.Type) *types.Var {
    +	return types.NewVar(token.NoPos, c.pkg.Types(), "", T)
    +}
    +
    +// derivableTypes iterates types you can derive from t. For example,
    +// from "foo" we might derive "&foo", and "foo()".
    +func derivableTypes(t types.Type, addressable bool, f func(t types.Type, addressable bool, mod typeModKind) bool) bool {
    +	switch t := t.Underlying().(type) {
    +	case *types.Signature:
    +		// If t is a func type with a single result, offer the result type.
    +		if t.Results().Len() == 1 && f(t.Results().At(0).Type(), false, invoke) {
    +			return true
    +		}
    +	case *types.Array:
    +		if f(t.Elem(), true, index) {
    +			return true
    +		}
    +		// Try converting array to slice.
    +		if f(types.NewSlice(t.Elem()), false, takeSlice) {
    +			return true
    +		}
    +	case *types.Pointer:
    +		if f(t.Elem(), false, dereference) {
    +			return true
    +		}
    +	case *types.Slice:
    +		if f(t.Elem(), true, index) {
    +			return true
    +		}
    +	case *types.Map:
    +		if f(t.Elem(), false, index) {
    +			return true
    +		}
    +	case *types.Chan:
    +		if f(t.Elem(), false, chanRead) {
    +			return true
    +		}
    +	}
    +
    +	// Check if c is addressable and a pointer to c matches our type inference.
    +	if addressable && f(types.NewPointer(t), false, reference) {
    +		return true
    +	}
    +
    +	return false
    +}
    +
    +// anyCandType reports whether f returns true for any candidate type
    +// derivable from c. It searches up to three levels of type
    +// modification. For example, given "foo" we could discover "***foo"
    +// or "*foo()".
    +func (c *candidate) anyCandType(f func(t types.Type, addressable bool) bool) bool {
    +	if c.obj == nil || c.obj.Type() == nil {
    +		return false
    +	}
    +
    +	const maxDepth = 3
    +
    +	var searchTypes func(t types.Type, addressable bool, mods []typeModKind) bool
    +	searchTypes = func(t types.Type, addressable bool, mods []typeModKind) bool {
    +		if f(t, addressable) {
    +			if len(mods) > 0 {
    +				newMods := make([]typeModKind, len(mods)+len(c.mods))
    +				copy(newMods, mods)
    +				copy(newMods[len(mods):], c.mods)
    +				c.mods = newMods
    +			}
    +			return true
    +		}
    +
    +		if len(mods) == maxDepth {
    +			return false
    +		}
    +
    +		return derivableTypes(t, addressable, func(t types.Type, addressable bool, mod typeModKind) bool {
    +			return searchTypes(t, addressable, append(mods, mod))
    +		})
    +	}
    +
    +	return searchTypes(c.obj.Type(), c.addressable, make([]typeModKind, 0, maxDepth))
    +}
    +
    +// matchingCandidate reports whether cand matches our type inferences.
    +// It mutates cand's score in certain cases.
    +func (c *completer) matchingCandidate(cand *candidate) bool {
    +	if c.completionContext.commentCompletion {
    +		return false
    +	}
    +
    +	// Bail out early if we are completing a field name in a composite literal.
    +	if v, ok := cand.obj.(*types.Var); ok && v.IsField() && c.wantStructFieldCompletions() {
    +		return true
    +	}
    +
    +	if isTypeName(cand.obj) {
    +		return c.matchingTypeName(cand)
    +	} else if c.wantTypeName() {
    +		// If we want a type, a non-type object never matches.
    +		return false
    +	}
    +
    +	if c.inference.candTypeMatches(cand) {
    +		return true
    +	}
    +
    +	candType := cand.obj.Type()
    +	if candType == nil {
    +		return false
    +	}
    +
    +	if sig, ok := candType.Underlying().(*types.Signature); ok {
    +		if c.inference.assigneesMatch(cand, sig) {
    +			// Invoke the candidate if its results are multi-assignable.
    +			cand.mods = append(cand.mods, invoke)
    +			return true
    +		}
    +	}
    +
    +	// Default to invoking *types.Func candidates. This is so function
    +	// completions in an empty statement (or other cases with no expected type)
    +	// are invoked by default.
    +	if isFunc(cand.obj) {
    +		cand.mods = append(cand.mods, invoke)
    +	}
    +
    +	return false
    +}
    +
    +// candTypeMatches reports whether cand makes a good completion
    +// candidate given the candidate inference. cand's score may be
    +// mutated to downrank the candidate in certain situations.
    +func (ci *candidateInference) candTypeMatches(cand *candidate) bool {
    +	var (
    +		expTypes     = make([]types.Type, 0, 2)
    +		variadicType types.Type
    +	)
    +	if ci.objType != nil {
    +		expTypes = append(expTypes, ci.objType)
    +
    +		if ci.variadic {
    +			variadicType = types.NewSlice(ci.objType)
    +			expTypes = append(expTypes, variadicType)
    +		}
    +	}
    +
    +	return cand.anyCandType(func(candType types.Type, addressable bool) bool {
    +		// Take into account any type modifiers on the expected type.
    +		candType = ci.applyTypeModifiers(candType, addressable)
    +		if candType == nil {
    +			return false
    +		}
    +
    +		if ci.convertibleTo != nil && convertibleTo(candType, ci.convertibleTo) {
    +			return true
    +		}
    +
    +		for _, expType := range expTypes {
    +			if isEmptyInterface(expType) {
    +				// If any type matches the expected type, fall back to other
    +				// considerations below.
    +				//
    +				// TODO(rfindley): can this be expressed via scoring, rather than a boolean?
    +				// Why is it the case that we break ties for the empty interface, but
    +				// not for other expected types that may be satisfied by a lot of
    +				// types, such as fmt.Stringer?
    +				continue
    +			}
    +
    +			matches := ci.typeMatches(expType, candType)
    +			if !matches {
    +				// If candType doesn't otherwise match, consider if we can
    +				// convert candType directly to expType.
    +				if considerTypeConversion(candType, expType, cand.path) {
    +					cand.convertTo = expType
    +					// Give a major score penalty so we always prefer directly
    +					// assignable candidates, all else equal.
    +					cand.score *= 0.5
    +					return true
    +				}
    +
    +				continue
    +			}
    +
    +			if expType == variadicType {
    +				cand.mods = append(cand.mods, takeDotDotDot)
    +			}
    +
    +			// Lower candidate score for untyped conversions. This avoids
    +			// ranking untyped constants above candidates with an exact type
    +			// match. Don't lower score of builtin constants, e.g. "true".
    +			if isUntyped(candType) && !types.Identical(candType, expType) && cand.obj.Parent() != types.Universe {
    +				// Bigger penalty for deep completions into other packages to
    +				// avoid random constants from other packages popping up all
    +				// the time.
    +				if len(cand.path) > 0 && isPkgName(cand.path[0]) {
    +					cand.score *= 0.5
    +				} else {
    +					cand.score *= 0.75
    +				}
    +			}
    +
    +			return true
    +		}
    +
    +		// If we don't have a specific expected type, fall back to coarser
    +		// object kind checks.
    +		if ci.objType == nil || isEmptyInterface(ci.objType) {
    +			// If we were able to apply type modifiers to our candidate type,
    +			// count that as a match. For example:
    +			//
    +			//   var foo chan int
    +			//   <-fo<>
    +			//
    +			// We were able to apply the "<-" type modifier to "foo", so "foo"
    +			// matches.
    +			if len(ci.modifiers) > 0 {
    +				return true
    +			}
    +
    +			// If we didn't have an exact type match, check if our object kind
    +			// matches.
    +			if ci.kindMatches(candType) {
    +				if ci.objKind == kindFunc {
    +					cand.mods = append(cand.mods, invoke)
    +				}
    +				return true
    +			}
    +		}
    +
    +		return false
    +	})
    +}
    +
    +// considerTypeConversion returns true if we should offer a completion
    +// automatically converting "from" to "to".
    +func considerTypeConversion(from, to types.Type, path []types.Object) bool {
    +	// Don't offer to convert deep completions from other packages.
    +	// Otherwise there are many random package level consts/vars that
    +	// pop up as candidates all the time.
    +	if len(path) > 0 && isPkgName(path[0]) {
    +		return false
    +	}
    +
    +	if _, ok := from.(*types.TypeParam); ok {
    +		return false
    +	}
    +
    +	if !convertibleTo(from, to) {
    +		return false
    +	}
    +
    +	// Don't offer to convert ints to strings since that probably
    +	// doesn't do what the user wants.
    +	if isBasicKind(from, types.IsInteger) && isBasicKind(to, types.IsString) {
    +		return false
    +	}
    +
    +	return true
    +}
    +
    +// typeMatches reports whether an object of candType makes a good
    +// completion candidate given the expected type expType.
    +func (ci *candidateInference) typeMatches(expType, candType types.Type) bool {
    +	// Handle untyped values specially since AssignableTo gives false negatives
    +	// for them (see https://golang.org/issue/32146).
    +	if candBasic, ok := candType.Underlying().(*types.Basic); ok {
    +		if expBasic, ok := expType.Underlying().(*types.Basic); ok {
    +			// Note that the candidate and/or the expected can be untyped.
    +			// In "fo<> == 100" the expected type is untyped, and the
    +			// candidate could also be an untyped constant.
    +
    +			// Sort by is_untyped and then by is_int to simplify below logic.
    +			a, b := candBasic.Info(), expBasic.Info()
    +			if a&types.IsUntyped == 0 || (b&types.IsInteger > 0 && b&types.IsUntyped > 0) {
    +				a, b = b, a
    +			}
    +
    +			// If at least one is untyped...
    +			if a&types.IsUntyped > 0 {
    +				switch {
    +				// Untyped integers are compatible with floats.
    +				case a&types.IsInteger > 0 && b&types.IsFloat > 0:
    +					return true
    +
    +				// Check if their constant kind (bool|int|float|complex|string) matches.
    +				// This doesn't take into account the constant value, so there will be some
    +				// false positives due to integer sign and overflow.
    +				case a&types.IsConstType == b&types.IsConstType:
    +					return true
    +				}
    +			}
    +		}
    +	}
    +
    +	// AssignableTo covers the case where the types are equal, but also handles
    +	// cases like assigning a concrete type to an interface type.
    +	return assignableTo(candType, expType)
    +}
    +
    +// kindMatches reports whether candType's kind matches our expected
    +// kind (e.g. slice, map, etc.).
    +func (ci *candidateInference) kindMatches(candType types.Type) bool {
    +	return ci.objKind > 0 && ci.objKind&candKind(candType) > 0
    +}
    +
    +// assigneesMatch reports whether an invocation of sig matches the
    +// number and type of any assignees.
    +func (ci *candidateInference) assigneesMatch(cand *candidate, sig *types.Signature) bool {
    +	if len(ci.assignees) == 0 {
    +		return false
    +	}
    +
    +	// Uniresult functions are always usable and are handled by the
    +	// normal, non-assignees type matching logic.
    +	if sig.Results().Len() == 1 {
    +		return false
    +	}
    +
    +	// Don't prefer completing into func(...interface{}) calls since all
    +	// functions would match.
    +	if ci.variadicAssignees && len(ci.assignees) == 1 && isEmptyInterface(deslice(ci.assignees[0])) {
    +		return false
    +	}
    +
    +	var numberOfResultsCouldMatch bool
    +	if ci.variadicAssignees {
    +		numberOfResultsCouldMatch = sig.Results().Len() >= len(ci.assignees)-1
    +	} else {
    +		numberOfResultsCouldMatch = sig.Results().Len() == len(ci.assignees)
    +	}
    +
    +	// If our signature doesn't return the right number of values, it's
    +	// not a match, so downrank it. For example:
    +	//
    +	//  var foo func() (int, int)
    +	//  a, b, c := <> // downrank "foo()" since it only returns two values
    +	if !numberOfResultsCouldMatch {
    +		cand.score /= 2
    +		return false
    +	}
    +
    +	// If at least one assignee has a valid type, and all valid
    +	// assignees match the corresponding sig result value, the signature
    +	// is a match.
    +	allMatch := false
    +	for i := 0; i < sig.Results().Len(); i++ {
    +		var assignee types.Type
    +
    +		// If we are completing into variadic parameters, deslice the
    +		// expected variadic type.
    +		if ci.variadicAssignees && i >= len(ci.assignees)-1 {
    +			assignee = ci.assignees[len(ci.assignees)-1]
    +			if elem := deslice(assignee); elem != nil {
    +				assignee = elem
    +			}
    +		} else {
    +			assignee = ci.assignees[i]
    +		}
    +
    +		if assignee == nil || assignee == types.Typ[types.Invalid] {
    +			continue
    +		}
    +
    +		allMatch = ci.typeMatches(assignee, sig.Results().At(i).Type())
    +		if !allMatch {
    +			break
    +		}
    +	}
    +	return allMatch
    +}
    +
    +func (c *completer) matchingTypeName(cand *candidate) bool {
    +	if !c.wantTypeName() {
    +		return false
    +	}
    +
    +	typeMatches := func(candType types.Type) bool {
    +		// Take into account any type name modifier prefixes.
    +		candType = c.inference.applyTypeNameModifiers(candType)
    +
    +		if from := c.inference.typeName.assertableFrom; from != nil {
    +			// Don't suggest the starting type in type assertions. For example,
    +			// if "foo" is an io.Writer, don't suggest "foo.(io.Writer)".
    +			if types.Identical(from, candType) {
    +				return false
    +			}
    +
    +			if intf, ok := from.Underlying().(*types.Interface); ok {
    +				if !types.AssertableTo(intf, candType) {
    +					return false
    +				}
    +			}
    +		}
    +
    +		if c.inference.typeName.wantComparable && !types.Comparable(candType) {
    +			return false
    +		}
    +
    +		// Skip this type if it has already been used in another type
    +		// switch case.
    +		for _, seen := range c.inference.typeName.seenTypeSwitchCases {
    +			if types.Identical(candType, seen) {
    +				return false
    +			}
    +		}
    +
    +		// We can expect a type name and have an expected type in cases like:
    +		//
    +		//   var foo []int
    +		//   foo = []i<>
    +		//
    +		// Where our expected type is "[]int", and we expect a type name.
    +		if c.inference.objType != nil {
    +			return assignableTo(candType, c.inference.objType)
    +		}
    +
    +		// Default to saying any type name is a match.
    +		return true
    +	}
    +
    +	t := cand.obj.Type()
    +
    +	if typeMatches(t) {
    +		return true
    +	}
    +
    +	if !types.IsInterface(t) && typeMatches(types.NewPointer(t)) {
    +		if c.inference.typeName.compLitType {
    +			// If we are completing a composite literal type as in
    +			// "foo<>{}", to make a pointer we must prepend "&".
    +			cand.mods = append(cand.mods, reference)
    +		} else {
    +			// If we are completing a normal type name such as "foo<>", to
    +			// make a pointer we must prepend "*".
    +			cand.mods = append(cand.mods, dereference)
    +		}
    +		return true
    +	}
    +
    +	return false
    +}
    +
    +var (
    +	// "interface { Error() string }" (i.e. error)
    +	errorIntf = types.Universe.Lookup("error").Type().Underlying().(*types.Interface)
    +
    +	// "interface { String() string }" (i.e. fmt.Stringer)
    +	stringerIntf = types.NewInterfaceType([]*types.Func{
    +		types.NewFunc(token.NoPos, nil, "String", types.NewSignatureType(
    +			nil, nil,
    +			nil, nil,
    +			types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])),
    +			false,
    +		)),
    +	}, nil).Complete()
    +
    +	byteType = types.Universe.Lookup("byte").Type()
    +
    +	boolType = types.Universe.Lookup("bool").Type()
    +)
    +
    +// candKind returns the objKind of candType, if any.
    +func candKind(candType types.Type) objKind {
    +	var kind objKind
    +
    +	switch t := candType.Underlying().(type) {
    +	case *types.Array:
    +		kind |= kindArray
    +		if t.Elem() == byteType {
    +			kind |= kindBytes
    +		}
    +	case *types.Slice:
    +		kind |= kindSlice
    +		if t.Elem() == byteType {
    +			kind |= kindBytes
    +		}
    +	case *types.Chan:
    +		kind |= kindChan
    +	case *types.Map:
    +		kind |= kindMap
    +	case *types.Pointer:
    +		kind |= kindPtr
    +
    +		// Some builtins handle array pointers as arrays, so just report a pointer
    +		// to an array as an array.
    +		if _, isArray := t.Elem().Underlying().(*types.Array); isArray {
    +			kind |= kindArray
    +		}
    +	case *types.Interface:
    +		kind |= kindInterface
    +	case *types.Basic:
    +		switch info := t.Info(); {
    +		case info&types.IsString > 0:
    +			kind |= kindString
    +		case info&types.IsInteger > 0:
    +			kind |= kindInt
    +		case info&types.IsFloat > 0:
    +			kind |= kindFloat
    +		case info&types.IsComplex > 0:
    +			kind |= kindComplex
    +		case info&types.IsBoolean > 0:
    +			kind |= kindBool
    +		}
    +	case *types.Signature:
    +		kind |= kindFunc
    +
    +		switch rangeFuncParamCount(t) {
    +		case 0:
    +			kind |= kindRange0Func
    +		case 1:
    +			kind |= kindRange1Func
    +		case 2:
    +			kind |= kindRange2Func
    +		}
    +	}
    +
    +	if types.Implements(candType, errorIntf) {
    +		kind |= kindError
    +	}
    +
    +	if types.Implements(candType, stringerIntf) {
    +		kind |= kindStringer
    +	}
    +
    +	return kind
    +}
    +
    +// If sig looks like a range func, return param count, else return -1.
    +func rangeFuncParamCount(sig *types.Signature) int {
    +	if sig.Results().Len() != 0 || sig.Params().Len() != 1 {
    +		return -1
    +	}
    +
    +	yieldSig, _ := sig.Params().At(0).Type().Underlying().(*types.Signature)
    +	if yieldSig == nil {
    +		return -1
    +	}
    +
    +	if yieldSig.Results().Len() != 1 || yieldSig.Results().At(0).Type() != boolType {
    +		return -1
    +	}
    +
    +	return yieldSig.Params().Len()
    +}
    +
    +// innermostScope returns the innermost scope for c.pos.
    +func (c *completer) innermostScope() *types.Scope {
    +	for _, s := range c.scopes {
    +		if s != nil {
    +			return s
    +		}
    +	}
    +	return nil
    +}
    +
    +// isSlice reports whether the object's underlying type is a slice.
    +func isSlice(obj types.Object) bool {
    +	if obj != nil && obj.Type() != nil {
    +		if _, ok := obj.Type().Underlying().(*types.Slice); ok {
    +			return true
    +		}
    +	}
    +	return false
    +}
    +
    +// forEachPackageMember calls f(tok, id, fn) for each package-level
    +// TYPE/VAR/CONST/FUNC declaration in the Go source file, based on a
    +// quick partial parse. fn is non-nil only for function declarations.
    +// The AST position information is garbage.
    +func forEachPackageMember(content []byte, f func(tok token.Token, id *ast.Ident, fn *ast.FuncDecl)) {
    +	purged := goplsastutil.PurgeFuncBodies(content)
    +	file, _ := parser.ParseFile(token.NewFileSet(), "", purged, 0)
    +	for _, decl := range file.Decls {
    +		switch decl := decl.(type) {
    +		case *ast.GenDecl:
    +			for _, spec := range decl.Specs {
    +				switch spec := spec.(type) {
    +				case *ast.ValueSpec: // var/const
    +					for _, id := range spec.Names {
    +						f(decl.Tok, id, nil)
    +					}
    +				case *ast.TypeSpec:
    +					f(decl.Tok, spec.Name, nil)
    +				}
    +			}
    +		case *ast.FuncDecl:
    +			if decl.Recv == nil {
    +				f(token.FUNC, decl.Name, decl)
    +			}
    +		}
    +	}
    +}
    +
    +func is[T any](x any) bool {
    +	_, ok := x.(T)
    +	return ok
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/deep_completion.go b/contribs/gnopls/internal/golang/completion/deep_completion.go
    new file mode 100644
    index 00000000000..053ece8219e
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/deep_completion.go
    @@ -0,0 +1,371 @@
    +// 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.
    +
    +package completion
    +
    +import (
    +	"context"
    +	"go/types"
    +	"strings"
    +	"time"
    +)
    +
    +// MaxDeepCompletions limits deep completion results because in most cases
    +// there are too many to be useful.
    +const MaxDeepCompletions = 3
    +
    +// deepCompletionState stores our state as we search for deep completions.
    +// "deep completion" refers to searching into objects' fields and methods to
    +// find more completion candidates.
    +type deepCompletionState struct {
    +	// enabled indicates whether deep completion is permitted.
    +	enabled bool
    +
    +	// queueClosed is used to disable adding new sub-fields to search queue
    +	// once we're running out of our time budget.
    +	queueClosed bool
    +
    +	// thisQueue holds the current breadth first search queue.
    +	thisQueue []candidate
    +
    +	// nextQueue holds the next breadth first search iteration's queue.
    +	nextQueue []candidate
    +
    +	// highScores tracks the highest deep candidate scores we have found
    +	// so far. This is used to avoid work for low scoring deep candidates.
    +	highScores [MaxDeepCompletions]float64
    +
    +	// candidateCount is the count of unique deep candidates encountered
    +	// so far.
    +	candidateCount int
    +}
    +
    +// enqueue adds a candidate to the search queue.
    +func (s *deepCompletionState) enqueue(cand candidate) {
    +	s.nextQueue = append(s.nextQueue, cand)
    +}
    +
    +// scorePenalty computes a deep candidate score penalty. A candidate is
    +// penalized based on depth to favor shallower candidates. We also give a
    +// slight bonus to unexported objects and a slight additional penalty to
    +// function objects.
    +func (s *deepCompletionState) scorePenalty(cand *candidate) float64 {
    +	var deepPenalty float64
    +	for _, dc := range cand.path {
    +		deepPenalty++
    +
    +		if !dc.Exported() {
    +			deepPenalty -= 0.1
    +		}
    +
    +		if _, isSig := dc.Type().Underlying().(*types.Signature); isSig {
    +			deepPenalty += 0.1
    +		}
    +	}
    +
    +	// Normalize penalty to a max depth of 10.
    +	return deepPenalty / 10
    +}
    +
    +// isHighScore returns whether score is among the top MaxDeepCompletions deep
    +// candidate scores encountered so far. If so, it adds score to highScores,
    +// possibly displacing an existing high score.
    +func (s *deepCompletionState) isHighScore(score float64) bool {
    +	// Invariant: s.highScores is sorted with highest score first. Unclaimed
    +	// positions are trailing zeros.
    +
    +	// If we beat an existing score then take its spot.
    +	for i, deepScore := range s.highScores {
    +		if score <= deepScore {
    +			continue
    +		}
    +
    +		if deepScore != 0 && i != len(s.highScores)-1 {
    +			// If this wasn't an empty slot then we need to scooch everyone
    +			// down one spot.
    +			copy(s.highScores[i+1:], s.highScores[i:])
    +		}
    +		s.highScores[i] = score
    +		return true
    +	}
    +
    +	return false
    +}
    +
    +// newPath returns path from search root for an object following a given
    +// candidate.
    +func (s *deepCompletionState) newPath(cand candidate, obj types.Object) []types.Object {
    +	path := make([]types.Object, len(cand.path)+1)
    +	copy(path, cand.path)
    +	path[len(path)-1] = obj
    +
    +	return path
    +}
    +
    +// deepSearch searches a candidate and its subordinate objects for completion
    +// items if deep completion is enabled and adds the valid candidates to
    +// completion items.
    +func (c *completer) deepSearch(ctx context.Context, minDepth int, deadline *time.Time) {
    +	defer func() {
    +		// We can return early before completing the search, so be sure to
    +		// clear out our queues to not impact any further invocations.
    +		c.deepState.thisQueue = c.deepState.thisQueue[:0]
    +		c.deepState.nextQueue = c.deepState.nextQueue[:0]
    +	}()
    +
    +	depth := 0 // current depth being processed
    +	// Stop reports whether we should stop the search immediately.
    +	stop := func() bool {
    +		// Context cancellation indicates that the actual completion operation was
    +		// cancelled, so ignore minDepth and deadline.
    +		select {
    +		case <-ctx.Done():
    +			return true
    +		default:
    +		}
    +		// Otherwise, only stop if we've searched at least minDepth and reached the deadline.
    +		return depth > minDepth && deadline != nil && time.Now().After(*deadline)
    +	}
    +
    +	for len(c.deepState.nextQueue) > 0 {
    +		depth++
    +		if stop() {
    +			return
    +		}
    +		c.deepState.thisQueue, c.deepState.nextQueue = c.deepState.nextQueue, c.deepState.thisQueue[:0]
    +
    +	outer:
    +		for _, cand := range c.deepState.thisQueue {
    +			obj := cand.obj
    +
    +			if obj == nil {
    +				continue
    +			}
    +
    +			// At the top level, dedupe by object.
    +			if len(cand.path) == 0 {
    +				if c.seen[obj] {
    +					continue
    +				}
    +				c.seen[obj] = true
    +			}
    +
    +			// If obj is not accessible because it lives in another package and is
    +			// not exported, don't treat it as a completion candidate unless it's
    +			// a package completion candidate.
    +			if !c.completionContext.packageCompletion &&
    +				obj.Pkg() != nil && obj.Pkg() != c.pkg.Types() && !obj.Exported() {
    +				continue
    +			}
    +
    +			// If we want a type name, don't offer non-type name candidates.
    +			// However, do offer package names since they can contain type names,
    +			// and do offer any candidate without a type since we aren't sure if it
    +			// is a type name or not (i.e. unimported candidate).
    +			if c.wantTypeName() && obj.Type() != nil && !isTypeName(obj) && !isPkgName(obj) {
    +				continue
    +			}
    +
    +			// When searching deep, make sure we don't have a cycle in our chain.
    +			// We don't dedupe by object because we want to allow both "foo.Baz"
    +			// and "bar.Baz" even though "Baz" is represented the same types.Object
    +			// in both.
    +			for _, seenObj := range cand.path {
    +				if seenObj == obj {
    +					continue outer
    +				}
    +			}
    +
    +			c.addCandidate(ctx, &cand)
    +
    +			c.deepState.candidateCount++
    +			if c.opts.budget > 0 && c.deepState.candidateCount%100 == 0 {
    +				if stop() {
    +					return
    +				}
    +				spent := float64(time.Since(c.startTime)) / float64(c.opts.budget)
    +				// If we are almost out of budgeted time, no further elements
    +				// should be added to the queue. This ensures remaining time is
    +				// used for processing current queue.
    +				if !c.deepState.queueClosed && spent >= 0.85 {
    +					c.deepState.queueClosed = true
    +				}
    +			}
    +
    +			// if deep search is disabled, don't add any more candidates.
    +			if !c.deepState.enabled || c.deepState.queueClosed {
    +				continue
    +			}
    +
    +			// Searching members for a type name doesn't make sense.
    +			if isTypeName(obj) {
    +				continue
    +			}
    +			if obj.Type() == nil {
    +				continue
    +			}
    +
    +			// Don't search embedded fields because they were already included in their
    +			// parent's fields.
    +			if v, ok := obj.(*types.Var); ok && v.Embedded() {
    +				continue
    +			}
    +
    +			if sig, ok := obj.Type().Underlying().(*types.Signature); ok {
    +				// If obj is a function that takes no arguments and returns one
    +				// value, keep searching across the function call.
    +				if sig.Params().Len() == 0 && sig.Results().Len() == 1 {
    +					path := c.deepState.newPath(cand, obj)
    +					// The result of a function call is not addressable.
    +					c.methodsAndFields(sig.Results().At(0).Type(), false, cand.imp, func(newCand candidate) {
    +						newCand.pathInvokeMask = cand.pathInvokeMask | (1 << uint64(len(cand.path)))
    +						newCand.path = path
    +						c.deepState.enqueue(newCand)
    +					})
    +				}
    +			}
    +
    +			path := c.deepState.newPath(cand, obj)
    +			switch obj := obj.(type) {
    +			case *types.PkgName:
    +				c.packageMembers(obj.Imported(), stdScore, cand.imp, func(newCand candidate) {
    +					newCand.pathInvokeMask = cand.pathInvokeMask
    +					newCand.path = path
    +					c.deepState.enqueue(newCand)
    +				})
    +			default:
    +				c.methodsAndFields(obj.Type(), cand.addressable, cand.imp, func(newCand candidate) {
    +					newCand.pathInvokeMask = cand.pathInvokeMask
    +					newCand.path = path
    +					c.deepState.enqueue(newCand)
    +				})
    +			}
    +		}
    +	}
    +}
    +
    +// addCandidate adds a completion candidate to suggestions, without searching
    +// its members for more candidates.
    +func (c *completer) addCandidate(ctx context.Context, cand *candidate) {
    +	obj := cand.obj
    +	if c.matchingCandidate(cand) {
    +		cand.score *= highScore
    +
    +		if p := c.penalty(cand); p > 0 {
    +			cand.score *= (1 - p)
    +		}
    +	} else if isTypeName(obj) {
    +		// If obj is a *types.TypeName that didn't otherwise match, check
    +		// if a literal object of this type makes a good candidate.
    +
    +		// We only care about named types (i.e. don't want builtin types).
    +		if _, isNamed := obj.Type().(*types.Named); isNamed {
    +			c.literal(ctx, obj.Type(), cand.imp)
    +		}
    +	}
    +
    +	// Lower score of method calls so we prefer fields and vars over calls.
    +	if cand.hasMod(invoke) {
    +		if sig, ok := obj.Type().Underlying().(*types.Signature); ok && sig.Recv() != nil {
    +			cand.score *= 0.9
    +		}
    +	}
    +
    +	// Prefer private objects over public ones.
    +	if !obj.Exported() && obj.Parent() != types.Universe {
    +		cand.score *= 1.1
    +	}
    +
    +	// Slight penalty for index modifier (e.g. changing "foo" to
    +	// "foo[]") to curb false positives.
    +	if cand.hasMod(index) {
    +		cand.score *= 0.9
    +	}
    +
    +	// Favor shallow matches by lowering score according to depth.
    +	cand.score -= cand.score * c.deepState.scorePenalty(cand)
    +
    +	if cand.score < 0 {
    +		cand.score = 0
    +	}
    +
    +	cand.name = deepCandName(cand)
    +	if item, err := c.item(ctx, *cand); err == nil {
    +		c.items = append(c.items, item)
    +	}
    +}
    +
    +// deepCandName produces the full candidate name including any
    +// ancestor objects. For example, "foo.bar().baz" for candidate "baz".
    +func deepCandName(cand *candidate) string {
    +	totalLen := len(cand.obj.Name())
    +	for i, obj := range cand.path {
    +		totalLen += len(obj.Name()) + 1
    +		if cand.pathInvokeMask&(1< 0 {
    +			totalLen += 2
    +		}
    +	}
    +
    +	var buf strings.Builder
    +	buf.Grow(totalLen)
    +
    +	for i, obj := range cand.path {
    +		buf.WriteString(obj.Name())
    +		if cand.pathInvokeMask&(1< 0 {
    +			buf.WriteByte('(')
    +			buf.WriteByte(')')
    +		}
    +		buf.WriteByte('.')
    +	}
    +
    +	buf.WriteString(cand.obj.Name())
    +
    +	return buf.String()
    +}
    +
    +// penalty reports a score penalty for cand in the range (0, 1).
    +// For example, a candidate is penalized if it has already been used
    +// in another switch case statement.
    +func (c *completer) penalty(cand *candidate) float64 {
    +	for _, p := range c.inference.penalized {
    +		if c.objChainMatches(cand, p.objChain) {
    +			return p.penalty
    +		}
    +	}
    +
    +	return 0
    +}
    +
    +// objChainMatches reports whether cand combined with the surrounding
    +// object prefix matches chain.
    +func (c *completer) objChainMatches(cand *candidate, chain []types.Object) bool {
    +	// For example, when completing:
    +	//
    +	//   foo.ba<>
    +	//
    +	// If we are considering the deep candidate "bar.baz", cand is baz,
    +	// objChain is [foo] and deepChain is [bar]. We would match the
    +	// chain [foo, bar, baz].
    +	if len(chain) != len(c.inference.objChain)+len(cand.path)+1 {
    +		return false
    +	}
    +
    +	if chain[len(chain)-1] != cand.obj {
    +		return false
    +	}
    +
    +	for i, o := range c.inference.objChain {
    +		if chain[i] != o {
    +			return false
    +		}
    +	}
    +
    +	for i, o := range cand.path {
    +		if chain[i+len(c.inference.objChain)] != o {
    +			return false
    +		}
    +	}
    +
    +	return true
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/deep_completion_test.go b/contribs/gnopls/internal/golang/completion/deep_completion_test.go
    new file mode 100644
    index 00000000000..27009af1b4f
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/deep_completion_test.go
    @@ -0,0 +1,33 @@
    +// 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 completion
    +
    +import (
    +	"testing"
    +)
    +
    +func TestDeepCompletionIsHighScore(t *testing.T) {
    +	// Test that deepCompletionState.isHighScore properly tracks the top
    +	// N=MaxDeepCompletions scores.
    +
    +	var s deepCompletionState
    +
    +	if !s.isHighScore(1) {
    +		// No other scores yet, anything is a winner.
    +		t.Error("1 should be high score")
    +	}
    +
    +	// Fill up with higher scores.
    +	for i := 0; i < MaxDeepCompletions; i++ {
    +		if !s.isHighScore(10) {
    +			t.Error("10 should be high score")
    +		}
    +	}
    +
    +	// High scores should be filled with 10s so 2 is not a high score.
    +	if s.isHighScore(2) {
    +		t.Error("2 shouldn't be high score")
    +	}
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/definition.go b/contribs/gnopls/internal/golang/completion/definition.go
    new file mode 100644
    index 00000000000..fc8b0ae5c69
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/definition.go
    @@ -0,0 +1,160 @@
    +// 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 completion
    +
    +import (
    +	"go/ast"
    +	"go/types"
    +	"strings"
    +	"unicode"
    +	"unicode/utf8"
    +
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/golang/completion/snippet"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +)
    +
    +// some function definitions in test files can be completed
    +// So far, TestFoo(t *testing.T), TestMain(m *testing.M)
    +// BenchmarkFoo(b *testing.B), FuzzFoo(f *testing.F)
    +
    +// path[0] is known to be *ast.Ident
    +func definition(path []ast.Node, obj types.Object, pgf *parsego.File) ([]CompletionItem, *Selection) {
    +	if _, ok := obj.(*types.Func); !ok {
    +		return nil, nil // not a function at all
    +	}
    +	if !strings.HasSuffix(pgf.URI.Path(), "_test.go") {
    +		return nil, nil // not a test file
    +	}
    +
    +	name := path[0].(*ast.Ident).Name
    +	if len(name) == 0 {
    +		// can't happen
    +		return nil, nil
    +	}
    +	start := path[0].Pos()
    +	end := path[0].End()
    +	sel := &Selection{
    +		content: "",
    +		cursor:  start,
    +		tokFile: pgf.Tok,
    +		start:   start,
    +		end:     end,
    +		mapper:  pgf.Mapper,
    +	}
    +	var ans []CompletionItem
    +	var hasParens bool
    +	n, ok := path[1].(*ast.FuncDecl)
    +	if !ok {
    +		return nil, nil // can't happen
    +	}
    +	if n.Recv != nil {
    +		return nil, nil // a method, not a function
    +	}
    +	t := n.Type.Params
    +	if t.Closing != t.Opening {
    +		hasParens = true
    +	}
    +
    +	// Always suggest TestMain, if possible
    +	if strings.HasPrefix("TestMain", name) {
    +		if hasParens {
    +			ans = append(ans, defItem("TestMain", obj))
    +		} else {
    +			ans = append(ans, defItem("TestMain(m *testing.M)", obj))
    +		}
    +	}
    +
    +	// If a snippet is possible, suggest it
    +	if strings.HasPrefix("Test", name) {
    +		if hasParens {
    +			ans = append(ans, defItem("Test", obj))
    +		} else {
    +			ans = append(ans, defSnippet("Test", "(t *testing.T)", obj))
    +		}
    +		return ans, sel
    +	} else if strings.HasPrefix("Benchmark", name) {
    +		if hasParens {
    +			ans = append(ans, defItem("Benchmark", obj))
    +		} else {
    +			ans = append(ans, defSnippet("Benchmark", "(b *testing.B)", obj))
    +		}
    +		return ans, sel
    +	} else if strings.HasPrefix("Fuzz", name) {
    +		if hasParens {
    +			ans = append(ans, defItem("Fuzz", obj))
    +		} else {
    +			ans = append(ans, defSnippet("Fuzz", "(f *testing.F)", obj))
    +		}
    +		return ans, sel
    +	}
    +
    +	// Fill in the argument for what the user has already typed
    +	if got := defMatches(name, "Test", path, "(t *testing.T)"); got != "" {
    +		ans = append(ans, defItem(got, obj))
    +	} else if got := defMatches(name, "Benchmark", path, "(b *testing.B)"); got != "" {
    +		ans = append(ans, defItem(got, obj))
    +	} else if got := defMatches(name, "Fuzz", path, "(f *testing.F)"); got != "" {
    +		ans = append(ans, defItem(got, obj))
    +	}
    +	return ans, sel
    +}
    +
    +// defMatches returns text for defItem, never for defSnippet
    +func defMatches(name, pat string, path []ast.Node, arg string) string {
    +	if !strings.HasPrefix(name, pat) {
    +		return ""
    +	}
    +	c, _ := utf8.DecodeRuneInString(name[len(pat):])
    +	if unicode.IsLower(c) {
    +		return ""
    +	}
    +	fd, ok := path[1].(*ast.FuncDecl)
    +	if !ok {
    +		// we don't know what's going on
    +		return ""
    +	}
    +	fp := fd.Type.Params
    +	if len(fp.List) > 0 {
    +		// signature already there, nothing to suggest
    +		return ""
    +	}
    +	if fp.Opening != fp.Closing {
    +		// nothing: completion works on words, not easy to insert arg
    +		return ""
    +	}
    +	// suggesting signature too
    +	return name + arg
    +}
    +
    +func defSnippet(prefix, suffix string, obj types.Object) CompletionItem {
    +	var sn snippet.Builder
    +	sn.WriteText(prefix)
    +	sn.WritePlaceholder(func(b *snippet.Builder) { b.WriteText("Xxx") })
    +	sn.WriteText(suffix + " {\n\t")
    +	sn.WriteFinalTabstop()
    +	sn.WriteText("\n}")
    +	return CompletionItem{
    +		Label:         prefix + "Xxx" + suffix,
    +		Detail:        "tab, type the rest of the name, then tab",
    +		Kind:          protocol.FunctionCompletion,
    +		Depth:         0,
    +		Score:         10,
    +		snippet:       &sn,
    +		Documentation: prefix + " test function",
    +		isSlice:       isSlice(obj),
    +	}
    +}
    +func defItem(val string, obj types.Object) CompletionItem {
    +	return CompletionItem{
    +		Label:         val,
    +		InsertText:    val,
    +		Kind:          protocol.FunctionCompletion,
    +		Depth:         0,
    +		Score:         9, // prefer the snippets when available
    +		Documentation: "complete the function name",
    +		isSlice:       isSlice(obj),
    +	}
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/format.go b/contribs/gnopls/internal/golang/completion/format.go
    new file mode 100644
    index 00000000000..2e35fd5de38
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/format.go
    @@ -0,0 +1,444 @@
    +// 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.
    +
    +package completion
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +	"go/ast"
    +	"go/doc"
    +	"go/types"
    +	"strings"
    +
    +	"golang.org/x/tools/gopls/internal/golang"
    +	"golang.org/x/tools/gopls/internal/golang/completion/snippet"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/internal/aliases"
    +	"golang.org/x/tools/internal/event"
    +	"golang.org/x/tools/internal/imports"
    +)
    +
    +var (
    +	errNoMatch  = errors.New("not a surrounding match")
    +	errLowScore = errors.New("not a high scoring candidate")
    +)
    +
    +// item formats a candidate to a CompletionItem.
    +func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) {
    +	obj := cand.obj
    +
    +	// if the object isn't a valid match against the surrounding, return early.
    +	matchScore := c.matcher.Score(cand.name)
    +	if matchScore <= 0 {
    +		return CompletionItem{}, errNoMatch
    +	}
    +	cand.score *= float64(matchScore)
    +
    +	// Ignore deep candidates that won't be in the MaxDeepCompletions anyway.
    +	if len(cand.path) != 0 && !c.deepState.isHighScore(cand.score) {
    +		return CompletionItem{}, errLowScore
    +	}
    +
    +	// Handle builtin types separately.
    +	if obj.Parent() == types.Universe {
    +		return c.formatBuiltin(ctx, cand)
    +	}
    +
    +	var (
    +		label         = cand.name
    +		detail        = types.TypeString(obj.Type(), c.qf)
    +		insert        = label
    +		kind          = protocol.TextCompletion
    +		snip          snippet.Builder
    +		protocolEdits []protocol.TextEdit
    +	)
    +	if obj.Type() == nil {
    +		detail = ""
    +	}
    +	if isTypeName(obj) && c.wantTypeParams() {
    +		x := cand.obj.(*types.TypeName)
    +		if named, ok := aliases.Unalias(x.Type()).(*types.Named); ok {
    +			tp := named.TypeParams()
    +			label += golang.FormatTypeParams(tp)
    +			insert = label // maintain invariant above (label == insert)
    +		}
    +	}
    +
    +	snip.WriteText(insert)
    +
    +	switch obj := obj.(type) {
    +	case *types.TypeName:
    +		detail, kind = golang.FormatType(obj.Type(), c.qf)
    +	case *types.Const:
    +		kind = protocol.ConstantCompletion
    +	case *types.Var:
    +		if _, ok := obj.Type().(*types.Struct); ok {
    +			detail = "struct{...}" // for anonymous unaliased struct types
    +		} else if obj.IsField() {
    +			var err error
    +			detail, err = golang.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf, c.mq)
    +			if err != nil {
    +				return CompletionItem{}, err
    +			}
    +		}
    +		if obj.IsField() {
    +			kind = protocol.FieldCompletion
    +			c.structFieldSnippet(cand, detail, &snip)
    +		} else {
    +			kind = protocol.VariableCompletion
    +		}
    +		if obj.Type() == nil {
    +			break
    +		}
    +	case *types.Func:
    +		if obj.Signature().Recv() == nil {
    +			kind = protocol.FunctionCompletion
    +		} else {
    +			kind = protocol.MethodCompletion
    +		}
    +	case *types.PkgName:
    +		kind = protocol.ModuleCompletion
    +		detail = fmt.Sprintf("%q", obj.Imported().Path())
    +	case *types.Label:
    +		kind = protocol.ConstantCompletion
    +		detail = "label"
    +	}
    +
    +	var prefix string
    +	for _, mod := range cand.mods {
    +		switch mod {
    +		case reference:
    +			prefix = "&" + prefix
    +		case dereference:
    +			prefix = "*" + prefix
    +		case chanRead:
    +			prefix = "<-" + prefix
    +		}
    +	}
    +
    +	var (
    +		suffix   string
    +		funcType = obj.Type()
    +	)
    +Suffixes:
    +	for _, mod := range cand.mods {
    +		switch mod {
    +		case invoke:
    +			if sig, ok := funcType.Underlying().(*types.Signature); ok {
    +				s, err := golang.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf, c.mq)
    +				if err != nil {
    +					return CompletionItem{}, err
    +				}
    +
    +				tparams := s.TypeParams()
    +				if len(tparams) > 0 {
    +					// Eliminate the suffix of type parameters that are
    +					// likely redundant because they can probably be
    +					// inferred from the argument types (#51783).
    +					//
    +					// We don't bother doing the reverse inference from
    +					// result types as result-only type parameters are
    +					// quite unusual.
    +					free := inferableTypeParams(sig)
    +					for i := sig.TypeParams().Len() - 1; i >= 0; i-- {
    +						tparam := sig.TypeParams().At(i)
    +						if !free[tparam] {
    +							break
    +						}
    +						tparams = tparams[:i] // eliminate
    +					}
    +				}
    +
    +				c.functionCallSnippet("", tparams, s.Params(), &snip)
    +				if sig.Results().Len() == 1 {
    +					funcType = sig.Results().At(0).Type()
    +				}
    +				detail = "func" + s.Format()
    +			}
    +
    +			if !c.opts.snippets {
    +				// Without snippets the candidate will not include "()". Don't
    +				// add further suffixes since they will be invalid. For
    +				// example, with snippets "foo()..." would become "foo..."
    +				// without snippets if we added the dotDotDot.
    +				break Suffixes
    +			}
    +		case takeSlice:
    +			suffix += "[:]"
    +		case takeDotDotDot:
    +			suffix += "..."
    +		case index:
    +			snip.WriteText("[")
    +			snip.WritePlaceholder(nil)
    +			snip.WriteText("]")
    +		}
    +	}
    +
    +	// If this candidate needs an additional import statement,
    +	// add the additional text edits needed.
    +	if cand.imp != nil {
    +		addlEdits, err := c.importEdits(cand.imp)
    +
    +		if err != nil {
    +			return CompletionItem{}, err
    +		}
    +
    +		protocolEdits = append(protocolEdits, addlEdits...)
    +		if kind != protocol.ModuleCompletion {
    +			if detail != "" {
    +				detail += " "
    +			}
    +			detail += fmt.Sprintf("(from %q)", cand.imp.importPath)
    +		}
    +	}
    +
    +	if cand.convertTo != nil {
    +		typeName := types.TypeString(cand.convertTo, c.qf)
    +
    +		switch t := cand.convertTo.(type) {
    +		// We need extra parens when casting to these types. For example,
    +		// we need "(*int)(foo)", not "*int(foo)".
    +		case *types.Pointer, *types.Signature:
    +			typeName = "(" + typeName + ")"
    +		case *types.Basic:
    +			// If the types are incompatible (as determined by typeMatches), then we
    +			// must need a conversion here. However, if the target type is untyped,
    +			// don't suggest converting to e.g. "untyped float" (golang/go#62141).
    +			if t.Info()&types.IsUntyped != 0 {
    +				typeName = types.TypeString(types.Default(cand.convertTo), c.qf)
    +			}
    +		}
    +
    +		prefix = typeName + "(" + prefix
    +		suffix = ")"
    +	}
    +
    +	if prefix != "" {
    +		// If we are in a selector, add an edit to place prefix before selector.
    +		if sel := enclosingSelector(c.path, c.pos); sel != nil {
    +			edits, err := c.editText(sel.Pos(), sel.Pos(), prefix)
    +			if err != nil {
    +				return CompletionItem{}, err
    +			}
    +			protocolEdits = append(protocolEdits, edits...)
    +		} else {
    +			// If there is no selector, just stick the prefix at the start.
    +			insert = prefix + insert
    +			snip.PrependText(prefix)
    +		}
    +	}
    +
    +	if suffix != "" {
    +		insert += suffix
    +		snip.WriteText(suffix)
    +	}
    +
    +	detail = strings.TrimPrefix(detail, "untyped ")
    +	// override computed detail with provided detail, if something is provided.
    +	if cand.detail != "" {
    +		detail = cand.detail
    +	}
    +	item := CompletionItem{
    +		Label:               label,
    +		InsertText:          insert,
    +		AdditionalTextEdits: protocolEdits,
    +		Detail:              detail,
    +		Kind:                kind,
    +		Score:               cand.score,
    +		Depth:               len(cand.path),
    +		snippet:             &snip,
    +		isSlice:             isSlice(obj),
    +	}
    +	// If the user doesn't want documentation for completion items.
    +	if !c.opts.documentation {
    +		return item, nil
    +	}
    +	pos := safetoken.StartPosition(c.pkg.FileSet(), obj.Pos())
    +
    +	// We ignore errors here, because some types, like "unsafe" or "error",
    +	// may not have valid positions that we can use to get documentation.
    +	if !pos.IsValid() {
    +		return item, nil
    +	}
    +
    +	comment, err := golang.HoverDocForObject(ctx, c.snapshot, c.pkg.FileSet(), obj)
    +	if err != nil {
    +		event.Error(ctx, fmt.Sprintf("failed to find Hover for %q", obj.Name()), err)
    +		return item, nil
    +	}
    +	if c.opts.fullDocumentation {
    +		item.Documentation = comment.Text()
    +	} else {
    +		item.Documentation = doc.Synopsis(comment.Text())
    +	}
    +	// The desired pattern is `^// Deprecated`, but the prefix has been removed
    +	// TODO(rfindley): It doesn't look like this does the right thing for
    +	// multi-line comments.
    +	if strings.HasPrefix(comment.Text(), "Deprecated") {
    +		if c.snapshot.Options().CompletionTags {
    +			item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated}
    +		} else if c.snapshot.Options().CompletionDeprecated {
    +			item.Deprecated = true
    +		}
    +	}
    +
    +	return item, nil
    +}
    +
    +// importEdits produces the text edits necessary to add the given import to the current file.
    +func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) {
    +	if imp == nil {
    +		return nil, nil
    +	}
    +
    +	pgf, err := c.pkg.File(protocol.URIFromPath(c.filename))
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	return golang.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{
    +		StmtInfo: imports.ImportInfo{
    +			ImportPath: imp.importPath,
    +			Name:       imp.name,
    +		},
    +		// IdentName is unused on this path and is difficult to get.
    +		FixType: imports.AddImport,
    +	})
    +}
    +
    +func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) {
    +	obj := cand.obj
    +	item := CompletionItem{
    +		Label:      obj.Name(),
    +		InsertText: obj.Name(),
    +		Score:      cand.score,
    +	}
    +	switch obj.(type) {
    +	case *types.Const:
    +		item.Kind = protocol.ConstantCompletion
    +	case *types.Builtin:
    +		item.Kind = protocol.FunctionCompletion
    +		sig, err := golang.NewBuiltinSignature(ctx, c.snapshot, obj.Name())
    +		if err != nil {
    +			return CompletionItem{}, err
    +		}
    +		item.Detail = "func" + sig.Format()
    +		item.snippet = &snippet.Builder{}
    +		// The signature inferred for a built-in is instantiated, so TypeParams=∅.
    +		c.functionCallSnippet(obj.Name(), sig.TypeParams(), sig.Params(), item.snippet)
    +	case *types.TypeName:
    +		if types.IsInterface(obj.Type()) {
    +			item.Kind = protocol.InterfaceCompletion
    +		} else {
    +			item.Kind = protocol.ClassCompletion
    +		}
    +	case *types.Nil:
    +		item.Kind = protocol.VariableCompletion
    +	}
    +	return item, nil
    +}
    +
    +// decide if the type params (if any) should be part of the completion
    +// which only possible for types.Named and types.Signature
    +// (so far, only in receivers, e.g.; func (s *GENERIC[K, V])..., which is a types.Named)
    +func (c *completer) wantTypeParams() bool {
    +	// Need to be lexically in a receiver, and a child of an IndexListExpr
    +	// (but IndexListExpr only exists with go1.18)
    +	start := c.path[0].Pos()
    +	for i, nd := range c.path {
    +		if fd, ok := nd.(*ast.FuncDecl); ok {
    +			if i > 0 && fd.Recv != nil && start < fd.Recv.End() {
    +				return true
    +			} else {
    +				return false
    +			}
    +		}
    +	}
    +	return false
    +}
    +
    +// inferableTypeParams returns the set of type parameters
    +// of sig that are constrained by (inferred from) the argument types.
    +func inferableTypeParams(sig *types.Signature) map[*types.TypeParam]bool {
    +	free := make(map[*types.TypeParam]bool)
    +
    +	// visit adds to free all the free type parameters of t.
    +	var visit func(t types.Type)
    +	visit = func(t types.Type) {
    +		switch t := t.(type) {
    +		case *types.Array:
    +			visit(t.Elem())
    +		case *types.Chan:
    +			visit(t.Elem())
    +		case *types.Map:
    +			visit(t.Key())
    +			visit(t.Elem())
    +		case *types.Pointer:
    +			visit(t.Elem())
    +		case *types.Slice:
    +			visit(t.Elem())
    +		case *types.Interface:
    +			for i := 0; i < t.NumExplicitMethods(); i++ {
    +				visit(t.ExplicitMethod(i).Type())
    +			}
    +			for i := 0; i < t.NumEmbeddeds(); i++ {
    +				visit(t.EmbeddedType(i))
    +			}
    +		case *types.Union:
    +			for i := 0; i < t.Len(); i++ {
    +				visit(t.Term(i).Type())
    +			}
    +		case *types.Signature:
    +			if tp := t.TypeParams(); tp != nil {
    +				// Generic signatures only appear as the type of generic
    +				// function declarations, so this isn't really reachable.
    +				for i := 0; i < tp.Len(); i++ {
    +					visit(tp.At(i).Constraint())
    +				}
    +			}
    +			visit(t.Params())
    +			visit(t.Results())
    +		case *types.Tuple:
    +			for i := 0; i < t.Len(); i++ {
    +				visit(t.At(i).Type())
    +			}
    +		case *types.Struct:
    +			for i := 0; i < t.NumFields(); i++ {
    +				visit(t.Field(i).Type())
    +			}
    +		case *types.TypeParam:
    +			free[t] = true
    +		case *aliases.Alias:
    +			visit(aliases.Unalias(t))
    +		case *types.Named:
    +			targs := t.TypeArgs()
    +			for i := 0; i < targs.Len(); i++ {
    +				visit(targs.At(i))
    +			}
    +		case *types.Basic:
    +			// nop
    +		default:
    +			panic(t)
    +		}
    +	}
    +
    +	visit(sig.Params())
    +
    +	// Perform induction through constraints.
    +restart:
    +	for i := 0; i < sig.TypeParams().Len(); i++ {
    +		tp := sig.TypeParams().At(i)
    +		if free[tp] {
    +			n := len(free)
    +			visit(tp.Constraint())
    +			if len(free) > n {
    +				goto restart // iterate until fixed point
    +			}
    +		}
    +	}
    +	return free
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/fuzz.go b/contribs/gnopls/internal/golang/completion/fuzz.go
    new file mode 100644
    index 00000000000..313e7f7b391
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/fuzz.go
    @@ -0,0 +1,141 @@
    +// 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 completion
    +
    +import (
    +	"fmt"
    +	"go/ast"
    +	"go/types"
    +	"strings"
    +
    +	"golang.org/x/tools/gopls/internal/protocol"
    +)
    +
    +// golang/go#51089
    +// *testing.F deserves special treatment as member use is constrained:
    +// The arguments to f.Fuzz are determined by the arguments to a previous f.Add
    +// Inside f.Fuzz only f.Failed and f.Name are allowed.
    +// PJW: are there other packages where we can deduce usage constraints?
    +
    +// if we find fuzz completions, then return true, as those are the only completions to offer
    +func (c *completer) fuzz(mset *types.MethodSet, imp *importInfo, cb func(candidate)) bool {
    +	// 1. inside f.Fuzz? (only f.Failed and f.Name)
    +	// 2. possible completing f.Fuzz?
    +	//    [Ident,SelectorExpr,Callexpr,ExprStmt,BlockiStmt,FuncDecl(Fuzz...)]
    +	// 3. before f.Fuzz, same (for 2., offer choice when looking at an F)
    +
    +	// does the path contain FuncLit as arg to f.Fuzz CallExpr?
    +	inside := false
    +Loop:
    +	for i, n := range c.path {
    +		switch v := n.(type) {
    +		case *ast.CallExpr:
    +			if len(v.Args) != 1 {
    +				continue Loop
    +			}
    +			if _, ok := v.Args[0].(*ast.FuncLit); !ok {
    +				continue
    +			}
    +			if s, ok := v.Fun.(*ast.SelectorExpr); !ok || s.Sel.Name != "Fuzz" {
    +				continue
    +			}
    +			if i > 2 { // avoid t.Fuzz itself in tests
    +				inside = true
    +				break Loop
    +			}
    +		}
    +	}
    +	if inside {
    +		for i := 0; i < mset.Len(); i++ {
    +			o := mset.At(i).Obj()
    +			if o.Name() == "Failed" || o.Name() == "Name" {
    +				cb(candidate{
    +					obj:         o,
    +					score:       stdScore,
    +					imp:         imp,
    +					addressable: true,
    +				})
    +			}
    +		}
    +		return true
    +	}
    +	// if it could be t.Fuzz, look for the preceding t.Add
    +	id, ok := c.path[0].(*ast.Ident)
    +	if ok && strings.HasPrefix("Fuzz", id.Name) {
    +		var add *ast.CallExpr
    +		f := func(n ast.Node) bool {
    +			if n == nil {
    +				return true
    +			}
    +			call, ok := n.(*ast.CallExpr)
    +			if !ok {
    +				return true
    +			}
    +			s, ok := call.Fun.(*ast.SelectorExpr)
    +			if !ok {
    +				return true
    +			}
    +			if s.Sel.Name != "Add" {
    +				return true
    +			}
    +			// Sel.X should be of type *testing.F
    +			got := c.pkg.TypesInfo().Types[s.X]
    +			if got.Type.String() == "*testing.F" {
    +				add = call
    +			}
    +			return false // because we're done...
    +		}
    +		// look at the enclosing FuzzFoo functions
    +		if len(c.path) < 2 {
    +			return false
    +		}
    +		n := c.path[len(c.path)-2]
    +		if _, ok := n.(*ast.FuncDecl); !ok {
    +			// the path should start with ast.File, ast.FuncDecl, ...
    +			// but it didn't, so give up
    +			return false
    +		}
    +		ast.Inspect(n, f)
    +		if add == nil {
    +			// looks like f.Fuzz without a preceding f.Add.
    +			// let the regular completion handle it.
    +			return false
    +		}
    +
    +		lbl := "Fuzz(func(t *testing.T"
    +		for i, a := range add.Args {
    +			info := c.pkg.TypesInfo().TypeOf(a)
    +			if info == nil {
    +				return false // How could this happen, but better safe than panic.
    +			}
    +			lbl += fmt.Sprintf(", %c %s", 'a'+i, info)
    +		}
    +		lbl += ")"
    +		xx := CompletionItem{
    +			Label:         lbl,
    +			InsertText:    lbl,
    +			Kind:          protocol.FunctionCompletion,
    +			Depth:         0,
    +			Score:         10, // pretty confident the user should see this
    +			Documentation: "argument types from f.Add",
    +			isSlice:       false,
    +		}
    +		c.items = append(c.items, xx)
    +		for i := 0; i < mset.Len(); i++ {
    +			o := mset.At(i).Obj()
    +			if o.Name() != "Fuzz" {
    +				cb(candidate{
    +					obj:         o,
    +					score:       stdScore,
    +					imp:         imp,
    +					addressable: true,
    +				})
    +			}
    +		}
    +		return true // done
    +	}
    +	// let the standard processing take care of it instead
    +	return false
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/keywords.go b/contribs/gnopls/internal/golang/completion/keywords.go
    new file mode 100644
    index 00000000000..3f2f5ac78cd
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/keywords.go
    @@ -0,0 +1,154 @@
    +// 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 completion
    +
    +import (
    +	"go/ast"
    +
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/astutil"
    +)
    +
    +const (
    +	BREAK       = "break"
    +	CASE        = "case"
    +	CHAN        = "chan"
    +	CONST       = "const"
    +	CONTINUE    = "continue"
    +	DEFAULT     = "default"
    +	DEFER       = "defer"
    +	ELSE        = "else"
    +	FALLTHROUGH = "fallthrough"
    +	FOR         = "for"
    +	FUNC        = "func"
    +	GO          = "go"
    +	GOTO        = "goto"
    +	IF          = "if"
    +	IMPORT      = "import"
    +	INTERFACE   = "interface"
    +	MAP         = "map"
    +	PACKAGE     = "package"
    +	RANGE       = "range"
    +	RETURN      = "return"
    +	SELECT      = "select"
    +	STRUCT      = "struct"
    +	SWITCH      = "switch"
    +	TYPE        = "type"
    +	VAR         = "var"
    +)
    +
    +// addKeywordCompletions offers keyword candidates appropriate at the position.
    +func (c *completer) addKeywordCompletions() {
    +	seen := make(map[string]bool)
    +
    +	if c.wantTypeName() && c.inference.objType == nil {
    +		// If we want a type name but don't have an expected obj type,
    +		// include "interface", "struct", "func", "chan", and "map".
    +
    +		// "interface" and "struct" are more common declaring named types.
    +		// Give them a higher score if we are in a type declaration.
    +		structIntf, funcChanMap := stdScore, highScore
    +		if len(c.path) > 1 {
    +			if _, namedDecl := c.path[1].(*ast.TypeSpec); namedDecl {
    +				structIntf, funcChanMap = highScore, stdScore
    +			}
    +		}
    +
    +		c.addKeywordItems(seen, structIntf, STRUCT, INTERFACE)
    +		c.addKeywordItems(seen, funcChanMap, FUNC, CHAN, MAP)
    +	}
    +
    +	// If we are at the file scope, only offer decl keywords. We don't
    +	// get *ast.Idents at the file scope because non-keyword identifiers
    +	// turn into *ast.BadDecl, not *ast.Ident.
    +	if len(c.path) == 1 || is[*ast.File](c.path[1]) {
    +		c.addKeywordItems(seen, stdScore, TYPE, CONST, VAR, FUNC, IMPORT)
    +		return
    +	} else if _, ok := c.path[0].(*ast.Ident); !ok {
    +		// Otherwise only offer keywords if the client is completing an identifier.
    +		return
    +	}
    +
    +	if len(c.path) > 2 {
    +		// Offer "range" if we are in ast.ForStmt.Init. This is what the
    +		// AST looks like before "range" is typed, e.g. "for i := r<>".
    +		if loop, ok := c.path[2].(*ast.ForStmt); ok && loop.Init != nil && astutil.NodeContains(loop.Init, c.pos) {
    +			c.addKeywordItems(seen, stdScore, RANGE)
    +		}
    +	}
    +
    +	// Only suggest keywords if we are beginning a statement.
    +	switch n := c.path[1].(type) {
    +	case *ast.BlockStmt, *ast.ExprStmt:
    +		// OK - our ident must be at beginning of statement.
    +	case *ast.CommClause:
    +		// Make sure we aren't in the Comm statement.
    +		if !n.Colon.IsValid() || c.pos <= n.Colon {
    +			return
    +		}
    +	case *ast.CaseClause:
    +		// Make sure we aren't in the case List.
    +		if !n.Colon.IsValid() || c.pos <= n.Colon {
    +			return
    +		}
    +	default:
    +		return
    +	}
    +
    +	// Filter out keywords depending on scope
    +	// Skip the first one because we want to look at the enclosing scopes
    +	path := c.path[1:]
    +	for i, n := range path {
    +		switch node := n.(type) {
    +		case *ast.CaseClause:
    +			// only recommend "fallthrough" and "break" within the bodies of a case clause
    +			if c.pos > node.Colon {
    +				c.addKeywordItems(seen, stdScore, BREAK)
    +				// "fallthrough" is only valid in switch statements.
    +				// A case clause is always nested within a block statement in a switch statement,
    +				// that block statement is nested within either a TypeSwitchStmt or a SwitchStmt.
    +				if i+2 >= len(path) {
    +					continue
    +				}
    +				if _, ok := path[i+2].(*ast.SwitchStmt); ok {
    +					c.addKeywordItems(seen, stdScore, FALLTHROUGH)
    +				}
    +			}
    +		case *ast.CommClause:
    +			if c.pos > node.Colon {
    +				c.addKeywordItems(seen, stdScore, BREAK)
    +			}
    +		case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
    +			c.addKeywordItems(seen, stdScore, CASE, DEFAULT)
    +		case *ast.ForStmt, *ast.RangeStmt:
    +			c.addKeywordItems(seen, stdScore, BREAK, CONTINUE)
    +		// This is a bit weak, functions allow for many keywords
    +		case *ast.FuncDecl:
    +			if node.Body != nil && c.pos > node.Body.Lbrace {
    +				c.addKeywordItems(seen, stdScore, DEFER, RETURN, FOR, GO, SWITCH, SELECT, IF, ELSE, VAR, CONST, GOTO, TYPE)
    +			}
    +		}
    +	}
    +}
    +
    +// addKeywordItems dedupes and adds completion items for the specified
    +// keywords with the specified score.
    +func (c *completer) addKeywordItems(seen map[string]bool, score float64, kws ...string) {
    +	for _, kw := range kws {
    +		if seen[kw] {
    +			continue
    +		}
    +		seen[kw] = true
    +
    +		if matchScore := c.matcher.Score(kw); matchScore > 0 {
    +			c.items = append(c.items, CompletionItem{
    +				Label:      kw,
    +				Kind:       protocol.KeywordCompletion,
    +				InsertText: kw,
    +				Score:      score * float64(matchScore),
    +			})
    +		}
    +	}
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/labels.go b/contribs/gnopls/internal/golang/completion/labels.go
    new file mode 100644
    index 00000000000..f0e5f42a67a
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/labels.go
    @@ -0,0 +1,112 @@
    +// 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.
    +
    +package completion
    +
    +import (
    +	"go/ast"
    +	"go/token"
    +	"math"
    +)
    +
    +type labelType int
    +
    +const (
    +	labelNone labelType = iota
    +	labelBreak
    +	labelContinue
    +	labelGoto
    +)
    +
    +// wantLabelCompletion returns true if we want (only) label
    +// completions at the position.
    +func (c *completer) wantLabelCompletion() labelType {
    +	if _, ok := c.path[0].(*ast.Ident); ok && len(c.path) > 1 {
    +		// We want a label if we are an *ast.Ident child of a statement
    +		// that accepts a label, e.g. "break Lo<>".
    +		return takesLabel(c.path[1])
    +	}
    +
    +	return labelNone
    +}
    +
    +// takesLabel returns the corresponding labelType if n is a statement
    +// that accepts a label, otherwise labelNone.
    +func takesLabel(n ast.Node) labelType {
    +	if bs, ok := n.(*ast.BranchStmt); ok {
    +		switch bs.Tok {
    +		case token.BREAK:
    +			return labelBreak
    +		case token.CONTINUE:
    +			return labelContinue
    +		case token.GOTO:
    +			return labelGoto
    +		}
    +	}
    +	return labelNone
    +}
    +
    +// labels adds completion items for labels defined in the enclosing
    +// function.
    +func (c *completer) labels(lt labelType) {
    +	if c.enclosingFunc == nil {
    +		return
    +	}
    +
    +	addLabel := func(score float64, l *ast.LabeledStmt) {
    +		labelObj := c.pkg.TypesInfo().ObjectOf(l.Label)
    +		if labelObj != nil {
    +			c.deepState.enqueue(candidate{obj: labelObj, score: score})
    +		}
    +	}
    +
    +	switch lt {
    +	case labelBreak, labelContinue:
    +		// "break" and "continue" only accept labels from enclosing statements.
    +
    +		for i, p := range c.path {
    +			switch p := p.(type) {
    +			case *ast.FuncLit:
    +				// Labels are function scoped, so don't continue out of functions.
    +				return
    +			case *ast.LabeledStmt:
    +				switch p.Stmt.(type) {
    +				case *ast.ForStmt, *ast.RangeStmt:
    +					// Loop labels can be used for "break" or "continue".
    +					addLabel(highScore*math.Pow(.99, float64(i)), p)
    +				case *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
    +					// Switch and select labels can be used only for "break".
    +					if lt == labelBreak {
    +						addLabel(highScore*math.Pow(.99, float64(i)), p)
    +					}
    +				}
    +			}
    +		}
    +	case labelGoto:
    +		// Goto accepts any label in the same function not in a nested
    +		// block. It also doesn't take labels that would jump across
    +		// variable definitions, but ignore that case for now.
    +		ast.Inspect(c.enclosingFunc.body, func(n ast.Node) bool {
    +			if n == nil {
    +				return false
    +			}
    +
    +			switch n := n.(type) {
    +			// Only search into block-like nodes enclosing our "goto".
    +			// This prevents us from finding labels in nested blocks.
    +			case *ast.BlockStmt, *ast.CommClause, *ast.CaseClause:
    +				for _, p := range c.path {
    +					if n == p {
    +						return true
    +					}
    +				}
    +				return false
    +			case *ast.LabeledStmt:
    +				addLabel(highScore, n)
    +			}
    +
    +			return true
    +		})
    +	}
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/literal.go b/contribs/gnopls/internal/golang/completion/literal.go
    new file mode 100644
    index 00000000000..62398f064c2
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/literal.go
    @@ -0,0 +1,602 @@
    +// 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.
    +
    +package completion
    +
    +import (
    +	"context"
    +	"fmt"
    +	"go/types"
    +	"strings"
    +	"unicode"
    +
    +	"golang.org/x/tools/gopls/internal/golang"
    +	"golang.org/x/tools/gopls/internal/golang/completion/snippet"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/internal/aliases"
    +	"golang.org/x/tools/internal/event"
    +	"golang.org/x/tools/internal/typesinternal"
    +)
    +
    +// literal generates composite literal, function literal, and make()
    +// completion items.
    +func (c *completer) literal(ctx context.Context, literalType types.Type, imp *importInfo) {
    +	if !c.opts.snippets {
    +		return
    +	}
    +
    +	expType := c.inference.objType
    +
    +	if c.inference.matchesVariadic(literalType) {
    +		// Don't offer literal slice candidates for variadic arguments.
    +		// For example, don't offer "[]interface{}{}" in "fmt.Print(<>)".
    +		return
    +	}
    +
    +	// Avoid literal candidates if the expected type is an empty
    +	// interface. It isn't very useful to suggest a literal candidate of
    +	// every possible type.
    +	if expType != nil && isEmptyInterface(expType) {
    +		return
    +	}
    +
    +	// We handle unnamed literal completions explicitly before searching
    +	// for candidates. Avoid named-type literal completions for
    +	// unnamed-type expected type since that results in duplicate
    +	// candidates. For example, in
    +	//
    +	// type mySlice []int
    +	// var []int = <>
    +	//
    +	// don't offer "mySlice{}" since we have already added a candidate
    +	// of "[]int{}".
    +
    +	// TODO(adonovan): think about aliases:
    +	// they should probably be treated more like Named.
    +	// Should this use Deref not Unpointer?
    +	if is[*types.Named](aliases.Unalias(literalType)) &&
    +		expType != nil &&
    +		!is[*types.Named](aliases.Unalias(typesinternal.Unpointer(expType))) {
    +
    +		return
    +	}
    +
    +	// Check if an object of type literalType would match our expected type.
    +	cand := candidate{
    +		obj: c.fakeObj(literalType),
    +	}
    +
    +	switch literalType.Underlying().(type) {
    +	// These literal types are addressable (e.g. "&[]int{}"), others are
    +	// not (e.g. can't do "&(func(){})").
    +	case *types.Struct, *types.Array, *types.Slice, *types.Map:
    +		cand.addressable = true
    +	}
    +
    +	if !c.matchingCandidate(&cand) || cand.convertTo != nil {
    +		return
    +	}
    +
    +	var (
    +		qf  = c.qf
    +		sel = enclosingSelector(c.path, c.pos)
    +	)
    +
    +	// Don't qualify the type name if we are in a selector expression
    +	// since the package name is already present.
    +	if sel != nil {
    +		qf = func(_ *types.Package) string { return "" }
    +	}
    +
    +	snip, typeName := c.typeNameSnippet(literalType, qf)
    +
    +	// A type name of "[]int" doesn't work very will with the matcher
    +	// since "[" isn't a valid identifier prefix. Here we strip off the
    +	// slice (and array) prefix yielding just "int".
    +	matchName := typeName
    +	switch t := literalType.(type) {
    +	case *types.Slice:
    +		matchName = types.TypeString(t.Elem(), qf)
    +	case *types.Array:
    +		matchName = types.TypeString(t.Elem(), qf)
    +	}
    +
    +	addlEdits, err := c.importEdits(imp)
    +	if err != nil {
    +		event.Error(ctx, "error adding import for literal candidate", err)
    +		return
    +	}
    +
    +	// If prefix matches the type name, client may want a composite literal.
    +	if score := c.matcher.Score(matchName); score > 0 {
    +		if cand.hasMod(reference) {
    +			if sel != nil {
    +				// If we are in a selector we must place the "&" before the selector.
    +				// For example, "foo.B<>" must complete to "&foo.Bar{}", not
    +				// "foo.&Bar{}".
    +				edits, err := c.editText(sel.Pos(), sel.Pos(), "&")
    +				if err != nil {
    +					event.Error(ctx, "error making edit for literal pointer completion", err)
    +					return
    +				}
    +				addlEdits = append(addlEdits, edits...)
    +			} else {
    +				// Otherwise we can stick the "&" directly before the type name.
    +				typeName = "&" + typeName
    +				snip.PrependText("&")
    +			}
    +		}
    +
    +		switch t := literalType.Underlying().(type) {
    +		case *types.Struct, *types.Array, *types.Slice, *types.Map:
    +			c.compositeLiteral(t, snip.Clone(), typeName, float64(score), addlEdits)
    +		case *types.Signature:
    +			// Add a literal completion for a signature type that implements
    +			// an interface. For example, offer "http.HandlerFunc()" when
    +			// expected type is "http.Handler".
    +			if expType != nil && types.IsInterface(expType) {
    +				c.basicLiteral(t, snip.Clone(), typeName, float64(score), addlEdits)
    +			}
    +		case *types.Basic:
    +			// Add a literal completion for basic types that implement our
    +			// expected interface (e.g. named string type http.Dir
    +			// implements http.FileSystem), or are identical to our expected
    +			// type (i.e. yielding a type conversion such as "float64()").
    +			if expType != nil && (types.IsInterface(expType) || types.Identical(expType, literalType)) {
    +				c.basicLiteral(t, snip.Clone(), typeName, float64(score), addlEdits)
    +			}
    +		}
    +	}
    +
    +	// If prefix matches "make", client may want a "make()"
    +	// invocation. We also include the type name to allow for more
    +	// flexible fuzzy matching.
    +	if score := c.matcher.Score("make." + matchName); !cand.hasMod(reference) && score > 0 {
    +		switch literalType.Underlying().(type) {
    +		case *types.Slice:
    +			// The second argument to "make()" for slices is required, so default to "0".
    +			c.makeCall(snip.Clone(), typeName, "0", float64(score), addlEdits)
    +		case *types.Map, *types.Chan:
    +			// Maps and channels don't require the second argument, so omit
    +			// to keep things simple for now.
    +			c.makeCall(snip.Clone(), typeName, "", float64(score), addlEdits)
    +		}
    +	}
    +
    +	// If prefix matches "func", client may want a function literal.
    +	if score := c.matcher.Score("func"); !cand.hasMod(reference) && score > 0 && (expType == nil || !types.IsInterface(expType)) {
    +		switch t := literalType.Underlying().(type) {
    +		case *types.Signature:
    +			c.functionLiteral(ctx, t, float64(score))
    +		}
    +	}
    +}
    +
    +// literalCandidateScore is the base score for literal candidates.
    +// Literal candidates match the expected type so they should be high
    +// scoring, but we want them ranked below lexical objects of the
    +// correct type, so scale down highScore.
    +const literalCandidateScore = highScore / 2
    +
    +// functionLiteral adds a function literal completion item for the
    +// given signature.
    +func (c *completer) functionLiteral(ctx context.Context, sig *types.Signature, matchScore float64) {
    +	snip := &snippet.Builder{}
    +	snip.WriteText("func(")
    +
    +	// First we generate names for each param and keep a seen count so
    +	// we know if we need to uniquify param names. For example,
    +	// "func(int)" will become "func(i int)", but "func(int, int64)"
    +	// will become "func(i1 int, i2 int64)".
    +	var (
    +		paramNames     = make([]string, sig.Params().Len())
    +		paramNameCount = make(map[string]int)
    +		hasTypeParams  bool
    +	)
    +	for i := 0; i < sig.Params().Len(); i++ {
    +		var (
    +			p    = sig.Params().At(i)
    +			name = p.Name()
    +		)
    +
    +		if tp, _ := aliases.Unalias(p.Type()).(*types.TypeParam); tp != nil && !c.typeParamInScope(tp) {
    +			hasTypeParams = true
    +		}
    +
    +		if name == "" {
    +			// If the param has no name in the signature, guess a name based
    +			// on the type. Use an empty qualifier to ignore the package.
    +			// For example, we want to name "http.Request" "r", not "hr".
    +			typeName, err := golang.FormatVarType(ctx, c.snapshot, c.pkg, p,
    +				func(p *types.Package) string { return "" },
    +				func(golang.PackageName, golang.ImportPath, golang.PackagePath) string { return "" })
    +			if err != nil {
    +				// In general, the only error we should encounter while formatting is
    +				// context cancellation.
    +				if ctx.Err() == nil {
    +					event.Error(ctx, "formatting var type", err)
    +				}
    +				return
    +			}
    +			name = abbreviateTypeName(typeName)
    +		}
    +		paramNames[i] = name
    +		if name != "_" {
    +			paramNameCount[name]++
    +		}
    +	}
    +
    +	for n, c := range paramNameCount {
    +		// Any names we saw more than once will need a unique suffix added
    +		// on. Reset the count to 1 to act as the suffix for the first
    +		// name.
    +		if c >= 2 {
    +			paramNameCount[n] = 1
    +		} else {
    +			delete(paramNameCount, n)
    +		}
    +	}
    +
    +	for i := 0; i < sig.Params().Len(); i++ {
    +		if hasTypeParams && !c.opts.placeholders {
    +			// If there are type params in the args then the user must
    +			// choose the concrete types. If placeholders are disabled just
    +			// drop them between the parens and let them fill things in.
    +			snip.WritePlaceholder(nil)
    +			break
    +		}
    +
    +		if i > 0 {
    +			snip.WriteText(", ")
    +		}
    +
    +		var (
    +			p    = sig.Params().At(i)
    +			name = paramNames[i]
    +		)
    +
    +		// Uniquify names by adding on an incrementing numeric suffix.
    +		if idx, found := paramNameCount[name]; found {
    +			paramNameCount[name]++
    +			name = fmt.Sprintf("%s%d", name, idx)
    +		}
    +
    +		if name != p.Name() && c.opts.placeholders {
    +			// If we didn't use the signature's param name verbatim then we
    +			// may have chosen a poor name. Give the user a placeholder so
    +			// they can easily fix the name.
    +			snip.WritePlaceholder(func(b *snippet.Builder) {
    +				b.WriteText(name)
    +			})
    +		} else {
    +			snip.WriteText(name)
    +		}
    +
    +		// If the following param's type is identical to this one, omit
    +		// this param's type string. For example, emit "i, j int" instead
    +		// of "i int, j int".
    +		if i == sig.Params().Len()-1 || !types.Identical(p.Type(), sig.Params().At(i+1).Type()) {
    +			snip.WriteText(" ")
    +			typeStr, err := golang.FormatVarType(ctx, c.snapshot, c.pkg, p, c.qf, c.mq)
    +			if err != nil {
    +				// In general, the only error we should encounter while formatting is
    +				// context cancellation.
    +				if ctx.Err() == nil {
    +					event.Error(ctx, "formatting var type", err)
    +				}
    +				return
    +			}
    +			if sig.Variadic() && i == sig.Params().Len()-1 {
    +				typeStr = strings.Replace(typeStr, "[]", "...", 1)
    +			}
    +
    +			if tp, ok := aliases.Unalias(p.Type()).(*types.TypeParam); ok && !c.typeParamInScope(tp) {
    +				snip.WritePlaceholder(func(snip *snippet.Builder) {
    +					snip.WriteText(typeStr)
    +				})
    +			} else {
    +				snip.WriteText(typeStr)
    +			}
    +		}
    +	}
    +	snip.WriteText(")")
    +
    +	results := sig.Results()
    +	if results.Len() > 0 {
    +		snip.WriteText(" ")
    +	}
    +
    +	resultsNeedParens := results.Len() > 1 ||
    +		results.Len() == 1 && results.At(0).Name() != ""
    +
    +	var resultHasTypeParams bool
    +	for i := 0; i < results.Len(); i++ {
    +		if tp, ok := aliases.Unalias(results.At(i).Type()).(*types.TypeParam); ok && !c.typeParamInScope(tp) {
    +			resultHasTypeParams = true
    +		}
    +	}
    +
    +	if resultsNeedParens {
    +		snip.WriteText("(")
    +	}
    +	for i := 0; i < results.Len(); i++ {
    +		if resultHasTypeParams && !c.opts.placeholders {
    +			// Leave an empty tabstop if placeholders are disabled and there
    +			// are type args that need specificying.
    +			snip.WritePlaceholder(nil)
    +			break
    +		}
    +
    +		if i > 0 {
    +			snip.WriteText(", ")
    +		}
    +		r := results.At(i)
    +		if name := r.Name(); name != "" {
    +			snip.WriteText(name + " ")
    +		}
    +
    +		text, err := golang.FormatVarType(ctx, c.snapshot, c.pkg, r, c.qf, c.mq)
    +		if err != nil {
    +			// In general, the only error we should encounter while formatting is
    +			// context cancellation.
    +			if ctx.Err() == nil {
    +				event.Error(ctx, "formatting var type", err)
    +			}
    +			return
    +		}
    +		if tp, ok := aliases.Unalias(r.Type()).(*types.TypeParam); ok && !c.typeParamInScope(tp) {
    +			snip.WritePlaceholder(func(snip *snippet.Builder) {
    +				snip.WriteText(text)
    +			})
    +		} else {
    +			snip.WriteText(text)
    +		}
    +	}
    +	if resultsNeedParens {
    +		snip.WriteText(")")
    +	}
    +
    +	snip.WriteText(" {")
    +	snip.WriteFinalTabstop()
    +	snip.WriteText("}")
    +
    +	c.items = append(c.items, CompletionItem{
    +		Label:   "func(...) {}",
    +		Score:   matchScore * literalCandidateScore,
    +		Kind:    protocol.VariableCompletion,
    +		snippet: snip,
    +	})
    +}
    +
    +// conventionalAcronyms contains conventional acronyms for type names
    +// in lower case. For example, "ctx" for "context" and "err" for "error".
    +var conventionalAcronyms = map[string]string{
    +	"context":        "ctx",
    +	"error":          "err",
    +	"tx":             "tx",
    +	"responsewriter": "w",
    +}
    +
    +// abbreviateTypeName abbreviates type names into acronyms. For
    +// example, "fooBar" is abbreviated "fb". Care is taken to ignore
    +// non-identifier runes. For example, "[]int" becomes "i", and
    +// "struct { i int }" becomes "s".
    +func abbreviateTypeName(s string) string {
    +	var (
    +		b            strings.Builder
    +		useNextUpper bool
    +	)
    +
    +	// Trim off leading non-letters. We trim everything between "[" and
    +	// "]" to handle array types like "[someConst]int".
    +	var inBracket bool
    +	s = strings.TrimFunc(s, func(r rune) bool {
    +		if inBracket {
    +			inBracket = r != ']'
    +			return true
    +		}
    +
    +		if r == '[' {
    +			inBracket = true
    +		}
    +
    +		return !unicode.IsLetter(r)
    +	})
    +
    +	if acr, ok := conventionalAcronyms[strings.ToLower(s)]; ok {
    +		return acr
    +	}
    +
    +	for i, r := range s {
    +		// Stop if we encounter a non-identifier rune.
    +		if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
    +			break
    +		}
    +
    +		if i == 0 {
    +			b.WriteRune(unicode.ToLower(r))
    +		}
    +
    +		if unicode.IsUpper(r) {
    +			if useNextUpper {
    +				b.WriteRune(unicode.ToLower(r))
    +				useNextUpper = false
    +			}
    +		} else {
    +			useNextUpper = true
    +		}
    +	}
    +
    +	return b.String()
    +}
    +
    +// compositeLiteral adds a composite literal completion item for the given typeName.
    +// T is an (unnamed, unaliased) struct, array, slice, or map type.
    +func (c *completer) compositeLiteral(T types.Type, snip *snippet.Builder, typeName string, matchScore float64, edits []protocol.TextEdit) {
    +	snip.WriteText("{")
    +	// Don't put the tab stop inside the composite literal curlies "{}"
    +	// for structs that have no accessible fields.
    +	if strct, ok := T.(*types.Struct); !ok || fieldsAccessible(strct, c.pkg.Types()) {
    +		snip.WriteFinalTabstop()
    +	}
    +	snip.WriteText("}")
    +
    +	nonSnippet := typeName + "{}"
    +
    +	c.items = append(c.items, CompletionItem{
    +		Label:               nonSnippet,
    +		InsertText:          nonSnippet,
    +		Score:               matchScore * literalCandidateScore,
    +		Kind:                protocol.VariableCompletion,
    +		AdditionalTextEdits: edits,
    +		snippet:             snip,
    +	})
    +}
    +
    +// basicLiteral adds a literal completion item for the given basic
    +// type name typeName.
    +func (c *completer) basicLiteral(T types.Type, snip *snippet.Builder, typeName string, matchScore float64, edits []protocol.TextEdit) {
    +	// Never give type conversions like "untyped int()".
    +	if isUntyped(T) {
    +		return
    +	}
    +
    +	snip.WriteText("(")
    +	snip.WriteFinalTabstop()
    +	snip.WriteText(")")
    +
    +	nonSnippet := typeName + "()"
    +
    +	c.items = append(c.items, CompletionItem{
    +		Label:               nonSnippet,
    +		InsertText:          nonSnippet,
    +		Detail:              T.String(),
    +		Score:               matchScore * literalCandidateScore,
    +		Kind:                protocol.VariableCompletion,
    +		AdditionalTextEdits: edits,
    +		snippet:             snip,
    +	})
    +}
    +
    +// makeCall adds a completion item for a "make()" call given a specific type.
    +func (c *completer) makeCall(snip *snippet.Builder, typeName string, secondArg string, matchScore float64, edits []protocol.TextEdit) {
    +	// Keep it simple and don't add any placeholders for optional "make()" arguments.
    +
    +	snip.PrependText("make(")
    +	if secondArg != "" {
    +		snip.WriteText(", ")
    +		snip.WritePlaceholder(func(b *snippet.Builder) {
    +			if c.opts.placeholders {
    +				b.WriteText(secondArg)
    +			}
    +		})
    +	}
    +	snip.WriteText(")")
    +
    +	var nonSnippet strings.Builder
    +	nonSnippet.WriteString("make(" + typeName)
    +	if secondArg != "" {
    +		nonSnippet.WriteString(", ")
    +		nonSnippet.WriteString(secondArg)
    +	}
    +	nonSnippet.WriteByte(')')
    +
    +	c.items = append(c.items, CompletionItem{
    +		Label:               nonSnippet.String(),
    +		InsertText:          nonSnippet.String(),
    +		Score:               matchScore * literalCandidateScore,
    +		Kind:                protocol.FunctionCompletion,
    +		AdditionalTextEdits: edits,
    +		snippet:             snip,
    +	})
    +}
    +
    +// Create a snippet for a type name where type params become placeholders.
    +func (c *completer) typeNameSnippet(literalType types.Type, qf types.Qualifier) (*snippet.Builder, string) {
    +	var (
    +		snip     snippet.Builder
    +		typeName string
    +		// TODO(adonovan): think more about aliases.
    +		// They should probably be treated more like Named.
    +		named, _ = aliases.Unalias(literalType).(*types.Named)
    +	)
    +
    +	if named != nil && named.Obj() != nil && named.TypeParams().Len() > 0 && !c.fullyInstantiated(named) {
    +		// We are not "fully instantiated" meaning we have type params that must be specified.
    +		if pkg := qf(named.Obj().Pkg()); pkg != "" {
    +			typeName = pkg + "."
    +		}
    +
    +		// We do this to get "someType" instead of "someType[T]".
    +		typeName += named.Obj().Name()
    +		snip.WriteText(typeName + "[")
    +
    +		if c.opts.placeholders {
    +			for i := 0; i < named.TypeParams().Len(); i++ {
    +				if i > 0 {
    +					snip.WriteText(", ")
    +				}
    +				snip.WritePlaceholder(func(snip *snippet.Builder) {
    +					snip.WriteText(types.TypeString(named.TypeParams().At(i), qf))
    +				})
    +			}
    +		} else {
    +			snip.WritePlaceholder(nil)
    +		}
    +		snip.WriteText("]")
    +		typeName += "[...]"
    +	} else {
    +		// We don't have unspecified type params so use default type formatting.
    +		typeName = types.TypeString(literalType, qf)
    +		snip.WriteText(typeName)
    +	}
    +
    +	return &snip, typeName
    +}
    +
    +// fullyInstantiated reports whether all of t's type params have
    +// specified type args.
    +func (c *completer) fullyInstantiated(t *types.Named) bool {
    +	tps := t.TypeParams()
    +	tas := t.TypeArgs()
    +
    +	if tps.Len() != tas.Len() {
    +		return false
    +	}
    +
    +	for i := 0; i < tas.Len(); i++ {
    +		// TODO(adonovan) think about generic aliases.
    +		switch ta := aliases.Unalias(tas.At(i)).(type) {
    +		case *types.TypeParam:
    +			// A *TypeParam only counts as specified if it is currently in
    +			// scope (i.e. we are in a generic definition).
    +			if !c.typeParamInScope(ta) {
    +				return false
    +			}
    +		case *types.Named:
    +			if !c.fullyInstantiated(ta) {
    +				return false
    +			}
    +		}
    +	}
    +	return true
    +}
    +
    +// typeParamInScope returns whether tp's object is in scope at c.pos.
    +// This tells you whether you are in a generic definition and can
    +// assume tp has been specified.
    +func (c *completer) typeParamInScope(tp *types.TypeParam) bool {
    +	obj := tp.Obj()
    +	if obj == nil {
    +		return false
    +	}
    +
    +	scope := c.innermostScope()
    +	if scope == nil {
    +		return false
    +	}
    +
    +	_, foundObj := scope.LookupParent(obj.Name(), c.pos)
    +	return obj == foundObj
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/package.go b/contribs/gnopls/internal/golang/completion/package.go
    new file mode 100644
    index 00000000000..e71f5c9dd02
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/package.go
    @@ -0,0 +1,334 @@
    +// 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 completion
    +
    +import (
    +	"bytes"
    +	"context"
    +	"errors"
    +	"fmt"
    +	"go/ast"
    +	"go/parser"
    +	"go/scanner"
    +	"go/token"
    +	"go/types"
    +	"path/filepath"
    +	"strings"
    +	"unicode"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/fuzzy"
    +	"golang.org/x/tools/gopls/internal/golang"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +)
    +
    +// packageClauseCompletions offers completions for a package declaration when
    +// one is not present in the given file.
    +func packageClauseCompletions(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]CompletionItem, *Selection, error) {
    +	// We know that the AST for this file will be empty due to the missing
    +	// package declaration, but parse it anyway to get a mapper.
    +	// TODO(adonovan): opt: there's no need to parse just to get a mapper.
    +	pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +
    +	offset, err := pgf.Mapper.PositionOffset(position)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +	surrounding, err := packageCompletionSurrounding(pgf, offset)
    +	if err != nil {
    +		return nil, nil, fmt.Errorf("invalid position for package completion: %w", err)
    +	}
    +
    +	packageSuggestions, err := packageSuggestions(ctx, snapshot, fh.URI(), "")
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +
    +	var items []CompletionItem
    +	for _, pkg := range packageSuggestions {
    +		insertText := fmt.Sprintf("package %s", pkg.name)
    +		items = append(items, CompletionItem{
    +			Label:      insertText,
    +			Kind:       protocol.ModuleCompletion,
    +			InsertText: insertText,
    +			Score:      pkg.score,
    +		})
    +	}
    +
    +	return items, surrounding, nil
    +}
    +
    +// packageCompletionSurrounding returns surrounding for package completion if a
    +// package completions can be suggested at a given cursor offset. A valid location
    +// for package completion is above any declarations or import statements.
    +func packageCompletionSurrounding(pgf *parsego.File, offset int) (*Selection, error) {
    +	m := pgf.Mapper
    +	// If the file lacks a package declaration, the parser will return an empty
    +	// AST. As a work-around, try to parse an expression from the file contents.
    +	fset := token.NewFileSet()
    +	expr, _ := parser.ParseExprFrom(fset, m.URI.Path(), pgf.Src, parser.Mode(0))
    +	if expr == nil {
    +		return nil, fmt.Errorf("unparseable file (%s)", m.URI)
    +	}
    +	tok := fset.File(expr.Pos())
    +	cursor := tok.Pos(offset)
    +
    +	// If we were able to parse out an identifier as the first expression from
    +	// the file, it may be the beginning of a package declaration ("pack ").
    +	// We can offer package completions if the cursor is in the identifier.
    +	if name, ok := expr.(*ast.Ident); ok {
    +		if cursor >= name.Pos() && cursor <= name.End() {
    +			if !strings.HasPrefix(PACKAGE, name.Name) {
    +				return nil, fmt.Errorf("cursor in non-matching ident")
    +			}
    +			return &Selection{
    +				content: name.Name,
    +				cursor:  cursor,
    +				tokFile: tok,
    +				start:   name.Pos(),
    +				end:     name.End(),
    +				mapper:  m,
    +			}, nil
    +		}
    +	}
    +
    +	// The file is invalid, but it contains an expression that we were able to
    +	// parse. We will use this expression to construct the cursor's
    +	// "surrounding".
    +
    +	// First, consider the possibility that we have a valid "package" keyword
    +	// with an empty package name ("package "). "package" is parsed as an
    +	// *ast.BadDecl since it is a keyword.
    +	start, err := safetoken.Offset(tok, expr.Pos())
    +	if err != nil {
    +		return nil, err
    +	}
    +	if offset > start && string(bytes.TrimRight(pgf.Src[start:offset], " ")) == PACKAGE {
    +		return &Selection{
    +			content: string(pgf.Src[start:offset]),
    +			cursor:  cursor,
    +			tokFile: tok,
    +			start:   expr.Pos(),
    +			end:     cursor,
    +			mapper:  m,
    +		}, nil
    +	}
    +
    +	// If the cursor is after the start of the expression, no package
    +	// declaration will be valid.
    +	if cursor > expr.Pos() {
    +		return nil, fmt.Errorf("cursor after expression")
    +	}
    +
    +	// If the cursor is in a comment, don't offer any completions.
    +	if cursorInComment(tok, cursor, m.Content) {
    +		return nil, fmt.Errorf("cursor in comment")
    +	}
    +
    +	// The surrounding range in this case is the cursor.
    +	return &Selection{
    +		content: "",
    +		tokFile: tok,
    +		start:   cursor,
    +		end:     cursor,
    +		cursor:  cursor,
    +		mapper:  m,
    +	}, nil
    +}
    +
    +func cursorInComment(file *token.File, cursor token.Pos, src []byte) bool {
    +	var s scanner.Scanner
    +	s.Init(file, src, func(_ token.Position, _ string) {}, scanner.ScanComments)
    +	for {
    +		pos, tok, lit := s.Scan()
    +		if pos <= cursor && cursor <= token.Pos(int(pos)+len(lit)) {
    +			return tok == token.COMMENT
    +		}
    +		if tok == token.EOF {
    +			break
    +		}
    +	}
    +	return false
    +}
    +
    +// packageNameCompletions returns name completions for a package clause using
    +// the current name as prefix.
    +func (c *completer) packageNameCompletions(ctx context.Context, fileURI protocol.DocumentURI, name *ast.Ident) error {
    +	cursor := int(c.pos - name.NamePos)
    +	if cursor < 0 || cursor > len(name.Name) {
    +		return errors.New("cursor is not in package name identifier")
    +	}
    +
    +	c.completionContext.packageCompletion = true
    +
    +	prefix := name.Name[:cursor]
    +	packageSuggestions, err := packageSuggestions(ctx, c.snapshot, fileURI, prefix)
    +	if err != nil {
    +		return err
    +	}
    +
    +	for _, pkg := range packageSuggestions {
    +		c.deepState.enqueue(pkg)
    +	}
    +	return nil
    +}
    +
    +// packageSuggestions returns a list of packages from workspace packages that
    +// have the given prefix and are used in the same directory as the given
    +// file. This also includes test packages for these packages (_test) and
    +// the directory name itself.
    +func packageSuggestions(ctx context.Context, snapshot *cache.Snapshot, fileURI protocol.DocumentURI, prefix string) (packages []candidate, err error) {
    +	active, err := snapshot.WorkspaceMetadata(ctx)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	toCandidate := func(name string, score float64) candidate {
    +		obj := types.NewPkgName(0, nil, name, types.NewPackage("", name))
    +		return candidate{obj: obj, name: name, detail: name, score: score}
    +	}
    +
    +	matcher := fuzzy.NewMatcher(prefix)
    +
    +	// Always try to suggest a main package
    +	defer func() {
    +		if score := float64(matcher.Score("main")); score > 0 {
    +			packages = append(packages, toCandidate("main", score*lowScore))
    +		}
    +	}()
    +
    +	dirPath := filepath.Dir(fileURI.Path())
    +	dirName := filepath.Base(dirPath)
    +	if !isValidDirName(dirName) {
    +		return packages, nil
    +	}
    +	pkgName := convertDirNameToPkgName(dirName)
    +
    +	seenPkgs := make(map[golang.PackageName]struct{})
    +
    +	// The `go` command by default only allows one package per directory but we
    +	// support multiple package suggestions since gopls is build system agnostic.
    +	for _, mp := range active {
    +		if mp.Name == "main" || mp.Name == "" {
    +			continue
    +		}
    +		if _, ok := seenPkgs[mp.Name]; ok {
    +			continue
    +		}
    +
    +		// Only add packages that are previously used in the current directory.
    +		var relevantPkg bool
    +		for _, uri := range mp.CompiledGoFiles {
    +			if filepath.Dir(uri.Path()) == dirPath {
    +				relevantPkg = true
    +				break
    +			}
    +		}
    +		if !relevantPkg {
    +			continue
    +		}
    +
    +		// Add a found package used in current directory as a high relevance
    +		// suggestion and the test package for it as a medium relevance
    +		// suggestion.
    +		if score := float64(matcher.Score(string(mp.Name))); score > 0 {
    +			packages = append(packages, toCandidate(string(mp.Name), score*highScore))
    +		}
    +		seenPkgs[mp.Name] = struct{}{}
    +
    +		testPkgName := mp.Name + "_test"
    +		if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(string(mp.Name), "_test") {
    +			continue
    +		}
    +		if score := float64(matcher.Score(string(testPkgName))); score > 0 {
    +			packages = append(packages, toCandidate(string(testPkgName), score*stdScore))
    +		}
    +		seenPkgs[testPkgName] = struct{}{}
    +	}
    +
    +	// Add current directory name as a low relevance suggestion.
    +	if _, ok := seenPkgs[pkgName]; !ok {
    +		if score := float64(matcher.Score(string(pkgName))); score > 0 {
    +			packages = append(packages, toCandidate(string(pkgName), score*lowScore))
    +		}
    +
    +		testPkgName := pkgName + "_test"
    +		if score := float64(matcher.Score(string(testPkgName))); score > 0 {
    +			packages = append(packages, toCandidate(string(testPkgName), score*lowScore))
    +		}
    +	}
    +
    +	return packages, nil
    +}
    +
    +// isValidDirName checks whether the passed directory name can be used in
    +// a package path. Requirements for a package path can be found here:
    +// https://golang.org/ref/mod#go-mod-file-ident.
    +func isValidDirName(dirName string) bool {
    +	if dirName == "" {
    +		return false
    +	}
    +
    +	for i, ch := range dirName {
    +		if isLetter(ch) || isDigit(ch) {
    +			continue
    +		}
    +		if i == 0 {
    +			// Directory name can start only with '_'. '.' is not allowed in module paths.
    +			// '-' and '~' are not allowed because elements of package paths must be
    +			// safe command-line arguments.
    +			if ch == '_' {
    +				continue
    +			}
    +		} else {
    +			// Modules path elements can't end with '.'
    +			if isAllowedPunctuation(ch) && (i != len(dirName)-1 || ch != '.') {
    +				continue
    +			}
    +		}
    +
    +		return false
    +	}
    +	return true
    +}
    +
    +// convertDirNameToPkgName converts a valid directory name to a valid package name.
    +// It leaves only letters and digits. All letters are mapped to lower case.
    +func convertDirNameToPkgName(dirName string) golang.PackageName {
    +	var buf bytes.Buffer
    +	for _, ch := range dirName {
    +		switch {
    +		case isLetter(ch):
    +			buf.WriteRune(unicode.ToLower(ch))
    +
    +		case buf.Len() != 0 && isDigit(ch):
    +			buf.WriteRune(ch)
    +		}
    +	}
    +	return golang.PackageName(buf.String())
    +}
    +
    +// isLetter and isDigit allow only ASCII characters because
    +// "Each path element is a non-empty string made of up ASCII letters,
    +// ASCII digits, and limited ASCII punctuation"
    +// (see https://golang.org/ref/mod#go-mod-file-ident).
    +
    +func isLetter(ch rune) bool {
    +	return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z'
    +}
    +
    +func isDigit(ch rune) bool {
    +	return '0' <= ch && ch <= '9'
    +}
    +
    +func isAllowedPunctuation(ch rune) bool {
    +	return ch == '_' || ch == '-' || ch == '~' || ch == '.'
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/package_test.go b/contribs/gnopls/internal/golang/completion/package_test.go
    new file mode 100644
    index 00000000000..dc4058fa651
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/package_test.go
    @@ -0,0 +1,81 @@
    +// 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 completion
    +
    +import (
    +	"testing"
    +
    +	"golang.org/x/tools/gopls/internal/golang"
    +)
    +
    +func TestIsValidDirName(t *testing.T) {
    +	tests := []struct {
    +		dirName string
    +		valid   bool
    +	}{
    +		{dirName: "", valid: false},
    +		//
    +		{dirName: "a", valid: true},
    +		{dirName: "abcdef", valid: true},
    +		{dirName: "AbCdEf", valid: true},
    +		//
    +		{dirName: "1a35", valid: true},
    +		{dirName: "a16", valid: true},
    +		//
    +		{dirName: "_a", valid: true},
    +		{dirName: "a_", valid: true},
    +		//
    +		{dirName: "~a", valid: false},
    +		{dirName: "a~", valid: true},
    +		//
    +		{dirName: "-a", valid: false},
    +		{dirName: "a-", valid: true},
    +		//
    +		{dirName: ".a", valid: false},
    +		{dirName: "a.", valid: false},
    +		//
    +		{dirName: "a~_b--c.-e", valid: true},
    +		{dirName: "~a~_b--c.-e", valid: false},
    +		{dirName: "a~_b--c.-e--~", valid: true},
    +		{dirName: "a~_b--2134dc42.-e6--~", valid: true},
    +		{dirName: "abc`def", valid: false},
    +		{dirName: "тест", valid: false},
    +		{dirName: "你好", valid: false},
    +	}
    +	for _, tt := range tests {
    +		valid := isValidDirName(tt.dirName)
    +		if tt.valid != valid {
    +			t.Errorf("%s: expected %v, got %v", tt.dirName, tt.valid, valid)
    +		}
    +	}
    +}
    +
    +func TestConvertDirNameToPkgName(t *testing.T) {
    +	tests := []struct {
    +		dirName string
    +		pkgName golang.PackageName
    +	}{
    +		{dirName: "a", pkgName: "a"},
    +		{dirName: "abcdef", pkgName: "abcdef"},
    +		{dirName: "AbCdEf", pkgName: "abcdef"},
    +		{dirName: "1a35", pkgName: "a35"},
    +		{dirName: "14a35", pkgName: "a35"},
    +		{dirName: "a16", pkgName: "a16"},
    +		{dirName: "_a", pkgName: "a"},
    +		{dirName: "a_", pkgName: "a"},
    +		{dirName: "a~", pkgName: "a"},
    +		{dirName: "a-", pkgName: "a"},
    +		{dirName: "a~_b--c.-e", pkgName: "abce"},
    +		{dirName: "a~_b--c.-e--~", pkgName: "abce"},
    +		{dirName: "a~_b--2134dc42.-e6--~", pkgName: "ab2134dc42e6"},
    +	}
    +	for _, tt := range tests {
    +		pkgName := convertDirNameToPkgName(tt.dirName)
    +		if tt.pkgName != pkgName {
    +			t.Errorf("%s: expected %v, got %v", tt.dirName, tt.pkgName, pkgName)
    +			continue
    +		}
    +	}
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/postfix_snippets.go b/contribs/gnopls/internal/golang/completion/postfix_snippets.go
    new file mode 100644
    index 00000000000..641fe8746eb
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/postfix_snippets.go
    @@ -0,0 +1,682 @@
    +// 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 completion
    +
    +import (
    +	"context"
    +	"fmt"
    +	"go/ast"
    +	"go/token"
    +	"go/types"
    +	"log"
    +	"reflect"
    +	"strings"
    +	"sync"
    +	"text/template"
    +
    +	"golang.org/x/tools/gopls/internal/cache/metadata"
    +	"golang.org/x/tools/gopls/internal/golang"
    +	"golang.org/x/tools/gopls/internal/golang/completion/snippet"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/internal/aliases"
    +	"golang.org/x/tools/internal/event"
    +	"golang.org/x/tools/internal/imports"
    +	"golang.org/x/tools/internal/typesinternal"
    +)
    +
    +// Postfix snippets are artificial methods that allow the user to
    +// compose common operations in an "argument oriented" fashion. For
    +// example, instead of "sort.Slice(someSlice, ...)" a user can expand
    +// "someSlice.sort!".
    +
    +// postfixTmpl represents a postfix snippet completion candidate.
    +type postfixTmpl struct {
    +	// label is the completion candidate's label presented to the user.
    +	label string
    +
    +	// details is passed along to the client as the candidate's details.
    +	details string
    +
    +	// body is the template text. See postfixTmplArgs for details on the
    +	// facilities available to the template.
    +	body string
    +
    +	tmpl *template.Template
    +}
    +
    +// postfixTmplArgs are the template execution arguments available to
    +// the postfix snippet templates.
    +type postfixTmplArgs struct {
    +	// StmtOK is true if it is valid to replace the selector with a
    +	// statement. For example:
    +	//
    +	//    func foo() {
    +	//      bar.sort! // statement okay
    +	//
    +	//      someMethod(bar.sort!) // statement not okay
    +	//    }
    +	StmtOK bool
    +
    +	// X is the textual SelectorExpr.X. For example, when completing
    +	// "foo.bar.print!", "X" is "foo.bar".
    +	X string
    +
    +	// Obj is the types.Object of SelectorExpr.X, if any.
    +	Obj types.Object
    +
    +	// Type is the type of "foo.bar" in "foo.bar.print!".
    +	Type types.Type
    +
    +	// FuncResult are results of the enclosed function
    +	FuncResults []*types.Var
    +
    +	sel            *ast.SelectorExpr
    +	scope          *types.Scope
    +	snip           snippet.Builder
    +	importIfNeeded func(pkgPath string, scope *types.Scope) (name string, edits []protocol.TextEdit, err error)
    +	edits          []protocol.TextEdit
    +	qf             types.Qualifier
    +	varNames       map[string]bool
    +	placeholders   bool
    +	currentTabStop int
    +}
    +
    +var postfixTmpls = []postfixTmpl{{
    +	label:   "sort",
    +	details: "sort.Slice()",
    +	body: `{{if and (eq .Kind "slice") .StmtOK -}}
    +{{.Import "sort"}}.Slice({{.X}}, func({{.VarName nil "i"}}, {{.VarName nil "j"}} int) bool {
    +	{{.Cursor}}
    +})
    +{{- end}}`,
    +}, {
    +	label:   "last",
    +	details: "s[len(s)-1]",
    +	body: `{{if and (eq .Kind "slice") .Obj -}}
    +{{.X}}[len({{.X}})-1]
    +{{- end}}`,
    +}, {
    +	label:   "reverse",
    +	details: "reverse slice",
    +	body: `{{if and (eq .Kind "slice") .StmtOK -}}
    +{{.Import "slices"}}.Reverse({{.X}})
    +{{- end}}`,
    +}, {
    +	label:   "range",
    +	details: "range over slice",
    +	body: `{{if and (eq .Kind "slice") .StmtOK -}}
    +for {{.VarName nil "i" | .Placeholder }}, {{.VarName .ElemType "v" | .Placeholder}} := range {{.X}} {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "for",
    +	details: "range over slice by index",
    +	body: `{{if and (eq .Kind "slice") .StmtOK -}}
    +for {{ .VarName nil "i" | .Placeholder }} := range {{.X}} {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "forr",
    +	details: "range over slice by index and value",
    +	body: `{{if and (eq .Kind "slice") .StmtOK -}}
    +for {{.VarName nil "i" | .Placeholder }}, {{.VarName .ElemType "v" | .Placeholder }} := range {{.X}} {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "append",
    +	details: "append and re-assign slice",
    +	body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}}
    +{{.X}} = append({{.X}}, {{.Cursor}})
    +{{- end}}`,
    +}, {
    +	label:   "append",
    +	details: "append to slice",
    +	body: `{{if and (eq .Kind "slice") (not .StmtOK) -}}
    +append({{.X}}, {{.Cursor}})
    +{{- end}}`,
    +}, {
    +	label:   "copy",
    +	details: "duplicate slice",
    +	body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}}
    +{{$v := (.VarName nil (printf "%sCopy" .X))}}{{$v}} := make([]{{.TypeName .ElemType}}, len({{.X}}))
    +copy({{$v}}, {{.X}})
    +{{end}}`,
    +}, {
    +	label:   "range",
    +	details: "range over map",
    +	body: `{{if and (eq .Kind "map") .StmtOK -}}
    +for {{.VarName .KeyType "k" | .Placeholder}}, {{.VarName .ElemType "v" | .Placeholder}} := range {{.X}} {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "for",
    +	details: "range over map by key",
    +	body: `{{if and (eq .Kind "map") .StmtOK -}}
    +for {{.VarName .KeyType "k" | .Placeholder}} := range {{.X}} {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "forr",
    +	details: "range over map by key and value",
    +	body: `{{if and (eq .Kind "map") .StmtOK -}}
    +for {{.VarName .KeyType "k" | .Placeholder}}, {{.VarName .ElemType "v" | .Placeholder}} := range {{.X}} {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "clear",
    +	details: "clear map contents",
    +	body: `{{if and (eq .Kind "map") .StmtOK -}}
    +{{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} {
    +	delete({{.X}}, {{$k}})
    +}
    +{{end}}`,
    +}, {
    +	label:   "keys",
    +	details: "create slice of keys",
    +	body: `{{if and (eq .Kind "map") .StmtOK -}}
    +{{$keysVar := (.VarName nil "keys")}}{{$keysVar}} := make([]{{.TypeName .KeyType}}, 0, len({{.X}}))
    +{{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} {
    +	{{$keysVar}} = append({{$keysVar}}, {{$k}})
    +}
    +{{end}}`,
    +}, {
    +	label:   "range",
    +	details: "range over channel",
    +	body: `{{if and (eq .Kind "chan") .StmtOK -}}
    +for {{.VarName .ElemType "e" | .Placeholder}} := range {{.X}} {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "for",
    +	details: "range over channel",
    +	body: `{{if and (eq .Kind "chan") .StmtOK -}}
    +for {{.VarName .ElemType "e" | .Placeholder}} := range {{.X}} {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "var",
    +	details: "assign to variables",
    +	body: `{{if and (eq .Kind "tuple") .StmtOK -}}
    +{{$a := .}}{{range $i, $v := .Tuple}}{{if $i}}, {{end}}{{$a.VarName $v.Type $v.Name | $a.Placeholder }}{{end}} := {{.X}}
    +{{- end}}`,
    +}, {
    +	label:   "var",
    +	details: "assign to variable",
    +	body: `{{if and (ne .Kind "tuple") .StmtOK -}}
    +{{.VarName .Type "" | .Placeholder }} := {{.X}}
    +{{- end}}`,
    +}, {
    +	label:   "print",
    +	details: "print to stdout",
    +	body: `{{if and (ne .Kind "tuple") .StmtOK -}}
    +{{.Import "fmt"}}.Printf("{{.EscapeQuotes .X}}: %v\n", {{.X}})
    +{{- end}}`,
    +}, {
    +	label:   "print",
    +	details: "print to stdout",
    +	body: `{{if and (eq .Kind "tuple") .StmtOK -}}
    +{{.Import "fmt"}}.Println({{.X}})
    +{{- end}}`,
    +}, {
    +	label:   "split",
    +	details: "split string",
    +	body: `{{if (eq (.TypeName .Type) "string") -}}
    +{{.Import "strings"}}.Split({{.X}}, "{{.Cursor}}")
    +{{- end}}`,
    +}, {
    +	label:   "join",
    +	details: "join string slice",
    +	body: `{{if and (eq .Kind "slice") (eq (.TypeName .ElemType) "string") -}}
    +{{.Import "strings"}}.Join({{.X}}, "{{.Cursor}}")
    +{{- end}}`,
    +}, {
    +	label:   "ifnotnil",
    +	details: "if expr != nil",
    +	body: `{{if and (or (eq .Kind "pointer") (eq .Kind "chan") (eq .Kind "signature") (eq .Kind "interface") (eq .Kind "map") (eq .Kind "slice")) .StmtOK -}}
    +if {{.X}} != nil {
    +	{{.Cursor}}
    +}
    +{{- end}}`,
    +}, {
    +	label:   "len",
    +	details: "len(s)",
    +	body: `{{if (eq .Kind "slice" "map" "array" "chan") -}}
    +len({{.X}})
    +{{- end}}`,
    +}, {
    +	label:   "iferr",
    +	details: "check error and return",
    +	body: `{{if and .StmtOK (eq (.TypeName .Type) "error") -}}
    +{{- $errName := (or (and .IsIdent .X) "err") -}}
    +if {{if not .IsIdent}}err := {{.X}}; {{end}}{{$errName}} != nil {
    +	return {{$a := .}}{{range $i, $v := .FuncResults}}
    +		{{- if $i}}, {{end -}}
    +		{{- if eq ($a.TypeName $v.Type) "error" -}}
    +			{{$a.Placeholder $errName}}
    +		{{- else -}}
    +			{{$a.Zero $v.Type}}
    +		{{- end -}}
    +	{{end}}
    +}
    +{{end}}`,
    +}, {
    +	label:   "iferr",
    +	details: "check error and return",
    +	body: `{{if and .StmtOK (eq .Kind "tuple") (len .Tuple) (eq (.TypeName .TupleLast.Type) "error") -}}
    +{{- $a := . -}}
    +if {{range $i, $v := .Tuple}}{{if $i}}, {{end}}{{if and (eq ($a.TypeName $v.Type) "error") (eq (inc $i) (len $a.Tuple))}}err{{else}}_{{end}}{{end}} := {{.X -}}
    +; err != nil {
    +	return {{range $i, $v := .FuncResults}}
    +		{{- if $i}}, {{end -}}
    +		{{- if eq ($a.TypeName $v.Type) "error" -}}
    +			{{$a.Placeholder "err"}}
    +		{{- else -}}
    +			{{$a.Zero $v.Type}}
    +		{{- end -}}
    +	{{end}}
    +}
    +{{end}}`,
    +}, {
    +	// variferr snippets use nested placeholders, as described in
    +	// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax,
    +	// so that users can wrap the returned error without modifying the error
    +	// variable name.
    +	label:   "variferr",
    +	details: "assign variables and check error",
    +	body: `{{if and .StmtOK (eq .Kind "tuple") (len .Tuple) (eq (.TypeName .TupleLast.Type) "error") -}}
    +{{- $a := . -}}
    +{{- $errName := "err" -}}
    +{{- range $i, $v := .Tuple -}}
    +	{{- if $i}}, {{end -}}
    +	{{- if and (eq ($a.TypeName $v.Type) "error") (eq (inc $i) (len $a.Tuple)) -}}
    +		{{$errName | $a.SpecifiedPlaceholder (len $a.Tuple)}}
    +	{{- else -}}
    +		{{$a.VarName $v.Type $v.Name | $a.Placeholder}}
    +	{{- end -}}
    +{{- end}} := {{.X}}
    +if {{$errName | $a.SpecifiedPlaceholder (len $a.Tuple)}} != nil {
    +	return {{range $i, $v := .FuncResults}}
    +		{{- if $i}}, {{end -}}
    +		{{- if eq ($a.TypeName $v.Type) "error" -}}
    +			{{$errName | $a.SpecifiedPlaceholder (len $a.Tuple) |
    +				$a.SpecifiedPlaceholder (inc (len $a.Tuple))}}
    +		{{- else -}}
    +			{{$a.Zero $v.Type}}
    +		{{- end -}}
    +	{{end}}
    +}
    +{{end}}`,
    +}, {
    +	label:   "variferr",
    +	details: "assign variables and check error",
    +	body: `{{if and .StmtOK (eq (.TypeName .Type) "error") -}}
    +{{- $a := . -}}
    +{{- $errName := .VarName nil "err" -}}
    +{{$errName | $a.SpecifiedPlaceholder 1}} := {{.X}}
    +if {{$errName | $a.SpecifiedPlaceholder 1}} != nil {
    +	return {{range $i, $v := .FuncResults}}
    +		{{- if $i}}, {{end -}}
    +		{{- if eq ($a.TypeName $v.Type) "error" -}}
    +			{{$errName | $a.SpecifiedPlaceholder 1 | $a.SpecifiedPlaceholder 2}}
    +		{{- else -}}
    +			{{$a.Zero $v.Type}}
    +		{{- end -}}
    +	{{end}}
    +}
    +{{end}}`,
    +}}
    +
    +// Cursor indicates where the client's cursor should end up after the
    +// snippet is done.
    +func (a *postfixTmplArgs) Cursor() string {
    +	return "$0"
    +}
    +
    +// Placeholder indicate a tab stop with the placeholder string, the order
    +// of tab stops is the same as the order of invocation
    +func (a *postfixTmplArgs) Placeholder(placeholder string) string {
    +	if !a.placeholders {
    +		placeholder = ""
    +	}
    +	return fmt.Sprintf("${%d:%s}", a.nextTabStop(), placeholder)
    +}
    +
    +// nextTabStop returns the next tab stop index for a new placeholder.
    +func (a *postfixTmplArgs) nextTabStop() int {
    +	// Tab stops start from 1, so increment before returning.
    +	a.currentTabStop++
    +	return a.currentTabStop
    +}
    +
    +// SpecifiedPlaceholder indicate a specified tab stop with the placeholder string.
    +// Sometimes the same tab stop appears in multiple places and their numbers
    +// need to be specified. e.g. variferr
    +func (a *postfixTmplArgs) SpecifiedPlaceholder(tabStop int, placeholder string) string {
    +	if !a.placeholders {
    +		placeholder = ""
    +	}
    +	return fmt.Sprintf("${%d:%s}", tabStop, placeholder)
    +}
    +
    +// Import makes sure the package corresponding to path is imported,
    +// returning the identifier to use to refer to the package.
    +func (a *postfixTmplArgs) Import(path string) (string, error) {
    +	name, edits, err := a.importIfNeeded(path, a.scope)
    +	if err != nil {
    +		return "", fmt.Errorf("couldn't import %q: %w", path, err)
    +	}
    +	a.edits = append(a.edits, edits...)
    +
    +	return name, nil
    +}
    +
    +func (a *postfixTmplArgs) EscapeQuotes(v string) string {
    +	return strings.ReplaceAll(v, `"`, `\\"`)
    +}
    +
    +// ElemType returns the Elem() type of xType, if applicable.
    +func (a *postfixTmplArgs) ElemType() types.Type {
    +	type hasElem interface{ Elem() types.Type } // Array, Chan, Map, Pointer, Slice
    +	if e, ok := a.Type.Underlying().(hasElem); ok {
    +		return e.Elem()
    +	}
    +	return nil
    +}
    +
    +// Kind returns the underlying kind of type, e.g. "slice", "struct",
    +// etc.
    +func (a *postfixTmplArgs) Kind() string {
    +	t := reflect.TypeOf(a.Type.Underlying())
    +	return strings.ToLower(strings.TrimPrefix(t.String(), "*types."))
    +}
    +
    +// KeyType returns the type of X's key. KeyType panics if X is not a
    +// map.
    +func (a *postfixTmplArgs) KeyType() types.Type {
    +	return a.Type.Underlying().(*types.Map).Key()
    +}
    +
    +// Tuple returns the tuple result vars if the type of X is tuple.
    +func (a *postfixTmplArgs) Tuple() []*types.Var {
    +	tuple, _ := a.Type.(*types.Tuple)
    +	if tuple == nil {
    +		return nil
    +	}
    +
    +	typs := make([]*types.Var, 0, tuple.Len())
    +	for i := 0; i < tuple.Len(); i++ {
    +		typs = append(typs, tuple.At(i))
    +	}
    +	return typs
    +}
    +
    +// TupleLast returns the last tuple result vars if the type of X is tuple.
    +func (a *postfixTmplArgs) TupleLast() *types.Var {
    +	tuple, _ := a.Type.(*types.Tuple)
    +	if tuple == nil {
    +		return nil
    +	}
    +	if tuple.Len() == 0 {
    +		return nil
    +	}
    +	return tuple.At(tuple.Len() - 1)
    +}
    +
    +// TypeName returns the textual representation of type t.
    +func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) {
    +	if t == nil || t == types.Typ[types.Invalid] {
    +		return "", fmt.Errorf("invalid type: %v", t)
    +	}
    +	return types.TypeString(t, a.qf), nil
    +}
    +
    +// Zero return the zero value representation of type t
    +func (a *postfixTmplArgs) Zero(t types.Type) string {
    +	return formatZeroValue(t, a.qf)
    +}
    +
    +func (a *postfixTmplArgs) IsIdent() bool {
    +	_, ok := a.sel.X.(*ast.Ident)
    +	return ok
    +}
    +
    +// VarName returns a suitable variable name for the type t. If t
    +// implements the error interface, "err" is used. If t is not a named
    +// type then nonNamedDefault is used. Otherwise a name is made by
    +// abbreviating the type name. If the resultant name is already in
    +// scope, an integer is appended to make a unique name.
    +func (a *postfixTmplArgs) VarName(t types.Type, nonNamedDefault string) string {
    +	if t == nil {
    +		t = types.Typ[types.Invalid]
    +	}
    +
    +	var name string
    +	// go/types predicates are undefined on types.Typ[types.Invalid].
    +	if !types.Identical(t, types.Typ[types.Invalid]) && types.Implements(t, errorIntf) {
    +		name = "err"
    +	} else if !is[*types.Named](aliases.Unalias(typesinternal.Unpointer(t))) {
    +		name = nonNamedDefault
    +	}
    +
    +	if name == "" {
    +		name = types.TypeString(t, func(p *types.Package) string {
    +			return ""
    +		})
    +		name = abbreviateTypeName(name)
    +	}
    +
    +	if dot := strings.LastIndex(name, "."); dot > -1 {
    +		name = name[dot+1:]
    +	}
    +
    +	uniqueName := name
    +	for i := 2; ; i++ {
    +		if s, _ := a.scope.LookupParent(uniqueName, token.NoPos); s == nil && !a.varNames[uniqueName] {
    +			break
    +		}
    +		uniqueName = fmt.Sprintf("%s%d", name, i)
    +	}
    +
    +	a.varNames[uniqueName] = true
    +
    +	return uniqueName
    +}
    +
    +func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.SelectorExpr) {
    +	if !c.opts.postfix {
    +		return
    +	}
    +
    +	initPostfixRules()
    +
    +	if sel == nil || sel.Sel == nil {
    +		return
    +	}
    +
    +	selType := c.pkg.TypesInfo().TypeOf(sel.X)
    +	if selType == nil {
    +		return
    +	}
    +
    +	// Skip empty tuples since there is no value to operate on.
    +	if tuple, ok := selType.(*types.Tuple); ok && tuple == nil {
    +		return
    +	}
    +
    +	tokFile := c.pkg.FileSet().File(c.pos)
    +
    +	// Only replace sel with a statement if sel is already a statement.
    +	var stmtOK bool
    +	for i, n := range c.path {
    +		if n == sel && i < len(c.path)-1 {
    +			switch p := c.path[i+1].(type) {
    +			case *ast.ExprStmt:
    +				stmtOK = true
    +			case *ast.AssignStmt:
    +				// In cases like:
    +				//
    +				//   foo.<>
    +				//   bar = 123
    +				//
    +				// detect that "foo." makes up the entire statement since the
    +				// apparent selector spans lines.
    +				stmtOK = safetoken.Line(tokFile, c.pos) < safetoken.Line(tokFile, p.TokPos)
    +			}
    +			break
    +		}
    +	}
    +
    +	var funcResults []*types.Var
    +	if c.enclosingFunc != nil {
    +		results := c.enclosingFunc.sig.Results()
    +		if results != nil {
    +			funcResults = make([]*types.Var, results.Len())
    +			for i := 0; i < results.Len(); i++ {
    +				funcResults[i] = results.At(i)
    +			}
    +		}
    +	}
    +
    +	scope := c.pkg.Types().Scope().Innermost(c.pos)
    +	if scope == nil {
    +		return
    +	}
    +
    +	// afterDot is the position after selector dot, e.g. "|" in
    +	// "foo.|print".
    +	afterDot := sel.Sel.Pos()
    +
    +	// We must detect dangling selectors such as:
    +	//
    +	//    foo.<>
    +	//    bar
    +	//
    +	// and adjust afterDot so that we don't mistakenly delete the
    +	// newline thinking "bar" is part of our selector.
    +	if startLine := safetoken.Line(tokFile, sel.Pos()); startLine != safetoken.Line(tokFile, afterDot) {
    +		if safetoken.Line(tokFile, c.pos) != startLine {
    +			return
    +		}
    +		afterDot = c.pos
    +	}
    +
    +	for _, rule := range postfixTmpls {
    +		// When completing foo.print<>, "print" is naturally overwritten,
    +		// but we need to also remove "foo." so the snippet has a clean
    +		// slate.
    +		edits, err := c.editText(sel.Pos(), afterDot, "")
    +		if err != nil {
    +			event.Error(ctx, "error calculating postfix edits", err)
    +			return
    +		}
    +
    +		tmplArgs := postfixTmplArgs{
    +			X:              golang.FormatNode(c.pkg.FileSet(), sel.X),
    +			StmtOK:         stmtOK,
    +			Obj:            exprObj(c.pkg.TypesInfo(), sel.X),
    +			Type:           selType,
    +			FuncResults:    funcResults,
    +			sel:            sel,
    +			qf:             c.qf,
    +			importIfNeeded: c.importIfNeeded,
    +			scope:          scope,
    +			varNames:       make(map[string]bool),
    +			placeholders:   c.opts.placeholders,
    +		}
    +
    +		// Feed the template straight into the snippet builder. This
    +		// allows templates to build snippets as they are executed.
    +		err = rule.tmpl.Execute(&tmplArgs.snip, &tmplArgs)
    +		if err != nil {
    +			event.Error(ctx, "error executing postfix template", err)
    +			continue
    +		}
    +
    +		if strings.TrimSpace(tmplArgs.snip.String()) == "" {
    +			continue
    +		}
    +
    +		score := c.matcher.Score(rule.label)
    +		if score <= 0 {
    +			continue
    +		}
    +
    +		c.items = append(c.items, CompletionItem{
    +			Label:               rule.label + "!",
    +			Detail:              rule.details,
    +			Score:               float64(score) * 0.01,
    +			Kind:                protocol.SnippetCompletion,
    +			snippet:             &tmplArgs.snip,
    +			AdditionalTextEdits: append(edits, tmplArgs.edits...),
    +		})
    +	}
    +}
    +
    +var postfixRulesOnce sync.Once
    +
    +func initPostfixRules() {
    +	postfixRulesOnce.Do(func() {
    +		var idx int
    +		for _, rule := range postfixTmpls {
    +			var err error
    +			rule.tmpl, err = template.New("postfix_snippet").Funcs(template.FuncMap{
    +				"inc": inc,
    +			}).Parse(rule.body)
    +			if err != nil {
    +				log.Panicf("error parsing postfix snippet template: %v", err)
    +			}
    +			postfixTmpls[idx] = rule
    +			idx++
    +		}
    +		postfixTmpls = postfixTmpls[:idx]
    +	})
    +}
    +
    +func inc(i int) int {
    +	return i + 1
    +}
    +
    +// importIfNeeded returns the package identifier and any necessary
    +// edits to import package pkgPath.
    +func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) {
    +	defaultName := imports.ImportPathToAssumedName(pkgPath)
    +
    +	// Check if file already imports pkgPath.
    +	for _, s := range c.file.Imports {
    +		// TODO(adonovan): what if pkgPath has a vendor/ suffix?
    +		// This may be the cause of go.dev/issue/56291.
    +		if string(metadata.UnquoteImportPath(s)) == pkgPath {
    +			if s.Name == nil {
    +				return defaultName, nil, nil
    +			}
    +			if s.Name.Name != "_" {
    +				return s.Name.Name, nil, nil
    +			}
    +		}
    +	}
    +
    +	// Give up if the package's name is already in use by another object.
    +	if _, obj := scope.LookupParent(defaultName, token.NoPos); obj != nil {
    +		return "", nil, fmt.Errorf("import name %q of %q already in use", defaultName, pkgPath)
    +	}
    +
    +	edits, err := c.importEdits(&importInfo{
    +		importPath: pkgPath,
    +	})
    +	if err != nil {
    +		return "", nil, err
    +	}
    +
    +	return defaultName, edits, nil
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/printf.go b/contribs/gnopls/internal/golang/completion/printf.go
    new file mode 100644
    index 00000000000..958d77efe2e
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/printf.go
    @@ -0,0 +1,174 @@
    +// 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 completion
    +
    +import (
    +	"go/ast"
    +	"go/constant"
    +	"go/types"
    +	"strconv"
    +	"strings"
    +	"unicode/utf8"
    +)
    +
    +// printfArgKind returns the expected objKind when completing a
    +// printf-like operand. call is the printf-like function call, and
    +// argIdx is the index of call.Args being completed.
    +func printfArgKind(info *types.Info, call *ast.CallExpr, argIdx int) objKind {
    +	// Printf-like function name must end in "f".
    +	fn := exprObj(info, call.Fun)
    +	if fn == nil || !strings.HasSuffix(fn.Name(), "f") {
    +		return kindAny
    +	}
    +
    +	sig, _ := fn.Type().Underlying().(*types.Signature)
    +	if sig == nil {
    +		return kindAny
    +	}
    +
    +	// Must be variadic and take at least two params.
    +	numParams := sig.Params().Len()
    +	if !sig.Variadic() || numParams < 2 || argIdx < numParams-1 {
    +		return kindAny
    +	}
    +
    +	// Param preceding variadic args must be a (format) string.
    +	if !types.Identical(sig.Params().At(numParams-2).Type(), types.Typ[types.String]) {
    +		return kindAny
    +	}
    +
    +	// Format string must be a constant.
    +	strArg := info.Types[call.Args[numParams-2]].Value
    +	if strArg == nil || strArg.Kind() != constant.String {
    +		return kindAny
    +	}
    +
    +	return formatOperandKind(constant.StringVal(strArg), argIdx-(numParams-1)+1)
    +}
    +
    +// formatOperandKind returns the objKind corresponding to format's
    +// operandIdx'th operand.
    +func formatOperandKind(format string, operandIdx int) objKind {
    +	var (
    +		prevOperandIdx int
    +		kind           = kindAny
    +	)
    +	for {
    +		i := strings.Index(format, "%")
    +		if i == -1 {
    +			break
    +		}
    +
    +		var operands []formatOperand
    +		format, operands = parsePrintfVerb(format[i+1:], prevOperandIdx)
    +
    +		// Check if any this verb's operands correspond to our target
    +		// operandIdx.
    +		for _, v := range operands {
    +			if v.idx == operandIdx {
    +				if kind == kindAny {
    +					kind = v.kind
    +				} else if v.kind != kindAny {
    +					// If multiple verbs refer to the same operand, take the
    +					// intersection of their kinds.
    +					kind &= v.kind
    +				}
    +			}
    +
    +			prevOperandIdx = v.idx
    +		}
    +	}
    +	return kind
    +}
    +
    +type formatOperand struct {
    +	// idx is the one-based printf operand index.
    +	idx int
    +	// kind is a mask of expected kinds of objects for this operand.
    +	kind objKind
    +}
    +
    +// parsePrintfVerb parses the leading printf verb in f. The opening
    +// "%" must already be trimmed from f. prevIdx is the previous
    +// operand's index, or zero if this is the first verb. The format
    +// string is returned with the leading verb removed. Multiple operands
    +// can be returned in the case of dynamic widths such as "%*.*f".
    +func parsePrintfVerb(f string, prevIdx int) (string, []formatOperand) {
    +	var verbs []formatOperand
    +
    +	addVerb := func(k objKind) {
    +		verbs = append(verbs, formatOperand{
    +			idx:  prevIdx + 1,
    +			kind: k,
    +		})
    +		prevIdx++
    +	}
    +
    +	for len(f) > 0 {
    +		// Trim first rune off of f so we are guaranteed to make progress.
    +		r, l := utf8.DecodeRuneInString(f)
    +		f = f[l:]
    +
    +		// We care about three things:
    +		// 1. The verb, which maps directly to object kind.
    +		// 2. Explicit operand indices like "%[2]s".
    +		// 3. Dynamic widths using "*".
    +		switch r {
    +		case '%':
    +			return f, nil
    +		case '*':
    +			addVerb(kindInt)
    +			continue
    +		case '[':
    +			// Parse operand index as in "%[2]s".
    +			i := strings.Index(f, "]")
    +			if i == -1 {
    +				return f, nil
    +			}
    +
    +			idx, err := strconv.Atoi(f[:i])
    +			f = f[i+1:]
    +			if err != nil {
    +				return f, nil
    +			}
    +
    +			prevIdx = idx - 1
    +			continue
    +		case 'v', 'T':
    +			addVerb(kindAny)
    +		case 't':
    +			addVerb(kindBool)
    +		case 'c', 'd', 'o', 'O', 'U':
    +			addVerb(kindInt)
    +		case 'e', 'E', 'f', 'F', 'g', 'G':
    +			addVerb(kindFloat | kindComplex)
    +		case 'b':
    +			addVerb(kindInt | kindFloat | kindComplex | kindBytes)
    +		case 'q', 's':
    +			addVerb(kindString | kindBytes | kindStringer | kindError)
    +		case 'x', 'X':
    +			// Omit kindStringer and kindError though technically allowed.
    +			addVerb(kindString | kindBytes | kindInt | kindFloat | kindComplex)
    +		case 'p':
    +			// Accept kindInterface even though it doesn't necessarily contain a pointer.
    +			// This avoids us offering "&foo" when "foo" is an interface type.
    +			addVerb(kindPtr | kindSlice | kindMap | kindFunc | kindInterface)
    +		case 'w':
    +			addVerb(kindError)
    +		case '+', '-', '#', ' ', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
    +			// Flag or numeric width/precision value.
    +			continue
    +		default:
    +			// Assume unrecognized rune is a custom fmt.Formatter verb.
    +			addVerb(kindAny)
    +		}
    +
    +		if len(verbs) > 0 {
    +			break
    +		}
    +	}
    +
    +	return f, verbs
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/printf_test.go b/contribs/gnopls/internal/golang/completion/printf_test.go
    new file mode 100644
    index 00000000000..1a3d971b4fe
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/printf_test.go
    @@ -0,0 +1,72 @@
    +// 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 completion
    +
    +import (
    +	"fmt"
    +	"testing"
    +)
    +
    +func TestFormatOperandKind(t *testing.T) {
    +	cases := []struct {
    +		f    string
    +		idx  int
    +		kind objKind
    +	}{
    +		{"", 1, kindAny},
    +		{"%", 1, kindAny},
    +		{"%%%", 1, kindAny},
    +		{"%[1", 1, kindAny},
    +		{"%[?%s", 2, kindAny},
    +		{"%[abc]v", 1, kindAny},
    +
    +		{"%v", 1, kindAny},
    +		{"%T", 1, kindAny},
    +		{"%t", 1, kindBool},
    +		{"%d", 1, kindInt},
    +		{"%c", 1, kindInt},
    +		{"%o", 1, kindInt},
    +		{"%O", 1, kindInt},
    +		{"%U", 1, kindInt},
    +		{"%e", 1, kindFloat | kindComplex},
    +		{"%E", 1, kindFloat | kindComplex},
    +		{"%f", 1, kindFloat | kindComplex},
    +		{"%F", 1, kindFloat | kindComplex},
    +		{"%g", 1, kindFloat | kindComplex},
    +		{"%G", 1, kindFloat | kindComplex},
    +		{"%b", 1, kindInt | kindFloat | kindComplex | kindBytes},
    +		{"%q", 1, kindString | kindBytes | kindStringer | kindError},
    +		{"%s", 1, kindString | kindBytes | kindStringer | kindError},
    +		{"%x", 1, kindString | kindBytes | kindInt | kindFloat | kindComplex},
    +		{"%X", 1, kindString | kindBytes | kindInt | kindFloat | kindComplex},
    +		{"%p", 1, kindPtr | kindSlice | kindMap | kindFunc | kindInterface},
    +		{"%w", 1, kindError},
    +
    +		{"%1.2f", 1, kindFloat | kindComplex},
    +		{"%*f", 1, kindInt},
    +		{"%*f", 2, kindFloat | kindComplex},
    +		{"%*.*f", 1, kindInt},
    +		{"%*.*f", 2, kindInt},
    +		{"%*.*f", 3, kindFloat | kindComplex},
    +		{"%[3]*.[2]*[1]f", 1, kindFloat | kindComplex},
    +		{"%[3]*.[2]*[1]f", 2, kindInt},
    +		{"%[3]*.[2]*[1]f", 3, kindInt},
    +
    +		{"foo %% %d", 1, kindInt},
    +		{"%#-12.34f", 1, kindFloat | kindComplex},
    +		{"% d", 1, kindInt},
    +
    +		{"%s %[1]X %d", 1, kindString | kindBytes},
    +		{"%s %[1]X %d", 2, kindInt},
    +	}
    +
    +	for _, c := range cases {
    +		t.Run(fmt.Sprintf("%q#%d", c.f, c.idx), func(t *testing.T) {
    +			if got := formatOperandKind(c.f, c.idx); got != c.kind {
    +				t.Errorf("expected %d (%[1]b), got %d (%[2]b)", c.kind, got)
    +			}
    +		})
    +	}
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/snippet.go b/contribs/gnopls/internal/golang/completion/snippet.go
    new file mode 100644
    index 00000000000..8df81f87672
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/snippet.go
    @@ -0,0 +1,126 @@
    +// 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.
    +
    +package completion
    +
    +import (
    +	"go/ast"
    +
    +	"golang.org/x/tools/gopls/internal/golang/completion/snippet"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +)
    +
    +// structFieldSnippet calculates the snippet for struct literal field names.
    +func (c *completer) structFieldSnippet(cand candidate, detail string, snip *snippet.Builder) {
    +	if !c.wantStructFieldCompletions() {
    +		return
    +	}
    +
    +	// If we are in a deep completion then we can't be completing a field
    +	// name (e.g. "Foo{f<>}" completing to "Foo{f.Bar}" should not generate
    +	// a snippet).
    +	if len(cand.path) > 0 {
    +		return
    +	}
    +
    +	clInfo := c.enclosingCompositeLiteral
    +
    +	// If we are already in a key-value expression, we don't want a snippet.
    +	if clInfo.kv != nil {
    +		return
    +	}
    +
    +	// A plain snippet turns "Foo{Ba<>" into "Foo{Bar: <>".
    +	snip.WriteText(": ")
    +	snip.WritePlaceholder(func(b *snippet.Builder) {
    +		// A placeholder snippet turns "Foo{Ba<>" into "Foo{Bar: <*int*>".
    +		if c.opts.placeholders {
    +			b.WriteText(detail)
    +		}
    +	})
    +
    +	fset := c.pkg.FileSet()
    +
    +	// If the cursor position is on a different line from the literal's opening brace,
    +	// we are in a multiline literal. Ignore line directives.
    +	if safetoken.StartPosition(fset, c.pos).Line != safetoken.StartPosition(fset, clInfo.cl.Lbrace).Line {
    +		snip.WriteText(",")
    +	}
    +}
    +
    +// functionCallSnippet calculates the snippet for function calls.
    +//
    +// Callers should omit the suffix of type parameters that are
    +// constrained by the argument types, to avoid offering completions
    +// that contain instantiations that are redundant because of type
    +// inference, such as f[int](1) for func f[T any](x T).
    +func (c *completer) functionCallSnippet(name string, tparams, params []string, snip *snippet.Builder) {
    +	if !c.opts.completeFunctionCalls {
    +		snip.WriteText(name)
    +		return
    +	}
    +
    +	// If there is no suffix then we need to reuse existing call parens
    +	// "()" if present. If there is an identifier suffix then we always
    +	// need to include "()" since we don't overwrite the suffix.
    +	if c.surrounding != nil && c.surrounding.Suffix() == "" && len(c.path) > 1 {
    +		// If we are the left side (i.e. "Fun") part of a call expression,
    +		// we don't want a snippet since there are already parens present.
    +		switch n := c.path[1].(type) {
    +		case *ast.CallExpr:
    +			// The Lparen != Rparen check detects fudged CallExprs we
    +			// inserted when fixing the AST. In this case, we do still need
    +			// to insert the calling "()" parens.
    +			if n.Fun == c.path[0] && n.Lparen != n.Rparen {
    +				return
    +			}
    +		case *ast.SelectorExpr:
    +			if len(c.path) > 2 {
    +				if call, ok := c.path[2].(*ast.CallExpr); ok && call.Fun == c.path[1] && call.Lparen != call.Rparen {
    +					return
    +				}
    +			}
    +		}
    +	}
    +
    +	snip.WriteText(name)
    +
    +	if len(tparams) > 0 {
    +		snip.WriteText("[")
    +		if c.opts.placeholders {
    +			for i, tp := range tparams {
    +				if i > 0 {
    +					snip.WriteText(", ")
    +				}
    +				snip.WritePlaceholder(func(b *snippet.Builder) {
    +					b.WriteText(tp)
    +				})
    +			}
    +		} else {
    +			snip.WritePlaceholder(nil)
    +		}
    +		snip.WriteText("]")
    +	}
    +
    +	snip.WriteText("(")
    +
    +	if c.opts.placeholders {
    +		// A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)".
    +		for i, p := range params {
    +			if i > 0 {
    +				snip.WriteText(", ")
    +			}
    +			snip.WritePlaceholder(func(b *snippet.Builder) {
    +				b.WriteText(p)
    +			})
    +		}
    +	} else {
    +		// A plain snippet turns "someFun<>" into "someFunc(<>)".
    +		if len(params) > 0 {
    +			snip.WritePlaceholder(nil)
    +		}
    +	}
    +
    +	snip.WriteText(")")
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/snippet/snippet_builder.go b/contribs/gnopls/internal/golang/completion/snippet/snippet_builder.go
    new file mode 100644
    index 00000000000..fa63e8d8324
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/snippet/snippet_builder.go
    @@ -0,0 +1,111 @@
    +// 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.
    +
    +// Package snippet implements the specification for the LSP snippet format.
    +//
    +// Snippets are "tab stop" templates returned as an optional attribute of LSP
    +// completion candidates. As the user presses tab, they cycle through a series of
    +// tab stops defined in the snippet. Each tab stop can optionally have placeholder
    +// text, which can be pre-selected by editors. For a full description of syntax
    +// and features, see "Snippet Syntax" at
    +// https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_completion.
    +//
    +// A typical snippet looks like "foo(${1:i int}, ${2:s string})".
    +package snippet
    +
    +import (
    +	"fmt"
    +	"strings"
    +)
    +
    +// A Builder is used to build an LSP snippet piecemeal.
    +// The zero value is ready to use. Do not copy a non-zero Builder.
    +type Builder struct {
    +	// currentTabStop is the index of the previous tab stop. The
    +	// next tab stop will be currentTabStop+1.
    +	currentTabStop int
    +	sb             strings.Builder
    +}
    +
    +// Escape characters defined in https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_completion under "Grammar".
    +var replacer = strings.NewReplacer(
    +	`\`, `\\`,
    +	`}`, `\}`,
    +	`$`, `\$`,
    +)
    +
    +func (b *Builder) WriteText(s string) {
    +	replacer.WriteString(&b.sb, s)
    +}
    +
    +func (b *Builder) PrependText(s string) {
    +	rawSnip := b.String()
    +	b.sb.Reset()
    +	b.WriteText(s)
    +	b.sb.WriteString(rawSnip)
    +}
    +
    +func (b *Builder) Write(data []byte) (int, error) {
    +	return b.sb.Write(data)
    +}
    +
    +// WritePlaceholder writes a tab stop and placeholder value to the Builder.
    +// The callback style allows for creating nested placeholders. To write an
    +// empty tab stop, provide a nil callback.
    +func (b *Builder) WritePlaceholder(fn func(*Builder)) {
    +	fmt.Fprintf(&b.sb, "${%d:", b.nextTabStop())
    +	if fn != nil {
    +		fn(b)
    +	}
    +	b.sb.WriteByte('}')
    +}
    +
    +// WriteFinalTabstop marks where cursor ends up after the user has
    +// cycled through all the normal tab stops. It defaults to the
    +// character after the snippet.
    +func (b *Builder) WriteFinalTabstop() {
    +	fmt.Fprint(&b.sb, "$0")
    +}
    +
    +// In addition to '\', '}', and '$', snippet choices also use '|' and ',' as
    +// meta characters, so they must be escaped within the choices.
    +var choiceReplacer = strings.NewReplacer(
    +	`\`, `\\`,
    +	`}`, `\}`,
    +	`$`, `\$`,
    +	`|`, `\|`,
    +	`,`, `\,`,
    +)
    +
    +// WriteChoice writes a tab stop and list of text choices to the Builder.
    +// The user's editor will prompt the user to choose one of the choices.
    +func (b *Builder) WriteChoice(choices []string) {
    +	fmt.Fprintf(&b.sb, "${%d|", b.nextTabStop())
    +	for i, c := range choices {
    +		if i != 0 {
    +			b.sb.WriteByte(',')
    +		}
    +		choiceReplacer.WriteString(&b.sb, c)
    +	}
    +	b.sb.WriteString("|}")
    +}
    +
    +// String returns the built snippet string.
    +func (b *Builder) String() string {
    +	return b.sb.String()
    +}
    +
    +// Clone returns a copy of b.
    +func (b *Builder) Clone() *Builder {
    +	var clone Builder
    +	clone.sb.WriteString(b.String())
    +	return &clone
    +}
    +
    +// nextTabStop returns the next tab stop index for a new placeholder.
    +func (b *Builder) nextTabStop() int {
    +	// Tab stops start from 1, so increment before returning.
    +	b.currentTabStop++
    +	return b.currentTabStop
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/snippet/snippet_builder_test.go b/contribs/gnopls/internal/golang/completion/snippet/snippet_builder_test.go
    new file mode 100644
    index 00000000000..bc814b16d3f
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/snippet/snippet_builder_test.go
    @@ -0,0 +1,62 @@
    +// 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.
    +
    +package snippet
    +
    +import (
    +	"testing"
    +)
    +
    +func TestSnippetBuilder(t *testing.T) {
    +	expect := func(expected string, fn func(*Builder)) {
    +		t.Helper()
    +
    +		var b Builder
    +		fn(&b)
    +		if got := b.String(); got != expected {
    +			t.Errorf("got %q, expected %q", got, expected)
    +		}
    +	}
    +
    +	expect("", func(b *Builder) {})
    +
    +	expect(`hi { \} \$ | " , / \\`, func(b *Builder) {
    +		b.WriteText(`hi { } $ | " , / \`)
    +	})
    +
    +	expect("${1:}", func(b *Builder) {
    +		b.WritePlaceholder(nil)
    +	})
    +
    +	expect("hi ${1:there}", func(b *Builder) {
    +		b.WriteText("hi ")
    +		b.WritePlaceholder(func(b *Builder) {
    +			b.WriteText("there")
    +		})
    +	})
    +
    +	expect(`${1:id=${2:{your id\}}}`, func(b *Builder) {
    +		b.WritePlaceholder(func(b *Builder) {
    +			b.WriteText("id=")
    +			b.WritePlaceholder(func(b *Builder) {
    +				b.WriteText("{your id}")
    +			})
    +		})
    +	})
    +
    +	expect(`${1|one,{ \} \$ \| " \, / \\,three|}`, func(b *Builder) {
    +		b.WriteChoice([]string{"one", `{ } $ | " , / \`, "three"})
    +	})
    +
    +	expect("$0 hello", func(b *Builder) {
    +		b.WriteFinalTabstop()
    +		b.WriteText(" hello")
    +	})
    +
    +	expect(`prepended \$5 ${1:} hello`, func(b *Builder) {
    +		b.WritePlaceholder(nil)
    +		b.WriteText(" hello")
    +		b.PrependText("prepended $5 ")
    +	})
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/statements.go b/contribs/gnopls/internal/golang/completion/statements.go
    new file mode 100644
    index 00000000000..ce80cfb08ce
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/statements.go
    @@ -0,0 +1,420 @@
    +// 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 completion
    +
    +import (
    +	"fmt"
    +	"go/ast"
    +	"go/token"
    +	"go/types"
    +	"strings"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/golang"
    +	"golang.org/x/tools/gopls/internal/golang/completion/snippet"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +)
    +
    +// addStatementCandidates adds full statement completion candidates
    +// appropriate for the current context.
    +func (c *completer) addStatementCandidates() {
    +	c.addErrCheck()
    +	c.addAssignAppend()
    +	c.addReturnZeroValues()
    +}
    +
    +// addAssignAppend offers a completion candidate of the form:
    +//
    +//	someSlice = append(someSlice, )
    +//
    +// It will offer the "append" completion in either of two situations:
    +//
    +//  1. Position is in RHS of assign, prefix matches "append", and
    +//     corresponding LHS object is a slice. For example,
    +//     "foo = ap<>" completes to "foo = append(foo, )".
    +//
    +//  2. Prefix is an ident or selector in an *ast.ExprStmt (i.e.
    +//     beginning of statement), and our best matching candidate is a
    +//     slice. For example: "foo.ba" completes to "foo.bar = append(foo.bar, )".
    +func (c *completer) addAssignAppend() {
    +	if len(c.path) < 3 {
    +		return
    +	}
    +
    +	ident, _ := c.path[0].(*ast.Ident)
    +	if ident == nil {
    +		return
    +	}
    +
    +	var (
    +		// sliceText is the full name of our slice object, e.g. "s.abc" in
    +		// "s.abc = app<>".
    +		sliceText string
    +		// needsLHS is true if we need to prepend the LHS slice name and
    +		// "=" to our candidate.
    +		needsLHS = false
    +		fset     = c.pkg.FileSet()
    +	)
    +
    +	switch n := c.path[1].(type) {
    +	case *ast.AssignStmt:
    +		// We are already in an assignment. Make sure our prefix matches "append".
    +		if c.matcher.Score("append") <= 0 {
    +			return
    +		}
    +
    +		exprIdx := exprAtPos(c.pos, n.Rhs)
    +		if exprIdx == len(n.Rhs) || exprIdx > len(n.Lhs)-1 {
    +			return
    +		}
    +
    +		lhsType := c.pkg.TypesInfo().TypeOf(n.Lhs[exprIdx])
    +		if lhsType == nil {
    +			return
    +		}
    +
    +		// Make sure our corresponding LHS object is a slice.
    +		if _, isSlice := lhsType.Underlying().(*types.Slice); !isSlice {
    +			return
    +		}
    +
    +		// The name or our slice is whatever's in the LHS expression.
    +		sliceText = golang.FormatNode(fset, n.Lhs[exprIdx])
    +	case *ast.SelectorExpr:
    +		// Make sure we are a selector at the beginning of a statement.
    +		if _, parentIsExprtStmt := c.path[2].(*ast.ExprStmt); !parentIsExprtStmt {
    +			return
    +		}
    +
    +		// So far we only know the first part of our slice name. For
    +		// example in "s.a<>" we only know our slice begins with "s."
    +		// since the user could still be typing.
    +		sliceText = golang.FormatNode(fset, n.X) + "."
    +		needsLHS = true
    +	case *ast.ExprStmt:
    +		needsLHS = true
    +	default:
    +		return
    +	}
    +
    +	var (
    +		label string
    +		snip  snippet.Builder
    +		score = highScore
    +	)
    +
    +	if needsLHS {
    +		// Offer the long form assign + append candidate if our best
    +		// candidate is a slice.
    +		bestItem := c.topCandidate()
    +		if bestItem == nil || !bestItem.isSlice {
    +			return
    +		}
    +
    +		// Don't rank the full form assign + append candidate above the
    +		// slice itself.
    +		score = bestItem.Score - 0.01
    +
    +		// Fill in rest of sliceText now that we have the object name.
    +		sliceText += bestItem.Label
    +
    +		// Fill in the candidate's LHS bits.
    +		label = fmt.Sprintf("%s = ", bestItem.Label)
    +		snip.WriteText(label)
    +	}
    +
    +	snip.WriteText(fmt.Sprintf("append(%s, ", sliceText))
    +	snip.WritePlaceholder(nil)
    +	snip.WriteText(")")
    +
    +	c.items = append(c.items, CompletionItem{
    +		Label:   label + fmt.Sprintf("append(%s, )", sliceText),
    +		Kind:    protocol.FunctionCompletion,
    +		Score:   score,
    +		snippet: &snip,
    +	})
    +}
    +
    +// topCandidate returns the strictly highest scoring candidate
    +// collected so far. If the top two candidates have the same score,
    +// nil is returned.
    +func (c *completer) topCandidate() *CompletionItem {
    +	var bestItem, secondBestItem *CompletionItem
    +	for i := range c.items {
    +		if bestItem == nil || c.items[i].Score > bestItem.Score {
    +			bestItem = &c.items[i]
    +		} else if secondBestItem == nil || c.items[i].Score > secondBestItem.Score {
    +			secondBestItem = &c.items[i]
    +		}
    +	}
    +
    +	// If secondBestItem has the same score, bestItem isn't
    +	// the strict best.
    +	if secondBestItem != nil && secondBestItem.Score == bestItem.Score {
    +		return nil
    +	}
    +
    +	return bestItem
    +}
    +
    +// addErrCheck offers a completion candidate of the form:
    +//
    +//	if err != nil {
    +//	  return nil, err
    +//	}
    +//
    +// In the case of test functions, it offers a completion candidate of the form:
    +//
    +//	if err != nil {
    +//	  t.Fatal(err)
    +//	}
    +//
    +// The position must be in a function that returns an error, and the
    +// statement preceding the position must be an assignment where the
    +// final LHS object is an error. addErrCheck will synthesize
    +// zero values as necessary to make the return statement valid.
    +func (c *completer) addErrCheck() {
    +	if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders {
    +		return
    +	}
    +
    +	var (
    +		errorType        = types.Universe.Lookup("error").Type()
    +		result           = c.enclosingFunc.sig.Results()
    +		testVar          = getTestVar(c.enclosingFunc, c.pkg)
    +		isTest           = testVar != ""
    +		doesNotReturnErr = result.Len() == 0 || !types.Identical(result.At(result.Len()-1).Type(), errorType)
    +	)
    +	// Make sure our enclosing function is a Test func or returns an error.
    +	if !isTest && doesNotReturnErr {
    +		return
    +	}
    +
    +	prevLine := prevStmt(c.pos, c.path)
    +	if prevLine == nil {
    +		return
    +	}
    +
    +	// Make sure our preceding statement was as assignment.
    +	assign, _ := prevLine.(*ast.AssignStmt)
    +	if assign == nil || len(assign.Lhs) == 0 {
    +		return
    +	}
    +
    +	lastAssignee := assign.Lhs[len(assign.Lhs)-1]
    +
    +	// Make sure the final assignee is an error.
    +	if !types.Identical(c.pkg.TypesInfo().TypeOf(lastAssignee), errorType) {
    +		return
    +	}
    +
    +	var (
    +		// errVar is e.g. "err" in "foo, err := bar()".
    +		errVar = golang.FormatNode(c.pkg.FileSet(), lastAssignee)
    +
    +		// Whether we need to include the "if" keyword in our candidate.
    +		needsIf = true
    +	)
    +
    +	// If the returned error from the previous statement is "_", it is not a real object.
    +	// If we don't have an error, and the function signature takes a testing.TB that is either ignored
    +	// or an "_", then we also can't call t.Fatal(err).
    +	if errVar == "_" {
    +		return
    +	}
    +
    +	// Below we try to detect if the user has already started typing "if
    +	// err" so we can replace what they've typed with our complete
    +	// statement.
    +	switch n := c.path[0].(type) {
    +	case *ast.Ident:
    +		switch c.path[1].(type) {
    +		case *ast.ExprStmt:
    +			// This handles:
    +			//
    +			//     f, err := os.Open("foo")
    +			//     i<>
    +
    +			// Make sure they are typing "if".
    +			if c.matcher.Score("if") <= 0 {
    +				return
    +			}
    +		case *ast.IfStmt:
    +			// This handles:
    +			//
    +			//     f, err := os.Open("foo")
    +			//     if er<>
    +
    +			// Make sure they are typing the error's name.
    +			if c.matcher.Score(errVar) <= 0 {
    +				return
    +			}
    +
    +			needsIf = false
    +		default:
    +			return
    +		}
    +	case *ast.IfStmt:
    +		// This handles:
    +		//
    +		//     f, err := os.Open("foo")
    +		//     if <>
    +
    +		// Avoid false positives by ensuring the if's cond is a bad
    +		// expression. For example, don't offer the completion in cases
    +		// like "if <> somethingElse".
    +		if _, bad := n.Cond.(*ast.BadExpr); !bad {
    +			return
    +		}
    +
    +		// If "if" is our direct prefix, we need to include it in our
    +		// candidate since the existing "if" will be overwritten.
    +		needsIf = c.pos == n.Pos()+token.Pos(len("if"))
    +	}
    +
    +	// Build up a snippet that looks like:
    +	//
    +	//     if err != nil {
    +	//       return , ..., ${1:err}
    +	//     }
    +	//
    +	// We make the error a placeholder so it is easy to alter the error.
    +	var snip snippet.Builder
    +	if needsIf {
    +		snip.WriteText("if ")
    +	}
    +	snip.WriteText(fmt.Sprintf("%s != nil {\n\t", errVar))
    +
    +	var label string
    +	if isTest {
    +		snip.WriteText(fmt.Sprintf("%s.Fatal(%s)", testVar, errVar))
    +		label = fmt.Sprintf("%[1]s != nil { %[2]s.Fatal(%[1]s) }", errVar, testVar)
    +	} else {
    +		snip.WriteText("return ")
    +		for i := 0; i < result.Len()-1; i++ {
    +			snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
    +			snip.WriteText(", ")
    +		}
    +		snip.WritePlaceholder(func(b *snippet.Builder) {
    +			b.WriteText(errVar)
    +		})
    +		label = fmt.Sprintf("%[1]s != nil { return %[1]s }", errVar)
    +	}
    +
    +	snip.WriteText("\n}")
    +
    +	if needsIf {
    +		label = "if " + label
    +	}
    +
    +	c.items = append(c.items, CompletionItem{
    +		Label:   label,
    +		Kind:    protocol.SnippetCompletion,
    +		Score:   highScore,
    +		snippet: &snip,
    +	})
    +}
    +
    +// getTestVar checks the function signature's input parameters and returns
    +// the name of the first parameter that implements "testing.TB". For example,
    +// func someFunc(t *testing.T) returns the string "t", func someFunc(b *testing.B)
    +// returns "b" etc. An empty string indicates that the function signature
    +// does not take a testing.TB parameter or does so but is ignored such
    +// as func someFunc(*testing.T).
    +func getTestVar(enclosingFunc *funcInfo, pkg *cache.Package) string {
    +	if enclosingFunc == nil || enclosingFunc.sig == nil {
    +		return ""
    +	}
    +
    +	var testingPkg *types.Package
    +	for _, p := range pkg.Types().Imports() {
    +		if p.Path() == "testing" {
    +			testingPkg = p
    +			break
    +		}
    +	}
    +	if testingPkg == nil {
    +		return ""
    +	}
    +	tbObj := testingPkg.Scope().Lookup("TB")
    +	if tbObj == nil {
    +		return ""
    +	}
    +	iface, ok := tbObj.Type().Underlying().(*types.Interface)
    +	if !ok {
    +		return ""
    +	}
    +
    +	sig := enclosingFunc.sig
    +	for i := 0; i < sig.Params().Len(); i++ {
    +		param := sig.Params().At(i)
    +		if param.Name() == "_" {
    +			continue
    +		}
    +		if !types.Implements(param.Type(), iface) {
    +			continue
    +		}
    +		return param.Name()
    +	}
    +
    +	return ""
    +}
    +
    +// addReturnZeroValues offers a snippet candidate on the form:
    +//
    +//	return 0, "", nil
    +//
    +// Requires a partially or fully written return keyword at position.
    +// Requires current position to be in a function with more than
    +// zero return parameters.
    +func (c *completer) addReturnZeroValues() {
    +	if len(c.path) < 2 || c.enclosingFunc == nil || !c.opts.placeholders {
    +		return
    +	}
    +	result := c.enclosingFunc.sig.Results()
    +	if result.Len() == 0 {
    +		return
    +	}
    +
    +	// Offer just less than we expect from return as a keyword.
    +	var score = stdScore - 0.01
    +	switch c.path[0].(type) {
    +	case *ast.ReturnStmt, *ast.Ident:
    +		f := c.matcher.Score("return")
    +		if f <= 0 {
    +			return
    +		}
    +		score *= float64(f)
    +	default:
    +		return
    +	}
    +
    +	// The snippet will have a placeholder over each return value.
    +	// The label will not.
    +	var snip snippet.Builder
    +	var label strings.Builder
    +	snip.WriteText("return ")
    +	fmt.Fprintf(&label, "return ")
    +
    +	for i := 0; i < result.Len(); i++ {
    +		if i > 0 {
    +			snip.WriteText(", ")
    +			fmt.Fprintf(&label, ", ")
    +		}
    +
    +		zero := formatZeroValue(result.At(i).Type(), c.qf)
    +		snip.WritePlaceholder(func(b *snippet.Builder) {
    +			b.WriteText(zero)
    +		})
    +		fmt.Fprint(&label, zero)
    +	}
    +
    +	c.items = append(c.items, CompletionItem{
    +		Label:   label.String(),
    +		Kind:    protocol.SnippetCompletion,
    +		Score:   score,
    +		snippet: &snip,
    +	})
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/util.go b/contribs/gnopls/internal/golang/completion/util.go
    new file mode 100644
    index 00000000000..ad4ee5e09fc
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/util.go
    @@ -0,0 +1,341 @@
    +// 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 completion
    +
    +import (
    +	"go/ast"
    +	"go/token"
    +	"go/types"
    +
    +	"golang.org/x/tools/go/types/typeutil"
    +	"golang.org/x/tools/gopls/internal/golang"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/internal/aliases"
    +	"golang.org/x/tools/internal/diff"
    +	"golang.org/x/tools/internal/typeparams"
    +)
    +
    +// exprAtPos returns the index of the expression containing pos.
    +func exprAtPos(pos token.Pos, args []ast.Expr) int {
    +	for i, expr := range args {
    +		if expr.Pos() <= pos && pos <= expr.End() {
    +			return i
    +		}
    +	}
    +	return len(args)
    +}
    +
    +// eachField invokes fn for each field that can be selected from a
    +// value of type T.
    +func eachField(T types.Type, fn func(*types.Var)) {
    +	// TODO(adonovan): this algorithm doesn't exclude ambiguous
    +	// selections that match more than one field/method.
    +	// types.NewSelectionSet should do that for us.
    +
    +	// for termination on recursive types
    +	var seen typeutil.Map
    +
    +	var visit func(T types.Type)
    +	visit = func(T types.Type) {
    +		// T may be a Struct, optionally Named, with an optional
    +		// Pointer (with optional Aliases at every step!):
    +		// Consider: type T *struct{ f int }; _ = T(nil).f
    +		if T, ok := typeparams.Deref(T).Underlying().(*types.Struct); ok {
    +			if seen.At(T) != nil {
    +				return
    +			}
    +
    +			for i := 0; i < T.NumFields(); i++ {
    +				f := T.Field(i)
    +				fn(f)
    +				if f.Anonymous() {
    +					seen.Set(T, true)
    +					visit(f.Type())
    +				}
    +			}
    +		}
    +	}
    +	visit(T)
    +}
    +
    +// typeIsValid reports whether typ doesn't contain any Invalid types.
    +func typeIsValid(typ types.Type) bool {
    +	// Check named types separately, because we don't want
    +	// to call Underlying() on them to avoid problems with recursive types.
    +	if _, ok := aliases.Unalias(typ).(*types.Named); ok {
    +		return true
    +	}
    +
    +	switch typ := typ.Underlying().(type) {
    +	case *types.Basic:
    +		return typ.Kind() != types.Invalid
    +	case *types.Array:
    +		return typeIsValid(typ.Elem())
    +	case *types.Slice:
    +		return typeIsValid(typ.Elem())
    +	case *types.Pointer:
    +		return typeIsValid(typ.Elem())
    +	case *types.Map:
    +		return typeIsValid(typ.Key()) && typeIsValid(typ.Elem())
    +	case *types.Chan:
    +		return typeIsValid(typ.Elem())
    +	case *types.Signature:
    +		return typeIsValid(typ.Params()) && typeIsValid(typ.Results())
    +	case *types.Tuple:
    +		for i := 0; i < typ.Len(); i++ {
    +			if !typeIsValid(typ.At(i).Type()) {
    +				return false
    +			}
    +		}
    +		return true
    +	case *types.Struct, *types.Interface:
    +		// Don't bother checking structs, interfaces for validity.
    +		return true
    +	default:
    +		return false
    +	}
    +}
    +
    +// resolveInvalid traverses the node of the AST that defines the scope
    +// containing the declaration of obj, and attempts to find a user-friendly
    +// name for its invalid type. The resulting Object and its Type are fake.
    +func resolveInvalid(fset *token.FileSet, obj types.Object, node ast.Node, info *types.Info) types.Object {
    +	var resultExpr ast.Expr
    +	ast.Inspect(node, func(node ast.Node) bool {
    +		switch n := node.(type) {
    +		case *ast.ValueSpec:
    +			for _, name := range n.Names {
    +				if info.Defs[name] == obj {
    +					resultExpr = n.Type
    +				}
    +			}
    +			return false
    +		case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
    +			for _, name := range n.Names {
    +				if info.Defs[name] == obj {
    +					resultExpr = n.Type
    +				}
    +			}
    +			return false
    +		default:
    +			return true
    +		}
    +	})
    +	// Construct a fake type for the object and return a fake object with this type.
    +	typename := golang.FormatNode(fset, resultExpr)
    +	typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil)
    +	return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
    +}
    +
    +// TODO(adonovan): inline these.
    +func isVar(obj types.Object) bool      { return is[*types.Var](obj) }
    +func isTypeName(obj types.Object) bool { return is[*types.TypeName](obj) }
    +func isFunc(obj types.Object) bool     { return is[*types.Func](obj) }
    +func isPkgName(obj types.Object) bool  { return is[*types.PkgName](obj) }
    +
    +// isPointer reports whether T is a Pointer, or an alias of one.
    +// It returns false for a Named type whose Underlying is a Pointer.
    +//
    +// TODO(adonovan): shouldn't this use CoreType(T)?
    +func isPointer(T types.Type) bool { return is[*types.Pointer](aliases.Unalias(T)) }
    +
    +// isEmptyInterface whether T is a (possibly Named or Alias) empty interface
    +// type, such that every type is assignable to T.
    +//
    +// isEmptyInterface returns false for type parameters, since they have
    +// different assignability rules.
    +func isEmptyInterface(T types.Type) bool {
    +	if _, ok := T.(*types.TypeParam); ok {
    +		return false
    +	}
    +	intf, _ := T.Underlying().(*types.Interface)
    +	return intf != nil && intf.Empty()
    +}
    +
    +func isUntyped(T types.Type) bool {
    +	if basic, ok := aliases.Unalias(T).(*types.Basic); ok {
    +		return basic.Info()&types.IsUntyped > 0
    +	}
    +	return false
    +}
    +
    +func deslice(T types.Type) types.Type {
    +	if slice, ok := T.Underlying().(*types.Slice); ok {
    +		return slice.Elem()
    +	}
    +	return nil
    +}
    +
    +// isSelector returns the enclosing *ast.SelectorExpr when pos is in the
    +// selector.
    +func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr {
    +	if len(path) == 0 {
    +		return nil
    +	}
    +
    +	if sel, ok := path[0].(*ast.SelectorExpr); ok {
    +		return sel
    +	}
    +
    +	// TODO(adonovan): consider ast.ParenExpr (e.g. (x).name)
    +	if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 {
    +		if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() {
    +			return sel
    +		}
    +	}
    +
    +	return nil
    +}
    +
    +// enclosingDeclLHS returns LHS idents from containing value spec or
    +// assign statement.
    +func enclosingDeclLHS(path []ast.Node) []*ast.Ident {
    +	for _, n := range path {
    +		switch n := n.(type) {
    +		case *ast.ValueSpec:
    +			return n.Names
    +		case *ast.AssignStmt:
    +			ids := make([]*ast.Ident, 0, len(n.Lhs))
    +			for _, e := range n.Lhs {
    +				if id, ok := e.(*ast.Ident); ok {
    +					ids = append(ids, id)
    +				}
    +			}
    +			return ids
    +		}
    +	}
    +
    +	return nil
    +}
    +
    +// exprObj returns the types.Object associated with the *ast.Ident or
    +// *ast.SelectorExpr e.
    +func exprObj(info *types.Info, e ast.Expr) types.Object {
    +	var ident *ast.Ident
    +	switch expr := e.(type) {
    +	case *ast.Ident:
    +		ident = expr
    +	case *ast.SelectorExpr:
    +		ident = expr.Sel
    +	default:
    +		return nil
    +	}
    +
    +	return info.ObjectOf(ident)
    +}
    +
    +// typeConversion returns the type being converted to if call is a type
    +// conversion expression.
    +func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
    +	// Type conversion (e.g. "float64(foo)").
    +	if fun, _ := exprObj(info, call.Fun).(*types.TypeName); fun != nil {
    +		return fun.Type()
    +	}
    +
    +	return nil
    +}
    +
    +// fieldsAccessible returns whether s has at least one field accessible by p.
    +func fieldsAccessible(s *types.Struct, p *types.Package) bool {
    +	for i := 0; i < s.NumFields(); i++ {
    +		f := s.Field(i)
    +		if f.Exported() || f.Pkg() == p {
    +			return true
    +		}
    +	}
    +	return false
    +}
    +
    +// prevStmt returns the statement that precedes the statement containing pos.
    +// For example:
    +//
    +//	foo := 1
    +//	bar(1 + 2<>)
    +//
    +// If "<>" is pos, prevStmt returns "foo := 1"
    +func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt {
    +	var blockLines []ast.Stmt
    +	for i := 0; i < len(path) && blockLines == nil; i++ {
    +		switch n := path[i].(type) {
    +		case *ast.BlockStmt:
    +			blockLines = n.List
    +		case *ast.CommClause:
    +			blockLines = n.Body
    +		case *ast.CaseClause:
    +			blockLines = n.Body
    +		}
    +	}
    +
    +	for i := len(blockLines) - 1; i >= 0; i-- {
    +		if blockLines[i].End() < pos {
    +			return blockLines[i]
    +		}
    +	}
    +
    +	return nil
    +}
    +
    +// formatZeroValue produces Go code representing the zero value of T. It
    +// returns the empty string if T is invalid.
    +func formatZeroValue(T types.Type, qf types.Qualifier) string {
    +	switch u := T.Underlying().(type) {
    +	case *types.Basic:
    +		switch {
    +		case u.Info()&types.IsNumeric > 0:
    +			return "0"
    +		case u.Info()&types.IsString > 0:
    +			return `""`
    +		case u.Info()&types.IsBoolean > 0:
    +			return "false"
    +		default:
    +			return ""
    +		}
    +	case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature:
    +		return "nil"
    +	default:
    +		return types.TypeString(T, qf) + "{}"
    +	}
    +}
    +
    +// isBasicKind returns whether t is a basic type of kind k.
    +func isBasicKind(t types.Type, k types.BasicInfo) bool {
    +	b, _ := t.Underlying().(*types.Basic)
    +	return b != nil && b.Info()&k > 0
    +}
    +
    +func (c *completer) editText(from, to token.Pos, newText string) ([]protocol.TextEdit, error) {
    +	start, end, err := safetoken.Offsets(c.tokFile, from, to)
    +	if err != nil {
    +		return nil, err // can't happen: from/to came from c
    +	}
    +	return protocol.EditsFromDiffEdits(c.mapper, []diff.Edit{{
    +		Start: start,
    +		End:   end,
    +		New:   newText,
    +	}})
    +}
    +
    +// assignableTo is like types.AssignableTo, but returns false if
    +// either type is invalid.
    +func assignableTo(x, to types.Type) bool {
    +	if aliases.Unalias(x) == types.Typ[types.Invalid] ||
    +		aliases.Unalias(to) == types.Typ[types.Invalid] {
    +		return false
    +	}
    +
    +	return types.AssignableTo(x, to)
    +}
    +
    +// convertibleTo is like types.ConvertibleTo, but returns false if
    +// either type is invalid.
    +func convertibleTo(x, to types.Type) bool {
    +	if aliases.Unalias(x) == types.Typ[types.Invalid] ||
    +		aliases.Unalias(to) == types.Typ[types.Invalid] {
    +		return false
    +	}
    +
    +	return types.ConvertibleTo(x, to)
    +}
    diff --git a/contribs/gnopls/internal/golang/completion/util_test.go b/contribs/gnopls/internal/golang/completion/util_test.go
    new file mode 100644
    index 00000000000..c94d279fbad
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/completion/util_test.go
    @@ -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 completion
    +
    +import (
    +	"go/types"
    +	"testing"
    +)
    +
    +func TestFormatZeroValue(t *testing.T) {
    +	tests := []struct {
    +		typ  types.Type
    +		want string
    +	}{
    +		{types.Typ[types.String], `""`},
    +		{types.Typ[types.Byte], "0"},
    +		{types.Typ[types.Invalid], ""},
    +		{types.Universe.Lookup("error").Type(), "nil"},
    +	}
    +
    +	for _, test := range tests {
    +		if got := formatZeroValue(test.typ, nil); got != test.want {
    +			t.Errorf("formatZeroValue(%v) = %q, want %q", test.typ, got, test.want)
    +		}
    +	}
    +}
    diff --git a/contribs/gnopls/internal/golang/definition.go b/contribs/gnopls/internal/golang/definition.go
    new file mode 100644
    index 00000000000..f20fe85f541
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/definition.go
    @@ -0,0 +1,407 @@
    +// 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 golang
    +
    +import (
    +	"context"
    +	"errors"
    +	"fmt"
    +	"go/ast"
    +	"go/parser"
    +	"go/token"
    +	"go/types"
    +	"regexp"
    +	"strings"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/metadata"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/astutil"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/internal/event"
    +)
    +
    +// Definition handles the textDocument/definition request for Go files.
    +func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Location, error) {
    +	ctx, done := event.Start(ctx, "golang.Definition")
    +	defer done()
    +
    +	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil {
    +		return nil, err
    +	}
    +	pos, err := pgf.PositionPos(position)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// Handle the case where the cursor is in an import.
    +	importLocations, err := importDefinition(ctx, snapshot, pkg, pgf, pos)
    +	if err != nil {
    +		return nil, err
    +	}
    +	if len(importLocations) > 0 {
    +		return importLocations, nil
    +	}
    +
    +	// Handle the case where the cursor is in the package name.
    +	// We use "<= End" to accept a query immediately after the package name.
    +	if pgf.File != nil && pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.End() {
    +		// If there's no package documentation, just use current file.
    +		declFile := pgf
    +		for _, pgf := range pkg.CompiledGoFiles() {
    +			if pgf.File.Name != nil && pgf.File.Doc != nil {
    +				declFile = pgf
    +				break
    +			}
    +		}
    +		loc, err := declFile.NodeLocation(declFile.File.Name)
    +		if err != nil {
    +			return nil, err
    +		}
    +		return []protocol.Location{loc}, nil
    +	}
    +
    +	// Handle the case where the cursor is in a linkname directive.
    +	locations, err := linknameDefinition(ctx, snapshot, pgf.Mapper, position)
    +	if !errors.Is(err, ErrNoLinkname) {
    +		return locations, err // may be success or failure
    +	}
    +
    +	// Handle the case where the cursor is in an embed directive.
    +	locations, err = embedDefinition(pgf.Mapper, position)
    +	if !errors.Is(err, ErrNoEmbed) {
    +		return locations, err // may be success or failure
    +	}
    +
    +	// Handle the case where the cursor is in a doc link.
    +	locations, err = docLinkDefinition(ctx, snapshot, pkg, pgf, pos)
    +	if !errors.Is(err, errNoCommentReference) {
    +		return locations, err // may be success or failure
    +	}
    +
    +	// The general case: the cursor is on an identifier.
    +	_, obj, _ := referencedObject(pkg, pgf, pos)
    +	if obj == nil {
    +		return nil, nil
    +	}
    +
    +	// Built-ins have no position.
    +	if isBuiltin(obj) {
    +		return builtinDefinition(ctx, snapshot, obj)
    +	}
    +
    +	// Non-go (e.g. assembly) symbols
    +	//
    +	// When already at the definition of a Go function without
    +	// a body, we jump to its non-Go (C or assembly) definition.
    +	for _, decl := range pgf.File.Decls {
    +		if decl, ok := decl.(*ast.FuncDecl); ok &&
    +			decl.Body == nil &&
    +			astutil.NodeContains(decl.Name, pos) {
    +			return nonGoDefinition(ctx, snapshot, pkg, decl.Name.Name)
    +		}
    +	}
    +
    +	// Finally, map the object position.
    +	loc, err := mapPosition(ctx, pkg.FileSet(), snapshot, obj.Pos(), adjustedObjEnd(obj))
    +	if err != nil {
    +		return nil, err
    +	}
    +	return []protocol.Location{loc}, nil
    +}
    +
    +// builtinDefinition returns the location of the fake source
    +// declaration of a built-in in {builtin,unsafe}.go.
    +func builtinDefinition(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) ([]protocol.Location, error) {
    +	pgf, ident, err := builtinDecl(ctx, snapshot, obj)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	loc, err := pgf.NodeLocation(ident)
    +	if err != nil {
    +		return nil, err
    +	}
    +	return []protocol.Location{loc}, nil
    +}
    +
    +// builtinDecl returns the parsed Go file and node corresponding to a builtin
    +// object, which may be a universe object or part of types.Unsafe, as well as
    +// its declaring identifier.
    +func builtinDecl(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) (*parsego.File, *ast.Ident, error) {
    +	// declaringIdent returns the file-level declaration node (as reported by
    +	// ast.Object) and declaring identifier of name using legacy (go/ast) object
    +	// resolution.
    +	declaringIdent := func(file *ast.File, name string) (ast.Node, *ast.Ident, error) {
    +		astObj := file.Scope.Lookup(name)
    +		if astObj == nil {
    +			// Every built-in should have documentation syntax.
    +			// However, it is possible to reach this statement by
    +			// commenting out declarations in {builtin,unsafe}.go.
    +			return nil, nil, fmt.Errorf("internal error: no object for %s", name)
    +		}
    +		decl, ok := astObj.Decl.(ast.Node)
    +		if !ok {
    +			return nil, nil, bug.Errorf("internal error: no declaration for %s", obj.Name())
    +		}
    +		var ident *ast.Ident
    +		switch node := decl.(type) {
    +		case *ast.Field:
    +			for _, id := range node.Names {
    +				if id.Name == name {
    +					ident = id
    +				}
    +			}
    +		case *ast.ValueSpec:
    +			for _, id := range node.Names {
    +				if id.Name == name {
    +					ident = id
    +				}
    +			}
    +		case *ast.TypeSpec:
    +			ident = node.Name
    +		case *ast.Ident:
    +			ident = node
    +		case *ast.FuncDecl:
    +			ident = node.Name
    +		case *ast.ImportSpec, *ast.LabeledStmt, *ast.AssignStmt:
    +			// Not reachable for imported objects.
    +		default:
    +			return nil, nil, bug.Errorf("internal error: unexpected decl type %T", decl)
    +		}
    +		if ident == nil {
    +			return nil, nil, bug.Errorf("internal error: no declaring identifier for %s", obj.Name())
    +		}
    +		return decl, ident, nil
    +	}
    +
    +	var (
    +		pgf   *parsego.File
    +		ident *ast.Ident
    +		err   error
    +	)
    +	if obj.Pkg() == types.Unsafe {
    +		// package "unsafe":
    +		// parse $GOROOT/src/unsafe/unsafe.go
    +		//
    +		// (Strictly, we shouldn't assume that the ID of a std
    +		// package is its PkgPath, but no Bazel+gopackagesdriver
    +		// users have complained about this yet.)
    +		unsafe := snapshot.Metadata("unsafe")
    +		if unsafe == nil {
    +			// If the type checker somehow resolved 'unsafe', we must have metadata
    +			// for it.
    +			return nil, nil, bug.Errorf("no metadata for package 'unsafe'")
    +		}
    +		uri := unsafe.GoFiles[0]
    +		fh, err := snapshot.ReadFile(ctx, uri)
    +		if err != nil {
    +			return nil, nil, err
    +		}
    +		// TODO(rfindley): treat unsafe symmetrically with the builtin file. Either
    +		// pre-parse them both, or look up metadata for both.
    +		pgf, err = snapshot.ParseGo(ctx, fh, parsego.Full&^parser.SkipObjectResolution)
    +		if err != nil {
    +			return nil, nil, err
    +		}
    +		_, ident, err = declaringIdent(pgf.File, obj.Name())
    +		if err != nil {
    +			return nil, nil, err
    +		}
    +	} else {
    +		// pseudo-package "builtin":
    +		// use parsed $GOROOT/src/builtin/builtin.go
    +		pgf, err = snapshot.BuiltinFile(ctx)
    +		if err != nil {
    +			return nil, nil, err
    +		}
    +
    +		if obj.Parent() == types.Universe {
    +			// built-in function or type
    +			_, ident, err = declaringIdent(pgf.File, obj.Name())
    +			if err != nil {
    +				return nil, nil, err
    +			}
    +		} else if obj.Name() == "Error" {
    +			// error.Error method
    +			decl, _, err := declaringIdent(pgf.File, "error")
    +			if err != nil {
    +				return nil, nil, err
    +			}
    +			field := decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List[0]
    +			ident = field.Names[0]
    +		} else {
    +			return nil, nil, bug.Errorf("unknown built-in %v", obj)
    +		}
    +	}
    +
    +	return pgf, ident, nil
    +}
    +
    +// referencedObject returns the identifier and object referenced at the
    +// specified position, which must be within the file pgf, for the purposes of
    +// definition/hover/call hierarchy operations. It returns a nil object if no
    +// object was found at the given position.
    +//
    +// If the returned identifier is a type-switch implicit (i.e. the x in x :=
    +// e.(type)), the third result will be the type of the expression being
    +// switched on (the type of e in the example). This facilitates workarounds for
    +// limitations of the go/types API, which does not report an object for the
    +// identifier x.
    +//
    +// For embedded fields, referencedObject returns the type name object rather
    +// than the var (field) object.
    +//
    +// TODO(rfindley): this function exists to preserve the pre-existing behavior
    +// of golang.Identifier. Eliminate this helper in favor of sharing
    +// functionality with objectsAt, after choosing suitable primitives.
    +func referencedObject(pkg *cache.Package, pgf *parsego.File, pos token.Pos) (*ast.Ident, types.Object, types.Type) {
    +	path := pathEnclosingObjNode(pgf.File, pos)
    +	if len(path) == 0 {
    +		return nil, nil, nil
    +	}
    +	var obj types.Object
    +	info := pkg.TypesInfo()
    +	switch n := path[0].(type) {
    +	case *ast.Ident:
    +		obj = info.ObjectOf(n)
    +		// If n is the var's declaring ident in a type switch
    +		// [i.e. the x in x := foo.(type)], it will not have an object. In this
    +		// case, set obj to the first implicit object (if any), and return the type
    +		// of the expression being switched on.
    +		//
    +		// The type switch may have no case clauses and thus no
    +		// implicit objects; this is a type error ("unused x"),
    +		if obj == nil {
    +			if implicits, typ := typeSwitchImplicits(info, path); len(implicits) > 0 {
    +				return n, implicits[0], typ
    +			}
    +		}
    +
    +		// If the original position was an embedded field, we want to jump
    +		// to the field's type definition, not the field's definition.
    +		if v, ok := obj.(*types.Var); ok && v.Embedded() {
    +			// types.Info.Uses contains the embedded field's *types.TypeName.
    +			if typeName := info.Uses[n]; typeName != nil {
    +				obj = typeName
    +			}
    +		}
    +		return n, obj, nil
    +	}
    +	return nil, nil, nil
    +}
    +
    +// importDefinition returns locations defining a package referenced by the
    +// import spec containing pos.
    +//
    +// If pos is not inside an import spec, it returns nil, nil.
    +func importDefinition(ctx context.Context, s *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, pos token.Pos) ([]protocol.Location, error) {
    +	var imp *ast.ImportSpec
    +	for _, spec := range pgf.File.Imports {
    +		// We use "<= End" to accept a query immediately after an ImportSpec.
    +		if spec.Path.Pos() <= pos && pos <= spec.Path.End() {
    +			imp = spec
    +		}
    +	}
    +	if imp == nil {
    +		return nil, nil
    +	}
    +
    +	importPath := metadata.UnquoteImportPath(imp)
    +	impID := pkg.Metadata().DepsByImpPath[importPath]
    +	if impID == "" {
    +		return nil, fmt.Errorf("failed to resolve import %q", importPath)
    +	}
    +	impMetadata := s.Metadata(impID)
    +	if impMetadata == nil {
    +		return nil, fmt.Errorf("missing information for package %q", impID)
    +	}
    +
    +	var locs []protocol.Location
    +	for _, f := range impMetadata.CompiledGoFiles {
    +		fh, err := s.ReadFile(ctx, f)
    +		if err != nil {
    +			if ctx.Err() != nil {
    +				return nil, ctx.Err()
    +			}
    +			continue
    +		}
    +		pgf, err := s.ParseGo(ctx, fh, parsego.Header)
    +		if err != nil {
    +			if ctx.Err() != nil {
    +				return nil, ctx.Err()
    +			}
    +			continue
    +		}
    +		loc, err := pgf.NodeLocation(pgf.File)
    +		if err != nil {
    +			return nil, err
    +		}
    +		locs = append(locs, loc)
    +	}
    +
    +	if len(locs) == 0 {
    +		return nil, fmt.Errorf("package %q has no readable files", impID) // incl. unsafe
    +	}
    +
    +	return locs, nil
    +}
    +
    +// TODO(rfindley): avoid the duplicate column mapping here, by associating a
    +// column mapper with each file handle.
    +func mapPosition(ctx context.Context, fset *token.FileSet, s file.Source, start, end token.Pos) (protocol.Location, error) {
    +	file := fset.File(start)
    +	uri := protocol.URIFromPath(file.Name())
    +	fh, err := s.ReadFile(ctx, uri)
    +	if err != nil {
    +		return protocol.Location{}, err
    +	}
    +	content, err := fh.Content()
    +	if err != nil {
    +		return protocol.Location{}, err
    +	}
    +	m := protocol.NewMapper(fh.URI(), content)
    +	return m.PosLocation(file, start, end)
    +}
    +
    +// nonGoDefinition returns the location of the definition of a non-Go symbol.
    +// Only assembly is supported for now.
    +func nonGoDefinition(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, symbol string) ([]protocol.Location, error) {
    +	// Examples:
    +	//   TEXT runtime·foo(SB)
    +	//   TEXT ·foo(SB)
    +	// TODO(adonovan): why does ^TEXT cause it not to match?
    +	pattern := regexp.MustCompile("TEXT\\b.*·(" + regexp.QuoteMeta(symbol) + ")[\\(<]")
    +
    +	for _, uri := range pkg.Metadata().OtherFiles {
    +		if strings.HasSuffix(uri.Path(), ".s") {
    +			fh, err := snapshot.ReadFile(ctx, uri)
    +			if err != nil {
    +				return nil, err // context cancelled
    +			}
    +			content, err := fh.Content()
    +			if err != nil {
    +				continue // can't read file
    +			}
    +			if match := pattern.FindSubmatchIndex(content); match != nil {
    +				mapper := protocol.NewMapper(uri, content)
    +				loc, err := mapper.OffsetLocation(match[2], match[3])
    +				if err != nil {
    +					return nil, err
    +				}
    +				return []protocol.Location{loc}, nil
    +			}
    +		}
    +	}
    +
    +	// TODO(adonovan): try C files
    +
    +	// This may be reached for functions that aren't implemented
    +	// in assembly (e.g. compiler intrinsics like getg).
    +	return nil, fmt.Errorf("can't find non-Go definition of %s", symbol)
    +}
    diff --git a/contribs/gnopls/internal/golang/diagnostics.go b/contribs/gnopls/internal/golang/diagnostics.go
    new file mode 100644
    index 00000000000..1c6da2e9d4e
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/diagnostics.go
    @@ -0,0 +1,44 @@
    +// Copyright 2018 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 golang
    +
    +import (
    +	"context"
    +	"maps"
    +	"slices"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/metadata"
    +	"golang.org/x/tools/gopls/internal/progress"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/settings"
    +	"golang.org/x/tools/gopls/internal/util/moremaps"
    +)
    +
    +// Analyze reports go/analysis-framework diagnostics in the specified package.
    +//
    +// If the provided tracker is non-nil, it may be used to provide notifications
    +// of the ongoing analysis pass.
    +//
    +// TODO(rfindley): merge this with snapshot.Analyze.
    +func Analyze(ctx context.Context, snapshot *cache.Snapshot, pkgIDs map[PackageID]*metadata.Package, tracker *progress.Tracker) (map[protocol.DocumentURI][]*cache.Diagnostic, error) {
    +	// Exit early if the context has been canceled. This also protects us
    +	// from a race on Options, see golang/go#36699.
    +	if ctx.Err() != nil {
    +		return nil, ctx.Err()
    +	}
    +
    +	analyzers := slices.Collect(maps.Values(settings.DefaultAnalyzers))
    +	if snapshot.Options().Staticcheck {
    +		analyzers = slices.AppendSeq(analyzers, maps.Values(settings.StaticcheckAnalyzers))
    +	}
    +
    +	analysisDiagnostics, err := snapshot.Analyze(ctx, pkgIDs, analyzers, tracker)
    +	if err != nil {
    +		return nil, err
    +	}
    +	byURI := func(d *cache.Diagnostic) protocol.DocumentURI { return d.URI }
    +	return moremaps.Group(analysisDiagnostics, byURI), nil
    +}
    diff --git a/contribs/gnopls/internal/golang/embeddirective.go b/contribs/gnopls/internal/golang/embeddirective.go
    new file mode 100644
    index 00000000000..3a35f907274
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/embeddirective.go
    @@ -0,0 +1,195 @@
    +// 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 golang
    +
    +import (
    +	"errors"
    +	"fmt"
    +	"io/fs"
    +	"path/filepath"
    +	"strconv"
    +	"strings"
    +	"unicode"
    +	"unicode/utf8"
    +
    +	"golang.org/x/tools/gopls/internal/protocol"
    +)
    +
    +// ErrNoEmbed is returned by EmbedDefinition when no embed
    +// directive is found at a particular position.
    +// As such it indicates that other definitions could be worth checking.
    +var ErrNoEmbed = errors.New("no embed directive found")
    +
    +var errStopWalk = errors.New("stop walk")
    +
    +// embedDefinition finds a file matching the embed directive at pos in the mapped file.
    +// If there is no embed directive at pos, returns ErrNoEmbed.
    +// If multiple files match the embed pattern, one is picked at random.
    +func embedDefinition(m *protocol.Mapper, pos protocol.Position) ([]protocol.Location, error) {
    +	pattern, _ := parseEmbedDirective(m, pos)
    +	if pattern == "" {
    +		return nil, ErrNoEmbed
    +	}
    +
    +	// Find the first matching file.
    +	var match string
    +	dir := filepath.Dir(m.URI.Path())
    +	err := filepath.WalkDir(dir, func(abs string, d fs.DirEntry, e error) error {
    +		if e != nil {
    +			return e
    +		}
    +		rel, err := filepath.Rel(dir, abs)
    +		if err != nil {
    +			return err
    +		}
    +		ok, err := filepath.Match(pattern, rel)
    +		if err != nil {
    +			return err
    +		}
    +		if ok && !d.IsDir() {
    +			match = abs
    +			return errStopWalk
    +		}
    +		return nil
    +	})
    +	if err != nil && !errors.Is(err, errStopWalk) {
    +		return nil, err
    +	}
    +	if match == "" {
    +		return nil, fmt.Errorf("%q does not match any files in %q", pattern, dir)
    +	}
    +
    +	loc := protocol.Location{
    +		URI: protocol.URIFromPath(match),
    +		Range: protocol.Range{
    +			Start: protocol.Position{Line: 0, Character: 0},
    +		},
    +	}
    +	return []protocol.Location{loc}, nil
    +}
    +
    +// parseEmbedDirective attempts to parse a go:embed directive argument at pos.
    +// If successful it return the directive argument and its range, else zero values are returned.
    +func parseEmbedDirective(m *protocol.Mapper, pos protocol.Position) (string, protocol.Range) {
    +	lineStart, err := m.PositionOffset(protocol.Position{Line: pos.Line, Character: 0})
    +	if err != nil {
    +		return "", protocol.Range{}
    +	}
    +	lineEnd, err := m.PositionOffset(protocol.Position{Line: pos.Line + 1, Character: 0})
    +	if err != nil {
    +		return "", protocol.Range{}
    +	}
    +
    +	text := string(m.Content[lineStart:lineEnd])
    +	if !strings.HasPrefix(text, "//go:embed") {
    +		return "", protocol.Range{}
    +	}
    +	text = text[len("//go:embed"):]
    +	offset := lineStart + len("//go:embed")
    +
    +	// Find the first pattern in text that covers the offset of the pos we are looking for.
    +	findOffset, err := m.PositionOffset(pos)
    +	if err != nil {
    +		return "", protocol.Range{}
    +	}
    +	patterns, err := parseGoEmbed(text, offset)
    +	if err != nil {
    +		return "", protocol.Range{}
    +	}
    +	for _, p := range patterns {
    +		if p.startOffset <= findOffset && findOffset <= p.endOffset {
    +			// Found our match.
    +			rng, err := m.OffsetRange(p.startOffset, p.endOffset)
    +			if err != nil {
    +				return "", protocol.Range{}
    +			}
    +			return p.pattern, rng
    +		}
    +	}
    +
    +	return "", protocol.Range{}
    +}
    +
    +type fileEmbed struct {
    +	pattern     string
    +	startOffset int
    +	endOffset   int
    +}
    +
    +// parseGoEmbed patterns that come after the directive.
    +//
    +// Copied and adapted from go/build/read.go.
    +// Replaced token.Position with start/end offset (including quotes if present).
    +func parseGoEmbed(args string, offset int) ([]fileEmbed, error) {
    +	trimBytes := func(n int) {
    +		offset += n
    +		args = args[n:]
    +	}
    +	trimSpace := func() {
    +		trim := strings.TrimLeftFunc(args, unicode.IsSpace)
    +		trimBytes(len(args) - len(trim))
    +	}
    +
    +	var list []fileEmbed
    +	for trimSpace(); args != ""; trimSpace() {
    +		var path string
    +		pathOffset := offset
    +	Switch:
    +		switch args[0] {
    +		default:
    +			i := len(args)
    +			for j, c := range args {
    +				if unicode.IsSpace(c) {
    +					i = j
    +					break
    +				}
    +			}
    +			path = args[:i]
    +			trimBytes(i)
    +
    +		case '`':
    +			var ok bool
    +			path, _, ok = strings.Cut(args[1:], "`")
    +			if !ok {
    +				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
    +			}
    +			trimBytes(1 + len(path) + 1)
    +
    +		case '"':
    +			i := 1
    +			for ; i < len(args); i++ {
    +				if args[i] == '\\' {
    +					i++
    +					continue
    +				}
    +				if args[i] == '"' {
    +					q, err := strconv.Unquote(args[:i+1])
    +					if err != nil {
    +						return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
    +					}
    +					path = q
    +					trimBytes(i + 1)
    +					break Switch
    +				}
    +			}
    +			if i >= len(args) {
    +				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
    +			}
    +		}
    +
    +		if args != "" {
    +			r, _ := utf8.DecodeRuneInString(args)
    +			if !unicode.IsSpace(r) {
    +				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
    +			}
    +		}
    +		list = append(list, fileEmbed{
    +			pattern:     path,
    +			startOffset: pathOffset,
    +			endOffset:   offset,
    +		})
    +	}
    +	return list, nil
    +}
    diff --git a/contribs/gnopls/internal/golang/extract.go b/contribs/gnopls/internal/golang/extract.go
    new file mode 100644
    index 00000000000..3610deeead3
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/extract.go
    @@ -0,0 +1,1380 @@
    +// 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 golang
    +
    +import (
    +	"bytes"
    +	"fmt"
    +	"go/ast"
    +	"go/format"
    +	"go/parser"
    +	"go/token"
    +	"go/types"
    +	"sort"
    +	"strings"
    +	"text/scanner"
    +
    +	"golang.org/x/tools/go/analysis"
    +	"golang.org/x/tools/go/ast/astutil"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/internal/analysisinternal"
    +)
    +
    +func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) {
    +	tokFile := fset.File(file.Pos())
    +	expr, path, ok, err := canExtractVariable(start, end, file)
    +	if !ok {
    +		return nil, nil, fmt.Errorf("extractVariable: cannot extract %s: %v", safetoken.StartPosition(fset, start), err)
    +	}
    +
    +	// Create new AST node for extracted code.
    +	var lhsNames []string
    +	switch expr := expr.(type) {
    +	// TODO: stricter rules for selectorExpr.
    +	case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.SliceExpr,
    +		*ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
    +		lhsName, _ := generateAvailableIdentifier(expr.Pos(), path, pkg, info, "x", 0)
    +		lhsNames = append(lhsNames, lhsName)
    +	case *ast.CallExpr:
    +		tup, ok := info.TypeOf(expr).(*types.Tuple)
    +		if !ok {
    +			// If the call expression only has one return value, we can treat it the
    +			// same as our standard extract variable case.
    +			lhsName, _ := generateAvailableIdentifier(expr.Pos(), path, pkg, info, "x", 0)
    +			lhsNames = append(lhsNames, lhsName)
    +			break
    +		}
    +		idx := 0
    +		for i := 0; i < tup.Len(); i++ {
    +			// Generate a unique variable for each return value.
    +			var lhsName string
    +			lhsName, idx = generateAvailableIdentifier(expr.Pos(), path, pkg, info, "x", idx)
    +			lhsNames = append(lhsNames, lhsName)
    +		}
    +	default:
    +		return nil, nil, fmt.Errorf("cannot extract %T", expr)
    +	}
    +
    +	// TODO: There is a bug here: for a variable declared in a labeled
    +	// switch/for statement it returns the for/switch statement itself
    +	// which produces the below code which is a compiler error e.g.
    +	// label:
    +	// switch r1 := r() { ... break label ... }
    +	// On extracting "r()" to a variable
    +	// label:
    +	// x := r()
    +	// switch r1 := x { ... break label ... } // compiler error
    +	insertBeforeStmt := analysisinternal.StmtToInsertVarBefore(path)
    +	if insertBeforeStmt == nil {
    +		return nil, nil, fmt.Errorf("cannot find location to insert extraction")
    +	}
    +	indent, err := calculateIndentation(src, tokFile, insertBeforeStmt)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +	newLineIndent := "\n" + indent
    +
    +	lhs := strings.Join(lhsNames, ", ")
    +	assignStmt := &ast.AssignStmt{
    +		Lhs: []ast.Expr{ast.NewIdent(lhs)},
    +		Tok: token.DEFINE,
    +		Rhs: []ast.Expr{expr},
    +	}
    +	var buf bytes.Buffer
    +	if err := format.Node(&buf, fset, assignStmt); err != nil {
    +		return nil, nil, err
    +	}
    +	assignment := strings.ReplaceAll(buf.String(), "\n", newLineIndent) + newLineIndent
    +
    +	return fset, &analysis.SuggestedFix{
    +		TextEdits: []analysis.TextEdit{
    +			{
    +				Pos:     insertBeforeStmt.Pos(),
    +				End:     insertBeforeStmt.Pos(),
    +				NewText: []byte(assignment),
    +			},
    +			{
    +				Pos:     start,
    +				End:     end,
    +				NewText: []byte(lhs),
    +			},
    +		},
    +	}, nil
    +}
    +
    +// canExtractVariable reports whether the code in the given range can be
    +// extracted to a variable.
    +func canExtractVariable(start, end token.Pos, file *ast.File) (ast.Expr, []ast.Node, bool, error) {
    +	if start == end {
    +		return nil, nil, false, fmt.Errorf("start and end are equal")
    +	}
    +	path, _ := astutil.PathEnclosingInterval(file, start, end)
    +	if len(path) == 0 {
    +		return nil, nil, false, fmt.Errorf("no path enclosing interval")
    +	}
    +	for _, n := range path {
    +		if _, ok := n.(*ast.ImportSpec); ok {
    +			return nil, nil, false, fmt.Errorf("cannot extract variable in an import block")
    +		}
    +	}
    +	node := path[0]
    +	if start != node.Pos() || end != node.End() {
    +		return nil, nil, false, fmt.Errorf("range does not map to an AST node")
    +	}
    +	expr, ok := node.(ast.Expr)
    +	if !ok {
    +		return nil, nil, false, fmt.Errorf("node is not an expression")
    +	}
    +	switch expr.(type) {
    +	case *ast.BasicLit, *ast.CompositeLit, *ast.IndexExpr, *ast.CallExpr,
    +		*ast.SliceExpr, *ast.UnaryExpr, *ast.BinaryExpr, *ast.SelectorExpr:
    +		return expr, path, true, nil
    +	}
    +	return nil, nil, false, fmt.Errorf("cannot extract an %T to a variable", expr)
    +}
    +
    +// Calculate indentation for insertion.
    +// When inserting lines of code, we must ensure that the lines have consistent
    +// formatting (i.e. the proper indentation). To do so, we observe the indentation on the
    +// line of code on which the insertion occurs.
    +func calculateIndentation(content []byte, tok *token.File, insertBeforeStmt ast.Node) (string, error) {
    +	line := safetoken.Line(tok, insertBeforeStmt.Pos())
    +	lineOffset, stmtOffset, err := safetoken.Offsets(tok, tok.LineStart(line), insertBeforeStmt.Pos())
    +	if err != nil {
    +		return "", err
    +	}
    +	return string(content[lineOffset:stmtOffset]), nil
    +}
    +
    +// generateAvailableIdentifier adjusts the new function name until there are no collisions in scope.
    +// Possible collisions include other function and variable names. Returns the next index to check for prefix.
    +func generateAvailableIdentifier(pos token.Pos, path []ast.Node, pkg *types.Package, info *types.Info, prefix string, idx int) (string, int) {
    +	scopes := CollectScopes(info, path, pos)
    +	scopes = append(scopes, pkg.Scope())
    +	return generateIdentifier(idx, prefix, func(name string) bool {
    +		for _, scope := range scopes {
    +			if scope != nil && scope.Lookup(name) != nil {
    +				return true
    +			}
    +		}
    +		return false
    +	})
    +}
    +
    +func generateIdentifier(idx int, prefix string, hasCollision func(string) bool) (string, int) {
    +	name := prefix
    +	if idx != 0 {
    +		name += fmt.Sprintf("%d", idx)
    +	}
    +	for hasCollision(name) {
    +		idx++
    +		name = fmt.Sprintf("%v%d", prefix, idx)
    +	}
    +	return name, idx + 1
    +}
    +
    +// returnVariable keeps track of the information we need to properly introduce a new variable
    +// that we will return in the extracted function.
    +type returnVariable struct {
    +	// name is the identifier that is used on the left-hand side of the call to
    +	// the extracted function.
    +	name ast.Expr
    +	// decl is the declaration of the variable. It is used in the type signature of the
    +	// extracted function and for variable declarations.
    +	decl *ast.Field
    +	// zeroVal is the "zero value" of the type of the variable. It is used in a return
    +	// statement in the extracted function.
    +	zeroVal ast.Expr
    +}
    +
    +// extractMethod refactors the selected block of code into a new method.
    +func extractMethod(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) {
    +	return extractFunctionMethod(fset, start, end, src, file, pkg, info, true)
    +}
    +
    +// extractFunction refactors the selected block of code into a new function.
    +func extractFunction(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) {
    +	return extractFunctionMethod(fset, start, end, src, file, pkg, info, false)
    +}
    +
    +// extractFunctionMethod refactors the selected block of code into a new function/method.
    +// It also replaces the selected block of code with a call to the extracted
    +// function. First, we manually adjust the selection range. We remove trailing
    +// and leading whitespace characters to ensure the range is precisely bounded
    +// by AST nodes. Next, we determine the variables that will be the parameters
    +// and return values of the extracted function/method. Lastly, we construct the call
    +// of the function/method and insert this call as well as the extracted function/method into
    +// their proper locations.
    +func extractFunctionMethod(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info, isMethod bool) (*token.FileSet, *analysis.SuggestedFix, error) {
    +	errorPrefix := "extractFunction"
    +	if isMethod {
    +		errorPrefix = "extractMethod"
    +	}
    +
    +	tok := fset.File(file.Pos())
    +	if tok == nil {
    +		return nil, nil, bug.Errorf("no file for position")
    +	}
    +	p, ok, methodOk, err := canExtractFunction(tok, start, end, src, file)
    +	if (!ok && !isMethod) || (!methodOk && isMethod) {
    +		return nil, nil, fmt.Errorf("%s: cannot extract %s: %v", errorPrefix,
    +			safetoken.StartPosition(fset, start), err)
    +	}
    +	tok, path, start, end, outer, node := p.tok, p.path, p.start, p.end, p.outer, p.node
    +	fileScope := info.Scopes[file]
    +	if fileScope == nil {
    +		return nil, nil, fmt.Errorf("%s: file scope is empty", errorPrefix)
    +	}
    +	pkgScope := fileScope.Parent()
    +	if pkgScope == nil {
    +		return nil, nil, fmt.Errorf("%s: package scope is empty", errorPrefix)
    +	}
    +
    +	// A return statement is non-nested if its parent node is equal to the parent node
    +	// of the first node in the selection. These cases must be handled separately because
    +	// non-nested return statements are guaranteed to execute.
    +	var retStmts []*ast.ReturnStmt
    +	var hasNonNestedReturn bool
    +	startParent := findParent(outer, node)
    +	ast.Inspect(outer, func(n ast.Node) bool {
    +		if n == nil {
    +			return false
    +		}
    +		if n.Pos() < start || n.End() > end {
    +			return n.Pos() <= end
    +		}
    +		ret, ok := n.(*ast.ReturnStmt)
    +		if !ok {
    +			return true
    +		}
    +		if findParent(outer, n) == startParent {
    +			hasNonNestedReturn = true
    +		}
    +		retStmts = append(retStmts, ret)
    +		return false
    +	})
    +	containsReturnStatement := len(retStmts) > 0
    +
    +	// Now that we have determined the correct range for the selection block,
    +	// we must determine the signature of the extracted function. We will then replace
    +	// the block with an assignment statement that calls the extracted function with
    +	// the appropriate parameters and return values.
    +	variables, err := collectFreeVars(info, file, fileScope, pkgScope, start, end, path[0])
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +
    +	var (
    +		receiverUsed bool
    +		receiver     *ast.Field
    +		receiverName string
    +		receiverObj  types.Object
    +	)
    +	if isMethod {
    +		if outer == nil || outer.Recv == nil || len(outer.Recv.List) == 0 {
    +			return nil, nil, fmt.Errorf("%s: cannot extract need method receiver", errorPrefix)
    +		}
    +		receiver = outer.Recv.List[0]
    +		if len(receiver.Names) == 0 || receiver.Names[0] == nil {
    +			return nil, nil, fmt.Errorf("%s: cannot extract need method receiver name", errorPrefix)
    +		}
    +		recvName := receiver.Names[0]
    +		receiverName = recvName.Name
    +		receiverObj = info.ObjectOf(recvName)
    +	}
    +
    +	var (
    +		params, returns         []ast.Expr     // used when calling the extracted function
    +		paramTypes, returnTypes []*ast.Field   // used in the signature of the extracted function
    +		uninitialized           []types.Object // vars we will need to initialize before the call
    +	)
    +
    +	// Avoid duplicates while traversing vars and uninitialized.
    +	seenVars := make(map[types.Object]ast.Expr)
    +	seenUninitialized := make(map[types.Object]struct{})
    +
    +	// Some variables on the left-hand side of our assignment statement may be free. If our
    +	// selection begins in the same scope in which the free variable is defined, we can
    +	// redefine it in our assignment statement. See the following example, where 'b' and
    +	// 'err' (both free variables) can be redefined in the second funcCall() while maintaining
    +	// correctness.
    +	//
    +	//
    +	// Not Redefined:
    +	//
    +	// a, err := funcCall()
    +	// var b int
    +	// b, err = funcCall()
    +	//
    +	// Redefined:
    +	//
    +	// a, err := funcCall()
    +	// b, err := funcCall()
    +	//
    +	// We track the number of free variables that can be redefined to maintain our preference
    +	// of using "x, y, z := fn()" style assignment statements.
    +	var canRedefineCount int
    +
    +	// Each identifier in the selected block must become (1) a parameter to the
    +	// extracted function, (2) a return value of the extracted function, or (3) a local
    +	// variable in the extracted function. Determine the outcome(s) for each variable
    +	// based on whether it is free, altered within the selected block, and used outside
    +	// of the selected block.
    +	for _, v := range variables {
    +		if _, ok := seenVars[v.obj]; ok {
    +			continue
    +		}
    +		if v.obj.Name() == "_" {
    +			// The blank identifier is always a local variable
    +			continue
    +		}
    +		typ := analysisinternal.TypeExpr(file, pkg, v.obj.Type())
    +		if typ == nil {
    +			return nil, nil, fmt.Errorf("nil AST expression for type: %v", v.obj.Name())
    +		}
    +		seenVars[v.obj] = typ
    +		identifier := ast.NewIdent(v.obj.Name())
    +		// An identifier must meet three conditions to become a return value of the
    +		// extracted function. (1) its value must be defined or reassigned within
    +		// the selection (isAssigned), (2) it must be used at least once after the
    +		// selection (isUsed), and (3) its first use after the selection
    +		// cannot be its own reassignment or redefinition (objOverriden).
    +		vscope := v.obj.Parent()
    +		if vscope == nil {
    +			return nil, nil, fmt.Errorf("parent nil")
    +		}
    +		isUsed, firstUseAfter := objUsed(info, end, vscope.End(), v.obj)
    +		if v.assigned && isUsed && !varOverridden(info, firstUseAfter, v.obj, v.free, outer) {
    +			returnTypes = append(returnTypes, &ast.Field{Type: typ})
    +			returns = append(returns, identifier)
    +			if !v.free {
    +				uninitialized = append(uninitialized, v.obj)
    +
    +			} else {
    +				// In go1.22, Scope.Pos for function scopes changed (#60752):
    +				// it used to start at the body ('{'), now it starts at "func".
    +				//
    +				// The second condition below handles the case when
    +				// v's block is the FuncDecl.Body itself.
    +				if vscope.Pos() == startParent.Pos() ||
    +					startParent == outer.Body && vscope == info.Scopes[outer.Type] {
    +					canRedefineCount++
    +				}
    +			}
    +		}
    +		// An identifier must meet two conditions to become a parameter of the
    +		// extracted function. (1) it must be free (isFree), and (2) its first
    +		// use within the selection cannot be its own definition (isDefined).
    +		if v.free && !v.defined {
    +			// Skip the selector for a method.
    +			if isMethod && v.obj == receiverObj {
    +				receiverUsed = true
    +				continue
    +			}
    +			params = append(params, identifier)
    +			paramTypes = append(paramTypes, &ast.Field{
    +				Names: []*ast.Ident{identifier},
    +				Type:  typ,
    +			})
    +		}
    +	}
    +
    +	reorderParams(params, paramTypes)
    +
    +	// Find the function literal that encloses the selection. The enclosing function literal
    +	// may not be the enclosing function declaration (i.e. 'outer'). For example, in the
    +	// following block:
    +	//
    +	// func main() {
    +	//     ast.Inspect(node, func(n ast.Node) bool {
    +	//         v := 1 // this line extracted
    +	//         return true
    +	//     })
    +	// }
    +	//
    +	// 'outer' is main(). However, the extracted selection most directly belongs to
    +	// the anonymous function literal, the second argument of ast.Inspect(). We use the
    +	// enclosing function literal to determine the proper return types for return statements
    +	// within the selection. We still need the enclosing function declaration because this is
    +	// the top-level declaration. We inspect the top-level declaration to look for variables
    +	// as well as for code replacement.
    +	enclosing := outer.Type
    +	for _, p := range path {
    +		if p == enclosing {
    +			break
    +		}
    +		if fl, ok := p.(*ast.FuncLit); ok {
    +			enclosing = fl.Type
    +			break
    +		}
    +	}
    +
    +	// We put the selection in a constructed file. We can then traverse and edit
    +	// the extracted selection without modifying the original AST.
    +	startOffset, endOffset, err := safetoken.Offsets(tok, start, end)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +	selection := src[startOffset:endOffset]
    +	extractedBlock, err := parseBlockStmt(fset, selection)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +
    +	// We need to account for return statements in the selected block, as they will complicate
    +	// the logical flow of the extracted function. See the following example, where ** denotes
    +	// the range to be extracted.
    +	//
    +	// Before:
    +	//
    +	// func _() int {
    +	//     a := 1
    +	//     b := 2
    +	//     **if a == b {
    +	//         return a
    +	//     }**
    +	//     ...
    +	// }
    +	//
    +	// After:
    +	//
    +	// func _() int {
    +	//     a := 1
    +	//     b := 2
    +	//     cond0, ret0 := x0(a, b)
    +	//     if cond0 {
    +	//         return ret0
    +	//     }
    +	//     ...
    +	// }
    +	//
    +	// func x0(a int, b int) (bool, int) {
    +	//     if a == b {
    +	//         return true, a
    +	//     }
    +	//     return false, 0
    +	// }
    +	//
    +	// We handle returns by adding an additional boolean return value to the extracted function.
    +	// This bool reports whether the original function would have returned. Because the
    +	// extracted selection contains a return statement, we must also add the types in the
    +	// return signature of the enclosing function to the return signature of the
    +	// extracted function. We then add an extra if statement checking this boolean value
    +	// in the original function. If the condition is met, the original function should
    +	// return a value, mimicking the functionality of the original return statement(s)
    +	// in the selection.
    +	//
    +	// If there is a return that is guaranteed to execute (hasNonNestedReturns=true), then
    +	// we don't need to include this additional condition check and can simply return.
    +	//
    +	// Before:
    +	//
    +	// func _() int {
    +	//     a := 1
    +	//     b := 2
    +	//     **if a == b {
    +	//         return a
    +	//     }
    +	//	   return b**
    +	// }
    +	//
    +	// After:
    +	//
    +	// func _() int {
    +	//     a := 1
    +	//     b := 2
    +	//     return x0(a, b)
    +	// }
    +	//
    +	// func x0(a int, b int) int {
    +	//     if a == b {
    +	//         return a
    +	//     }
    +	//     return b
    +	// }
    +
    +	var retVars []*returnVariable
    +	var ifReturn *ast.IfStmt
    +	if containsReturnStatement {
    +		if !hasNonNestedReturn {
    +			// The selected block contained return statements, so we have to modify the
    +			// signature of the extracted function as described above. Adjust all of
    +			// the return statements in the extracted function to reflect this change in
    +			// signature.
    +			if err := adjustReturnStatements(returnTypes, seenVars, file, pkg, extractedBlock); err != nil {
    +				return nil, nil, err
    +			}
    +		}
    +		// Collect the additional return values and types needed to accommodate return
    +		// statements in the selection. Update the type signature of the extracted
    +		// function and construct the if statement that will be inserted in the enclosing
    +		// function.
    +		retVars, ifReturn, err = generateReturnInfo(enclosing, pkg, path, file, info, start, hasNonNestedReturn)
    +		if err != nil {
    +			return nil, nil, err
    +		}
    +	}
    +
    +	// Add a return statement to the end of the new function. This return statement must include
    +	// the values for the types of the original extracted function signature and (if a return
    +	// statement is present in the selection) enclosing function signature.
    +	// This only needs to be done if the selections does not have a non-nested return, otherwise
    +	// it already terminates with a return statement.
    +	hasReturnValues := len(returns)+len(retVars) > 0
    +	if hasReturnValues && !hasNonNestedReturn {
    +		extractedBlock.List = append(extractedBlock.List, &ast.ReturnStmt{
    +			Results: append(returns, getZeroVals(retVars)...),
    +		})
    +	}
    +
    +	// Construct the appropriate call to the extracted function.
    +	// We must meet two conditions to use ":=" instead of '='. (1) there must be at least
    +	// one variable on the lhs that is uninitialized (non-free) prior to the assignment.
    +	// (2) all of the initialized (free) variables on the lhs must be able to be redefined.
    +	sym := token.ASSIGN
    +	canDefineCount := len(uninitialized) + canRedefineCount
    +	canDefine := len(uninitialized)+len(retVars) > 0 && canDefineCount == len(returns)
    +	if canDefine {
    +		sym = token.DEFINE
    +	}
    +	var name, funName string
    +	if isMethod {
    +		name = "newMethod"
    +		// TODO(suzmue): generate a name that does not conflict for "newMethod".
    +		funName = name
    +	} else {
    +		name = "newFunction"
    +		funName, _ = generateAvailableIdentifier(start, path, pkg, info, name, 0)
    +	}
    +	extractedFunCall := generateFuncCall(hasNonNestedReturn, hasReturnValues, params,
    +		append(returns, getNames(retVars)...), funName, sym, receiverName)
    +
    +	// Build the extracted function.
    +	newFunc := &ast.FuncDecl{
    +		Name: ast.NewIdent(funName),
    +		Type: &ast.FuncType{
    +			Params:  &ast.FieldList{List: paramTypes},
    +			Results: &ast.FieldList{List: append(returnTypes, getDecls(retVars)...)},
    +		},
    +		Body: extractedBlock,
    +	}
    +	if isMethod {
    +		var names []*ast.Ident
    +		if receiverUsed {
    +			names = append(names, ast.NewIdent(receiverName))
    +		}
    +		newFunc.Recv = &ast.FieldList{
    +			List: []*ast.Field{{
    +				Names: names,
    +				Type:  receiver.Type,
    +			}},
    +		}
    +	}
    +
    +	// Create variable declarations for any identifiers that need to be initialized prior to
    +	// calling the extracted function. We do not manually initialize variables if every return
    +	// value is uninitialized. We can use := to initialize the variables in this situation.
    +	var declarations []ast.Stmt
    +	if canDefineCount != len(returns) {
    +		declarations = initializeVars(uninitialized, retVars, seenUninitialized, seenVars)
    +	}
    +
    +	var declBuf, replaceBuf, newFuncBuf, ifBuf, commentBuf bytes.Buffer
    +	if err := format.Node(&declBuf, fset, declarations); err != nil {
    +		return nil, nil, err
    +	}
    +	if err := format.Node(&replaceBuf, fset, extractedFunCall); err != nil {
    +		return nil, nil, err
    +	}
    +	if ifReturn != nil {
    +		if err := format.Node(&ifBuf, fset, ifReturn); err != nil {
    +			return nil, nil, err
    +		}
    +	}
    +	if err := format.Node(&newFuncBuf, fset, newFunc); err != nil {
    +		return nil, nil, err
    +	}
    +	// Find all the comments within the range and print them to be put somewhere.
    +	// TODO(suzmue): print these in the extracted function at the correct place.
    +	for _, cg := range file.Comments {
    +		if cg.Pos().IsValid() && cg.Pos() < end && cg.Pos() >= start {
    +			for _, c := range cg.List {
    +				fmt.Fprintln(&commentBuf, c.Text)
    +			}
    +		}
    +	}
    +
    +	// We're going to replace the whole enclosing function,
    +	// so preserve the text before and after the selected block.
    +	outerStart, outerEnd, err := safetoken.Offsets(tok, outer.Pos(), outer.End())
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +	before := src[outerStart:startOffset]
    +	after := src[endOffset:outerEnd]
    +	indent, err := calculateIndentation(src, tok, node)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +	newLineIndent := "\n" + indent
    +
    +	var fullReplacement strings.Builder
    +	fullReplacement.Write(before)
    +	if commentBuf.Len() > 0 {
    +		comments := strings.ReplaceAll(commentBuf.String(), "\n", newLineIndent)
    +		fullReplacement.WriteString(comments)
    +	}
    +	if declBuf.Len() > 0 { // add any initializations, if needed
    +		initializations := strings.ReplaceAll(declBuf.String(), "\n", newLineIndent) +
    +			newLineIndent
    +		fullReplacement.WriteString(initializations)
    +	}
    +	fullReplacement.Write(replaceBuf.Bytes()) // call the extracted function
    +	if ifBuf.Len() > 0 {                      // add the if statement below the function call, if needed
    +		ifstatement := newLineIndent +
    +			strings.ReplaceAll(ifBuf.String(), "\n", newLineIndent)
    +		fullReplacement.WriteString(ifstatement)
    +	}
    +	fullReplacement.Write(after)
    +	fullReplacement.WriteString("\n\n")       // add newlines after the enclosing function
    +	fullReplacement.Write(newFuncBuf.Bytes()) // insert the extracted function
    +
    +	return fset, &analysis.SuggestedFix{
    +		TextEdits: []analysis.TextEdit{{
    +			Pos:     outer.Pos(),
    +			End:     outer.End(),
    +			NewText: []byte(fullReplacement.String()),
    +		}},
    +	}, nil
    +}
    +
    +// isSelector reports if e is the selector expr , . It works for pointer and non-pointer selector expressions.
    +func isSelector(e ast.Expr, x, sel string) bool {
    +	unary, ok := e.(*ast.UnaryExpr)
    +	if ok && unary.Op == token.MUL {
    +		e = unary.X
    +	}
    +	selectorExpr, ok := e.(*ast.SelectorExpr)
    +	if !ok {
    +		return false
    +	}
    +	ident, ok := selectorExpr.X.(*ast.Ident)
    +	if !ok {
    +		return false
    +	}
    +	return ident.Name == x && selectorExpr.Sel.Name == sel
    +}
    +
    +// reorderParams reorders the given parameters in-place to follow common Go conventions.
    +func reorderParams(params []ast.Expr, paramTypes []*ast.Field) {
    +	moveParamToFrontIfFound(params, paramTypes, "testing", "T")
    +	moveParamToFrontIfFound(params, paramTypes, "testing", "B")
    +	moveParamToFrontIfFound(params, paramTypes, "context", "Context")
    +}
    +
    +func moveParamToFrontIfFound(params []ast.Expr, paramTypes []*ast.Field, x, sel string) {
    +	// Move Context parameter (if any) to front.
    +	for i, t := range paramTypes {
    +		if isSelector(t.Type, x, sel) {
    +			p, t := params[i], paramTypes[i]
    +			copy(params[1:], params[:i])
    +			copy(paramTypes[1:], paramTypes[:i])
    +			params[0], paramTypes[0] = p, t
    +			break
    +		}
    +	}
    +}
    +
    +// adjustRangeForCommentsAndWhiteSpace adjusts the given range to exclude unnecessary leading or
    +// trailing whitespace characters from selection as well as leading or trailing comments.
    +// In the following example, each line of the if statement is indented once. There are also two
    +// extra spaces after the sclosing bracket before the line break and a comment.
    +//
    +// \tif (true) {
    +// \t    _ = 1
    +// \t} // hello \n
    +//
    +// By default, a valid range begins at 'if' and ends at the first whitespace character
    +// after the '}'. But, users are likely to highlight full lines rather than adjusting
    +// their cursors for whitespace. To support this use case, we must manually adjust the
    +// ranges to match the correct AST node. In this particular example, we would adjust
    +// rng.Start forward to the start of 'if' and rng.End backward to after '}'.
    +func adjustRangeForCommentsAndWhiteSpace(tok *token.File, start, end token.Pos, content []byte, file *ast.File) (token.Pos, token.Pos, error) {
    +	// Adjust the end of the range to after leading whitespace and comments.
    +	prevStart := token.NoPos
    +	startComment := sort.Search(len(file.Comments), func(i int) bool {
    +		// Find the index for the first comment that ends after range start.
    +		return file.Comments[i].End() > start
    +	})
    +	for prevStart != start {
    +		prevStart = start
    +		// If start is within a comment, move start to the end
    +		// of the comment group.
    +		if startComment < len(file.Comments) && file.Comments[startComment].Pos() <= start && start < file.Comments[startComment].End() {
    +			start = file.Comments[startComment].End()
    +			startComment++
    +		}
    +		// Move forwards to find a non-whitespace character.
    +		offset, err := safetoken.Offset(tok, start)
    +		if err != nil {
    +			return 0, 0, err
    +		}
    +		for offset < len(content) && isGoWhiteSpace(content[offset]) {
    +			offset++
    +		}
    +		start = tok.Pos(offset)
    +	}
    +
    +	// Adjust the end of the range to before trailing whitespace and comments.
    +	prevEnd := token.NoPos
    +	endComment := sort.Search(len(file.Comments), func(i int) bool {
    +		// Find the index for the first comment that ends after the range end.
    +		return file.Comments[i].End() >= end
    +	})
    +	// Search will return n if not found, so we need to adjust if there are no
    +	// comments that would match.
    +	if endComment == len(file.Comments) {
    +		endComment = -1
    +	}
    +	for prevEnd != end {
    +		prevEnd = end
    +		// If end is within a comment, move end to the start
    +		// of the comment group.
    +		if endComment >= 0 && file.Comments[endComment].Pos() < end && end <= file.Comments[endComment].End() {
    +			end = file.Comments[endComment].Pos()
    +			endComment--
    +		}
    +		// Move backwards to find a non-whitespace character.
    +		offset, err := safetoken.Offset(tok, end)
    +		if err != nil {
    +			return 0, 0, err
    +		}
    +		for offset > 0 && isGoWhiteSpace(content[offset-1]) {
    +			offset--
    +		}
    +		end = tok.Pos(offset)
    +	}
    +
    +	return start, end, nil
    +}
    +
    +// isGoWhiteSpace returns true if b is a considered white space in
    +// Go as defined by scanner.GoWhitespace.
    +func isGoWhiteSpace(b byte) bool {
    +	return uint64(scanner.GoWhitespace)&(1< not free
    +		}
    +		return obj, true
    +	}
    +	// sel returns non-nil if n denotes a selection o.x.y that is referenced by the
    +	// span and defined either within the span or in the lexical environment. The bool
    +	// return value acts as an indicator for where it was defined.
    +	var sel func(n *ast.SelectorExpr) (types.Object, bool)
    +	sel = func(n *ast.SelectorExpr) (types.Object, bool) {
    +		switch x := astutil.Unparen(n.X).(type) {
    +		case *ast.SelectorExpr:
    +			return sel(x)
    +		case *ast.Ident:
    +			return id(x)
    +		}
    +		return nil, false
    +	}
    +	seen := make(map[types.Object]*variable)
    +	firstUseIn := make(map[types.Object]token.Pos)
    +	var vars []types.Object
    +	ast.Inspect(node, func(n ast.Node) bool {
    +		if n == nil {
    +			return false
    +		}
    +		if start <= n.Pos() && n.End() <= end {
    +			var obj types.Object
    +			var isFree, prune bool
    +			switch n := n.(type) {
    +			case *ast.Ident:
    +				obj, isFree = id(n)
    +			case *ast.SelectorExpr:
    +				obj, isFree = sel(n)
    +				prune = true
    +			}
    +			if obj != nil {
    +				seen[obj] = &variable{
    +					obj:  obj,
    +					free: isFree,
    +				}
    +				vars = append(vars, obj)
    +				// Find the first time that the object is used in the selection.
    +				first, ok := firstUseIn[obj]
    +				if !ok || n.Pos() < first {
    +					firstUseIn[obj] = n.Pos()
    +				}
    +				if prune {
    +					return false
    +				}
    +			}
    +		}
    +		return n.Pos() <= end
    +	})
    +
    +	// Find identifiers that are initialized or whose values are altered at some
    +	// point in the selected block. For example, in a selected block from lines 2-4,
    +	// variables x, y, and z are included in assigned. However, in a selected block
    +	// from lines 3-4, only variables y and z are included in assigned.
    +	//
    +	// 1: var a int
    +	// 2: var x int
    +	// 3: y := 3
    +	// 4: z := x + a
    +	//
    +	ast.Inspect(node, func(n ast.Node) bool {
    +		if n == nil {
    +			return false
    +		}
    +		if n.Pos() < start || n.End() > end {
    +			return n.Pos() <= end
    +		}
    +		switch n := n.(type) {
    +		case *ast.AssignStmt:
    +			for _, assignment := range n.Lhs {
    +				lhs, ok := assignment.(*ast.Ident)
    +				if !ok {
    +					continue
    +				}
    +				obj, _ := id(lhs)
    +				if obj == nil {
    +					continue
    +				}
    +				if _, ok := seen[obj]; !ok {
    +					continue
    +				}
    +				seen[obj].assigned = true
    +				if n.Tok != token.DEFINE {
    +					continue
    +				}
    +				// Find identifiers that are defined prior to being used
    +				// elsewhere in the selection.
    +				// TODO: Include identifiers that are assigned prior to being
    +				// used elsewhere in the selection. Then, change the assignment
    +				// to a definition in the extracted function.
    +				if firstUseIn[obj] != lhs.Pos() {
    +					continue
    +				}
    +				// Ensure that the object is not used in its own re-definition.
    +				// For example:
    +				// var f float64
    +				// f, e := math.Frexp(f)
    +				for _, expr := range n.Rhs {
    +					if referencesObj(info, expr, obj) {
    +						continue
    +					}
    +					if _, ok := seen[obj]; !ok {
    +						continue
    +					}
    +					seen[obj].defined = true
    +					break
    +				}
    +			}
    +			return false
    +		case *ast.DeclStmt:
    +			gen, ok := n.Decl.(*ast.GenDecl)
    +			if !ok {
    +				return false
    +			}
    +			for _, spec := range gen.Specs {
    +				vSpecs, ok := spec.(*ast.ValueSpec)
    +				if !ok {
    +					continue
    +				}
    +				for _, vSpec := range vSpecs.Names {
    +					obj, _ := id(vSpec)
    +					if obj == nil {
    +						continue
    +					}
    +					if _, ok := seen[obj]; !ok {
    +						continue
    +					}
    +					seen[obj].assigned = true
    +				}
    +			}
    +			return false
    +		case *ast.IncDecStmt:
    +			if ident, ok := n.X.(*ast.Ident); !ok {
    +				return false
    +			} else if obj, _ := id(ident); obj == nil {
    +				return false
    +			} else {
    +				if _, ok := seen[obj]; !ok {
    +					return false
    +				}
    +				seen[obj].assigned = true
    +			}
    +		}
    +		return true
    +	})
    +	var variables []*variable
    +	for _, obj := range vars {
    +		v, ok := seen[obj]
    +		if !ok {
    +			return nil, fmt.Errorf("no seen types.Object for %v", obj)
    +		}
    +		variables = append(variables, v)
    +	}
    +	return variables, nil
    +}
    +
    +// referencesObj checks whether the given object appears in the given expression.
    +func referencesObj(info *types.Info, expr ast.Expr, obj types.Object) bool {
    +	var hasObj bool
    +	ast.Inspect(expr, func(n ast.Node) bool {
    +		if n == nil {
    +			return false
    +		}
    +		ident, ok := n.(*ast.Ident)
    +		if !ok {
    +			return true
    +		}
    +		objUse := info.Uses[ident]
    +		if obj == objUse {
    +			hasObj = true
    +			return false
    +		}
    +		return false
    +	})
    +	return hasObj
    +}
    +
    +type fnExtractParams struct {
    +	tok        *token.File
    +	start, end token.Pos
    +	path       []ast.Node
    +	outer      *ast.FuncDecl
    +	node       ast.Node
    +}
    +
    +// canExtractFunction reports whether the code in the given range can be
    +// extracted to a function.
    +func canExtractFunction(tok *token.File, start, end token.Pos, src []byte, file *ast.File) (*fnExtractParams, bool, bool, error) {
    +	if start == end {
    +		return nil, false, false, fmt.Errorf("start and end are equal")
    +	}
    +	var err error
    +	start, end, err = adjustRangeForCommentsAndWhiteSpace(tok, start, end, src, file)
    +	if err != nil {
    +		return nil, false, false, err
    +	}
    +	path, _ := astutil.PathEnclosingInterval(file, start, end)
    +	if len(path) == 0 {
    +		return nil, false, false, fmt.Errorf("no path enclosing interval")
    +	}
    +	// Node that encloses the selection must be a statement.
    +	// TODO: Support function extraction for an expression.
    +	_, ok := path[0].(ast.Stmt)
    +	if !ok {
    +		return nil, false, false, fmt.Errorf("node is not a statement")
    +	}
    +
    +	// Find the function declaration that encloses the selection.
    +	var outer *ast.FuncDecl
    +	for _, p := range path {
    +		if p, ok := p.(*ast.FuncDecl); ok {
    +			outer = p
    +			break
    +		}
    +	}
    +	if outer == nil {
    +		return nil, false, false, fmt.Errorf("no enclosing function")
    +	}
    +
    +	// Find the nodes at the start and end of the selection.
    +	var startNode, endNode ast.Node
    +	ast.Inspect(outer, func(n ast.Node) bool {
    +		if n == nil {
    +			return false
    +		}
    +		// Do not override 'start' with a node that begins at the same location
    +		// but is nested further from 'outer'.
    +		if startNode == nil && n.Pos() == start && n.End() <= end {
    +			startNode = n
    +		}
    +		if endNode == nil && n.End() == end && n.Pos() >= start {
    +			endNode = n
    +		}
    +		return n.Pos() <= end
    +	})
    +	if startNode == nil || endNode == nil {
    +		return nil, false, false, fmt.Errorf("range does not map to AST nodes")
    +	}
    +	// If the region is a blockStmt, use the first and last nodes in the block
    +	// statement.
    +	// { ... } => { ... }
    +	if blockStmt, ok := startNode.(*ast.BlockStmt); ok {
    +		if len(blockStmt.List) == 0 {
    +			return nil, false, false, fmt.Errorf("range maps to empty block statement")
    +		}
    +		startNode, endNode = blockStmt.List[0], blockStmt.List[len(blockStmt.List)-1]
    +		start, end = startNode.Pos(), endNode.End()
    +	}
    +	return &fnExtractParams{
    +		tok:   tok,
    +		start: start,
    +		end:   end,
    +		path:  path,
    +		outer: outer,
    +		node:  startNode,
    +	}, true, outer.Recv != nil, nil
    +}
    +
    +// objUsed checks if the object is used within the range. It returns the first
    +// occurrence of the object in the range, if it exists.
    +func objUsed(info *types.Info, start, end token.Pos, obj types.Object) (bool, *ast.Ident) {
    +	var firstUse *ast.Ident
    +	for id, objUse := range info.Uses {
    +		if obj != objUse {
    +			continue
    +		}
    +		if id.Pos() < start || id.End() > end {
    +			continue
    +		}
    +		if firstUse == nil || id.Pos() < firstUse.Pos() {
    +			firstUse = id
    +		}
    +	}
    +	return firstUse != nil, firstUse
    +}
    +
    +// varOverridden traverses the given AST node until we find the given identifier. Then, we
    +// examine the occurrence of the given identifier and check for (1) whether the identifier
    +// is being redefined. If the identifier is free, we also check for (2) whether the identifier
    +// is being reassigned. We will not include an identifier in the return statement of the
    +// extracted function if it meets one of the above conditions.
    +func varOverridden(info *types.Info, firstUse *ast.Ident, obj types.Object, isFree bool, node ast.Node) bool {
    +	var isOverriden bool
    +	ast.Inspect(node, func(n ast.Node) bool {
    +		if n == nil {
    +			return false
    +		}
    +		assignment, ok := n.(*ast.AssignStmt)
    +		if !ok {
    +			return true
    +		}
    +		// A free variable is initialized prior to the selection. We can always reassign
    +		// this variable after the selection because it has already been defined.
    +		// Conversely, a non-free variable is initialized within the selection. Thus, we
    +		// cannot reassign this variable after the selection unless it is initialized and
    +		// returned by the extracted function.
    +		if !isFree && assignment.Tok == token.ASSIGN {
    +			return false
    +		}
    +		for _, assigned := range assignment.Lhs {
    +			ident, ok := assigned.(*ast.Ident)
    +			// Check if we found the first use of the identifier.
    +			if !ok || ident != firstUse {
    +				continue
    +			}
    +			objUse := info.Uses[ident]
    +			if objUse == nil || objUse != obj {
    +				continue
    +			}
    +			// Ensure that the object is not used in its own definition.
    +			// For example:
    +			// var f float64
    +			// f, e := math.Frexp(f)
    +			for _, expr := range assignment.Rhs {
    +				if referencesObj(info, expr, obj) {
    +					return false
    +				}
    +			}
    +			isOverriden = true
    +			return false
    +		}
    +		return false
    +	})
    +	return isOverriden
    +}
    +
    +// parseBlockStmt generates an AST file from the given text. We then return the portion of the
    +// file that represents the text.
    +func parseBlockStmt(fset *token.FileSet, src []byte) (*ast.BlockStmt, error) {
    +	text := "package main\nfunc _() { " + string(src) + " }"
    +	extract, err := parser.ParseFile(fset, "", text, 0)
    +	if err != nil {
    +		return nil, err
    +	}
    +	if len(extract.Decls) == 0 {
    +		return nil, fmt.Errorf("parsed file does not contain any declarations")
    +	}
    +	decl, ok := extract.Decls[0].(*ast.FuncDecl)
    +	if !ok {
    +		return nil, fmt.Errorf("parsed file does not contain expected function declaration")
    +	}
    +	if decl.Body == nil {
    +		return nil, fmt.Errorf("extracted function has no body")
    +	}
    +	return decl.Body, nil
    +}
    +
    +// generateReturnInfo generates the information we need to adjust the return statements and
    +// signature of the extracted function. We prepare names, signatures, and "zero values" that
    +// represent the new variables. We also use this information to construct the if statement that
    +// is inserted below the call to the extracted function.
    +func generateReturnInfo(enclosing *ast.FuncType, pkg *types.Package, path []ast.Node, file *ast.File, info *types.Info, pos token.Pos, hasNonNestedReturns bool) ([]*returnVariable, *ast.IfStmt, error) {
    +	var retVars []*returnVariable
    +	var cond *ast.Ident
    +	if !hasNonNestedReturns {
    +		// Generate information for the added bool value.
    +		name, _ := generateAvailableIdentifier(pos, path, pkg, info, "shouldReturn", 0)
    +		cond = &ast.Ident{Name: name}
    +		retVars = append(retVars, &returnVariable{
    +			name:    cond,
    +			decl:    &ast.Field{Type: ast.NewIdent("bool")},
    +			zeroVal: ast.NewIdent("false"),
    +		})
    +	}
    +	// Generate information for the values in the return signature of the enclosing function.
    +	if enclosing.Results != nil {
    +		idx := 0
    +		for _, field := range enclosing.Results.List {
    +			typ := info.TypeOf(field.Type)
    +			if typ == nil {
    +				return nil, nil, fmt.Errorf(
    +					"failed type conversion, AST expression: %T", field.Type)
    +			}
    +			expr := analysisinternal.TypeExpr(file, pkg, typ)
    +			if expr == nil {
    +				return nil, nil, fmt.Errorf("nil AST expression")
    +			}
    +			var name string
    +			name, idx = generateAvailableIdentifier(pos, path, pkg, info, "returnValue", idx)
    +			retVars = append(retVars, &returnVariable{
    +				name:    ast.NewIdent(name),
    +				decl:    &ast.Field{Type: expr},
    +				zeroVal: analysisinternal.ZeroValue(file, pkg, typ),
    +			})
    +		}
    +	}
    +	var ifReturn *ast.IfStmt
    +	if !hasNonNestedReturns {
    +		// Create the return statement for the enclosing function. We must exclude the variable
    +		// for the condition of the if statement (cond) from the return statement.
    +		ifReturn = &ast.IfStmt{
    +			Cond: cond,
    +			Body: &ast.BlockStmt{
    +				List: []ast.Stmt{&ast.ReturnStmt{Results: getNames(retVars)[1:]}},
    +			},
    +		}
    +	}
    +	return retVars, ifReturn, nil
    +}
    +
    +// adjustReturnStatements adds "zero values" of the given types to each return statement
    +// in the given AST node.
    +func adjustReturnStatements(returnTypes []*ast.Field, seenVars map[types.Object]ast.Expr, file *ast.File, pkg *types.Package, extractedBlock *ast.BlockStmt) error {
    +	var zeroVals []ast.Expr
    +	// Create "zero values" for each type.
    +	for _, returnType := range returnTypes {
    +		var val ast.Expr
    +		for obj, typ := range seenVars {
    +			if typ != returnType.Type {
    +				continue
    +			}
    +			val = analysisinternal.ZeroValue(file, pkg, obj.Type())
    +			break
    +		}
    +		if val == nil {
    +			return fmt.Errorf(
    +				"could not find matching AST expression for %T", returnType.Type)
    +		}
    +		zeroVals = append(zeroVals, val)
    +	}
    +	// Add "zero values" to each return statement.
    +	// The bool reports whether the enclosing function should return after calling the
    +	// extracted function. We set the bool to 'true' because, if these return statements
    +	// execute, the extracted function terminates early, and the enclosing function must
    +	// return as well.
    +	zeroVals = append(zeroVals, ast.NewIdent("true"))
    +	ast.Inspect(extractedBlock, func(n ast.Node) bool {
    +		if n == nil {
    +			return false
    +		}
    +		if n, ok := n.(*ast.ReturnStmt); ok {
    +			n.Results = append(zeroVals, n.Results...)
    +			return false
    +		}
    +		return true
    +	})
    +	return nil
    +}
    +
    +// generateFuncCall constructs a call expression for the extracted function, described by the
    +// given parameters and return variables.
    +func generateFuncCall(hasNonNestedReturn, hasReturnVals bool, params, returns []ast.Expr, name string, token token.Token, selector string) ast.Node {
    +	var replace ast.Node
    +	callExpr := &ast.CallExpr{
    +		Fun:  ast.NewIdent(name),
    +		Args: params,
    +	}
    +	if selector != "" {
    +		callExpr = &ast.CallExpr{
    +			Fun: &ast.SelectorExpr{
    +				X:   ast.NewIdent(selector),
    +				Sel: ast.NewIdent(name),
    +			},
    +			Args: params,
    +		}
    +	}
    +	if hasReturnVals {
    +		if hasNonNestedReturn {
    +			// Create a return statement that returns the result of the function call.
    +			replace = &ast.ReturnStmt{
    +				Return:  0,
    +				Results: []ast.Expr{callExpr},
    +			}
    +		} else {
    +			// Assign the result of the function call.
    +			replace = &ast.AssignStmt{
    +				Lhs: returns,
    +				Tok: token,
    +				Rhs: []ast.Expr{callExpr},
    +			}
    +		}
    +	} else {
    +		replace = callExpr
    +	}
    +	return replace
    +}
    +
    +// initializeVars creates variable declarations, if needed.
    +// Our preference is to replace the selected block with an "x, y, z := fn()" style
    +// assignment statement. We can use this style when all of the variables in the
    +// extracted function's return statement are either not defined prior to the extracted block
    +// or can be safely redefined. However, for example, if z is already defined
    +// in a different scope, we replace the selected block with:
    +//
    +// var x int
    +// var y string
    +// x, y, z = fn()
    +func initializeVars(uninitialized []types.Object, retVars []*returnVariable, seenUninitialized map[types.Object]struct{}, seenVars map[types.Object]ast.Expr) []ast.Stmt {
    +	var declarations []ast.Stmt
    +	for _, obj := range uninitialized {
    +		if _, ok := seenUninitialized[obj]; ok {
    +			continue
    +		}
    +		seenUninitialized[obj] = struct{}{}
    +		valSpec := &ast.ValueSpec{
    +			Names: []*ast.Ident{ast.NewIdent(obj.Name())},
    +			Type:  seenVars[obj],
    +		}
    +		genDecl := &ast.GenDecl{
    +			Tok:   token.VAR,
    +			Specs: []ast.Spec{valSpec},
    +		}
    +		declarations = append(declarations, &ast.DeclStmt{Decl: genDecl})
    +	}
    +	// Each variable added from a return statement in the selection
    +	// must be initialized.
    +	for i, retVar := range retVars {
    +		n := retVar.name.(*ast.Ident)
    +		valSpec := &ast.ValueSpec{
    +			Names: []*ast.Ident{n},
    +			Type:  retVars[i].decl.Type,
    +		}
    +		genDecl := &ast.GenDecl{
    +			Tok:   token.VAR,
    +			Specs: []ast.Spec{valSpec},
    +		}
    +		declarations = append(declarations, &ast.DeclStmt{Decl: genDecl})
    +	}
    +	return declarations
    +}
    +
    +// getNames returns the names from the given list of returnVariable.
    +func getNames(retVars []*returnVariable) []ast.Expr {
    +	var names []ast.Expr
    +	for _, retVar := range retVars {
    +		names = append(names, retVar.name)
    +	}
    +	return names
    +}
    +
    +// getZeroVals returns the "zero values" from the given list of returnVariable.
    +func getZeroVals(retVars []*returnVariable) []ast.Expr {
    +	var zvs []ast.Expr
    +	for _, retVar := range retVars {
    +		zvs = append(zvs, retVar.zeroVal)
    +	}
    +	return zvs
    +}
    +
    +// getDecls returns the declarations from the given list of returnVariable.
    +func getDecls(retVars []*returnVariable) []*ast.Field {
    +	var decls []*ast.Field
    +	for _, retVar := range retVars {
    +		decls = append(decls, retVar.decl)
    +	}
    +	return decls
    +}
    diff --git a/contribs/gnopls/internal/golang/extracttofile.go b/contribs/gnopls/internal/golang/extracttofile.go
    new file mode 100644
    index 00000000000..0a1d74408d7
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/extracttofile.go
    @@ -0,0 +1,301 @@
    +// 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 golang
    +
    +// This file defines the code action "Extract declarations to new file".
    +
    +import (
    +	"bytes"
    +	"context"
    +	"errors"
    +	"fmt"
    +	"go/ast"
    +	"go/format"
    +	"go/token"
    +	"go/types"
    +	"os"
    +	"path/filepath"
    +	"strings"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +)
    +
    +// canExtractToNewFile reports whether the code in the given range can be extracted to a new file.
    +func canExtractToNewFile(pgf *parsego.File, start, end token.Pos) bool {
    +	_, _, _, ok := selectedToplevelDecls(pgf, start, end)
    +	return ok
    +}
    +
    +// findImportEdits finds imports specs that needs to be added to the new file
    +// or deleted from the old file if the range is extracted to a new file.
    +//
    +// TODO: handle dot imports.
    +func findImportEdits(file *ast.File, info *types.Info, start, end token.Pos) (adds, deletes []*ast.ImportSpec, _ error) {
    +	// make a map from a pkgName to its references
    +	pkgNameReferences := make(map[*types.PkgName][]*ast.Ident)
    +	for ident, use := range info.Uses {
    +		if pkgName, ok := use.(*types.PkgName); ok {
    +			pkgNameReferences[pkgName] = append(pkgNameReferences[pkgName], ident)
    +		}
    +	}
    +
    +	// PkgName referenced in the extracted selection must be
    +	// imported in the new file.
    +	// PkgName only referenced in the extracted selection must be
    +	// deleted from the original file.
    +	for _, spec := range file.Imports {
    +		if spec.Name != nil && spec.Name.Name == "." {
    +			// TODO: support dot imports.
    +			return nil, nil, errors.New("\"extract to new file\" does not support files containing dot imports")
    +		}
    +		pkgName := info.PkgNameOf(spec)
    +		if pkgName == nil {
    +			continue
    +		}
    +		usedInSelection := false
    +		usedInNonSelection := false
    +		for _, ident := range pkgNameReferences[pkgName] {
    +			if posRangeContains(start, end, ident.Pos(), ident.End()) {
    +				usedInSelection = true
    +			} else {
    +				usedInNonSelection = true
    +			}
    +		}
    +		if usedInSelection {
    +			adds = append(adds, spec)
    +		}
    +		if usedInSelection && !usedInNonSelection {
    +			deletes = append(deletes, spec)
    +		}
    +	}
    +
    +	return adds, deletes, nil
    +}
    +
    +// ExtractToNewFile moves selected declarations into a new file.
    +func ExtractToNewFile(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) (*protocol.WorkspaceEdit, error) {
    +	errorPrefix := "ExtractToNewFile"
    +
    +	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	start, end, err := pgf.RangePos(rng)
    +	if err != nil {
    +		return nil, fmt.Errorf("%s: %w", errorPrefix, err)
    +	}
    +
    +	start, end, firstSymbol, ok := selectedToplevelDecls(pgf, start, end)
    +	if !ok {
    +		return nil, bug.Errorf("invalid selection")
    +	}
    +
    +	// select trailing empty lines
    +	offset, err := safetoken.Offset(pgf.Tok, end)
    +	if err != nil {
    +		return nil, err
    +	}
    +	rest := pgf.Src[offset:]
    +	end += token.Pos(len(rest) - len(bytes.TrimLeft(rest, " \t\n")))
    +
    +	replaceRange, err := pgf.PosRange(start, end)
    +	if err != nil {
    +		return nil, bug.Errorf("invalid range: %v", err)
    +	}
    +
    +	adds, deletes, err := findImportEdits(pgf.File, pkg.TypesInfo(), start, end)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	var importDeletes []protocol.TextEdit
    +	// For unparenthesised declarations like `import "fmt"` we remove
    +	// the whole declaration because simply removing importSpec leaves
    +	// `import \n`, which does not compile.
    +	// For parenthesised declarations like `import ("fmt"\n "log")`
    +	// we only remove the ImportSpec, because removing the whole declaration
    +	// might remove other ImportsSpecs we don't want to touch.
    +	unparenthesizedImports := unparenthesizedImports(pgf)
    +	for _, importSpec := range deletes {
    +		if decl := unparenthesizedImports[importSpec]; decl != nil {
    +			importDeletes = append(importDeletes, removeNode(pgf, decl))
    +		} else {
    +			importDeletes = append(importDeletes, removeNode(pgf, importSpec))
    +		}
    +	}
    +
    +	var buf bytes.Buffer
    +	fmt.Fprintf(&buf, "package %s\n", pgf.File.Name.Name)
    +	if len(adds) > 0 {
    +		buf.WriteString("import (")
    +		for _, importSpec := range adds {
    +			if importSpec.Name != nil {
    +				fmt.Fprintf(&buf, "%s %s\n", importSpec.Name.Name, importSpec.Path.Value)
    +			} else {
    +				fmt.Fprintf(&buf, "%s\n", importSpec.Path.Value)
    +			}
    +		}
    +		buf.WriteString(")\n")
    +	}
    +
    +	newFile, err := chooseNewFile(ctx, snapshot, pgf.URI.Dir().Path(), firstSymbol)
    +	if err != nil {
    +		return nil, fmt.Errorf("%s: %w", errorPrefix, err)
    +	}
    +
    +	fileStart := pgf.File.FileStart
    +	buf.Write(pgf.Src[start-fileStart : end-fileStart])
    +
    +	// TODO: attempt to duplicate the copyright header, if any.
    +	newFileContent, err := format.Source(buf.Bytes())
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	return protocol.NewWorkspaceEdit(
    +		// edit the original file
    +		protocol.DocumentChangeEdit(fh, append(importDeletes, protocol.TextEdit{Range: replaceRange, NewText: ""})),
    +		// create a new file
    +		protocol.DocumentChangeCreate(newFile.URI()),
    +		// edit the created file
    +		protocol.DocumentChangeEdit(newFile, []protocol.TextEdit{
    +			{Range: protocol.Range{}, NewText: string(newFileContent)},
    +		})), nil
    +}
    +
    +// chooseNewFile chooses a new filename in dir, based on the name of the
    +// first extracted symbol, and if necessary to disambiguate, a numeric suffix.
    +func chooseNewFile(ctx context.Context, snapshot *cache.Snapshot, dir string, firstSymbol string) (file.Handle, error) {
    +	basename := strings.ToLower(firstSymbol)
    +	newPath := protocol.URIFromPath(filepath.Join(dir, basename+".go"))
    +	for count := 1; count < 5; count++ {
    +		fh, err := snapshot.ReadFile(ctx, newPath)
    +		if err != nil {
    +			return nil, err // canceled
    +		}
    +		if _, err := fh.Content(); errors.Is(err, os.ErrNotExist) {
    +			return fh, nil
    +		}
    +		filename := fmt.Sprintf("%s.%d.go", basename, count)
    +		newPath = protocol.URIFromPath(filepath.Join(dir, filename))
    +	}
    +	return nil, fmt.Errorf("chooseNewFileURI: exceeded retry limit")
    +}
    +
    +// selectedToplevelDecls returns the lexical extent of the top-level
    +// declarations enclosed by [start, end), along with the name of the
    +// first declaration. The returned boolean reports whether the selection
    +// should be offered a code action to extract the declarations.
    +func selectedToplevelDecls(pgf *parsego.File, start, end token.Pos) (token.Pos, token.Pos, string, bool) {
    +	// selection cannot intersect a package declaration
    +	if posRangeIntersects(start, end, pgf.File.Package, pgf.File.Name.End()) {
    +		return 0, 0, "", false
    +	}
    +	firstName := ""
    +	for _, decl := range pgf.File.Decls {
    +		if posRangeIntersects(start, end, decl.Pos(), decl.End()) {
    +			var id *ast.Ident
    +			switch v := decl.(type) {
    +			case *ast.BadDecl:
    +				return 0, 0, "", false
    +			case *ast.FuncDecl:
    +				// if only selecting keyword "func" or function name, extend selection to the
    +				// whole function
    +				if posRangeContains(v.Pos(), v.Name.End(), start, end) {
    +					start, end = v.Pos(), v.End()
    +				}
    +				id = v.Name
    +			case *ast.GenDecl:
    +				// selection cannot intersect an import declaration
    +				if v.Tok == token.IMPORT {
    +					return 0, 0, "", false
    +				}
    +				// if only selecting keyword "type", "const", or "var", extend selection to the
    +				// whole declaration
    +				if v.Tok == token.TYPE && posRangeContains(v.Pos(), v.Pos()+token.Pos(len("type")), start, end) ||
    +					v.Tok == token.CONST && posRangeContains(v.Pos(), v.Pos()+token.Pos(len("const")), start, end) ||
    +					v.Tok == token.VAR && posRangeContains(v.Pos(), v.Pos()+token.Pos(len("var")), start, end) {
    +					start, end = v.Pos(), v.End()
    +				}
    +				if len(v.Specs) > 0 {
    +					switch spec := v.Specs[0].(type) {
    +					case *ast.TypeSpec:
    +						id = spec.Name
    +					case *ast.ValueSpec:
    +						id = spec.Names[0]
    +					}
    +				}
    +			}
    +			// selection cannot partially intersect a node
    +			if !posRangeContains(start, end, decl.Pos(), decl.End()) {
    +				return 0, 0, "", false
    +			}
    +			if id != nil && firstName == "" {
    +				// may be "_"
    +				firstName = id.Name
    +			}
    +			// extends selection to docs comments
    +			var c *ast.CommentGroup
    +			switch decl := decl.(type) {
    +			case *ast.GenDecl:
    +				c = decl.Doc
    +			case *ast.FuncDecl:
    +				c = decl.Doc
    +			}
    +			if c != nil && c.Pos() < start {
    +				start = c.Pos()
    +			}
    +		}
    +	}
    +	for _, comment := range pgf.File.Comments {
    +		if posRangeIntersects(start, end, comment.Pos(), comment.End()) {
    +			if !posRangeContains(start, end, comment.Pos(), comment.End()) {
    +				// selection cannot partially intersect a comment
    +				return 0, 0, "", false
    +			}
    +		}
    +	}
    +	if firstName == "" {
    +		return 0, 0, "", false
    +	}
    +	return start, end, firstName, true
    +}
    +
    +// unparenthesizedImports returns a map from each unparenthesized ImportSpec
    +// to its enclosing declaration (which may need to be deleted too).
    +func unparenthesizedImports(pgf *parsego.File) map[*ast.ImportSpec]*ast.GenDecl {
    +	decls := make(map[*ast.ImportSpec]*ast.GenDecl)
    +	for _, decl := range pgf.File.Decls {
    +		if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT && !decl.Lparen.IsValid() {
    +			decls[decl.Specs[0].(*ast.ImportSpec)] = decl
    +		}
    +	}
    +	return decls
    +}
    +
    +// removeNode returns a TextEdit that removes the node.
    +func removeNode(pgf *parsego.File, node ast.Node) protocol.TextEdit {
    +	rng, err := pgf.NodeRange(node)
    +	if err != nil {
    +		bug.Reportf("removeNode: %v", err)
    +	}
    +	return protocol.TextEdit{Range: rng, NewText: ""}
    +}
    +
    +// posRangeIntersects checks if [a, b) and [c, d) intersects, assuming a <= b and c <= d.
    +func posRangeIntersects(a, b, c, d token.Pos) bool {
    +	return !(b <= c || d <= a)
    +}
    +
    +// posRangeContains checks if [a, b) contains [c, d), assuming a <= b and c <= d.
    +func posRangeContains(a, b, c, d token.Pos) bool {
    +	return a <= c && d <= b
    +}
    diff --git a/contribs/gnopls/internal/golang/fix.go b/contribs/gnopls/internal/golang/fix.go
    new file mode 100644
    index 00000000000..3844fc0d65c
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/fix.go
    @@ -0,0 +1,217 @@
    +// 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 golang
    +
    +import (
    +	"context"
    +	"fmt"
    +	"go/ast"
    +	"go/token"
    +	"go/types"
    +
    +	"golang.org/x/tools/go/analysis"
    +	"golang.org/x/tools/gopls/internal/analysis/embeddirective"
    +	"golang.org/x/tools/gopls/internal/analysis/fillstruct"
    +	"golang.org/x/tools/gopls/internal/analysis/stubmethods"
    +	"golang.org/x/tools/gopls/internal/analysis/undeclaredname"
    +	"golang.org/x/tools/gopls/internal/analysis/unusedparams"
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/internal/imports"
    +)
    +
    +// A fixer is a function that suggests a fix for a diagnostic produced
    +// by the analysis framework. This is done outside of the analyzer Run
    +// function so that the construction of expensive fixes can be
    +// deferred until they are requested by the user.
    +//
    +// The actual diagnostic is not provided; only its position, as the
    +// triple (pgf, start, end); the resulting SuggestedFix implicitly
    +// relates to that file.
    +//
    +// The supplied token positions (start, end) must belong to
    +// pkg.FileSet(), and the returned positions
    +// (SuggestedFix.TextEdits[*].{Pos,End}) must belong to the returned
    +// FileSet.
    +//
    +// A fixer may return (nil, nil) if no fix is available.
    +type fixer func(ctx context.Context, s *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error)
    +
    +// A singleFileFixer is a Fixer that inspects only a single file,
    +// and does not depend on data types from the cache package.
    +//
    +// TODO(adonovan): move fillstruct and undeclaredname into this
    +// package, so we can remove the import restriction and push
    +// the singleFile wrapper down into each singleFileFixer?
    +type singleFileFixer func(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error)
    +
    +// singleFile adapts a single-file fixer to a Fixer.
    +func singleFile(fixer1 singleFileFixer) fixer {
    +	return func(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
    +		return fixer1(pkg.FileSet(), start, end, pgf.Src, pgf.File, pkg.Types(), pkg.TypesInfo())
    +	}
    +}
    +
    +// Names of ApplyFix.Fix created directly by the CodeAction handler.
    +const (
    +	fixExtractVariable   = "extract_variable"
    +	fixExtractFunction   = "extract_function"
    +	fixExtractMethod     = "extract_method"
    +	fixInlineCall        = "inline_call"
    +	fixInvertIfCondition = "invert_if_condition"
    +	fixSplitLines        = "split_lines"
    +	fixJoinLines         = "join_lines"
    +)
    +
    +// ApplyFix applies the specified kind of suggested fix to the given
    +// file and range, returning the resulting changes.
    +//
    +// A fix kind is either the Category of an analysis.Diagnostic that
    +// had a SuggestedFix with no edits; or the name of a fix agreed upon
    +// by [CodeActions] and this function.
    +// Fix kinds identify fixes in the command protocol.
    +//
    +// TODO(adonovan): come up with a better mechanism for registering the
    +// connection between analyzers, code actions, and fixers. A flaw of
    +// the current approach is that the same Category could in theory
    +// apply to a Diagnostic with several lazy fixes, making them
    +// impossible to distinguish. It would more precise if there was a
    +// SuggestedFix.Category field, or some other way to squirrel metadata
    +// in the fix.
    +func ApplyFix(ctx context.Context, fix string, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.DocumentChange, error) {
    +	// This can't be expressed as an entry in the fixer table below
    +	// because it operates in the protocol (not go/{token,ast}) domain.
    +	// (Sigh; perhaps it was a mistake to factor out the
    +	// NarrowestPackageForFile/RangePos/suggestedFixToEdits
    +	// steps.)
    +	if fix == unusedparams.FixCategory {
    +		return RemoveUnusedParameter(ctx, fh, rng, snapshot)
    +	}
    +
    +	fixers := map[string]fixer{
    +		// Fixes for analyzer-provided diagnostics.
    +		// These match the Diagnostic.Category.
    +		embeddirective.FixCategory: addEmbedImport,
    +		fillstruct.FixCategory:     singleFile(fillstruct.SuggestedFix),
    +		stubmethods.FixCategory:    stubMethodsFixer,
    +		undeclaredname.FixCategory: singleFile(undeclaredname.SuggestedFix),
    +
    +		// Ad-hoc fixers: these are used when the command is
    +		// constructed directly by logic in server/code_action.
    +		fixExtractFunction:   singleFile(extractFunction),
    +		fixExtractMethod:     singleFile(extractMethod),
    +		fixExtractVariable:   singleFile(extractVariable),
    +		fixInlineCall:        inlineCall,
    +		fixInvertIfCondition: singleFile(invertIfCondition),
    +		fixSplitLines:        singleFile(splitLines),
    +		fixJoinLines:         singleFile(joinLines),
    +	}
    +	fixer, ok := fixers[fix]
    +	if !ok {
    +		return nil, fmt.Errorf("no suggested fix function for %s", fix)
    +	}
    +	pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
    +	if err != nil {
    +		return nil, err
    +	}
    +	start, end, err := pgf.RangePos(rng)
    +	if err != nil {
    +		return nil, err
    +	}
    +	fixFset, suggestion, err := fixer(ctx, snapshot, pkg, pgf, start, end)
    +	if err != nil {
    +		return nil, err
    +	}
    +	if suggestion == nil {
    +		return nil, nil
    +	}
    +	return suggestedFixToDocumentChange(ctx, snapshot, fixFset, suggestion)
    +}
    +
    +// suggestedFixToDocumentChange converts the suggestion's edits from analysis form into protocol form.
    +func suggestedFixToDocumentChange(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, suggestion *analysis.SuggestedFix) ([]protocol.DocumentChange, error) {
    +	type fileInfo struct {
    +		fh     file.Handle
    +		mapper *protocol.Mapper
    +		edits  []protocol.TextEdit
    +	}
    +	files := make(map[protocol.DocumentURI]*fileInfo)
    +	for _, edit := range suggestion.TextEdits {
    +		tokFile := fset.File(edit.Pos)
    +		if tokFile == nil {
    +			return nil, bug.Errorf("no file for edit position")
    +		}
    +		end := edit.End
    +		if !end.IsValid() {
    +			end = edit.Pos
    +		}
    +		uri := protocol.URIFromPath(tokFile.Name())
    +		info, ok := files[uri]
    +		if !ok {
    +			// First edit: create a mapper.
    +			fh, err := snapshot.ReadFile(ctx, uri)
    +			if err != nil {
    +				return nil, err
    +			}
    +			content, err := fh.Content()
    +			if err != nil {
    +				return nil, err
    +			}
    +			mapper := protocol.NewMapper(uri, content)
    +			info = &fileInfo{fh, mapper, nil}
    +			files[uri] = info
    +		}
    +		rng, err := info.mapper.PosRange(tokFile, edit.Pos, end)
    +		if err != nil {
    +			return nil, err
    +		}
    +		info.edits = append(info.edits, protocol.TextEdit{
    +			Range:   rng,
    +			NewText: string(edit.NewText),
    +		})
    +	}
    +	var changes []protocol.DocumentChange
    +	for _, info := range files {
    +		change := protocol.DocumentChangeEdit(info.fh, info.edits)
    +		changes = append(changes, change)
    +	}
    +	return changes, nil
    +}
    +
    +// addEmbedImport adds a missing embed "embed" import with blank name.
    +func addEmbedImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, _, _ token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
    +	// Like golang.AddImport, but with _ as Name and using our pgf.
    +	protoEdits, err := ComputeOneImportFixEdits(snapshot, pgf, &imports.ImportFix{
    +		StmtInfo: imports.ImportInfo{
    +			ImportPath: "embed",
    +			Name:       "_",
    +		},
    +		FixType: imports.AddImport,
    +	})
    +	if err != nil {
    +		return nil, nil, fmt.Errorf("compute edits: %w", err)
    +	}
    +
    +	var edits []analysis.TextEdit
    +	for _, e := range protoEdits {
    +		start, end, err := pgf.RangePos(e.Range)
    +		if err != nil {
    +			return nil, nil, err // e.g. invalid range
    +		}
    +		edits = append(edits, analysis.TextEdit{
    +			Pos:     start,
    +			End:     end,
    +			NewText: []byte(e.NewText),
    +		})
    +	}
    +
    +	return pkg.FileSet(), &analysis.SuggestedFix{
    +		Message:   "Add embed import",
    +		TextEdits: edits,
    +	}, nil
    +}
    diff --git a/contribs/gnopls/internal/golang/folding_range.go b/contribs/gnopls/internal/golang/folding_range.go
    new file mode 100644
    index 00000000000..85faea5e31a
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/folding_range.go
    @@ -0,0 +1,197 @@
    +// 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.
    +
    +package golang
    +
    +import (
    +	"context"
    +	"go/ast"
    +	"go/token"
    +	"sort"
    +	"strings"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/bug"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +)
    +
    +// FoldingRangeInfo holds range and kind info of folding for an ast.Node
    +type FoldingRangeInfo struct {
    +	MappedRange protocol.MappedRange
    +	Kind        protocol.FoldingRangeKind
    +}
    +
    +// FoldingRange gets all of the folding range for f.
    +func FoldingRange(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, lineFoldingOnly bool) (ranges []*FoldingRangeInfo, err error) {
    +	// TODO(suzmue): consider limiting the number of folding ranges returned, and
    +	// implement a way to prioritize folding ranges in that case.
    +	pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	// With parse errors, we wouldn't be able to produce accurate folding info.
    +	// LSP protocol (3.16) currently does not have a way to handle this case
    +	// (https://github.com/microsoft/language-server-protocol/issues/1200).
    +	// We cannot return an error either because we are afraid some editors
    +	// may not handle errors nicely. As a workaround, we now return an empty
    +	// result and let the client handle this case by double check the file
    +	// contents (i.e. if the file is not empty and the folding range result
    +	// is empty, raise an internal error).
    +	if pgf.ParseErr != nil {
    +		return nil, nil
    +	}
    +
    +	// Get folding ranges for comments separately as they are not walked by ast.Inspect.
    +	ranges = append(ranges, commentsFoldingRange(pgf)...)
    +
    +	visit := func(n ast.Node) bool {
    +		rng := foldingRangeFunc(pgf, n, lineFoldingOnly)
    +		if rng != nil {
    +			ranges = append(ranges, rng)
    +		}
    +		return true
    +	}
    +	// Walk the ast and collect folding ranges.
    +	ast.Inspect(pgf.File, visit)
    +
    +	sort.Slice(ranges, func(i, j int) bool {
    +		irng := ranges[i].MappedRange.Range()
    +		jrng := ranges[j].MappedRange.Range()
    +		return protocol.CompareRange(irng, jrng) < 0
    +	})
    +
    +	return ranges, nil
    +}
    +
    +// foldingRangeFunc calculates the line folding range for ast.Node n
    +func foldingRangeFunc(pgf *parsego.File, n ast.Node, lineFoldingOnly bool) *FoldingRangeInfo {
    +	// TODO(suzmue): include trailing empty lines before the closing
    +	// parenthesis/brace.
    +	var kind protocol.FoldingRangeKind
    +	var start, end token.Pos
    +	switch n := n.(type) {
    +	case *ast.BlockStmt:
    +		// Fold between positions of or lines between "{" and "}".
    +		var startList, endList token.Pos
    +		if num := len(n.List); num != 0 {
    +			startList, endList = n.List[0].Pos(), n.List[num-1].End()
    +		}
    +		start, end = validLineFoldingRange(pgf.Tok, n.Lbrace, n.Rbrace, startList, endList, lineFoldingOnly)
    +	case *ast.CaseClause:
    +		// Fold from position of ":" to end.
    +		start, end = n.Colon+1, n.End()
    +	case *ast.CommClause:
    +		// Fold from position of ":" to end.
    +		start, end = n.Colon+1, n.End()
    +	case *ast.CallExpr:
    +		// Fold from position of "(" to position of ")".
    +		start, end = n.Lparen+1, n.Rparen
    +	case *ast.FieldList:
    +		// Fold between positions of or lines between opening parenthesis/brace and closing parenthesis/brace.
    +		var startList, endList token.Pos
    +		if num := len(n.List); num != 0 {
    +			startList, endList = n.List[0].Pos(), n.List[num-1].End()
    +		}
    +		start, end = validLineFoldingRange(pgf.Tok, n.Opening, n.Closing, startList, endList, lineFoldingOnly)
    +	case *ast.GenDecl:
    +		// If this is an import declaration, set the kind to be protocol.Imports.
    +		if n.Tok == token.IMPORT {
    +			kind = protocol.Imports
    +		}
    +		// Fold between positions of or lines between "(" and ")".
    +		var startSpecs, endSpecs token.Pos
    +		if num := len(n.Specs); num != 0 {
    +			startSpecs, endSpecs = n.Specs[0].Pos(), n.Specs[num-1].End()
    +		}
    +		start, end = validLineFoldingRange(pgf.Tok, n.Lparen, n.Rparen, startSpecs, endSpecs, lineFoldingOnly)
    +	case *ast.BasicLit:
    +		// Fold raw string literals from position of "`" to position of "`".
    +		if n.Kind == token.STRING && len(n.Value) >= 2 && n.Value[0] == '`' && n.Value[len(n.Value)-1] == '`' {
    +			start, end = n.Pos(), n.End()
    +		}
    +	case *ast.CompositeLit:
    +		// Fold between positions of or lines between "{" and "}".
    +		var startElts, endElts token.Pos
    +		if num := len(n.Elts); num != 0 {
    +			startElts, endElts = n.Elts[0].Pos(), n.Elts[num-1].End()
    +		}
    +		start, end = validLineFoldingRange(pgf.Tok, n.Lbrace, n.Rbrace, startElts, endElts, lineFoldingOnly)
    +	}
    +
    +	// Check that folding positions are valid.
    +	if !start.IsValid() || !end.IsValid() {
    +		return nil
    +	}
    +	// in line folding mode, do not fold if the start and end lines are the same.
    +	if lineFoldingOnly && safetoken.Line(pgf.Tok, start) == safetoken.Line(pgf.Tok, end) {
    +		return nil
    +	}
    +	mrng, err := pgf.PosMappedRange(start, end)
    +	if err != nil {
    +		bug.Errorf("%w", err) // can't happen
    +	}
    +	return &FoldingRangeInfo{
    +		MappedRange: mrng,
    +		Kind:        kind,
    +	}
    +}
    +
    +// validLineFoldingRange returns start and end token.Pos for folding range if the range is valid.
    +// returns token.NoPos otherwise, which fails token.IsValid check
    +func validLineFoldingRange(tokFile *token.File, open, close, start, end token.Pos, lineFoldingOnly bool) (token.Pos, token.Pos) {
    +	if lineFoldingOnly {
    +		if !open.IsValid() || !close.IsValid() {
    +			return token.NoPos, token.NoPos
    +		}
    +
    +		// Don't want to fold if the start/end is on the same line as the open/close
    +		// as an example, the example below should *not* fold:
    +		// var x = [2]string{"d",
    +		// "e" }
    +		if safetoken.Line(tokFile, open) == safetoken.Line(tokFile, start) ||
    +			safetoken.Line(tokFile, close) == safetoken.Line(tokFile, end) {
    +			return token.NoPos, token.NoPos
    +		}
    +
    +		return open + 1, end
    +	}
    +	return open + 1, close
    +}
    +
    +// commentsFoldingRange returns the folding ranges for all comment blocks in file.
    +// The folding range starts at the end of the first line of the comment block, and ends at the end of the
    +// comment block and has kind protocol.Comment.
    +func commentsFoldingRange(pgf *parsego.File) (comments []*FoldingRangeInfo) {
    +	tokFile := pgf.Tok
    +	for _, commentGrp := range pgf.File.Comments {
    +		startGrpLine, endGrpLine := safetoken.Line(tokFile, commentGrp.Pos()), safetoken.Line(tokFile, commentGrp.End())
    +		if startGrpLine == endGrpLine {
    +			// Don't fold single line comments.
    +			continue
    +		}
    +
    +		firstComment := commentGrp.List[0]
    +		startPos, endLinePos := firstComment.Pos(), firstComment.End()
    +		startCmmntLine, endCmmntLine := safetoken.Line(tokFile, startPos), safetoken.Line(tokFile, endLinePos)
    +		if startCmmntLine != endCmmntLine {
    +			// If the first comment spans multiple lines, then we want to have the
    +			// folding range start at the end of the first line.
    +			endLinePos = token.Pos(int(startPos) + len(strings.Split(firstComment.Text, "\n")[0]))
    +		}
    +		mrng, err := pgf.PosMappedRange(endLinePos, commentGrp.End())
    +		if err != nil {
    +			bug.Errorf("%w", err) // can't happen
    +		}
    +		comments = append(comments, &FoldingRangeInfo{
    +			// Fold from the end of the first line comment to the end of the comment block.
    +			MappedRange: mrng,
    +			Kind:        protocol.Comment,
    +		})
    +	}
    +	return comments
    +}
    diff --git a/contribs/gnopls/internal/golang/format.go b/contribs/gnopls/internal/golang/format.go
    new file mode 100644
    index 00000000000..8f735f38cf4
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/format.go
    @@ -0,0 +1,340 @@
    +// Copyright 2018 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 golang defines the LSP features for navigation, analysis,
    +// and refactoring of Go source code.
    +package golang
    +
    +import (
    +	"bytes"
    +	"context"
    +	"fmt"
    +	"go/ast"
    +	"go/format"
    +	"go/parser"
    +	"go/token"
    +	"strings"
    +	"text/scanner"
    +
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/file"
    +	"golang.org/x/tools/gopls/internal/protocol"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/internal/diff"
    +	"golang.org/x/tools/internal/event"
    +	"golang.org/x/tools/internal/imports"
    +	"golang.org/x/tools/internal/tokeninternal"
    +	gofumptFormat "mvdan.cc/gofumpt/format"
    +)
    +
    +// Format formats a file with a given range.
    +func Format(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.TextEdit, error) {
    +	ctx, done := event.Start(ctx, "golang.Format")
    +	defer done()
    +
    +	// Generated files shouldn't be edited. So, don't format them
    +	if IsGenerated(ctx, snapshot, fh.URI()) {
    +		return nil, fmt.Errorf("can't format %q: file is generated", fh.URI().Path())
    +	}
    +
    +	pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
    +	if err != nil {
    +		return nil, err
    +	}
    +	// Even if this file has parse errors, it might still be possible to format it.
    +	// Using format.Node on an AST with errors may result in code being modified.
    +	// Attempt to format the source of this file instead.
    +	if pgf.ParseErr != nil {
    +		formatted, err := formatSource(ctx, fh)
    +		if err != nil {
    +			return nil, err
    +		}
    +		return computeTextEdits(ctx, pgf, string(formatted))
    +	}
    +
    +	// format.Node changes slightly from one release to another, so the version
    +	// of Go used to build the LSP server will determine how it formats code.
    +	// This should be acceptable for all users, who likely be prompted to rebuild
    +	// the LSP server on each Go release.
    +	buf := &bytes.Buffer{}
    +	fset := tokeninternal.FileSetFor(pgf.Tok)
    +	if err := format.Node(buf, fset, pgf.File); err != nil {
    +		return nil, err
    +	}
    +	formatted := buf.String()
    +
    +	// Apply additional formatting, if any is supported. Currently, the only
    +	// supported additional formatter is gofumpt.
    +	if snapshot.Options().Gofumpt {
    +		// gofumpt can customize formatting based on language version and module
    +		// path, if available.
    +		//
    +		// Try to derive this information, but fall-back on the default behavior.
    +		//
    +		// TODO: under which circumstances can we fail to find module information?
    +		// Can this, for example, result in inconsistent formatting across saves,
    +		// due to pending calls to packages.Load?
    +		var opts gofumptFormat.Options
    +		meta, err := NarrowestMetadataForFile(ctx, snapshot, fh.URI())
    +		if err == nil {
    +			if mi := meta.Module; mi != nil {
    +				if v := mi.GoVersion; v != "" {
    +					opts.LangVersion = "go" + v
    +				}
    +				opts.ModulePath = mi.Path
    +			}
    +		}
    +		b, err := gofumptFormat.Source(buf.Bytes(), opts)
    +		if err != nil {
    +			return nil, err
    +		}
    +		formatted = string(b)
    +	}
    +	return computeTextEdits(ctx, pgf, formatted)
    +}
    +
    +func formatSource(ctx context.Context, fh file.Handle) ([]byte, error) {
    +	_, done := event.Start(ctx, "golang.formatSource")
    +	defer done()
    +
    +	data, err := fh.Content()
    +	if err != nil {
    +		return nil, err
    +	}
    +	return format.Source(data)
    +}
    +
    +type importFix struct {
    +	fix   *imports.ImportFix
    +	edits []protocol.TextEdit
    +}
    +
    +// allImportsFixes formats f for each possible fix to the imports.
    +// In addition to returning the result of applying all edits,
    +// it returns a list of fixes that could be applied to the file, with the
    +// corresponding TextEdits that would be needed to apply that fix.
    +func allImportsFixes(ctx context.Context, snapshot *cache.Snapshot, pgf *parsego.File) (allFixEdits []protocol.TextEdit, editsPerFix []*importFix, err error) {
    +	ctx, done := event.Start(ctx, "golang.allImportsFixes")
    +	defer done()
    +
    +	if err := snapshot.RunProcessEnvFunc(ctx, func(ctx context.Context, opts *imports.Options) error {
    +		allFixEdits, editsPerFix, err = computeImportEdits(ctx, pgf, opts)
    +		return err
    +	}); err != nil {
    +		return nil, nil, fmt.Errorf("allImportsFixes: %v", err)
    +	}
    +	return allFixEdits, editsPerFix, nil
    +}
    +
    +// computeImportEdits computes a set of edits that perform one or all of the
    +// necessary import fixes.
    +func computeImportEdits(ctx context.Context, pgf *parsego.File, options *imports.Options) (allFixEdits []protocol.TextEdit, editsPerFix []*importFix, err error) {
    +	filename := pgf.URI.Path()
    +
    +	// Build up basic information about the original file.
    +	allFixes, err := imports.FixImports(ctx, filename, pgf.Src, options)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +
    +	allFixEdits, err = computeFixEdits(pgf, options, allFixes)
    +	if err != nil {
    +		return nil, nil, err
    +	}
    +
    +	// Apply all of the import fixes to the file.
    +	// Add the edits for each fix to the result.
    +	for _, fix := range allFixes {
    +		edits, err := computeFixEdits(pgf, options, []*imports.ImportFix{fix})
    +		if err != nil {
    +			return nil, nil, err
    +		}
    +		editsPerFix = append(editsPerFix, &importFix{
    +			fix:   fix,
    +			edits: edits,
    +		})
    +	}
    +	return allFixEdits, editsPerFix, nil
    +}
    +
    +// ComputeOneImportFixEdits returns text edits for a single import fix.
    +func ComputeOneImportFixEdits(snapshot *cache.Snapshot, pgf *parsego.File, fix *imports.ImportFix) ([]protocol.TextEdit, error) {
    +	options := &imports.Options{
    +		LocalPrefix: snapshot.Options().Local,
    +		// Defaults.
    +		AllErrors:  true,
    +		Comments:   true,
    +		Fragment:   true,
    +		FormatOnly: false,
    +		TabIndent:  true,
    +		TabWidth:   8,
    +	}
    +	return computeFixEdits(pgf, options, []*imports.ImportFix{fix})
    +}
    +
    +func computeFixEdits(pgf *parsego.File, options *imports.Options, fixes []*imports.ImportFix) ([]protocol.TextEdit, error) {
    +	// trim the original data to match fixedData
    +	left, err := importPrefix(pgf.Src)
    +	if err != nil {
    +		return nil, err
    +	}
    +	extra := !strings.Contains(left, "\n") // one line may have more than imports
    +	if extra {
    +		left = string(pgf.Src)
    +	}
    +	if len(left) > 0 && left[len(left)-1] != '\n' {
    +		left += "\n"
    +	}
    +	// Apply the fixes and re-parse the file so that we can locate the
    +	// new imports.
    +	flags := parser.ImportsOnly
    +	if extra {
    +		// used all of origData above, use all of it here too
    +		flags = 0
    +	}
    +	fixedData, err := imports.ApplyFixes(fixes, "", pgf.Src, options, flags)
    +	if err != nil {
    +		return nil, err
    +	}
    +	if fixedData == nil || fixedData[len(fixedData)-1] != '\n' {
    +		fixedData = append(fixedData, '\n') // ApplyFixes may miss the newline, go figure.
    +	}
    +	edits := diff.Strings(left, string(fixedData))
    +	return protocolEditsFromSource([]byte(left), edits)
    +}
    +
    +// importPrefix returns the prefix of the given file content through the final
    +// import statement. If there are no imports, the prefix is the package
    +// statement and any comment groups below it.
    +func importPrefix(src []byte) (string, error) {
    +	fset := token.NewFileSet()
    +	// do as little parsing as possible
    +	f, err := parser.ParseFile(fset, "", src, parser.ImportsOnly|parser.ParseComments)
    +	if err != nil { // This can happen if 'package' is misspelled
    +		return "", fmt.Errorf("importPrefix: failed to parse: %s", err)
    +	}
    +	tok := fset.File(f.Pos())
    +	var importEnd int
    +	for _, d := range f.Decls {
    +		if x, ok := d.(*ast.GenDecl); ok && x.Tok == token.IMPORT {
    +			if e, err := safetoken.Offset(tok, d.End()); err != nil {
    +				return "", fmt.Errorf("importPrefix: %s", err)
    +			} else if e > importEnd {
    +				importEnd = e
    +			}
    +		}
    +	}
    +
    +	maybeAdjustToLineEnd := func(pos token.Pos, isCommentNode bool) int {
    +		offset, err := safetoken.Offset(tok, pos)
    +		if err != nil {
    +			return -1
    +		}
    +
    +		// Don't go past the end of the file.
    +		if offset > len(src) {
    +			offset = len(src)
    +		}
    +		// The go/ast package does not account for different line endings, and
    +		// specifically, in the text of a comment, it will strip out \r\n line
    +		// endings in favor of \n. To account for these differences, we try to
    +		// return a position on the next line whenever possible.
    +		switch line := safetoken.Line(tok, tok.Pos(offset)); {
    +		case line < tok.LineCount():
    +			nextLineOffset, err := safetoken.Offset(tok, tok.LineStart(line+1))
    +			if err != nil {
    +				return -1
    +			}
    +			// If we found a position that is at the end of a line, move the
    +			// offset to the start of the next line.
    +			if offset+1 == nextLineOffset {
    +				offset = nextLineOffset
    +			}
    +		case isCommentNode, offset+1 == tok.Size():
    +			// If the last line of the file is a comment, or we are at the end
    +			// of the file, the prefix is the entire file.
    +			offset = len(src)
    +		}
    +		return offset
    +	}
    +	if importEnd == 0 {
    +		pkgEnd := f.Name.End()
    +		importEnd = maybeAdjustToLineEnd(pkgEnd, false)
    +	}
    +	for _, cgroup := range f.Comments {
    +		for _, c := range cgroup.List {
    +			if end, err := safetoken.Offset(tok, c.End()); err != nil {
    +				return "", err
    +			} else if end > importEnd {
    +				startLine := safetoken.Position(tok, c.Pos()).Line
    +				endLine := safetoken.Position(tok, c.End()).Line
    +
    +				// Work around golang/go#41197 by checking if the comment might
    +				// contain "\r", and if so, find the actual end position of the
    +				// comment by scanning the content of the file.
    +				startOffset, err := safetoken.Offset(tok, c.Pos())
    +				if err != nil {
    +					return "", err
    +				}
    +				if startLine != endLine && bytes.Contains(src[startOffset:], []byte("\r")) {
    +					if commentEnd := scanForCommentEnd(src[startOffset:]); commentEnd > 0 {
    +						end = startOffset + commentEnd
    +					}
    +				}
    +				importEnd = maybeAdjustToLineEnd(tok.Pos(end), true)
    +			}
    +		}
    +	}
    +	if importEnd > len(src) {
    +		importEnd = len(src)
    +	}
    +	return string(src[:importEnd]), nil
    +}
    +
    +// scanForCommentEnd returns the offset of the end of the multi-line comment
    +// at the start of the given byte slice.
    +func scanForCommentEnd(src []byte) int {
    +	var s scanner.Scanner
    +	s.Init(bytes.NewReader(src))
    +	s.Mode ^= scanner.SkipComments
    +
    +	t := s.Scan()
    +	if t == scanner.Comment {
    +		return s.Pos().Offset
    +	}
    +	return 0
    +}
    +
    +func computeTextEdits(ctx context.Context, pgf *parsego.File, formatted string) ([]protocol.TextEdit, error) {
    +	_, done := event.Start(ctx, "golang.computeTextEdits")
    +	defer done()
    +
    +	edits := diff.Strings(string(pgf.Src), formatted)
    +	return protocol.EditsFromDiffEdits(pgf.Mapper, edits)
    +}
    +
    +// protocolEditsFromSource converts text edits to LSP edits using the original
    +// source.
    +func protocolEditsFromSource(src []byte, edits []diff.Edit) ([]protocol.TextEdit, error) {
    +	m := protocol.NewMapper("", src)
    +	var result []protocol.TextEdit
    +	for _, edit := range edits {
    +		rng, err := m.OffsetRange(edit.Start, edit.End)
    +		if err != nil {
    +			return nil, err
    +		}
    +
    +		if rng.Start == rng.End && edit.New == "" {
    +			// Degenerate case, which may result from a diff tool wanting to delete
    +			// '\r' in line endings. Filter it out.
    +			continue
    +		}
    +		result = append(result, protocol.TextEdit{
    +			Range:   rng,
    +			NewText: edit.New,
    +		})
    +	}
    +	return result, nil
    +}
    diff --git a/contribs/gnopls/internal/golang/format_test.go b/contribs/gnopls/internal/golang/format_test.go
    new file mode 100644
    index 00000000000..4dbb4db71c0
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/format_test.go
    @@ -0,0 +1,75 @@
    +// 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 golang
    +
    +import (
    +	"strings"
    +	"testing"
    +
    +	"golang.org/x/tools/gopls/internal/test/compare"
    +)
    +
    +func TestImportPrefix(t *testing.T) {
    +	for i, tt := range []struct {
    +		input, want string
    +	}{
    +		{"package foo", "package foo"},
    +		{"package foo\n", "package foo\n"},
    +		{"package foo\n\nfunc f(){}\n", "package foo\n"},
    +		{"package foo\n\nimport \"fmt\"\n", "package foo\n\nimport \"fmt\""},
    +		{"package foo\nimport (\n\"fmt\"\n)\n", "package foo\nimport (\n\"fmt\"\n)"},
    +		{"\n\n\npackage foo\n", "\n\n\npackage foo\n"},
    +		{"// hi \n\npackage foo //xx\nfunc _(){}\n", "// hi \n\npackage foo //xx\n"},
    +		{"package foo //hi\n", "package foo //hi\n"},
    +		{"//hi\npackage foo\n//a\n\n//b\n", "//hi\npackage foo\n//a\n\n//b\n"},
    +		{
    +			"package a\n\nimport (\n  \"fmt\"\n)\n//hi\n",
    +			"package a\n\nimport (\n  \"fmt\"\n)\n//hi\n",
    +		},
    +		{`package a /*hi*/`, `package a /*hi*/`},
    +		{"package main\r\n\r\nimport \"go/types\"\r\n\r\n/*\r\n\r\n */\r\n", "package main\r\n\r\nimport \"go/types\"\r\n\r\n/*\r\n\r\n */\r\n"},
    +		{"package x; import \"os\"; func f() {}\n\n", "package x; import \"os\""},
    +		{"package x; func f() {fmt.Println()}\n\n", "package x"},
    +	} {
    +		got, err := importPrefix([]byte(tt.input))
    +		if err != nil {
    +			t.Fatal(err)
    +		}
    +		if d := compare.Text(tt.want, got); d != "" {
    +			t.Errorf("%d: failed for %q:\n%s", i, tt.input, d)
    +		}
    +	}
    +}
    +
    +func TestCRLFFile(t *testing.T) {
    +	for i, tt := range []struct {
    +		input, want string
    +	}{
    +		{
    +			input: `package main
    +
    +/*
    +Hi description
    +*/
    +func Hi() {
    +}
    +`,
    +			want: `package main
    +
    +/*
    +Hi description
    +*/`,
    +		},
    +	} {
    +		got, err := importPrefix([]byte(strings.ReplaceAll(tt.input, "\n", "\r\n")))
    +		if err != nil {
    +			t.Fatal(err)
    +		}
    +		want := strings.ReplaceAll(tt.want, "\n", "\r\n")
    +		if d := compare.Text(want, got); d != "" {
    +			t.Errorf("%d: failed for %q:\n%s", i, tt.input, d)
    +		}
    +	}
    +}
    diff --git a/contribs/gnopls/internal/golang/freesymbols.go b/contribs/gnopls/internal/golang/freesymbols.go
    new file mode 100644
    index 00000000000..0e2422d421b
    --- /dev/null
    +++ b/contribs/gnopls/internal/golang/freesymbols.go
    @@ -0,0 +1,416 @@
    +// 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 golang
    +
    +// This file implements the "Browse free symbols" code action.
    +
    +import (
    +	"bytes"
    +	"fmt"
    +	"go/ast"
    +	"go/token"
    +	"go/types"
    +	"html"
    +	"slices"
    +	"sort"
    +	"strings"
    +
    +	"golang.org/x/tools/go/ast/astutil"
    +	"golang.org/x/tools/gopls/internal/cache"
    +	"golang.org/x/tools/gopls/internal/cache/metadata"
    +	"golang.org/x/tools/gopls/internal/cache/parsego"
    +	"golang.org/x/tools/gopls/internal/util/moremaps"
    +	"golang.org/x/tools/gopls/internal/util/safetoken"
    +	"golang.org/x/tools/internal/typesinternal"
    +)
    +
    +// FreeSymbolsHTML returns an HTML document containing the report of
    +// free symbols referenced by the selection.
    +func FreeSymbolsHTML(viewID string, pkg *cache.Package, pgf *parsego.File, start, end token.Pos, web Web) []byte {
    +
    +	// Compute free references.
    +	refs := freeRefs(pkg.Types(), pkg.TypesInfo(), pgf.File, start, end)
    +
    +	// -- model --
    +
    +	type Import struct {
    +		Path    metadata.PackagePath
    +		Symbols []string
    +	}
    +	type Symbol struct {
    +		Kind string
    +		Type string
    +		Refs []types.Object
    +	}
    +	var model struct {
    +		Imported []Import
    +		PkgLevel []Symbol
    +		Local    []Symbol
    +	}
    +
    +	qualifier := typesinternal.NameRelativeTo(pkg.Types())
    +
    +	// Populate model.
    +	{
    +		// List the refs in order of dotted paths.
    +		sort.Slice(refs, func(i, j int) bool {
    +			return refs[i].dotted < refs[j].dotted
    +		})
    +
    +		// Inspect the references.
    +		imported := make(map[string][]*freeRef) // refs to imported symbols, by package path
    +		seen := make(map[string]bool)           // to de-dup dotted paths
    +		for _, ref := range refs {
    +			if seen[ref.dotted] {
    +				continue // de-dup
    +			}
    +			seen[ref.dotted] = true
    +
    +			var symbols *[]Symbol
    +			switch ref.scope {
    +			case "file":
    +				// imported symbol: group by package
    +				if pkgname, ok := ref.objects[0].(*types.PkgName); ok {
    +					path := pkgname.Imported().Path()
    +					imported[path] = append(imported[path], ref)
    +				}
    +				continue
    +			case "pkg":
    +				symbols = &model.PkgLevel
    +			case "local":
    +				symbols = &model.Local
    +			default:
    +				panic(ref.scope)
    +			}
    +
    +			// Package and local symbols are presented the same way.
    +			// We treat each dotted path x.y.z as a separate entity.
    +
    +			// Compute kind and type of last object (y in obj.x.y).
    +			typestr := " " + types.TypeString(ref.typ, qualifier)
    +			var kind string
    +			switch obj := ref.objects[len(ref.objects)-1].(type) {
    +			case *types.Var:
    +				kind = "var"
    +			case *types.Func:
    +				kind = "func"
    +			case *types.TypeName:
    +				if is[*types.TypeParam](obj.Type()) {
    +					kind = "type parameter"
    +				} else {
    +					kind = "type"
    +				}
    +				typestr = "" // avoid "type T T"
    +			case *types.Const:
    +				kind = "const"
    +			case *types.Label:
    +				kind = "label"
    +				typestr = "" // avoid "label L L"
    +			}
    +
    +			*symbols = append(*symbols, Symbol{
    +				Kind: kind,
    +				Type: typestr,
    +				Refs: ref.objects,
    +			})
    +		}
    +
    +		// Imported symbols.
    +		// Produce one record per package, with a list of symbols.
    +		for pkgPath, refs := range moremaps.Sorted(imported) {
    +			var syms []string
    +			for _, ref := range refs {
    +				// strip package name (bytes.Buffer.Len -> Buffer.Len)
    +				syms = append(syms, ref.dotted[len(ref.objects[0].Name())+len("."):])
    +			}
    +			sort.Strings(syms)
    +			const max = 4
    +			if len(syms) > max {
    +				syms[max-1] = fmt.Sprintf("... (%d)", len(syms))
    +				syms = syms[:max]
    +			}
    +
    +			model.Imported = append(model.Imported, Import{
    +				Path:    PackagePath(pkgPath),
    +				Symbols: syms,
    +			})
    +		}
    +	}
    +
    +	// -- presentation --
    +
    +	var buf bytes.Buffer
    +	buf.WriteString(`
    +
    +
    +
    +  
    +  
    +
    +
    +

    Free symbols

    +

    + The selected code contains references to these free* symbols: +

    +`) + + // Present the refs in three sections: imported, same package, local. + + // -- imported symbols -- + + // Show one item per package, with a list of symbols. + fmt.Fprintf(&buf, "

    Imported symbols

    \n") + fmt.Fprintf(&buf, "
      \n") + for _, imp := range model.Imported { + fmt.Fprintf(&buf, "
    • import \"%s\" // for %s
    • \n", + web.PkgURL(viewID, imp.Path, ""), + html.EscapeString(string(imp.Path)), + strings.Join(imp.Symbols, ", ")) + } + if len(model.Imported) == 0 { + fmt.Fprintf(&buf, "
    • (none)
    • \n") + } + buf.WriteString("
    \n") + + // -- package and local symbols -- + + showSymbols := func(scope, title string, symbols []Symbol) { + fmt.Fprintf(&buf, "

    %s

    \n", scope, title) + fmt.Fprintf(&buf, "
      \n") + pre := buf.Len() + for _, sym := range symbols { + fmt.Fprintf(&buf, "
    • %s ", sym.Kind) // of rightmost symbol in dotted path + for i, obj := range sym.Refs { + if i > 0 { + buf.WriteByte('.') + } + buf.WriteString(objHTML(pkg.FileSet(), web, obj)) + } + fmt.Fprintf(&buf, " %s
    • \n", html.EscapeString(sym.Type)) + } + if buf.Len() == pre { + fmt.Fprintf(&buf, "
    • (none)
    • \n") + } + buf.WriteString("
    \n") + } + showSymbols("pkg", "Package-level symbols", model.PkgLevel) + showSymbols("local", "Local symbols", model.Local) + + // -- code selection -- + + // Print the selection, highlighting references to free symbols. + buf.WriteString("
    \n") + sort.Slice(refs, func(i, j int) bool { + return refs[i].expr.Pos() < refs[j].expr.Pos() + }) + pos := start + emitTo := func(end token.Pos) { + if pos < end { + fileStart := pgf.File.FileStart + text := pgf.Mapper.Content[pos-fileStart : end-fileStart] + buf.WriteString(html.EscapeString(string(text))) + pos = end + } + } + buf.WriteString(`
    `)
    +	for _, ref := range refs {
    +		emitTo(ref.expr.Pos())
    +		fmt.Fprintf(&buf, ``, ref.scope)
    +		emitTo(ref.expr.End())
    +		buf.WriteString(``)
    +	}
    +	emitTo(end)
    +	buf.WriteString(`
    +
    +

    + *A symbol is "free" if it is referenced within the selection but declared + outside of it. + + The free variables are approximately the set of parameters that + would be needed if the block were extracted into its own function in + the same package. + + Free identifiers may include local types and control labels as well. + + 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 referenced by 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. +

    +`) + return buf.Bytes() +} + +// A freeRef records a reference to a dotted path obj.x.y, +// where obj (=objects[0]) is a free symbol. +type freeRef struct { + objects []types.Object // [obj x y] + dotted string // "obj.x.y" (used as sort key) + scope string // scope of obj: pkg|file|local + expr ast.Expr // =*Ident|*SelectorExpr + typ types.Type // type of obj.x.y +} + +// freeRefs returns the list of references to free symbols (from +// within the selection to a symbol declared outside of it). +// It uses only info.{Scopes,Types,Uses}. +func freeRefs(pkg *types.Package, info *types.Info, file *ast.File, start, end token.Pos) []*freeRef { + // Keep us honest about which fields we access. + info = &types.Info{ + Scopes: info.Scopes, + Types: info.Types, + Uses: info.Uses, + } + + fileScope := info.Scopes[file] + pkgScope := fileScope.Parent() + + // id is called for the leftmost id x in each dotted chain such as (x.y).z. + // suffix is the reversed suffix of selections (e.g. [z y]). + id := func(n *ast.Ident, suffix []types.Object) *freeRef { + obj := info.Uses[n] + if obj == nil { + return nil // not a reference + } + if start <= obj.Pos() && obj.Pos() < end { + return nil // defined within selection => not free + } + parent := obj.Parent() + + // Compute dotted path. + objects := append(suffix, obj) + if obj.Pkg() != nil && obj.Pkg() != pkg && isPackageLevel(obj) { // dot import + // Synthesize the implicit PkgName. + pkgName := types.NewPkgName(token.NoPos, pkg, obj.Pkg().Name(), obj.Pkg()) + parent = fileScope + objects = append(objects, pkgName) + } + slices.Reverse(objects) + var dotted strings.Builder + for i, obj := range objects { + if obj == nil { + return nil // type error + } + if i > 0 { + dotted.WriteByte('.') + } + dotted.WriteString(obj.Name()) + } + + // Compute scope of base object. + var scope string + switch parent { + case nil: + return nil // interface method or struct field + case types.Universe: + return nil // built-in (not interesting) + case fileScope: + scope = "file" // defined at file scope (imported package) + case pkgScope: + scope = "pkg" // defined at package level + default: + scope = "local" // defined within current function + } + + return &freeRef{ + objects: objects, + dotted: dotted.String(), + scope: scope, + } + } + + // sel(x.y.z, []) calls sel(x.y, [z]) calls id(x, [z, y]). + sel := func(sel *ast.SelectorExpr, suffix []types.Object) *freeRef { + for { + suffix = append(suffix, info.Uses[sel.Sel]) + + switch x := astutil.Unparen(sel.X).(type) { + case *ast.Ident: + return id(x, suffix) + default: + return nil + case *ast.SelectorExpr: + sel = x + } + } + } + + // Visit all the identifiers in the selected ASTs. + var free []*freeRef + path, _ := astutil.PathEnclosingInterval(file, start, end) + var visit func(n ast.Node) bool + visit = func(n ast.Node) bool { + // Is this node contained within the selection? + // (freesymbols permits inexact selections, + // like two stmts in a block.) + if n != nil && start <= n.Pos() && n.End() <= end { + var ref *freeRef + switch n := n.(type) { + case *ast.Ident: + ref = id(n, nil) + case *ast.SelectorExpr: + ref = sel(n, nil) + } + + if ref != nil { + ref.expr = n.(ast.Expr) + ref.typ = info.Types[n.(ast.Expr)].Type + free = append(free, ref) + } + + // After visiting x.sel, don't descend into sel. + // Descend into x only if we didn't get a ref for x.sel. + if sel, ok := n.(*ast.SelectorExpr); ok { + if ref == nil { + ast.Inspect(sel.X, visit) + } + return false + } + } + + return true // descend + } + ast.Inspect(path[0], visit) + return free +} + +// objHTML returns HTML for obj.Name(), possibly marked up as a link +// to the web server that, when visited, opens the declaration in the +// client editor. +func objHTML(fset *token.FileSet, web Web, obj types.Object) string { + text := obj.Name() + if posn := safetoken.StartPosition(fset, obj.Pos()); posn.IsValid() { + url := web.SrcURL(posn.Filename, posn.Line, posn.Column) + return sourceLink(text, url) + } + return text +} + +// sourceLink returns HTML for a link to open a file in the client editor. +func sourceLink(text, url string) string { + // The /src URL returns nothing but has the side effect + // of causing the LSP client to open the requested file. + // So we use onclick to prevent the browser from navigating. + // We keep the href attribute as it causes the to render + // as a link: blue, underlined, with URL hover information. + return fmt.Sprintf(`%[2]s`, + html.EscapeString(url), text) +} diff --git a/contribs/gnopls/internal/golang/freesymbols_test.go b/contribs/gnopls/internal/golang/freesymbols_test.go new file mode 100644 index 00000000000..9ad8ca3ee6e --- /dev/null +++ b/contribs/gnopls/internal/golang/freesymbols_test.go @@ -0,0 +1,132 @@ +// 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 golang + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "reflect" + "runtime" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" +) + +// TestFreeRefs is a unit test of the free-references algorithm. +func TestFreeRefs(t *testing.T) { + if runtime.GOOS == "js" || runtime.GOARCH == "wasm" { + t.Skip("some test imports are unsupported on js or wasm") + } + + for i, test := range []struct { + src string + want []string // expected list of "scope kind dotted-path" triples + }{ + { + // basic example (has a "cannot infer" type error) + `package p; func f[T ~int](x any) { var y T; « f(x.(T) + y) » }`, + []string{"pkg func f", "local var x", "local typename T", "local var y"}, + }, + { + // selection need not be tree-aligned + `package p; type T int; type U « T; func _(x U) »`, + []string{"pkg typename T", "pkg typename U"}, + }, + { + // imported symbols + `package p; import "fmt"; func f() { « var x fmt.Stringer » }`, + []string{"file pkgname fmt.Stringer"}, + }, + { + // unsafe and error, our old nemeses + `package p; import "unsafe"; var ( « _ unsafe.Pointer; _ = error(nil).Error »; )`, + []string{"file pkgname unsafe.Pointer"}, + }, + { + // two attributes of a var, but not the var itself + `package p; import "bytes"; func _(buf bytes.Buffer) { « buf.WriteByte(0); buf.WriteString(""); » }`, + []string{"local var buf.WriteByte", "local var buf.WriteString"}, + }, + { + // dot imports (an edge case) + `package p; import . "errors"; var _ = « New»`, + []string{"file pkgname errors.New"}, + }, + { + // struct field (regression test for overzealous dot import logic) + `package p; import "net/url"; var _ = «url.URL{Host: ""}»`, + []string{"file pkgname url.URL"}, + }, + { + // dot imports (another regression test of same) + `package p; import . "net/url"; var _ = «URL{Host: ""}»`, + []string{"file pkgname url.URL"}, + }, + { + // dot import of unsafe (a corner case) + `package p; import . "unsafe"; var _ « Pointer»`, + []string{"file pkgname unsafe.Pointer"}, + }, + { + // dotted path + `package p; import "go/build"; var _ = « build.Default.GOOS »`, + []string{"file pkgname build.Default.GOOS"}, + }, + { + // type error + `package p; import "nope"; var _ = « nope.nope.nope »`, + []string{"file pkgname nope"}, + }, + } { + name := fmt.Sprintf("file%d.go", i) + t.Run(name, func(t *testing.T) { + fset := token.NewFileSet() + startOffset := strings.Index(test.src, "«") + endOffset := strings.Index(test.src, "»") + if startOffset < 0 || endOffset < startOffset { + t.Fatalf("invalid «...» selection (%d:%d)", startOffset, endOffset) + } + src := test.src[:startOffset] + + " " + + test.src[startOffset+len("«"):endOffset] + + " " + + test.src[endOffset+len("»"):] + f, err := parser.ParseFile(fset, name, src, 0) + if err != nil { + t.Fatal(err) + } + conf := &types.Config{ + Importer: importer.Default(), + Error: func(err error) { t.Log(err) }, // not fatal + } + info := &types.Info{ + Uses: make(map[*ast.Ident]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Types: make(map[ast.Expr]types.TypeAndValue), + } + pkg, _ := conf.Check(f.Name.Name, fset, []*ast.File{f}, info) // ignore errors + tf := fset.File(f.Package) + refs := freeRefs(pkg, info, f, tf.Pos(startOffset), tf.Pos(endOffset)) + + kind := func(obj types.Object) string { // e.g. "var", "const" + return strings.ToLower(reflect.TypeOf(obj).Elem().Name()) + } + + var got []string + for _, ref := range refs { + msg := ref.scope + " " + kind(ref.objects[0]) + " " + ref.dotted + got = append(got, msg) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("(-want +got)\n%s", diff) + } + }) + } +} diff --git a/contribs/gnopls/internal/golang/gc_annotations.go b/contribs/gnopls/internal/golang/gc_annotations.go new file mode 100644 index 00000000000..03db9e74760 --- /dev/null +++ b/contribs/gnopls/internal/golang/gc_annotations.go @@ -0,0 +1,226 @@ +// 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 golang + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/gocommand" +) + +// GCOptimizationDetails invokes the Go compiler on the specified +// package and reports its log of optimizations decisions as a set of +// diagnostics. +// +// TODO(adonovan): this feature needs more consistent and informative naming. +// Now that the compiler is cmd/compile, "GC" now means only "garbage collection". +// I propose "(Toggle|Display) Go compiler optimization details" in the UI, +// and CompilerOptimizationDetails for this function and compileropts.go for the file. +func GCOptimizationDetails(ctx context.Context, snapshot *cache.Snapshot, mp *metadata.Package) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { + if len(mp.CompiledGoFiles) == 0 { + return nil, nil + } + pkgDir := filepath.Dir(mp.CompiledGoFiles[0].Path()) + outDir, err := os.MkdirTemp("", fmt.Sprintf("gopls-%d.details", os.Getpid())) + if err != nil { + return nil, err + } + defer func() { + if err := os.RemoveAll(outDir); err != nil { + event.Error(ctx, "cleaning gcdetails dir", err) + } + }() + + tmpFile, err := os.CreateTemp(os.TempDir(), "gopls-x") + if err != nil { + return nil, err + } + tmpFile.Close() // ignore error + defer os.Remove(tmpFile.Name()) + + outDirURI := protocol.URIFromPath(outDir) + // GC details doesn't handle Windows URIs in the form of "file:///C:/...", + // so rewrite them to "file://C:/...". See golang/go#41614. + if !strings.HasPrefix(outDir, "/") { + outDirURI = protocol.DocumentURI(strings.Replace(string(outDirURI), "file:///", "file://", 1)) + } + inv, cleanupInvocation, err := snapshot.GoCommandInvocation(false, &gocommand.Invocation{ + Verb: "build", + Args: []string{ + fmt.Sprintf("-gcflags=-json=0,%s", outDirURI), + fmt.Sprintf("-o=%s", tmpFile.Name()), + ".", + }, + WorkingDir: pkgDir, + }) + if err != nil { + return nil, err + } + defer cleanupInvocation() + _, err = snapshot.View().GoCommandRunner().Run(ctx, *inv) + if err != nil { + return nil, err + } + files, err := findJSONFiles(outDir) + if err != nil { + return nil, err + } + reports := make(map[protocol.DocumentURI][]*cache.Diagnostic) + opts := snapshot.Options() + var parseError error + for _, fn := range files { + uri, diagnostics, err := parseDetailsFile(fn, opts) + if err != nil { + // expect errors for all the files, save 1 + parseError = err + } + fh := snapshot.FindFile(uri) + if fh == nil { + continue + } + if pkgDir != filepath.Dir(fh.URI().Path()) { + // https://github.com/golang/go/issues/42198 + // sometimes the detail diagnostics generated for files + // outside the package can never be taken back. + continue + } + reports[fh.URI()] = diagnostics + } + return reports, parseError +} + +func parseDetailsFile(filename string, options *settings.Options) (protocol.DocumentURI, []*cache.Diagnostic, error) { + buf, err := os.ReadFile(filename) + if err != nil { + return "", nil, err + } + var ( + uri protocol.DocumentURI + i int + diagnostics []*cache.Diagnostic + ) + type metadata struct { + File string `json:"file,omitempty"` + } + for dec := json.NewDecoder(bytes.NewReader(buf)); dec.More(); { + // The first element always contains metadata. + if i == 0 { + i++ + m := new(metadata) + if err := dec.Decode(m); err != nil { + return "", nil, err + } + if !strings.HasSuffix(m.File, ".go") { + continue // + } + uri = protocol.URIFromPath(m.File) + continue + } + d := new(protocol.Diagnostic) + if err := dec.Decode(d); err != nil { + return "", nil, err + } + d.Tags = []protocol.DiagnosticTag{} // must be an actual slice + msg := d.Code.(string) + if msg != "" { + msg = fmt.Sprintf("%s(%s)", msg, d.Message) + } + if !showDiagnostic(msg, d.Source, options) { + continue + } + var related []protocol.DiagnosticRelatedInformation + for _, ri := range d.RelatedInformation { + // TODO(rfindley): The compiler uses LSP-like JSON to encode gc details, + // however the positions it uses are 1-based UTF-8: + // https://github.com/golang/go/blob/master/src/cmd/compile/internal/logopt/log_opts.go + // + // Here, we adjust for 0-based positions, but do not translate UTF-8 to UTF-16. + related = append(related, protocol.DiagnosticRelatedInformation{ + Location: protocol.Location{ + URI: ri.Location.URI, + Range: zeroIndexedRange(ri.Location.Range), + }, + Message: ri.Message, + }) + } + diagnostic := &cache.Diagnostic{ + URI: uri, + Range: zeroIndexedRange(d.Range), + Message: msg, + Severity: d.Severity, + Source: cache.OptimizationDetailsError, // d.Source is always "go compiler" as of 1.16, use our own + Tags: d.Tags, + Related: related, + } + diagnostics = append(diagnostics, diagnostic) + i++ + } + return uri, diagnostics, nil +} + +// showDiagnostic reports whether a given diagnostic should be shown to the end +// user, given the current options. +func showDiagnostic(msg, source string, o *settings.Options) bool { + if source != "go compiler" { + return false + } + if o.Annotations == nil { + return true + } + switch { + case strings.HasPrefix(msg, "canInline") || + strings.HasPrefix(msg, "cannotInline") || + strings.HasPrefix(msg, "inlineCall"): + return o.Annotations[settings.Inline] + case strings.HasPrefix(msg, "escape") || msg == "leak": + return o.Annotations[settings.Escape] + case strings.HasPrefix(msg, "nilcheck"): + return o.Annotations[settings.Nil] + case strings.HasPrefix(msg, "isInBounds") || + strings.HasPrefix(msg, "isSliceInBounds"): + return o.Annotations[settings.Bounds] + } + return false +} + +// The range produced by the compiler is 1-indexed, so subtract range by 1. +func zeroIndexedRange(rng protocol.Range) protocol.Range { + return protocol.Range{ + Start: protocol.Position{ + Line: rng.Start.Line - 1, + Character: rng.Start.Character - 1, + }, + End: protocol.Position{ + Line: rng.End.Line - 1, + Character: rng.End.Character - 1, + }, + } +} + +func findJSONFiles(dir string) ([]string, error) { + ans := []string{} + f := func(path string, fi os.FileInfo, _ error) error { + if fi.IsDir() { + return nil + } + if strings.HasSuffix(path, ".json") { + ans = append(ans, path) + } + return nil + } + err := filepath.Walk(dir, f) + return ans, err +} diff --git a/contribs/gnopls/internal/golang/highlight.go b/contribs/gnopls/internal/golang/highlight.go new file mode 100644 index 00000000000..f53e73f3053 --- /dev/null +++ b/contribs/gnopls/internal/golang/highlight.go @@ -0,0 +1,604 @@ +// 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. + +package golang + +import ( + "context" + "fmt" + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.DocumentHighlight, error) { + ctx, done := event.Start(ctx, "golang.Highlight") + defer done() + + // We always want fully parsed files for highlight, regardless + // of whether the file belongs to a workspace package. + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) + if err != nil { + return nil, fmt.Errorf("getting package for Highlight: %w", err) + } + + pos, err := pgf.PositionPos(position) + if err != nil { + return nil, err + } + path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos) + if len(path) == 0 { + return nil, fmt.Errorf("no enclosing position found for %v:%v", position.Line, position.Character) + } + // If start == end for astutil.PathEnclosingInterval, the 1-char interval + // following start is used instead. As a result, we might not get an exact + // match so we should check the 1-char interval to the left of the passed + // in position to see if that is an exact match. + if _, ok := path[0].(*ast.Ident); !ok { + if p, _ := astutil.PathEnclosingInterval(pgf.File, pos-1, pos-1); p != nil { + switch p[0].(type) { + case *ast.Ident, *ast.SelectorExpr: + path = p // use preceding ident/selector + } + } + } + result, err := highlightPath(path, pgf.File, pkg.TypesInfo()) + if err != nil { + return nil, err + } + var ranges []protocol.DocumentHighlight + for rng, kind := range result { + rng, err := pgf.PosRange(rng.start, rng.end) + if err != nil { + return nil, err + } + ranges = append(ranges, protocol.DocumentHighlight{ + Range: rng, + Kind: kind, + }) + } + return ranges, nil +} + +// highlightPath returns ranges to highlight for the given enclosing path, +// which should be the result of astutil.PathEnclosingInterval. +func highlightPath(path []ast.Node, file *ast.File, info *types.Info) (map[posRange]protocol.DocumentHighlightKind, error) { + result := make(map[posRange]protocol.DocumentHighlightKind) + switch node := path[0].(type) { + case *ast.BasicLit: + // Import path string literal? + if len(path) > 1 { + if imp, ok := path[1].(*ast.ImportSpec); ok { + highlight := func(n ast.Node) { + highlightNode(result, n, protocol.Text) + } + + // Highlight the import itself... + highlight(imp) + + // ...and all references to it in the file. + if pkgname := info.PkgNameOf(imp); pkgname != nil { + ast.Inspect(file, func(n ast.Node) bool { + if id, ok := n.(*ast.Ident); ok && + info.Uses[id] == pkgname { + highlight(id) + } + return true + }) + } + return result, nil + } + } + highlightFuncControlFlow(path, result) + case *ast.ReturnStmt, *ast.FuncDecl, *ast.FuncType: + highlightFuncControlFlow(path, result) + case *ast.Ident: + // Check if ident is inside return or func decl. + highlightFuncControlFlow(path, result) + highlightIdentifier(node, file, info, result) + case *ast.ForStmt, *ast.RangeStmt: + highlightLoopControlFlow(path, info, result) + case *ast.SwitchStmt, *ast.TypeSwitchStmt: + highlightSwitchFlow(path, info, result) + case *ast.BranchStmt: + // BREAK can exit a loop, switch or select, while CONTINUE exit a loop so + // these need to be handled separately. They can also be embedded in any + // other loop/switch/select if they have a label. TODO: add support for + // GOTO and FALLTHROUGH as well. + switch node.Tok { + case token.BREAK: + if node.Label != nil { + highlightLabeledFlow(path, info, node, result) + } else { + highlightUnlabeledBreakFlow(path, info, result) + } + case token.CONTINUE: + if node.Label != nil { + highlightLabeledFlow(path, info, node, result) + } else { + highlightLoopControlFlow(path, info, result) + } + } + } + + return result, nil +} + +type posRange struct { + start, end token.Pos +} + +// highlightFuncControlFlow adds highlight ranges to the result map to +// associate results and result parameters. +// +// Specifically, if the cursor is in a result or result parameter, all +// results and result parameters with the same index are highlighted. If the +// cursor is in a 'func' or 'return' keyword, the func keyword as well as all +// returns from that func are highlighted. +// +// As a special case, if the cursor is within a complicated expression, control +// flow highlighting is disabled, as it would highlight too much. +func highlightFuncControlFlow(path []ast.Node, result map[posRange]protocol.DocumentHighlightKind) { + + var ( + funcType *ast.FuncType // type of enclosing func, or nil + funcBody *ast.BlockStmt // body of enclosing func, or nil + returnStmt *ast.ReturnStmt // enclosing ReturnStmt within the func, or nil + ) + +findEnclosingFunc: + for i, n := range path { + switch n := n.(type) { + // TODO(rfindley, low priority): these pre-existing cases for KeyValueExpr + // and CallExpr appear to avoid highlighting when the cursor is in a + // complicated expression. However, the basis for this heuristic is + // unclear. Can we formalize a rationale? + case *ast.KeyValueExpr: + // If cursor is in a key: value expr, we don't want control flow highlighting. + return + + case *ast.CallExpr: + // If cursor is an arg in a callExpr, we don't want control flow highlighting. + if i > 0 { + for _, arg := range n.Args { + if arg == path[i-1] { + return + } + } + } + + case *ast.FuncLit: + funcType = n.Type + funcBody = n.Body + break findEnclosingFunc + + case *ast.FuncDecl: + funcType = n.Type + funcBody = n.Body + break findEnclosingFunc + + case *ast.ReturnStmt: + returnStmt = n + } + } + + if funcType == nil { + return // cursor is not in a function + } + + // Helper functions for inspecting the current location. + var ( + pos = path[0].Pos() + inSpan = func(start, end token.Pos) bool { return start <= pos && pos < end } + inNode = func(n ast.Node) bool { return inSpan(n.Pos(), n.End()) } + ) + + inResults := funcType.Results != nil && inNode(funcType.Results) + + // If the cursor is on a "return" or "func" keyword, but not highlighting any + // specific field or expression, we should highlight all of the exit points + // of the function, including the "return" and "func" keywords. + funcEnd := funcType.Func + token.Pos(len("func")) + highlightAll := path[0] == returnStmt || inSpan(funcType.Func, funcEnd) + var highlightIndexes map[int]bool + + if highlightAll { + // Add the "func" part of the func declaration. + highlightRange(result, funcType.Func, funcEnd, protocol.Text) + } else if returnStmt == nil && !inResults { + return // nothing to highlight + } else { + // If we're not highighting the entire return statement, we need to collect + // specific result indexes to highlight. This may be more than one index if + // the cursor is on a multi-name result field, but not in any specific name. + if !highlightAll { + highlightIndexes = make(map[int]bool) + if returnStmt != nil { + for i, n := range returnStmt.Results { + if inNode(n) { + highlightIndexes[i] = true + break + } + } + } + + if funcType.Results != nil { + // Scan fields, either adding highlights according to the highlightIndexes + // computed above, or accounting for the cursor position within the result + // list. + // (We do both at once to avoid repeating the cumbersome field traversal.) + i := 0 + findField: + for _, field := range funcType.Results.List { + for j, name := range field.Names { + if inNode(name) || highlightIndexes[i+j] { + highlightNode(result, name, protocol.Text) + highlightIndexes[i+j] = true + break findField // found/highlighted the specific name + } + } + // If the cursor is in a field but not in a name (e.g. in the space, or + // the type), highlight the whole field. + // + // Note that this may not be ideal if we're at e.g. + // + // (x,‸y int, z int8) + // + // ...where it would make more sense to highlight only y. But we don't + // reach this function if not in a func, return, ident, or basiclit. + if inNode(field) || highlightIndexes[i] { + highlightNode(result, field, protocol.Text) + highlightIndexes[i] = true + if inNode(field) { + for j := range field.Names { + highlightIndexes[i+j] = true + } + } + break findField // found/highlighted the field + } + + n := len(field.Names) + if n == 0 { + n = 1 + } + i += n + } + } + } + } + + if funcBody != nil { + ast.Inspect(funcBody, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.FuncDecl, *ast.FuncLit: + // Don't traverse into any functions other than enclosingFunc. + return false + case *ast.ReturnStmt: + if highlightAll { + // Add the entire return statement. + highlightNode(result, n, protocol.Text) + } else { + // Add the highlighted indexes. + for i, expr := range n.Results { + if highlightIndexes[i] { + highlightNode(result, expr, protocol.Text) + } + } + } + return false + + } + return true + }) + } +} + +// highlightUnlabeledBreakFlow highlights the innermost enclosing for/range/switch or swlect +func highlightUnlabeledBreakFlow(path []ast.Node, info *types.Info, result map[posRange]protocol.DocumentHighlightKind) { + // Reverse walk the path until we find closest loop, select, or switch. + for _, n := range path { + switch n.(type) { + case *ast.ForStmt, *ast.RangeStmt: + highlightLoopControlFlow(path, info, result) + return // only highlight the innermost statement + case *ast.SwitchStmt, *ast.TypeSwitchStmt: + highlightSwitchFlow(path, info, result) + return + case *ast.SelectStmt: + // TODO: add highlight when breaking a select. + return + } + } +} + +// highlightLabeledFlow highlights the enclosing labeled for, range, +// or switch statement denoted by a labeled break or continue stmt. +func highlightLabeledFlow(path []ast.Node, info *types.Info, stmt *ast.BranchStmt, result map[posRange]protocol.DocumentHighlightKind) { + use := info.Uses[stmt.Label] + if use == nil { + return + } + for _, n := range path { + if label, ok := n.(*ast.LabeledStmt); ok && info.Defs[label.Label] == use { + switch label.Stmt.(type) { + case *ast.ForStmt, *ast.RangeStmt: + highlightLoopControlFlow([]ast.Node{label.Stmt, label}, info, result) + case *ast.SwitchStmt, *ast.TypeSwitchStmt: + highlightSwitchFlow([]ast.Node{label.Stmt, label}, info, result) + } + return + } + } +} + +func labelFor(path []ast.Node) *ast.Ident { + if len(path) > 1 { + if n, ok := path[1].(*ast.LabeledStmt); ok { + return n.Label + } + } + return nil +} + +func highlightLoopControlFlow(path []ast.Node, info *types.Info, result map[posRange]protocol.DocumentHighlightKind) { + var loop ast.Node + var loopLabel *ast.Ident + stmtLabel := labelFor(path) +Outer: + // Reverse walk the path till we get to the for loop. + for i := range path { + switch n := path[i].(type) { + case *ast.ForStmt, *ast.RangeStmt: + loopLabel = labelFor(path[i:]) + + if stmtLabel == nil || loopLabel == stmtLabel { + loop = n + break Outer + } + } + } + if loop == nil { + return + } + + // Add the for statement. + rngStart := loop.Pos() + rngEnd := loop.Pos() + token.Pos(len("for")) + highlightRange(result, rngStart, rngEnd, protocol.Text) + + // Traverse AST to find branch statements within the same for-loop. + ast.Inspect(loop, func(n ast.Node) bool { + switch n.(type) { + case *ast.ForStmt, *ast.RangeStmt: + return loop == n + case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt: + return false + } + b, ok := n.(*ast.BranchStmt) + if !ok { + return true + } + if b.Label == nil || info.Uses[b.Label] == info.Defs[loopLabel] { + highlightNode(result, b, protocol.Text) + } + return true + }) + + // Find continue statements in the same loop or switches/selects. + ast.Inspect(loop, func(n ast.Node) bool { + switch n.(type) { + case *ast.ForStmt, *ast.RangeStmt: + return loop == n + } + + if n, ok := n.(*ast.BranchStmt); ok && n.Tok == token.CONTINUE { + highlightNode(result, n, protocol.Text) + } + return true + }) + + // We don't need to check other for loops if we aren't looking for labeled statements. + if loopLabel == nil { + return + } + + // Find labeled branch statements in any loop. + ast.Inspect(loop, func(n ast.Node) bool { + b, ok := n.(*ast.BranchStmt) + if !ok { + return true + } + // statement with labels that matches the loop + if b.Label != nil && info.Uses[b.Label] == info.Defs[loopLabel] { + highlightNode(result, b, protocol.Text) + } + return true + }) +} + +func highlightSwitchFlow(path []ast.Node, info *types.Info, result map[posRange]protocol.DocumentHighlightKind) { + var switchNode ast.Node + var switchNodeLabel *ast.Ident + stmtLabel := labelFor(path) +Outer: + // Reverse walk the path till we get to the switch statement. + for i := range path { + switch n := path[i].(type) { + case *ast.SwitchStmt, *ast.TypeSwitchStmt: + switchNodeLabel = labelFor(path[i:]) + if stmtLabel == nil || switchNodeLabel == stmtLabel { + switchNode = n + break Outer + } + } + } + // Cursor is not in a switch statement + if switchNode == nil { + return + } + + // Add the switch statement. + rngStart := switchNode.Pos() + rngEnd := switchNode.Pos() + token.Pos(len("switch")) + highlightRange(result, rngStart, rngEnd, protocol.Text) + + // Traverse AST to find break statements within the same switch. + ast.Inspect(switchNode, func(n ast.Node) bool { + switch n.(type) { + case *ast.SwitchStmt, *ast.TypeSwitchStmt: + return switchNode == n + case *ast.ForStmt, *ast.RangeStmt, *ast.SelectStmt: + return false + } + + b, ok := n.(*ast.BranchStmt) + if !ok || b.Tok != token.BREAK { + return true + } + + if b.Label == nil || info.Uses[b.Label] == info.Defs[switchNodeLabel] { + highlightNode(result, b, protocol.Text) + } + return true + }) + + // We don't need to check other switches if we aren't looking for labeled statements. + if switchNodeLabel == nil { + return + } + + // Find labeled break statements in any switch + ast.Inspect(switchNode, func(n ast.Node) bool { + b, ok := n.(*ast.BranchStmt) + if !ok || b.Tok != token.BREAK { + return true + } + + if b.Label != nil && info.Uses[b.Label] == info.Defs[switchNodeLabel] { + highlightNode(result, b, protocol.Text) + } + + return true + }) +} + +func highlightNode(result map[posRange]protocol.DocumentHighlightKind, n ast.Node, kind protocol.DocumentHighlightKind) { + highlightRange(result, n.Pos(), n.End(), kind) +} + +func highlightRange(result map[posRange]protocol.DocumentHighlightKind, pos, end token.Pos, kind protocol.DocumentHighlightKind) { + rng := posRange{pos, end} + // Order of traversal is important: some nodes (e.g. identifiers) are + // visited more than once, but the kind set during the first visitation "wins". + if _, exists := result[rng]; !exists { + result[rng] = kind + } +} + +func highlightIdentifier(id *ast.Ident, file *ast.File, info *types.Info, result map[posRange]protocol.DocumentHighlightKind) { + + // obj may be nil if the Ident is undefined. + // In this case, the behavior expected by tests is + // to match other undefined Idents of the same name. + obj := info.ObjectOf(id) + + highlightIdent := func(n *ast.Ident, kind protocol.DocumentHighlightKind) { + if n.Name == id.Name && info.ObjectOf(n) == obj { + highlightNode(result, n, kind) + } + } + // highlightWriteInExpr is called for expressions that are + // logically on the left side of an assignment. + // We follow the behavior of VSCode+Rust and GoLand, which differs + // slightly from types.TypeAndValue.Assignable: + // *ptr = 1 // ptr write + // *ptr.field = 1 // ptr read, field write + // s.field = 1 // s read, field write + // array[i] = 1 // array read + var highlightWriteInExpr func(expr ast.Expr) + highlightWriteInExpr = func(expr ast.Expr) { + switch expr := expr.(type) { + case *ast.Ident: + highlightIdent(expr, protocol.Write) + case *ast.SelectorExpr: + highlightIdent(expr.Sel, protocol.Write) + case *ast.StarExpr: + highlightWriteInExpr(expr.X) + case *ast.ParenExpr: + highlightWriteInExpr(expr.X) + } + } + + ast.Inspect(file, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.AssignStmt: + for _, s := range n.Lhs { + highlightWriteInExpr(s) + } + case *ast.GenDecl: + if n.Tok == token.CONST || n.Tok == token.VAR { + for _, spec := range n.Specs { + if spec, ok := spec.(*ast.ValueSpec); ok { + for _, ele := range spec.Names { + highlightWriteInExpr(ele) + } + } + } + } + case *ast.IncDecStmt: + highlightWriteInExpr(n.X) + case *ast.SendStmt: + highlightWriteInExpr(n.Chan) + case *ast.CompositeLit: + t := info.TypeOf(n) + // Every expression should have a type; + // work around https://github.com/golang/go/issues/69092. + if t == nil { + t = types.Typ[types.Invalid] + } + if ptr, ok := t.Underlying().(*types.Pointer); ok { + t = ptr.Elem() + } + if _, ok := t.Underlying().(*types.Struct); ok { + for _, expr := range n.Elts { + if expr, ok := (expr).(*ast.KeyValueExpr); ok { + highlightWriteInExpr(expr.Key) + } + } + } + case *ast.RangeStmt: + highlightWriteInExpr(n.Key) + highlightWriteInExpr(n.Value) + case *ast.Field: + for _, name := range n.Names { + highlightIdent(name, protocol.Text) + } + case *ast.Ident: + // This case is reached for all Idents, + // including those also visited by highlightWriteInExpr. + if is[*types.Var](info.ObjectOf(n)) { + highlightIdent(n, protocol.Read) + } else { + // kind of idents in PkgName, etc. is Text + highlightIdent(n, protocol.Text) + } + case *ast.ImportSpec: + pkgname := info.PkgNameOf(n) + if pkgname == obj { + if n.Name != nil { + highlightNode(result, n.Name, protocol.Text) + } else { + highlightNode(result, n, protocol.Text) + } + } + } + return true + }) +} diff --git a/contribs/gnopls/internal/golang/hover.go b/contribs/gnopls/internal/golang/hover.go new file mode 100644 index 00000000000..019d09ac027 --- /dev/null +++ b/contribs/gnopls/internal/golang/hover.go @@ -0,0 +1,1587 @@ +// 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. + +package golang + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "go/ast" + "go/constant" + "go/doc" + "go/format" + "go/token" + "go/types" + "io/fs" + "path/filepath" + "sort" + "strconv" + "strings" + "text/tabwriter" + "time" + "unicode/utf8" + + "golang.org/x/text/unicode/runenames" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + gastutil "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/gopls/internal/util/typesutil" + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/stdlib" + "golang.org/x/tools/internal/tokeninternal" + "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" +) + +// hoverJSON contains the structured result of a hover query. It is +// formatted in one of several formats as determined by the HoverKind +// setting, one of which is JSON. +// +// We believe this is used only by govim. +// TODO(adonovan): see if we can wean all clients of this interface. +type hoverJSON struct { + // Synopsis is a single sentence synopsis of the symbol's documentation. + Synopsis string `json:"synopsis"` + + // FullDocumentation is the symbol's full documentation. + FullDocumentation string `json:"fullDocumentation"` + + // Signature is the symbol's signature. + Signature string `json:"signature"` + + // SingleLine is a single line describing the symbol. + // This is recommended only for use in clients that show a single line for hover. + SingleLine string `json:"singleLine"` + + // SymbolName is the human-readable name to use for the symbol in links. + SymbolName string `json:"symbolName"` + + // LinkPath is the pkg.go.dev link for the given symbol. + // For example, the "go/ast" part of "pkg.go.dev/go/ast#Node". + // It may have a module version suffix "@v1.2.3". + LinkPath string `json:"linkPath"` + + // LinkAnchor is the pkg.go.dev link anchor for the given symbol. + // For example, the "Node" part of "pkg.go.dev/go/ast#Node". + LinkAnchor string `json:"linkAnchor"` + + // stdVersion is the Go release version at which this symbol became available. + // It is nil for non-std library. + stdVersion *stdlib.Version + + // New fields go below, and are unexported. The existing + // exported fields are underspecified and have already + // constrained our movements too much. A detailed JSON + // interface might be nice, but it needs a design and a + // precise specification. + + // typeDecl is the declaration syntax for a type, + // or "" for a non-type. + typeDecl string + + // methods is the list of descriptions of methods of a type, + // omitting any that are obvious from typeDecl. + // It is "" for a non-type. + methods string + + // promotedFields is the list of descriptions of accessible + // fields of a (struct) type that were promoted through an + // embedded field. + promotedFields string +} + +// Hover implements the "textDocument/hover" RPC for Go files. +// It may return nil even on success. +// +// If pkgURL is non-nil, it should be used to generate doc links. +func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position, pkgURL func(path PackagePath, fragment string) protocol.URI) (*protocol.Hover, error) { + ctx, done := event.Start(ctx, "golang.Hover") + defer done() + + rng, h, err := hover(ctx, snapshot, fh, position) + if err != nil { + return nil, err + } + if h == nil { + return nil, nil + } + hover, err := formatHover(h, snapshot.Options(), pkgURL) + if err != nil { + return nil, err + } + return &protocol.Hover{ + Contents: protocol.MarkupContent{ + Kind: snapshot.Options().PreferredContentFormat, + Value: hover, + }, + Range: rng, + }, nil +} + +// hover computes hover information at the given position. If we do not support +// hovering at the position, it returns _, nil, nil: an error is only returned +// if the position is valid but we fail to compute hover information. +// +// TODO(adonovan): strength-reduce file.Handle to protocol.DocumentURI. +func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) (protocol.Range, *hoverJSON, error) { + // Check for hover inside the builtin file before attempting type checking + // below. NarrowestPackageForFile may or may not succeed, depending on + // whether this is a GOROOT view, but even if it does succeed the resulting + // package will be command-line-arguments package. The user should get a + // hover for the builtin object, not the object type checked from the + // builtin.go. + if snapshot.IsBuiltin(fh.URI()) { + pgf, err := snapshot.BuiltinFile(ctx) + if err != nil { + return protocol.Range{}, nil, err + } + pos, err := pgf.PositionPos(pp) + if err != nil { + return protocol.Range{}, nil, err + } + path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos) + if id, ok := path[0].(*ast.Ident); ok { + rng, err := pgf.NodeRange(id) + if err != nil { + return protocol.Range{}, nil, err + } + var obj types.Object + if id.Name == "Error" { + obj = types.Universe.Lookup("error").Type().Underlying().(*types.Interface).Method(0) + } else { + obj = types.Universe.Lookup(id.Name) + } + if obj != nil { + h, err := hoverBuiltin(ctx, snapshot, obj) + return rng, h, err + } + } + return protocol.Range{}, nil, nil // no object to hover + } + + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) + if err != nil { + return protocol.Range{}, nil, err + } + pos, err := pgf.PositionPos(pp) + if err != nil { + return protocol.Range{}, nil, err + } + + // Handle hovering over the package name, which does not have an associated + // object. + // As with import paths, we allow hovering just after the package name. + if pgf.File.Name != nil && gastutil.NodeContains(pgf.File.Name, pos) { + return hoverPackageName(pkg, pgf) + } + + // Handle hovering over embed directive argument. + pattern, embedRng := parseEmbedDirective(pgf.Mapper, pp) + if pattern != "" { + return hoverEmbed(fh, embedRng, pattern) + } + + // hoverRange is the range reported to the client (e.g. for highlighting). + // It may be an expansion around the selected identifier, + // for instance when hovering over a linkname directive or doc link. + var hoverRange *protocol.Range + // Handle linkname directive by overriding what to look for. + if pkgPath, name, offset := parseLinkname(pgf.Mapper, pp); pkgPath != "" && name != "" { + // rng covering 2nd linkname argument: pkgPath.name. + rng, err := pgf.PosRange(pgf.Tok.Pos(offset), pgf.Tok.Pos(offset+len(pkgPath)+len(".")+len(name))) + if err != nil { + return protocol.Range{}, nil, fmt.Errorf("range over linkname arg: %w", err) + } + hoverRange = &rng + + pkg, pgf, pos, err = findLinkname(ctx, snapshot, PackagePath(pkgPath), name) + if err != nil { + return protocol.Range{}, nil, fmt.Errorf("find linkname: %w", err) + } + } + + // Handle hovering over a doc link + if obj, rng, _ := parseDocLink(pkg, pgf, pos); obj != nil { + // Built-ins have no position. + if isBuiltin(obj) { + h, err := hoverBuiltin(ctx, snapshot, obj) + return rng, h, err + } + + // Find position in declaring file. + hoverRange = &rng + objURI := safetoken.StartPosition(pkg.FileSet(), obj.Pos()) + pkg, pgf, err = NarrowestPackageForFile(ctx, snapshot, protocol.URIFromPath(objURI.Filename)) + if err != nil { + return protocol.Range{}, nil, err + } + pos = pgf.Tok.Pos(objURI.Offset) + } + + // Handle hovering over import paths, which do not have an associated + // identifier. + for _, spec := range pgf.File.Imports { + if gastutil.NodeContains(spec, pos) { + rng, hoverJSON, err := hoverImport(ctx, snapshot, pkg, pgf, spec) + if err != nil { + return protocol.Range{}, nil, err + } + if hoverRange == nil { + hoverRange = &rng + } + return *hoverRange, hoverJSON, nil // (hoverJSON may be nil) + } + } + // Handle hovering over (non-import-path) literals. + if path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos); len(path) > 0 { + if lit, _ := path[0].(*ast.BasicLit); lit != nil { + return hoverLit(pgf, lit, pos) + } + } + + // Handle hover over identifier. + + // The general case: compute hover information for the object referenced by + // the identifier at pos. + ident, obj, selectedType := referencedObject(pkg, pgf, pos) + if obj == nil || ident == nil { + return protocol.Range{}, nil, nil // no object to hover + } + + // Unless otherwise specified, rng covers the ident being hovered. + if hoverRange == nil { + rng, err := pgf.NodeRange(ident) + if err != nil { + return protocol.Range{}, nil, err + } + hoverRange = &rng + } + + // By convention, we qualify hover information relative to the package + // from which the request originated. + qf := typesutil.FileQualifier(pgf.File, pkg.Types(), pkg.TypesInfo()) + + // Handle type switch identifiers as a special case, since they don't have an + // object. + // + // There's not much useful information to provide. + if selectedType != nil { + fakeObj := types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), selectedType) + signature := types.ObjectString(fakeObj, qf) + return *hoverRange, &hoverJSON{ + Signature: signature, + SingleLine: signature, + SymbolName: fakeObj.Name(), + }, nil + } + + if isBuiltin(obj) { + // Built-ins have no position. + h, err := hoverBuiltin(ctx, snapshot, obj) + return *hoverRange, h, err + } + + // For all other objects, consider the full syntax of their declaration in + // order to correctly compute their documentation, signature, and link. + // + // Beware: decl{PGF,Pos} are not necessarily associated with pkg.FileSet(). + declPGF, declPos, err := parseFull(ctx, snapshot, pkg.FileSet(), obj.Pos()) + if err != nil { + return protocol.Range{}, nil, fmt.Errorf("re-parsing declaration of %s: %v", obj.Name(), err) + } + decl, spec, field := findDeclInfo([]*ast.File{declPGF.File}, declPos) // may be nil^3 + comment := chooseDocComment(decl, spec, field) + docText := comment.Text() + + // By default, types.ObjectString provides a reasonable signature. + signature := objectString(obj, qf, declPos, declPGF.Tok, spec) + singleLineSignature := signature + + // Display struct tag for struct fields at the end of the signature. + if field != nil && field.Tag != nil { + signature += " " + field.Tag.Value + } + + // TODO(rfindley): we could do much better for inferred signatures. + // TODO(adonovan): fuse the two calls below. + if inferred := inferredSignature(pkg.TypesInfo(), ident); inferred != nil { + if s := inferredSignatureString(obj, qf, inferred); s != "" { + signature = s + } + } + + // Compute size information for types, + // and (size, offset) for struct fields. + // + // Also, if a struct type's field ordering is significantly + // wasteful of space, report its optimal size. + // + // This information is useful when debugging crashes or + // optimizing layout. To reduce distraction, we show it only + // when hovering over the declaring identifier, + // but not referring identifiers. + // + // Size and alignment vary across OS/ARCH. + // Gopls will select the appropriate build configuration when + // viewing a type declaration in a build-tagged file, but will + // use the default build config for all other types, even + // if they embed platform-variant types. + // + var sizeOffset string // optional size/offset description + if def, ok := pkg.TypesInfo().Defs[ident]; ok && ident.Pos() == def.Pos() { + // This is the declaring identifier. + // (We can't simply use ident.Pos() == obj.Pos() because + // referencedObject prefers the TypeName for an embedded field). + + // format returns the decimal and hex representation of x. + format := func(x int64) string { + if x < 10 { + return fmt.Sprintf("%d", x) + } + return fmt.Sprintf("%[1]d (%#[1]x)", x) + } + + path := pathEnclosingObjNode(pgf.File, pos) + + // Build string of form "size=... (X% wasted), offset=...". + size, wasted, offset := computeSizeOffsetInfo(pkg, path, obj) + var buf strings.Builder + if size >= 0 { + fmt.Fprintf(&buf, "size=%s", format(size)) + if wasted >= 20 { // >=20% wasted + fmt.Fprintf(&buf, " (%d%% wasted)", wasted) + } + } + if offset >= 0 { + if buf.Len() > 0 { + buf.WriteString(", ") + } + fmt.Fprintf(&buf, "offset=%s", format(offset)) + } + sizeOffset = buf.String() + } + + var typeDecl, methods, fields string + + // For "objects defined by a type spec", the signature produced by + // objectString is insufficient: + // (1) large structs are formatted poorly, with no newlines + // (2) we lose inline comments + // Furthermore, we include a summary of their method set. + _, isTypeName := obj.(*types.TypeName) + _, isTypeParam := aliases.Unalias(obj.Type()).(*types.TypeParam) + if isTypeName && !isTypeParam { + spec, ok := spec.(*ast.TypeSpec) + if !ok { + // We cannot find a TypeSpec for this type or alias declaration + // (that is not a type parameter or a built-in). + // This should be impossible even for ill-formed trees; + // we suspect that AST repair may be creating inconsistent + // positions. Don't report a bug in that case. (#64241) + errorf := fmt.Errorf + if !declPGF.Fixed() { + errorf = bug.Errorf + } + return protocol.Range{}, nil, errorf("type name %q without type spec", obj.Name()) + } + + // Format the type's declaration syntax. + { + // Don't duplicate comments. + spec2 := *spec + spec2.Doc = nil + spec2.Comment = nil + + var b strings.Builder + b.WriteString("type ") + fset := tokeninternal.FileSetFor(declPGF.Tok) + // TODO(adonovan): use a smarter formatter that omits + // inaccessible fields (non-exported ones from other packages). + if err := format.Node(&b, fset, &spec2); err != nil { + return protocol.Range{}, nil, err + } + typeDecl = b.String() + + // Splice in size/offset at end of first line. + // "type T struct { // size=..." + if sizeOffset != "" { + nl := strings.IndexByte(typeDecl, '\n') + if nl < 0 { + nl = len(typeDecl) + } + typeDecl = typeDecl[:nl] + " // " + sizeOffset + typeDecl[nl:] + } + } + + // Promoted fields + // + // Show a table of accessible fields of the (struct) + // type that may not be visible in the syntax (above) + // due to promotion through embedded fields. + // + // Example: + // + // // Embedded fields: + // foo int // through x.y + // z string // through x.y + if prom := promotedFields(obj.Type(), pkg.Types()); len(prom) > 0 { + var b strings.Builder + b.WriteString("// Embedded fields:\n") + w := tabwriter.NewWriter(&b, 0, 8, 1, ' ', 0) + for _, f := range prom { + fmt.Fprintf(w, "%s\t%s\t// through %s\t\n", + f.field.Name(), + types.TypeString(f.field.Type(), qf), + f.path) + } + w.Flush() + b.WriteByte('\n') + fields = b.String() + } + + // -- methods -- + + // For an interface type, explicit methods will have + // already been displayed when the node was formatted + // above. Don't list these again. + var skip map[string]bool + if iface, ok := spec.Type.(*ast.InterfaceType); ok { + if iface.Methods.List != nil { + for _, m := range iface.Methods.List { + if len(m.Names) == 1 { + if skip == nil { + skip = make(map[string]bool) + } + skip[m.Names[0].Name] = true + } + } + } + } + + // Display all the type's accessible methods, + // including those that require a pointer receiver, + // and those promoted from embedded struct fields or + // embedded interfaces. + var b strings.Builder + for _, m := range typeutil.IntuitiveMethodSet(obj.Type(), nil) { + if !accessibleTo(m.Obj(), pkg.Types()) { + continue // inaccessible + } + if skip[m.Obj().Name()] { + continue // redundant with format.Node above + } + if b.Len() > 0 { + b.WriteByte('\n') + } + + // Use objectString for its prettier rendering of method receivers. + b.WriteString(objectString(m.Obj(), qf, token.NoPos, nil, nil)) + } + methods = b.String() + + signature = typeDecl + "\n" + methods + } else { + // Non-types + if sizeOffset != "" { + signature += " // " + sizeOffset + } + } + + // Compute link data (on pkg.go.dev or other documentation host). + // + // If linkPath is empty, the symbol is not linkable. + var ( + linkName string // => link title, always non-empty + linkPath string // => link path + anchor string // link anchor + linkMeta *metadata.Package // metadata for the linked package + ) + { + linkMeta = findFileInDeps(snapshot, pkg.Metadata(), declPGF.URI) + if linkMeta == nil { + return protocol.Range{}, nil, bug.Errorf("no package data for %s", declPGF.URI) + } + + // For package names, we simply link to their imported package. + if pkgName, ok := obj.(*types.PkgName); ok { + linkName = pkgName.Name() + linkPath = pkgName.Imported().Path() + impID := linkMeta.DepsByPkgPath[PackagePath(pkgName.Imported().Path())] + linkMeta = snapshot.Metadata(impID) + if linkMeta == nil { + // Broken imports have fake package paths, so it is not a bug if we + // don't have metadata. As of writing, there is no way to distinguish + // broken imports from a true bug where expected metadata is missing. + return protocol.Range{}, nil, fmt.Errorf("no package data for %s", declPGF.URI) + } + } else { + // For all others, check whether the object is in the package scope, or + // an exported field or method of an object in the package scope. + // + // We try to match pkgsite's heuristics for what is linkable, and what is + // not. + var recv types.Object + switch obj := obj.(type) { + case *types.Func: + sig := obj.Signature() + if sig.Recv() != nil { + tname := typeToObject(sig.Recv().Type()) + if tname != nil { // beware typed nil + recv = tname + } + } + case *types.Var: + if obj.IsField() { + if spec, ok := spec.(*ast.TypeSpec); ok { + typeName := spec.Name + scopeObj, _ := obj.Pkg().Scope().Lookup(typeName.Name).(*types.TypeName) + if scopeObj != nil { + if st, _ := scopeObj.Type().Underlying().(*types.Struct); st != nil { + for i := 0; i < st.NumFields(); i++ { + if obj == st.Field(i) { + recv = scopeObj + } + } + } + } + } + } + } + + // Even if the object is not available in package documentation, it may + // be embedded in a documented receiver. Detect this by searching + // enclosing selector expressions. + // + // TODO(rfindley): pkgsite doesn't document fields from embedding, just + // methods. + if recv == nil || !recv.Exported() { + path := pathEnclosingObjNode(pgf.File, pos) + if enclosing := searchForEnclosing(pkg.TypesInfo(), path); enclosing != nil { + recv = enclosing + } else { + recv = nil // note: just recv = ... could result in a typed nil. + } + } + + pkg := obj.Pkg() + if recv != nil { + linkName = fmt.Sprintf("(%s.%s).%s", pkg.Name(), recv.Name(), obj.Name()) + if obj.Exported() && recv.Exported() && isPackageLevel(recv) { + linkPath = pkg.Path() + anchor = fmt.Sprintf("%s.%s", recv.Name(), obj.Name()) + } + } else { + linkName = fmt.Sprintf("%s.%s", pkg.Name(), obj.Name()) + if obj.Exported() && isPackageLevel(obj) { + linkPath = pkg.Path() + anchor = obj.Name() + } + } + } + } + + if snapshot.IsGoPrivatePath(linkPath) || linkMeta.ForTest != "" { + linkPath = "" + } else if linkMeta.Module != nil && linkMeta.Module.Version != "" { + mod := linkMeta.Module + linkPath = strings.Replace(linkPath, mod.Path, mod.Path+"@"+mod.Version, 1) + } + + var version *stdlib.Version + if symbol := StdSymbolOf(obj); symbol != nil { + version = &symbol.Version + } + + return *hoverRange, &hoverJSON{ + Synopsis: doc.Synopsis(docText), + FullDocumentation: docText, + SingleLine: singleLineSignature, + SymbolName: linkName, + Signature: signature, + LinkPath: linkPath, + LinkAnchor: anchor, + typeDecl: typeDecl, + methods: methods, + promotedFields: fields, + stdVersion: version, + }, nil +} + +// hoverBuiltin computes hover information when hovering over a builtin +// identifier. +func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) (*hoverJSON, error) { + // Special handling for error.Error, which is the only builtin method. + // + // TODO(rfindley): can this be unified with the handling below? + if obj.Name() == "Error" { + signature := obj.String() + return &hoverJSON{ + Signature: signature, + SingleLine: signature, + // TODO(rfindley): these are better than the current behavior. + // SymbolName: "(error).Error", + // LinkPath: "builtin", + // LinkAnchor: "error.Error", + }, nil + } + + pgf, ident, err := builtinDecl(ctx, snapshot, obj) + if err != nil { + return nil, err + } + + var ( + comment *ast.CommentGroup + decl ast.Decl + ) + path, _ := astutil.PathEnclosingInterval(pgf.File, ident.Pos(), ident.Pos()) + for _, n := range path { + switch n := n.(type) { + case *ast.GenDecl: + // Separate documentation and signature. + comment = n.Doc + node2 := *n + node2.Doc = nil + decl = &node2 + case *ast.FuncDecl: + // Ditto. + comment = n.Doc + node2 := *n + node2.Doc = nil + decl = &node2 + } + } + + signature := formatNodeFile(pgf.Tok, decl) + // Replace fake types with their common equivalent. + // TODO(rfindley): we should instead use obj.Type(), which would have the + // *actual* types of the builtin call. + signature = replacer.Replace(signature) + + docText := comment.Text() + return &hoverJSON{ + Synopsis: doc.Synopsis(docText), + FullDocumentation: docText, + Signature: signature, + SingleLine: obj.String(), + SymbolName: obj.Name(), + LinkPath: "builtin", + LinkAnchor: obj.Name(), + }, nil +} + +// hoverImport computes hover information when hovering over the import path of +// imp in the file pgf of pkg. +// +// If we do not have metadata for the hovered import, it returns _ +func hoverImport(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, imp *ast.ImportSpec) (protocol.Range, *hoverJSON, error) { + rng, err := pgf.NodeRange(imp.Path) + if err != nil { + return protocol.Range{}, nil, err + } + + importPath := metadata.UnquoteImportPath(imp) + if importPath == "" { + return protocol.Range{}, nil, fmt.Errorf("invalid import path") + } + impID := pkg.Metadata().DepsByImpPath[importPath] + if impID == "" { + return protocol.Range{}, nil, fmt.Errorf("no package data for import %q", importPath) + } + impMetadata := snapshot.Metadata(impID) + if impMetadata == nil { + return protocol.Range{}, nil, bug.Errorf("failed to resolve import ID %q", impID) + } + + // Find the first file with a package doc comment. + var comment *ast.CommentGroup + for _, f := range impMetadata.CompiledGoFiles { + fh, err := snapshot.ReadFile(ctx, f) + if err != nil { + if ctx.Err() != nil { + return protocol.Range{}, nil, ctx.Err() + } + continue + } + pgf, err := snapshot.ParseGo(ctx, fh, parsego.Header) + if err != nil { + if ctx.Err() != nil { + return protocol.Range{}, nil, ctx.Err() + } + continue + } + if pgf.File.Doc != nil { + comment = pgf.File.Doc + break + } + } + + docText := comment.Text() + return rng, &hoverJSON{ + Synopsis: doc.Synopsis(docText), + FullDocumentation: docText, + }, nil +} + +// hoverPackageName computes hover information for the package name of the file +// pgf in pkg. +func hoverPackageName(pkg *cache.Package, pgf *parsego.File) (protocol.Range, *hoverJSON, error) { + var comment *ast.CommentGroup + for _, pgf := range pkg.CompiledGoFiles() { + if pgf.File.Doc != nil { + comment = pgf.File.Doc + break + } + } + rng, err := pgf.NodeRange(pgf.File.Name) + if err != nil { + return protocol.Range{}, nil, err + } + docText := comment.Text() + return rng, &hoverJSON{ + Synopsis: doc.Synopsis(docText), + FullDocumentation: docText, + // Note: including a signature is redundant, since the cursor is already on the + // package name. + }, nil +} + +// hoverLit computes hover information when hovering over the basic literal lit +// in the file pgf. The provided pos must be the exact position of the cursor, +// as it is used to extract the hovered rune in strings. +// +// For example, hovering over "\u2211" in "foo \u2211 bar" yields: +// +// '∑', U+2211, N-ARY SUMMATION +func hoverLit(pgf *parsego.File, lit *ast.BasicLit, pos token.Pos) (protocol.Range, *hoverJSON, error) { + var ( + value string // if non-empty, a constant value to format in hover + r rune // if non-zero, format a description of this rune in hover + start, end token.Pos // hover span + ) + // Extract a rune from the current position. + // 'Ω', "...Ω...", or 0x03A9 => 'Ω', U+03A9, GREEK CAPITAL LETTER OMEGA + switch lit.Kind { + case token.CHAR: + s, err := strconv.Unquote(lit.Value) + if err != nil { + // If the conversion fails, it's because of an invalid syntax, therefore + // there is no rune to be found. + return protocol.Range{}, nil, nil + } + r, _ = utf8.DecodeRuneInString(s) + if r == utf8.RuneError { + return protocol.Range{}, nil, fmt.Errorf("rune error") + } + start, end = lit.Pos(), lit.End() + + case token.INT: + // Short literals (e.g. 99 decimal, 07 octal) are uninteresting. + if len(lit.Value) < 3 { + return protocol.Range{}, nil, nil + } + + v := constant.MakeFromLiteral(lit.Value, lit.Kind, 0) + if v.Kind() != constant.Int { + return protocol.Range{}, nil, nil + } + + switch lit.Value[:2] { + case "0x", "0X": + // As a special case, try to recognize hexadecimal literals as runes if + // they are within the range of valid unicode values. + if v, ok := constant.Int64Val(v); ok && v > 0 && v <= utf8.MaxRune && utf8.ValidRune(rune(v)) { + r = rune(v) + } + fallthrough + case "0o", "0O", "0b", "0B": + // Format the decimal value of non-decimal literals. + value = v.ExactString() + start, end = lit.Pos(), lit.End() + default: + return protocol.Range{}, nil, nil + } + + case token.STRING: + // It's a string, scan only if it contains a unicode escape sequence under or before the + // current cursor position. + litOffset, err := safetoken.Offset(pgf.Tok, lit.Pos()) + if err != nil { + return protocol.Range{}, nil, err + } + offset, err := safetoken.Offset(pgf.Tok, pos) + if err != nil { + return protocol.Range{}, nil, err + } + for i := offset - litOffset; i > 0; i-- { + // Start at the cursor position and search backward for the beginning of a rune escape sequence. + rr, _ := utf8.DecodeRuneInString(lit.Value[i:]) + if rr == utf8.RuneError { + return protocol.Range{}, nil, fmt.Errorf("rune error") + } + if rr == '\\' { + // Got the beginning, decode it. + var tail string + r, _, tail, err = strconv.UnquoteChar(lit.Value[i:], '"') + if err != nil { + // If the conversion fails, it's because of an invalid syntax, + // therefore is no rune to be found. + return protocol.Range{}, nil, nil + } + // Only the rune escape sequence part of the string has to be highlighted, recompute the range. + runeLen := len(lit.Value) - (i + len(tail)) + start = token.Pos(int(lit.Pos()) + i) + end = token.Pos(int(start) + runeLen) + break + } + } + } + + if value == "" && r == 0 { // nothing to format + return protocol.Range{}, nil, nil + } + + rng, err := pgf.PosRange(start, end) + if err != nil { + return protocol.Range{}, nil, err + } + + var b strings.Builder + if value != "" { + b.WriteString(value) + } + if r != 0 { + runeName := runenames.Name(r) + if len(runeName) > 0 && runeName[0] == '<' { + // Check if the rune looks like an HTML tag. If so, trim the surrounding <> + // characters to work around https://github.com/microsoft/vscode/issues/124042. + runeName = strings.TrimRight(runeName[1:], ">") + } + if b.Len() > 0 { + b.WriteString(", ") + } + if strconv.IsPrint(r) { + fmt.Fprintf(&b, "'%c', ", r) + } + fmt.Fprintf(&b, "U+%04X, %s", r, runeName) + } + hover := b.String() + return rng, &hoverJSON{ + Synopsis: hover, + FullDocumentation: hover, + }, nil +} + +// hoverEmbed computes hover information for a filepath.Match pattern. +// Assumes that the pattern is relative to the location of fh. +func hoverEmbed(fh file.Handle, rng protocol.Range, pattern string) (protocol.Range, *hoverJSON, error) { + s := &strings.Builder{} + + dir := filepath.Dir(fh.URI().Path()) + var matches []string + err := filepath.WalkDir(dir, func(abs string, d fs.DirEntry, e error) error { + if e != nil { + return e + } + rel, err := filepath.Rel(dir, abs) + if err != nil { + return err + } + ok, err := filepath.Match(pattern, rel) + if err != nil { + return err + } + if ok && !d.IsDir() { + matches = append(matches, rel) + } + return nil + }) + if err != nil { + return protocol.Range{}, nil, err + } + + for _, m := range matches { + // TODO: Renders each file as separate markdown paragraphs. + // If forcing (a single) newline is possible it might be more clear. + fmt.Fprintf(s, "%s\n\n", m) + } + + json := &hoverJSON{ + Signature: fmt.Sprintf("Embedding %q", pattern), + Synopsis: s.String(), + FullDocumentation: s.String(), + } + return rng, json, nil +} + +// inferredSignatureString is a wrapper around the types.ObjectString function +// that adds more information to inferred signatures. It will return an empty string +// if the passed types.Object is not a signature. +func inferredSignatureString(obj types.Object, qf types.Qualifier, inferred *types.Signature) string { + // If the signature type was inferred, prefer the inferred signature with a + // comment showing the generic signature. + if sig, _ := obj.Type().Underlying().(*types.Signature); sig != nil && sig.TypeParams().Len() > 0 && inferred != nil { + obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred) + str := types.ObjectString(obj2, qf) + // Try to avoid overly long lines. + if len(str) > 60 { + str += "\n" + } else { + str += " " + } + str += "// " + types.TypeString(sig, qf) + return str + } + return "" +} + +// objectString is a wrapper around the types.ObjectString function. +// It handles adding more information to the object string. +// If spec is non-nil, it may be used to format additional declaration +// syntax, and file must be the token.File describing its positions. +// +// Precondition: obj is not a built-in function or method. +func objectString(obj types.Object, qf types.Qualifier, declPos token.Pos, file *token.File, spec ast.Spec) string { + str := types.ObjectString(obj, qf) + + switch obj := obj.(type) { + case *types.Func: + // We fork ObjectString to improve its rendering of methods: + // specifically, we show the receiver name, + // and replace the period in (T).f by a space (#62190). + + sig := obj.Signature() + + var buf bytes.Buffer + buf.WriteString("func ") + if recv := sig.Recv(); recv != nil { + buf.WriteByte('(') + if _, ok := recv.Type().(*types.Interface); ok { + // gcimporter creates abstract methods of + // named interfaces using the interface type + // (not the named type) as the receiver. + // Don't print it in full. + buf.WriteString("interface") + } else { + // Show receiver name (go/types does not). + name := recv.Name() + if name != "" && name != "_" { + buf.WriteString(name) + buf.WriteString(" ") + } + types.WriteType(&buf, recv.Type(), qf) + } + buf.WriteByte(')') + buf.WriteByte(' ') // space (go/types uses a period) + } else if s := qf(obj.Pkg()); s != "" { + buf.WriteString(s) + buf.WriteString(".") + } + buf.WriteString(obj.Name()) + types.WriteSignature(&buf, sig, qf) + str = buf.String() + + case *types.Const: + // Show value of a constant. + var ( + declaration = obj.Val().String() // default formatted declaration + comment = "" // if non-empty, a clarifying comment + ) + + // Try to use the original declaration. + switch obj.Val().Kind() { + case constant.String: + // Usually the original declaration of a string doesn't carry much information. + // Also strings can be very long. So, just use the constant's value. + + default: + if spec, _ := spec.(*ast.ValueSpec); spec != nil { + for i, name := range spec.Names { + if declPos == name.Pos() { + if i < len(spec.Values) { + originalDeclaration := formatNodeFile(file, spec.Values[i]) + if originalDeclaration != declaration { + comment = declaration + declaration = originalDeclaration + } + } + break + } + } + } + } + + // Special formatting cases. + switch typ := aliases.Unalias(obj.Type()).(type) { + case *types.Named: + // Try to add a formatted duration as an inline comment. + pkg := typ.Obj().Pkg() + if pkg.Path() == "time" && typ.Obj().Name() == "Duration" && obj.Val().Kind() == constant.Int { + if d, ok := constant.Int64Val(obj.Val()); ok { + comment = time.Duration(d).String() + } + } + } + if comment == declaration { + comment = "" + } + + str += " = " + declaration + if comment != "" { + str += " // " + comment + } + } + return str +} + +// HoverDocForObject returns the best doc comment for obj (for which +// fset provides file/line information). +// +// TODO(rfindley): there appears to be zero(!) tests for this functionality. +func HoverDocForObject(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, obj types.Object) (*ast.CommentGroup, error) { + if is[*types.TypeName](obj) && is[*types.TypeParam](obj.Type()) { + return nil, nil + } + + pgf, pos, err := parseFull(ctx, snapshot, fset, obj.Pos()) + if err != nil { + return nil, fmt.Errorf("re-parsing: %v", err) + } + + decl, spec, field := findDeclInfo([]*ast.File{pgf.File}, pos) + return chooseDocComment(decl, spec, field), nil +} + +func chooseDocComment(decl ast.Decl, spec ast.Spec, field *ast.Field) *ast.CommentGroup { + if field != nil { + if field.Doc != nil { + return field.Doc + } + if field.Comment != nil { + return field.Comment + } + return nil + } + switch decl := decl.(type) { + case *ast.FuncDecl: + return decl.Doc + case *ast.GenDecl: + switch spec := spec.(type) { + case *ast.ValueSpec: + if spec.Doc != nil { + return spec.Doc + } + if decl.Doc != nil { + return decl.Doc + } + return spec.Comment + case *ast.TypeSpec: + if spec.Doc != nil { + return spec.Doc + } + if decl.Doc != nil { + return decl.Doc + } + return spec.Comment + } + } + return nil +} + +// parseFull fully parses the file corresponding to position pos (for +// which fset provides file/line information). +// +// It returns the resulting parsego.File as well as new pos contained +// in the parsed file. +// +// BEWARE: the provided FileSet is used only to interpret the provided +// pos; the resulting File and Pos may belong to the same or a +// different FileSet, such as one synthesized by the parser cache, if +// parse-caching is enabled. +func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, pos token.Pos) (*parsego.File, token.Pos, error) { + f := fset.File(pos) + if f == nil { + return nil, 0, bug.Errorf("internal error: no file for position %d", pos) + } + + uri := protocol.URIFromPath(f.Name()) + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return nil, 0, err + } + + pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full) + if err != nil { + return nil, 0, err + } + + offset, err := safetoken.Offset(f, pos) + if err != nil { + return nil, 0, bug.Errorf("offset out of bounds in %q", uri) + } + + fullPos, err := safetoken.Pos(pgf.Tok, offset) + if err != nil { + return nil, 0, err + } + + return pgf, fullPos, nil +} + +// If pkgURL is non-nil, it should be used to generate doc links. +func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) (string, error) { + maybeMarkdown := func(s string) string { + if s != "" && options.PreferredContentFormat == protocol.Markdown { + s = fmt.Sprintf("```go\n%s\n```", strings.Trim(s, "\n")) + } + return s + } + + switch options.HoverKind { + case settings.SingleLine: + return h.SingleLine, nil + + case settings.NoDocumentation: + return maybeMarkdown(h.Signature), nil + + case settings.Structured: + b, err := json.Marshal(h) + if err != nil { + return "", err + } + return string(b), nil + + case settings.SynopsisDocumentation, + settings.FullDocumentation: + // For types, we display TypeDecl and Methods, + // but not Signature, which is redundant (= TypeDecl + "\n" + Methods). + // For all other symbols, we display Signature; + // TypeDecl and Methods are empty. + // (This awkwardness is to preserve JSON compatibility.) + parts := []string{ + maybeMarkdown(h.Signature), + maybeMarkdown(h.typeDecl), + formatDoc(h, options), + maybeMarkdown(h.promotedFields), + maybeMarkdown(h.methods), + fmt.Sprintf("Added in %v", h.stdVersion), + formatLink(h, options, pkgURL), + } + if h.typeDecl != "" { + parts[0] = "" // type: suppress redundant Signature + } + if h.stdVersion == nil || *h.stdVersion == stdlib.Version(0) { + parts[5] = "" // suppress stdlib version if not applicable or initial version 1.0 + } + + var b strings.Builder + for _, part := range parts { + if part == "" { + continue + } + if b.Len() > 0 { + if options.PreferredContentFormat == protocol.Markdown { + b.WriteString("\n\n") + } else { + b.WriteByte('\n') + } + } + b.WriteString(part) + } + return b.String(), nil + + default: + return "", fmt.Errorf("invalid HoverKind: %v", options.HoverKind) + } +} + +// StdSymbolOf returns the std lib symbol information of the given obj. +// It returns nil if the input obj is not an exported standard library symbol. +func StdSymbolOf(obj types.Object) *stdlib.Symbol { + if !obj.Exported() || obj.Pkg() == nil { + return nil + } + + // Symbols that not defined in standard library should return early. + // TODO(hxjiang): The returned slices is binary searchable. + symbols := stdlib.PackageSymbols[obj.Pkg().Path()] + if symbols == nil { + return nil + } + + // Handle Function, Type, Const & Var. + if isPackageLevel(obj) { + for _, s := range symbols { + if s.Kind == stdlib.Method || s.Kind == stdlib.Field { + continue + } + if s.Name == obj.Name() { + return &s + } + } + return nil + } + + // Handle Method. + if fn, _ := obj.(*types.Func); fn != nil { + isPtr, named := typesinternal.ReceiverNamed(fn.Signature().Recv()) + if isPackageLevel(named.Obj()) { + for _, s := range symbols { + if s.Kind != stdlib.Method { + continue + } + ptr, recv, name := s.SplitMethod() + if ptr == isPtr && recv == named.Obj().Name() && name == fn.Name() { + return &s + } + } + return nil + } + } + + // Handle Field. + if v, _ := obj.(*types.Var); v != nil && v.IsField() { + for _, s := range symbols { + if s.Kind != stdlib.Field { + continue + } + + typeName, fieldName := s.SplitField() + if fieldName != v.Name() { + continue + } + + typeObj := obj.Pkg().Scope().Lookup(typeName) + if typeObj == nil { + continue + } + + if fieldObj, _, _ := types.LookupFieldOrMethod(typeObj.Type(), true, obj.Pkg(), fieldName); obj == fieldObj { + return &s + } + } + return nil + } + + return nil +} + +// If pkgURL is non-nil, it should be used to generate doc links. +func formatLink(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) string { + if options.LinksInHover == settings.LinksInHover_None || h.LinkPath == "" { + return "" + } + var url protocol.URI + var caption string + if pkgURL != nil { // LinksInHover == "gopls" + path, _, _ := strings.Cut(h.LinkPath, "@") // remove optional module version suffix + url = pkgURL(PackagePath(path), h.LinkAnchor) + caption = "in gopls doc viewer" + } else { + if options.LinkTarget == "" { + return "" + } + url = cache.BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor) + caption = "on " + options.LinkTarget + } + switch options.PreferredContentFormat { + case protocol.Markdown: + return fmt.Sprintf("[`%s` %s](%s)", h.SymbolName, caption, url) + case protocol.PlainText: + return "" + default: + return url + } +} + +func formatDoc(h *hoverJSON, options *settings.Options) string { + var doc string + switch options.HoverKind { + case settings.SynopsisDocumentation: + doc = h.Synopsis + case settings.FullDocumentation: + doc = h.FullDocumentation + } + if options.PreferredContentFormat == protocol.Markdown { + return CommentToMarkdown(doc, options) + } + return doc +} + +// findDeclInfo returns the syntax nodes involved in the declaration of the +// types.Object with position pos, searching the given list of file syntax +// trees. +// +// Pos may be the position of the name-defining identifier in a FuncDecl, +// ValueSpec, TypeSpec, Field, or as a special case the position of +// Ellipsis.Elt in an ellipsis field. +// +// If found, the resulting decl, spec, and field will be the inner-most +// instance of each node type surrounding pos. +// +// If field is non-nil, pos is the position of a field Var. If field is nil and +// spec is non-nil, pos is the position of a Var, Const, or TypeName object. If +// both field and spec are nil and decl is non-nil, pos is the position of a +// Func object. +// +// It returns a nil decl if no object-defining node is found at pos. +// +// TODO(rfindley): this function has tricky semantics, and may be worth unit +// testing and/or refactoring. +func findDeclInfo(files []*ast.File, pos token.Pos) (decl ast.Decl, spec ast.Spec, field *ast.Field) { + found := false + + // Visit the files in search of the node at pos. + stack := make([]ast.Node, 0, 20) + + // Allocate the closure once, outside the loop. + f := func(n ast.Node) bool { + if found { + return false + } + if n != nil { + stack = append(stack, n) // push + } else { + stack = stack[:len(stack)-1] // pop + return false + } + + // Skip subtrees (incl. files) that don't contain the search point. + if !(n.Pos() <= pos && pos < n.End()) { + return false + } + + switch n := n.(type) { + case *ast.Field: + findEnclosingDeclAndSpec := func() { + for i := len(stack) - 1; i >= 0; i-- { + switch n := stack[i].(type) { + case ast.Spec: + spec = n + case ast.Decl: + decl = n + return + } + } + } + + // Check each field name since you can have + // multiple names for the same type expression. + for _, id := range n.Names { + if id.Pos() == pos { + field = n + findEnclosingDeclAndSpec() + found = true + return false + } + } + + // Check *ast.Field itself. This handles embedded + // fields which have no associated *ast.Ident name. + if n.Pos() == pos { + field = n + findEnclosingDeclAndSpec() + found = true + return false + } + + // Also check "X" in "...X". This makes it easy to format variadic + // signature params properly. + // + // TODO(rfindley): I don't understand this comment. How does finding the + // field in this case make it easier to format variadic signature params? + if ell, ok := n.Type.(*ast.Ellipsis); ok && ell.Elt != nil && ell.Elt.Pos() == pos { + field = n + findEnclosingDeclAndSpec() + found = true + return false + } + + case *ast.FuncDecl: + if n.Name.Pos() == pos { + decl = n + found = true + return false + } + + case *ast.GenDecl: + for _, s := range n.Specs { + switch s := s.(type) { + case *ast.TypeSpec: + if s.Name.Pos() == pos { + decl = n + spec = s + found = true + return false + } + case *ast.ValueSpec: + for _, id := range s.Names { + if id.Pos() == pos { + decl = n + spec = s + found = true + return false + } + } + } + } + } + return true + } + for _, file := range files { + ast.Inspect(file, f) + if found { + return decl, spec, field + } + } + + return nil, nil, nil +} + +type promotedField struct { + path string // path (e.g. "x.y" through embedded fields) + field *types.Var +} + +// promotedFields returns the list of accessible promoted fields of a struct type t. +// (Logic plundered from x/tools/cmd/guru/describe.go.) +func promotedFields(t types.Type, from *types.Package) []promotedField { + wantField := func(f *types.Var) bool { + if !accessibleTo(f, from) { + return false + } + // Check that the field is not shadowed. + obj, _, _ := types.LookupFieldOrMethod(t, true, f.Pkg(), f.Name()) + return obj == f + } + + var fields []promotedField + var visit func(t types.Type, stack []*types.Named) + visit = func(t types.Type, stack []*types.Named) { + tStruct, ok := typesinternal.Unpointer(t).Underlying().(*types.Struct) + if !ok { + return + } + fieldloop: + for i := 0; i < tStruct.NumFields(); i++ { + f := tStruct.Field(i) + + // Handle recursion through anonymous fields. + if f.Anonymous() { + if _, named := typesinternal.ReceiverNamed(f); named != nil { + // If we've already visited this named type + // on this path, break the cycle. + for _, x := range stack { + if x.Origin() == named.Origin() { + continue fieldloop + } + } + visit(f.Type(), append(stack, named)) + } + } + + // Save accessible promoted fields. + if len(stack) > 0 && wantField(f) { + var path strings.Builder + for i, t := range stack { + if i > 0 { + path.WriteByte('.') + } + path.WriteString(t.Obj().Name()) + } + fields = append(fields, promotedField{ + path: path.String(), + field: f, + }) + } + } + } + visit(t, nil) + + return fields +} + +func accessibleTo(obj types.Object, pkg *types.Package) bool { + return obj.Exported() || obj.Pkg() == pkg +} + +// computeSizeOffsetInfo reports the size of obj (if a type or struct +// field), its wasted space percentage (if a struct type), and its +// offset (if a struct field). It returns -1 for undefined components. +func computeSizeOffsetInfo(pkg *cache.Package, path []ast.Node, obj types.Object) (size, wasted, offset int64) { + size, wasted, offset = -1, -1, -1 + + var free typeparams.Free + sizes := pkg.TypesSizes() + + // size (types and fields) + if v, ok := obj.(*types.Var); ok && v.IsField() || is[*types.TypeName](obj) { + // If the field's type has free type parameters, + // its size cannot be computed. + if !free.Has(obj.Type()) { + size = sizes.Sizeof(obj.Type()) + } + + // wasted space (struct types) + if tStruct, ok := obj.Type().Underlying().(*types.Struct); ok && is[*types.TypeName](obj) && size > 0 { + var fields []*types.Var + for i := 0; i < tStruct.NumFields(); i++ { + fields = append(fields, tStruct.Field(i)) + } + if len(fields) > 0 { + // Sort into descending (most compact) order + // and recompute size of entire struct. + sort.Slice(fields, func(i, j int) bool { + return sizes.Sizeof(fields[i].Type()) > + sizes.Sizeof(fields[j].Type()) + }) + offsets := sizes.Offsetsof(fields) + compactSize := offsets[len(offsets)-1] + sizes.Sizeof(fields[len(fields)-1].Type()) + wasted = 100 * (size - compactSize) / size + } + } + } + + // offset (fields) + if v, ok := obj.(*types.Var); ok && v.IsField() { + // Find enclosing struct type. + var tStruct *types.Struct + for _, n := range path { + if n, ok := n.(*ast.StructType); ok { + tStruct = pkg.TypesInfo().TypeOf(n).(*types.Struct) + break + } + } + if tStruct != nil { + var fields []*types.Var + for i := 0; i < tStruct.NumFields(); i++ { + f := tStruct.Field(i) + // If any preceding field's type has free type parameters, + // its offset cannot be computed. + if free.Has(f.Type()) { + break + } + fields = append(fields, f) + if f == v { + offsets := sizes.Offsetsof(fields) + offset = offsets[len(offsets)-1] + break + } + } + } + } + + return +} diff --git a/contribs/gnopls/internal/golang/identifier.go b/contribs/gnopls/internal/golang/identifier.go new file mode 100644 index 00000000000..30a83d3a05a --- /dev/null +++ b/contribs/gnopls/internal/golang/identifier.go @@ -0,0 +1,183 @@ +// Copyright 2018 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 golang + +import ( + "errors" + "go/ast" + "go/types" + + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/typesinternal" +) + +// ErrNoIdentFound is error returned when no identifier is found at a particular position +var ErrNoIdentFound = errors.New("no identifier found") + +// inferredSignature determines the resolved non-generic signature for an +// identifier in an instantiation expression. +// +// If no such signature exists, it returns nil. +func inferredSignature(info *types.Info, id *ast.Ident) *types.Signature { + inst := info.Instances[id] + sig, _ := aliases.Unalias(inst.Type).(*types.Signature) + return sig +} + +// searchForEnclosing returns, given the AST path to a SelectorExpr, +// the exported named type of the innermost implicit field selection. +// +// For example, given "new(A).d" where this is (due to embedding) a +// shorthand for "new(A).b.c.d", it returns the named type of c, +// if it is exported, otherwise the type of b, or A. +func searchForEnclosing(info *types.Info, path []ast.Node) *types.TypeName { + for _, n := range path { + switch n := n.(type) { + case *ast.SelectorExpr: + if sel, ok := info.Selections[n]; ok { + recv := typesinternal.Unpointer(sel.Recv()) + + // Keep track of the last exported type seen. + var exported *types.TypeName + if named, ok := aliases.Unalias(recv).(*types.Named); ok && named.Obj().Exported() { + exported = named.Obj() + } + // We don't want the last element, as that's the field or + // method itself. + for _, index := range sel.Index()[:len(sel.Index())-1] { + if r, ok := recv.Underlying().(*types.Struct); ok { + recv = typesinternal.Unpointer(r.Field(index).Type()) + if named, ok := aliases.Unalias(recv).(*types.Named); ok && named.Obj().Exported() { + exported = named.Obj() + } + } + } + return exported + } + } + } + return nil +} + +// typeToObject returns the relevant type name for the given type, after +// unwrapping pointers, arrays, slices, channels, and function signatures with +// a single non-error result, and ignoring built-in named types. +func typeToObject(typ types.Type) *types.TypeName { + switch typ := typ.(type) { + case *aliases.Alias: + return typ.Obj() + case *types.Named: + // TODO(rfindley): this should use typeparams.NamedTypeOrigin. + return typ.Obj() + case *types.Pointer: + return typeToObject(typ.Elem()) + case *types.Array: + return typeToObject(typ.Elem()) + case *types.Slice: + return typeToObject(typ.Elem()) + case *types.Chan: + return typeToObject(typ.Elem()) + case *types.Signature: + // Try to find a return value of a named type. If there's only one + // such value, jump to its type definition. + var res *types.TypeName + + results := typ.Results() + for i := 0; i < results.Len(); i++ { + obj := typeToObject(results.At(i).Type()) + if obj == nil || hasErrorType(obj) { + // Skip builtins. TODO(rfindley): should comparable be handled here as well? + continue + } + if res != nil { + // The function/method must have only one return value of a named type. + return nil + } + + res = obj + } + return res + default: + return nil + } +} + +func hasErrorType(obj types.Object) bool { + return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error" +} + +// typeSwitchImplicits returns all the implicit type switch objects that +// correspond to the leaf *ast.Ident. It also returns the original type +// associated with the identifier (outside of a case clause). +func typeSwitchImplicits(info *types.Info, path []ast.Node) ([]types.Object, types.Type) { + ident, _ := path[0].(*ast.Ident) + if ident == nil { + return nil, nil + } + + var ( + ts *ast.TypeSwitchStmt + assign *ast.AssignStmt + cc *ast.CaseClause + obj = info.ObjectOf(ident) + ) + + // Walk our ancestors to determine if our leaf ident refers to a + // type switch variable, e.g. the "a" from "switch a := b.(type)". +Outer: + for i := 1; i < len(path); i++ { + switch n := path[i].(type) { + case *ast.AssignStmt: + // Check if ident is the "a" in "a := foo.(type)". The "a" in + // this case has no types.Object, so check for ident equality. + if len(n.Lhs) == 1 && n.Lhs[0] == ident { + assign = n + } + case *ast.CaseClause: + // Check if ident is a use of "a" within a case clause. Each + // case clause implicitly maps "a" to a different types.Object, + // so check if ident's object is the case clause's implicit + // object. + if obj != nil && info.Implicits[n] == obj { + cc = n + } + case *ast.TypeSwitchStmt: + // Look for the type switch that owns our previously found + // *ast.AssignStmt or *ast.CaseClause. + if n.Assign == assign { + ts = n + break Outer + } + + for _, stmt := range n.Body.List { + if stmt == cc { + ts = n + break Outer + } + } + } + } + if ts == nil { + return nil, nil + } + // Our leaf ident refers to a type switch variable. Fan out to the + // type switch's implicit case clause objects. + var objs []types.Object + for _, cc := range ts.Body.List { + if ccObj := info.Implicits[cc]; ccObj != nil { + objs = append(objs, ccObj) + } + } + // The right-hand side of a type switch should only have one + // element, and we need to track its type in order to generate + // hover information for implicit type switch variables. + var typ types.Type + if assign, ok := ts.Assign.(*ast.AssignStmt); ok && len(assign.Rhs) == 1 { + if rhs := assign.Rhs[0].(*ast.TypeAssertExpr); ok { + typ = info.TypeOf(rhs.X) + } + } + return objs, typ +} diff --git a/contribs/gnopls/internal/golang/identifier_test.go b/contribs/gnopls/internal/golang/identifier_test.go new file mode 100644 index 00000000000..b1e6d5a75a2 --- /dev/null +++ b/contribs/gnopls/internal/golang/identifier_test.go @@ -0,0 +1,107 @@ +// 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 golang + +import ( + "bytes" + "go/ast" + "go/parser" + "go/token" + "go/types" + "testing" + + "golang.org/x/tools/internal/versions" +) + +func TestSearchForEnclosing(t *testing.T) { + tests := []struct { + desc string + // For convenience, consider the first occurrence of the identifier "X" in + // src. + src string + // By convention, "" means no type found. + wantTypeName string + }{ + { + // TODO(rFindley): is this correct, or do we want to resolve I2 here? + desc: "embedded interface in interface", + src: `package a; var y = i1.X; type i1 interface {I2}; type I2 interface{X()}`, + wantTypeName: "", + }, + { + desc: "embedded interface in struct", + src: `package a; var y = t.X; type t struct {I}; type I interface{X()}`, + wantTypeName: "I", + }, + { + desc: "double embedding", + src: `package a; var y = t1.X; type t1 struct {t2}; type t2 struct {I}; type I interface{X()}`, + wantTypeName: "I", + }, + } + + for _, test := range tests { + test := test + t.Run(test.desc, func(t *testing.T) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "a.go", test.src, parser.AllErrors) + if err != nil { + t.Fatal(err) + } + column := 1 + bytes.IndexRune([]byte(test.src), 'X') + pos := posAt(1, column, fset, "a.go") + path := pathEnclosingObjNode(file, pos) + if path == nil { + t.Fatalf("no ident found at (1, %d)", column) + } + info := newInfo() + if _, err = (*types.Config)(nil).Check("p", fset, []*ast.File{file}, info); err != nil { + t.Fatal(err) + } + obj := searchForEnclosing(info, path) + if obj == nil { + if test.wantTypeName != "" { + t.Errorf("searchForEnclosing(...) = , want %q", test.wantTypeName) + } + return + } + if got := obj.Name(); got != test.wantTypeName { + t.Errorf("searchForEnclosing(...) = %q, want %q", got, test.wantTypeName) + } + }) + } +} + +// posAt returns the token.Pos corresponding to the 1-based (line, column) +// coordinates in the file fname of fset. +func posAt(line, column int, fset *token.FileSet, fname string) token.Pos { + var tok *token.File + fset.Iterate(func(tf *token.File) bool { + if tf.Name() == fname { + tok = tf + return false + } + return true + }) + if tok == nil { + return token.NoPos + } + start := tok.LineStart(line) + return start + token.Pos(column-1) +} + +// newInfo returns a types.Info with all maps populated. +func newInfo() *types.Info { + info := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + } + versions.InitFileVersions(info) + return info +} diff --git a/contribs/gnopls/internal/golang/implementation.go b/contribs/gnopls/internal/golang/implementation.go new file mode 100644 index 00000000000..b3accff452f --- /dev/null +++ b/contribs/gnopls/internal/golang/implementation.go @@ -0,0 +1,543 @@ +// 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. + +package golang + +import ( + "context" + "errors" + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" + "sort" + "strings" + "sync" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/methodsets" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/event" +) + +// This file defines the new implementation of the 'implementation' +// operator that does not require type-checker data structures for an +// unbounded number of packages. +// +// TODO(adonovan): +// - Audit to ensure robustness in face of type errors. +// - Eliminate false positives due to 'tricky' cases of the global algorithm. +// - Ensure we have test coverage of: +// type aliases +// nil, PkgName, Builtin (all errors) +// any (empty result) +// method of unnamed interface type (e.g. var x interface { f() }) +// (the global algorithm may find implementations of this type +// but will not include it in the index.) + +// Implementation returns a new sorted array of locations of +// declarations of types that implement (or are implemented by) the +// type referred to at the given position. +// +// If the position denotes a method, the computation is applied to its +// receiver type and then its corresponding methods are returned. +func Implementation(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position) ([]protocol.Location, error) { + ctx, done := event.Start(ctx, "golang.Implementation") + defer done() + + locs, err := implementations(ctx, snapshot, f, pp) + if err != nil { + return nil, err + } + + // Sort and de-duplicate locations. + sort.Slice(locs, func(i, j int) bool { + return protocol.CompareLocation(locs[i], locs[j]) < 0 + }) + out := locs[:0] + for _, loc := range locs { + if len(out) == 0 || out[len(out)-1] != loc { + out = append(out, loc) + } + } + locs = out + + return locs, nil +} + +func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.Location, error) { + // First, find the object referenced at the cursor by type checking the + // current package. + obj, pkg, err := implementsObj(ctx, snapshot, fh.URI(), pp) + if err != nil { + return nil, err + } + + // If the resulting object has a position, we can expand the search to types + // in the declaring package(s). In this case, we must re-type check these + // packages in the same realm. + var ( + declOffset int + declURI protocol.DocumentURI + localPkgs []*cache.Package + ) + if obj.Pos().IsValid() { // no local package for error or error.Error + declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos()) + declOffset = declPosn.Offset + // Type-check the declaring package (incl. variants) for use + // by the "local" search, which uses type information to + // enumerate all types within the package that satisfy the + // query type, even those defined local to a function. + declURI = protocol.URIFromPath(declPosn.Filename) + declMPs, err := snapshot.MetadataForFile(ctx, declURI) + if err != nil { + return nil, err + } + metadata.RemoveIntermediateTestVariants(&declMPs) + if len(declMPs) == 0 { + return nil, fmt.Errorf("no packages for file %s", declURI) + } + ids := make([]PackageID, len(declMPs)) + for i, mp := range declMPs { + ids[i] = mp.ID + } + localPkgs, err = snapshot.TypeCheck(ctx, ids...) + if err != nil { + return nil, err + } + } + + pkg = nil // no longer used + + // Is the selected identifier a type name or method? + // (For methods, report the corresponding method names.) + // + // This logic is reused for local queries. + typeOrMethod := func(obj types.Object) (types.Type, string) { + switch obj := obj.(type) { + case *types.TypeName: + return obj.Type(), "" + case *types.Func: + // For methods, use the receiver type, which may be anonymous. + if recv := obj.Signature().Recv(); recv != nil { + return recv.Type(), obj.Id() + } + } + return nil, "" + } + queryType, queryMethodID := typeOrMethod(obj) + if queryType == nil { + return nil, bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj + } + + // Compute the method-set fingerprint used as a key to the global search. + key, hasMethods := methodsets.KeyOf(queryType) + if !hasMethods { + // A type with no methods yields an empty result. + // (No point reporting that every type satisfies 'any'.) + return nil, nil + } + + // The global search needs to look at every package in the + // forward transitive closure of the workspace; see package + // ./methodsets. + // + // For now we do all the type checking before beginning the search. + // TODO(adonovan): opt: search in parallel topological order + // so that we can overlap index lookup with typechecking. + // I suspect a number of algorithms on the result of TypeCheck could + // be optimized by being applied as soon as each package is available. + globalMetas, err := snapshot.AllMetadata(ctx) + if err != nil { + return nil, err + } + metadata.RemoveIntermediateTestVariants(&globalMetas) + globalIDs := make([]PackageID, 0, len(globalMetas)) + + var pkgPath PackagePath + if obj.Pkg() != nil { // nil for error + pkgPath = PackagePath(obj.Pkg().Path()) + } + for _, mp := range globalMetas { + if mp.PkgPath == pkgPath { + continue // declaring package is handled by local implementation + } + globalIDs = append(globalIDs, mp.ID) + } + indexes, err := snapshot.MethodSets(ctx, globalIDs...) + if err != nil { + return nil, fmt.Errorf("querying method sets: %v", err) + } + + // Search local and global packages in parallel. + var ( + group errgroup.Group + locsMu sync.Mutex + locs []protocol.Location + ) + // local search + for _, localPkg := range localPkgs { + // The localImplementations algorithm assumes needle and haystack + // belong to a single package (="realm" of types symbol identities), + // so we need to recompute obj for each local package. + // (By contrast the global algorithm is name-based.) + declPkg := localPkg + group.Go(func() error { + pkgID := declPkg.Metadata().ID + declFile, err := declPkg.File(declURI) + if err != nil { + return err // "can't happen" + } + + // Find declaration of corresponding object + // in this package based on (URI, offset). + pos, err := safetoken.Pos(declFile.Tok, declOffset) + if err != nil { + return err // also "can't happen" + } + // TODO(adonovan): simplify: use objectsAt? + path := pathEnclosingObjNode(declFile.File, pos) + if path == nil { + return ErrNoIdentFound // checked earlier + } + id, ok := path[0].(*ast.Ident) + if !ok { + return ErrNoIdentFound // checked earlier + } + // Shadow obj, queryType, and queryMethodID in this package. + obj := declPkg.TypesInfo().ObjectOf(id) // may be nil + queryType, queryMethodID := typeOrMethod(obj) + if queryType == nil { + return fmt.Errorf("querying method sets in package %q: %v", pkgID, err) + } + localLocs, err := localImplementations(ctx, snapshot, declPkg, queryType, queryMethodID) + if err != nil { + return fmt.Errorf("querying local implementations %q: %v", pkgID, err) + } + locsMu.Lock() + locs = append(locs, localLocs...) + locsMu.Unlock() + return nil + }) + } + // global search + for _, index := range indexes { + index := index + group.Go(func() error { + for _, res := range index.Search(key, queryMethodID) { + loc := res.Location + // Map offsets to protocol.Locations in parallel (may involve I/O). + group.Go(func() error { + ploc, err := offsetToLocation(ctx, snapshot, loc.Filename, loc.Start, loc.End) + if err != nil { + return err + } + locsMu.Lock() + locs = append(locs, ploc) + locsMu.Unlock() + return nil + }) + } + return nil + }) + } + if err := group.Wait(); err != nil { + return nil, err + } + + return locs, nil +} + +// offsetToLocation converts an offset-based position to a protocol.Location, +// which requires reading the file. +func offsetToLocation(ctx context.Context, snapshot *cache.Snapshot, filename string, start, end int) (protocol.Location, error) { + uri := protocol.URIFromPath(filename) + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return protocol.Location{}, err // cancelled, perhaps + } + content, err := fh.Content() + if err != nil { + return protocol.Location{}, err // nonexistent or deleted ("can't happen") + } + m := protocol.NewMapper(uri, content) + return m.OffsetLocation(start, end) +} + +// implementsObj returns the object to query for implementations, which is a +// type name or method. +// +// The returned Package is the narrowest package containing ppos, which is the +// package using the resulting obj but not necessarily the declaring package. +func implementsObj(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, ppos protocol.Position) (types.Object, *cache.Package, error) { + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, uri) + if err != nil { + return nil, nil, err + } + pos, err := pgf.PositionPos(ppos) + if err != nil { + return nil, nil, err + } + + // This function inherits the limitation of its predecessor in + // requiring the selection to be an identifier (of a type or + // method). But there's no fundamental reason why one could + // not pose this query about any selected piece of syntax that + // has a type and thus a method set. + // (If LSP was more thorough about passing text selections as + // intervals to queries, you could ask about the method set of a + // subexpression such as x.f().) + + // TODO(adonovan): simplify: use objectsAt? + path := pathEnclosingObjNode(pgf.File, pos) + if path == nil { + return nil, nil, ErrNoIdentFound + } + id, ok := path[0].(*ast.Ident) + if !ok { + return nil, nil, ErrNoIdentFound + } + + // Is the object a type or method? Reject other kinds. + obj := pkg.TypesInfo().Uses[id] + if obj == nil { + // Check uses first (unlike ObjectOf) so that T in + // struct{T} is treated as a reference to a type, + // not a declaration of a field. + obj = pkg.TypesInfo().Defs[id] + } + switch obj := obj.(type) { + case *types.TypeName: + // ok + case *types.Func: + if obj.Signature().Recv() == nil { + return nil, nil, fmt.Errorf("%s is a function, not a method", id.Name) + } + case nil: + return nil, nil, fmt.Errorf("%s denotes unknown object", id.Name) + default: + // e.g. *types.Var -> "var". + kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types.")) + return nil, nil, fmt.Errorf("%s is a %s, not a type", id.Name, kind) + } + + return obj, pkg, nil +} + +// localImplementations searches within pkg for declarations of all +// types that are assignable to/from the query type, and returns a new +// unordered array of their locations. +// +// If methodID is non-empty, the function instead returns the location +// of each type's method (if any) of that ID. +// +// ("Local" refers to the search within the same package, but this +// function's results may include type declarations that are local to +// a function body. The global search index excludes such types +// because reliably naming such types is hard.) +func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, queryType types.Type, methodID string) ([]protocol.Location, error) { + queryType = methodsets.EnsurePointer(queryType) + + // Scan through all type declarations in the syntax. + var locs []protocol.Location + var methodLocs []methodsets.Location + for _, pgf := range pkg.CompiledGoFiles() { + ast.Inspect(pgf.File, func(n ast.Node) bool { + spec, ok := n.(*ast.TypeSpec) + if !ok { + return true // not a type declaration + } + def := pkg.TypesInfo().Defs[spec.Name] + if def == nil { + return true // "can't happen" for types + } + if def.(*types.TypeName).IsAlias() { + return true // skip type aliases to avoid duplicate reporting + } + candidateType := methodsets.EnsurePointer(def.Type()) + + // The historical behavior enshrined by this + // function rejects cases where both are + // (nontrivial) interface types? + // That seems like useful information. + // TODO(adonovan): UX: report I/I pairs too? + // The same question appears in the global algorithm (methodsets). + if !concreteImplementsIntf(candidateType, queryType) { + return true // not assignable + } + + // Ignore types with empty method sets. + // (No point reporting that every type satisfies 'any'.) + mset := types.NewMethodSet(candidateType) + if mset.Len() == 0 { + return true + } + + if methodID == "" { + // Found matching type. + locs = append(locs, mustLocation(pgf, spec.Name)) + return true + } + + // Find corresponding method. + // + // We can't use LookupFieldOrMethod because it requires + // the methodID's types.Package, which we don't know. + // We could recursively search pkg.Imports for it, + // but it's easier to walk the method set. + for i := 0; i < mset.Len(); i++ { + method := mset.At(i).Obj() + if method.Id() == methodID { + posn := safetoken.StartPosition(pkg.FileSet(), method.Pos()) + methodLocs = append(methodLocs, methodsets.Location{ + Filename: posn.Filename, + Start: posn.Offset, + End: posn.Offset + len(method.Name()), + }) + break + } + } + return true + }) + } + + // Finally convert method positions to protocol form by reading the files. + for _, mloc := range methodLocs { + loc, err := offsetToLocation(ctx, snapshot, mloc.Filename, mloc.Start, mloc.End) + if err != nil { + return nil, err + } + locs = append(locs, loc) + } + + // Special case: for types that satisfy error, report builtin.go (see #59527). + if types.Implements(queryType, errorInterfaceType) { + loc, err := errorLocation(ctx, snapshot) + if err != nil { + return nil, err + } + locs = append(locs, loc) + } + + return locs, nil +} + +var errorInterfaceType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + +// errorLocation returns the location of the 'error' type in builtin.go. +func errorLocation(ctx context.Context, snapshot *cache.Snapshot) (protocol.Location, error) { + pgf, err := snapshot.BuiltinFile(ctx) + if err != nil { + return protocol.Location{}, err + } + for _, decl := range pgf.File.Decls { + if decl, ok := decl.(*ast.GenDecl); ok { + for _, spec := range decl.Specs { + if spec, ok := spec.(*ast.TypeSpec); ok && spec.Name.Name == "error" { + return pgf.NodeLocation(spec.Name) + } + } + } + } + return protocol.Location{}, fmt.Errorf("built-in error type not found") +} + +// concreteImplementsIntf returns true if a is an interface type implemented by +// concrete type b, or vice versa. +func concreteImplementsIntf(a, b types.Type) bool { + aIsIntf, bIsIntf := types.IsInterface(a), types.IsInterface(b) + + // Make sure exactly one is an interface type. + if aIsIntf == bIsIntf { + return false + } + + // Rearrange if needed so "a" is the concrete type. + if aIsIntf { + a, b = b, a + } + + // TODO(adonovan): this should really use GenericAssignableTo + // to report (e.g.) "ArrayList[T] implements List[T]", but + // GenericAssignableTo doesn't work correctly on pointers to + // generic named types. Thus the legacy implementation and the + // "local" part of implementations fail to report generics. + // The global algorithm based on subsets does the right thing. + return types.AssignableTo(a, b) +} + +var ( + // TODO(adonovan): why do various RPC handlers related to + // IncomingCalls return (nil, nil) on the protocol in response + // to this error? That seems like a violation of the protocol. + // Is it perhaps a workaround for VSCode behavior? + errNoObjectFound = errors.New("no object found") +) + +// pathEnclosingObjNode returns the AST path to the object-defining +// node associated with pos. "Object-defining" means either an +// *ast.Ident mapped directly to a types.Object or an ast.Node mapped +// implicitly to a types.Object. +func pathEnclosingObjNode(f *ast.File, pos token.Pos) []ast.Node { + var ( + path []ast.Node + found bool + ) + + ast.Inspect(f, func(n ast.Node) bool { + if found { + return false + } + + if n == nil { + path = path[:len(path)-1] + return false + } + + path = append(path, n) + + switch n := n.(type) { + case *ast.Ident: + // Include the position directly after identifier. This handles + // the common case where the cursor is right after the + // identifier the user is currently typing. Previously we + // handled this by calling astutil.PathEnclosingInterval twice, + // once for "pos" and once for "pos-1". + found = n.Pos() <= pos && pos <= n.End() + case *ast.ImportSpec: + if n.Path.Pos() <= pos && pos < n.Path.End() { + found = true + // If import spec has a name, add name to path even though + // position isn't in the name. + if n.Name != nil { + path = append(path, n.Name) + } + } + case *ast.StarExpr: + // Follow star expressions to the inner identifier. + if pos == n.Star { + pos = n.X.Pos() + } + } + + return !found + }) + + if len(path) == 0 { + return nil + } + + // Reverse path so leaf is first element. + for i := 0; i < len(path)/2; i++ { + path[i], path[len(path)-1-i] = path[len(path)-1-i], path[i] + } + + return path +} diff --git a/contribs/gnopls/internal/golang/inlay_hint.go b/contribs/gnopls/internal/golang/inlay_hint.go new file mode 100644 index 00000000000..6e2b7f40d33 --- /dev/null +++ b/contribs/gnopls/internal/golang/inlay_hint.go @@ -0,0 +1,354 @@ +// 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 golang + +import ( + "context" + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/typesutil" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" +) + +func InlayHint(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pRng protocol.Range) ([]protocol.InlayHint, error) { + ctx, done := event.Start(ctx, "golang.InlayHint") + defer done() + + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) + if err != nil { + return nil, fmt.Errorf("getting file for InlayHint: %w", err) + } + + // Collect a list of the inlay hints that are enabled. + inlayHintOptions := snapshot.Options().InlayHintOptions + var enabledHints []inlayHintFunc + for hint, enabled := range inlayHintOptions.Hints { + if !enabled { + continue + } + if fn, ok := allInlayHints[hint]; ok { + enabledHints = append(enabledHints, fn) + } + } + if len(enabledHints) == 0 { + return nil, nil + } + + info := pkg.TypesInfo() + q := typesutil.FileQualifier(pgf.File, pkg.Types(), info) + + // Set the range to the full file if the range is not valid. + start, end := pgf.File.Pos(), pgf.File.End() + if pRng.Start.Line < pRng.End.Line || pRng.Start.Character < pRng.End.Character { + // Adjust start and end for the specified range. + var err error + start, end, err = pgf.RangePos(pRng) + if err != nil { + return nil, err + } + } + + var hints []protocol.InlayHint + ast.Inspect(pgf.File, func(node ast.Node) bool { + // If not in range, we can stop looking. + if node == nil || node.End() < start || node.Pos() > end { + return false + } + for _, fn := range enabledHints { + hints = append(hints, fn(node, pgf.Mapper, pgf.Tok, info, &q)...) + } + return true + }) + return hints, nil +} + +type inlayHintFunc func(node ast.Node, m *protocol.Mapper, tf *token.File, info *types.Info, q *types.Qualifier) []protocol.InlayHint + +var allInlayHints = map[settings.InlayHint]inlayHintFunc{ + settings.AssignVariableTypes: assignVariableTypes, + settings.ConstantValues: constantValues, + settings.ParameterNames: parameterNames, + settings.RangeVariableTypes: rangeVariableTypes, + settings.CompositeLiteralTypes: compositeLiteralTypes, + settings.CompositeLiteralFieldNames: compositeLiteralFields, + settings.FunctionTypeParameters: funcTypeParams, +} + +func parameterNames(node ast.Node, m *protocol.Mapper, tf *token.File, info *types.Info, _ *types.Qualifier) []protocol.InlayHint { + callExpr, ok := node.(*ast.CallExpr) + if !ok { + return nil + } + t := info.TypeOf(callExpr.Fun) + if t == nil { + return nil + } + signature, ok := typeparams.CoreType(t).(*types.Signature) + if !ok { + return nil + } + + var hints []protocol.InlayHint + for i, v := range callExpr.Args { + start, err := m.PosPosition(tf, v.Pos()) + if err != nil { + continue + } + params := signature.Params() + // When a function has variadic params, we skip args after + // params.Len(). + if i > params.Len()-1 { + break + } + param := params.At(i) + // param.Name is empty for built-ins like append + if param.Name() == "" { + continue + } + // Skip the parameter name hint if the arg matches + // the parameter name. + if i, ok := v.(*ast.Ident); ok && i.Name == param.Name() { + continue + } + + label := param.Name() + if signature.Variadic() && i == params.Len()-1 { + label = label + "..." + } + hints = append(hints, protocol.InlayHint{ + Position: start, + Label: buildLabel(label + ":"), + Kind: protocol.Parameter, + PaddingRight: true, + }) + } + return hints +} + +func funcTypeParams(node ast.Node, m *protocol.Mapper, tf *token.File, info *types.Info, _ *types.Qualifier) []protocol.InlayHint { + ce, ok := node.(*ast.CallExpr) + if !ok { + return nil + } + id, ok := ce.Fun.(*ast.Ident) + if !ok { + return nil + } + inst := info.Instances[id] + if inst.TypeArgs == nil { + return nil + } + start, err := m.PosPosition(tf, id.End()) + if err != nil { + return nil + } + var args []string + for i := 0; i < inst.TypeArgs.Len(); i++ { + args = append(args, inst.TypeArgs.At(i).String()) + } + if len(args) == 0 { + return nil + } + return []protocol.InlayHint{{ + Position: start, + Label: buildLabel("[" + strings.Join(args, ", ") + "]"), + Kind: protocol.Type, + }} +} + +func assignVariableTypes(node ast.Node, m *protocol.Mapper, tf *token.File, info *types.Info, q *types.Qualifier) []protocol.InlayHint { + stmt, ok := node.(*ast.AssignStmt) + if !ok || stmt.Tok != token.DEFINE { + return nil + } + + var hints []protocol.InlayHint + for _, v := range stmt.Lhs { + if h := variableType(v, m, tf, info, q); h != nil { + hints = append(hints, *h) + } + } + return hints +} + +func rangeVariableTypes(node ast.Node, m *protocol.Mapper, tf *token.File, info *types.Info, q *types.Qualifier) []protocol.InlayHint { + rStmt, ok := node.(*ast.RangeStmt) + if !ok { + return nil + } + var hints []protocol.InlayHint + if h := variableType(rStmt.Key, m, tf, info, q); h != nil { + hints = append(hints, *h) + } + if h := variableType(rStmt.Value, m, tf, info, q); h != nil { + hints = append(hints, *h) + } + return hints +} + +func variableType(e ast.Expr, m *protocol.Mapper, tf *token.File, info *types.Info, q *types.Qualifier) *protocol.InlayHint { + typ := info.TypeOf(e) + if typ == nil { + return nil + } + end, err := m.PosPosition(tf, e.End()) + if err != nil { + return nil + } + return &protocol.InlayHint{ + Position: end, + Label: buildLabel(types.TypeString(typ, *q)), + Kind: protocol.Type, + PaddingLeft: true, + } +} + +func constantValues(node ast.Node, m *protocol.Mapper, tf *token.File, info *types.Info, _ *types.Qualifier) []protocol.InlayHint { + genDecl, ok := node.(*ast.GenDecl) + if !ok || genDecl.Tok != token.CONST { + return nil + } + + var hints []protocol.InlayHint + for _, v := range genDecl.Specs { + spec, ok := v.(*ast.ValueSpec) + if !ok { + continue + } + end, err := m.PosPosition(tf, v.End()) + if err != nil { + continue + } + // Show hints when values are missing or at least one value is not + // a basic literal. + showHints := len(spec.Values) == 0 + checkValues := len(spec.Names) == len(spec.Values) + var values []string + for i, w := range spec.Names { + obj, ok := info.ObjectOf(w).(*types.Const) + if !ok || obj.Val().Kind() == constant.Unknown { + return nil + } + if checkValues { + switch spec.Values[i].(type) { + case *ast.BadExpr: + return nil + case *ast.BasicLit: + default: + if obj.Val().Kind() != constant.Bool { + showHints = true + } + } + } + values = append(values, fmt.Sprintf("%v", obj.Val())) + } + if !showHints || len(values) == 0 { + continue + } + hints = append(hints, protocol.InlayHint{ + Position: end, + Label: buildLabel("= " + strings.Join(values, ", ")), + PaddingLeft: true, + }) + } + return hints +} + +func compositeLiteralFields(node ast.Node, m *protocol.Mapper, tf *token.File, info *types.Info, _ *types.Qualifier) []protocol.InlayHint { + compLit, ok := node.(*ast.CompositeLit) + if !ok { + return nil + } + typ := info.TypeOf(compLit) + if typ == nil { + return nil + } + typ = typesinternal.Unpointer(typ) + strct, ok := typeparams.CoreType(typ).(*types.Struct) + if !ok { + return nil + } + + var hints []protocol.InlayHint + var allEdits []protocol.TextEdit + for i, v := range compLit.Elts { + if _, ok := v.(*ast.KeyValueExpr); !ok { + start, err := m.PosPosition(tf, v.Pos()) + if err != nil { + continue + } + if i > strct.NumFields()-1 { + break + } + hints = append(hints, protocol.InlayHint{ + Position: start, + Label: buildLabel(strct.Field(i).Name() + ":"), + Kind: protocol.Parameter, + PaddingRight: true, + }) + allEdits = append(allEdits, protocol.TextEdit{ + Range: protocol.Range{Start: start, End: start}, + NewText: strct.Field(i).Name() + ": ", + }) + } + } + // It is not allowed to have a mix of keyed and unkeyed fields, so + // have the text edits add keys to all fields. + for i := range hints { + hints[i].TextEdits = allEdits + } + return hints +} + +func compositeLiteralTypes(node ast.Node, m *protocol.Mapper, tf *token.File, info *types.Info, q *types.Qualifier) []protocol.InlayHint { + compLit, ok := node.(*ast.CompositeLit) + if !ok { + return nil + } + typ := info.TypeOf(compLit) + if typ == nil { + return nil + } + if compLit.Type != nil { + return nil + } + prefix := "" + if t, ok := typeparams.CoreType(typ).(*types.Pointer); ok { + typ = t.Elem() + prefix = "&" + } + // The type for this composite literal is implicit, add an inlay hint. + start, err := m.PosPosition(tf, compLit.Lbrace) + if err != nil { + return nil + } + return []protocol.InlayHint{{ + Position: start, + Label: buildLabel(fmt.Sprintf("%s%s", prefix, types.TypeString(typ, *q))), + Kind: protocol.Type, + }} +} + +func buildLabel(s string) []protocol.InlayHintLabelPart { + const maxLabelLength = 28 + label := protocol.InlayHintLabelPart{ + Value: s, + } + if len(s) > maxLabelLength+len("...") { + label.Value = s[:maxLabelLength] + "..." + } + return []protocol.InlayHintLabelPart{label} +} diff --git a/contribs/gnopls/internal/golang/inline.go b/contribs/gnopls/internal/golang/inline.go new file mode 100644 index 00000000000..8e5e906c566 --- /dev/null +++ b/contribs/gnopls/internal/golang/inline.go @@ -0,0 +1,136 @@ +// 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 golang + +// This file defines the refactor.inline code action. + +import ( + "context" + "fmt" + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/refactor/inline" +) + +// enclosingStaticCall returns the innermost function call enclosing +// the selected range, along with the callee. +func enclosingStaticCall(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*ast.CallExpr, *types.Func, error) { + path, _ := astutil.PathEnclosingInterval(pgf.File, start, end) + + var call *ast.CallExpr +loop: + for _, n := range path { + switch n := n.(type) { + case *ast.FuncLit: + break loop + case *ast.CallExpr: + call = n + break loop + } + } + if call == nil { + return nil, nil, fmt.Errorf("no enclosing call") + } + if safetoken.Line(pgf.Tok, call.Lparen) != safetoken.Line(pgf.Tok, start) { + return nil, nil, fmt.Errorf("enclosing call is not on this line") + } + fn := typeutil.StaticCallee(pkg.TypesInfo(), call) + if fn == nil { + return nil, nil, fmt.Errorf("not a static call to a Go function") + } + return call, fn, nil +} + +func inlineCall(ctx context.Context, snapshot *cache.Snapshot, callerPkg *cache.Package, callerPGF *parsego.File, start, end token.Pos) (_ *token.FileSet, _ *analysis.SuggestedFix, err error) { + // Find enclosing static call. + call, fn, err := enclosingStaticCall(callerPkg, callerPGF, start, end) + if err != nil { + return nil, nil, err + } + + // Locate callee by file/line and analyze it. + calleePosn := safetoken.StartPosition(callerPkg.FileSet(), fn.Pos()) + calleePkg, calleePGF, err := NarrowestPackageForFile(ctx, snapshot, protocol.URIFromPath(calleePosn.Filename)) + if err != nil { + return nil, nil, err + } + var calleeDecl *ast.FuncDecl + for _, decl := range calleePGF.File.Decls { + if decl, ok := decl.(*ast.FuncDecl); ok { + posn := safetoken.StartPosition(calleePkg.FileSet(), decl.Name.Pos()) + if posn.Line == calleePosn.Line && posn.Column == calleePosn.Column { + calleeDecl = decl + break + } + } + } + if calleeDecl == nil { + return nil, nil, fmt.Errorf("can't find callee") + } + + // The inliner assumes that input is well-typed, + // but that is frequently not the case within gopls. + // Until we are able to harden the inliner, + // report panics as errors to avoid crashing the server. + bad := func(p *cache.Package) bool { return len(p.ParseErrors())+len(p.TypeErrors()) > 0 } + if bad(calleePkg) || bad(callerPkg) { + defer func() { + if x := recover(); x != nil { + err = fmt.Errorf("inlining failed (%q), likely because inputs were ill-typed", x) + } + }() + } + + // Users can consult the gopls event log to see + // why a particular inlining strategy was chosen. + logf := logger(ctx, "inliner", snapshot.Options().VerboseOutput) + + callee, err := inline.AnalyzeCallee(logf, calleePkg.FileSet(), calleePkg.Types(), calleePkg.TypesInfo(), calleeDecl, calleePGF.Src) + if err != nil { + return nil, nil, err + } + + // Inline the call. + caller := &inline.Caller{ + Fset: callerPkg.FileSet(), + Types: callerPkg.Types(), + Info: callerPkg.TypesInfo(), + File: callerPGF.File, + Call: call, + Content: callerPGF.Src, + } + + res, err := inline.Inline(caller, callee, &inline.Options{Logf: logf}) + if err != nil { + return nil, nil, err + } + + return callerPkg.FileSet(), &analysis.SuggestedFix{ + Message: fmt.Sprintf("inline call of %v", callee), + TextEdits: diffToTextEdits(callerPGF.Tok, diff.Bytes(callerPGF.Src, res.Content)), + }, nil +} + +// TODO(adonovan): change the inliner to instead accept an io.Writer. +func logger(ctx context.Context, name string, verbose bool) func(format string, args ...any) { + if verbose { + return func(format string, args ...any) { + event.Log(ctx, name+": "+fmt.Sprintf(format, args...)) + } + } else { + return func(string, ...any) {} + } +} diff --git a/contribs/gnopls/internal/golang/inline_all.go b/contribs/gnopls/internal/golang/inline_all.go new file mode 100644 index 00000000000..addfe2bc250 --- /dev/null +++ b/contribs/gnopls/internal/golang/inline_all.go @@ -0,0 +1,276 @@ +// 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 golang + +import ( + "context" + "fmt" + "go/ast" + "go/parser" + "go/types" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/refactor/inline" +) + +// inlineAllCalls inlines all calls to the original function declaration +// described by callee, returning the resulting modified file content. +// +// inlining everything is currently an expensive operation: it involves re-type +// checking every package that contains a potential call, as reported by +// References. In cases where there are multiple calls per file, inlineAllCalls +// must type check repeatedly for each additional call. +// +// The provided post processing function is applied to the resulting source +// after each transformation. This is necessary because we are using this +// function to inline synthetic wrappers for the purpose of signature +// rewriting. The delegated function has a fake name that doesn't exist in the +// snapshot, and so we can't re-type check until we replace this fake name. +// +// TODO(rfindley): this only works because removing a parameter is a very +// narrow operation. A better solution would be to allow for ad-hoc snapshots +// that expose the full machinery of real snapshots: minimal invalidation, +// batched type checking, etc. Then we could actually rewrite the declaring +// package in this snapshot (and so 'post' would not be necessary), and could +// robustly re-type check for the purpose of iterative inlining, even if the +// inlined code pulls in new imports that weren't present in export data. +// +// The code below notes where are assumptions are made that only hold true in +// the case of parameter removal (annotated with 'Assumption:') +func inlineAllCalls(ctx context.Context, logf func(string, ...any), snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, origDecl *ast.FuncDecl, callee *inline.Callee, post func([]byte) []byte) (map[protocol.DocumentURI][]byte, error) { + // Collect references. + var refs []protocol.Location + { + funcPos, err := pgf.Mapper.PosPosition(pgf.Tok, origDecl.Name.NamePos) + if err != nil { + return nil, err + } + fh, err := snapshot.ReadFile(ctx, pgf.URI) + if err != nil { + return nil, err + } + refs, err = References(ctx, snapshot, fh, funcPos, false) + if err != nil { + return nil, fmt.Errorf("finding references to rewrite: %v", err) + } + } + + // Type-check the narrowest package containing each reference. + // TODO(rfindley): we should expose forEachPackage in order to operate in + // parallel and to reduce peak memory for this operation. + var ( + pkgForRef = make(map[protocol.Location]PackageID) + pkgs = make(map[PackageID]*cache.Package) + ) + { + needPkgs := make(map[PackageID]struct{}) + for _, ref := range refs { + md, err := NarrowestMetadataForFile(ctx, snapshot, ref.URI) + if err != nil { + return nil, fmt.Errorf("finding ref metadata: %v", err) + } + pkgForRef[ref] = md.ID + needPkgs[md.ID] = struct{}{} + } + var pkgIDs []PackageID + for id := range needPkgs { // TODO: use maps.Keys once it is available to us + pkgIDs = append(pkgIDs, id) + } + + refPkgs, err := snapshot.TypeCheck(ctx, pkgIDs...) + if err != nil { + return nil, fmt.Errorf("type checking reference packages: %v", err) + } + + for _, p := range refPkgs { + pkgs[p.Metadata().ID] = p + } + } + + // Organize calls by top file declaration. Calls within a single file may + // affect each other, as the inlining edit may affect the surrounding scope + // or imports Therefore, when inlining subsequent calls in the same + // declaration, we must re-type check. + + type fileCalls struct { + pkg *cache.Package + pgf *parsego.File + calls []*ast.CallExpr + } + + refsByFile := make(map[protocol.DocumentURI]*fileCalls) + for _, ref := range refs { + refpkg := pkgs[pkgForRef[ref]] + pgf, err := refpkg.File(ref.URI) + if err != nil { + return nil, bug.Errorf("finding %s in %s: %v", ref.URI, refpkg.Metadata().ID, err) + } + start, end, err := pgf.RangePos(ref.Range) + if err != nil { + return nil, err // e.g. invalid range + } + + // Look for the surrounding call expression. + var ( + name *ast.Ident + call *ast.CallExpr + ) + path, _ := astutil.PathEnclosingInterval(pgf.File, start, end) + name, _ = path[0].(*ast.Ident) + if _, ok := path[1].(*ast.SelectorExpr); ok { + call, _ = path[2].(*ast.CallExpr) + } else { + call, _ = path[1].(*ast.CallExpr) + } + if name == nil || call == nil { + // TODO(rfindley): handle this case with eta-abstraction: + // a reference to the target function f in a non-call position + // use(f) + // is replaced by + // use(func(...) { f(...) }) + return nil, fmt.Errorf("cannot inline: found non-call function reference %v", ref) + } + // Sanity check. + if obj := refpkg.TypesInfo().ObjectOf(name); obj == nil || + obj.Name() != origDecl.Name.Name || + obj.Pkg() == nil || + obj.Pkg().Path() != string(pkg.Metadata().PkgPath) { + return nil, bug.Errorf("cannot inline: corrupted reference %v", ref) + } + + callInfo, ok := refsByFile[ref.URI] + if !ok { + callInfo = &fileCalls{ + pkg: refpkg, + pgf: pgf, + } + refsByFile[ref.URI] = callInfo + } + callInfo.calls = append(callInfo.calls, call) + } + + // Inline each call within the same decl in sequence, re-typechecking after + // each one. If there is only a single call within the decl, we can avoid + // additional type checking. + // + // Assumption: inlining does not affect the package scope, so we can operate + // on separate files independently. + result := make(map[protocol.DocumentURI][]byte) + for uri, callInfo := range refsByFile { + var ( + calls = callInfo.calls + fset = callInfo.pkg.FileSet() + tpkg = callInfo.pkg.Types() + tinfo = callInfo.pkg.TypesInfo() + file = callInfo.pgf.File + content = callInfo.pgf.Src + ) + + // Check for overlapping calls (such as Foo(Foo())). We can't handle these + // because inlining may change the source order of the inner call with + // respect to the inlined outer call, and so the heuristic we use to find + // the next call (counting from top-to-bottom) does not work. + for i := range calls { + if i > 0 && calls[i-1].End() > calls[i].Pos() { + return nil, fmt.Errorf("%s: can't inline overlapping call %s", uri, types.ExprString(calls[i-1])) + } + } + + currentCall := 0 + for currentCall < len(calls) { + caller := &inline.Caller{ + Fset: fset, + Types: tpkg, + Info: tinfo, + File: file, + Call: calls[currentCall], + Content: content, + } + res, err := inline.Inline(caller, callee, &inline.Options{Logf: logf}) + if err != nil { + return nil, fmt.Errorf("inlining failed: %v", err) + } + content = res.Content + if post != nil { + content = post(content) + } + if len(calls) <= 1 { + // No need to re-type check, as we've inlined all calls. + break + } + + // TODO(rfindley): develop a theory of "trivial" inlining, which are + // inlinings that don't require re-type checking. + // + // In principle, if the inlining only involves replacing one call with + // another, the scope of the caller is unchanged and there is no need to + // type check again before inlining subsequent calls (edits should not + // overlap, and should not affect each other semantically). However, it + // feels sufficiently complicated that, to be safe, this optimization is + // deferred until later. + + file, err = parser.ParseFile(fset, uri.Path(), content, parser.ParseComments|parser.SkipObjectResolution) + if err != nil { + return nil, bug.Errorf("inlined file failed to parse: %v", err) + } + + // After inlining one call with a removed parameter, the package will + // fail to type check due to "not enough arguments". Therefore, we must + // allow type errors here. + // + // Assumption: the resulting type errors do not affect the correctness of + // subsequent inlining, because invalid arguments to a call do not affect + // anything in the surrounding scope. + // + // TODO(rfindley): improve this. + tpkg, tinfo, err = reTypeCheck(logf, callInfo.pkg, map[protocol.DocumentURI]*ast.File{uri: file}, true) + if err != nil { + return nil, bug.Errorf("type checking after inlining failed: %v", err) + } + + // Collect calls to the target function in the modified declaration. + var calls2 []*ast.CallExpr + ast.Inspect(file, func(n ast.Node) bool { + if call, ok := n.(*ast.CallExpr); ok { + fn := typeutil.StaticCallee(tinfo, call) + if fn != nil && fn.Pkg().Path() == string(pkg.Metadata().PkgPath) && fn.Name() == origDecl.Name.Name { + calls2 = append(calls2, call) + } + } + return true + }) + + // If the number of calls has increased, this process will never cease. + // If the number of calls has decreased, assume that inlining removed a + // call. + // If the number of calls didn't change, assume that inlining replaced + // a call, and move on to the next. + // + // Assumption: we're inlining a call that has at most one recursive + // reference (which holds for signature rewrites). + // + // TODO(rfindley): this isn't good enough. We should be able to support + // inlining all existing calls even if they increase calls. How do we + // correlate the before and after syntax? + switch { + case len(calls2) > len(calls): + return nil, fmt.Errorf("inlining increased calls %d->%d, possible recursive call? content:\n%s", len(calls), len(calls2), content) + case len(calls2) < len(calls): + calls = calls2 + case len(calls2) == len(calls): + calls = calls2 + currentCall++ + } + } + + result[callInfo.pgf.URI] = content + } + return result, nil +} diff --git a/contribs/gnopls/internal/golang/invertifcondition.go b/contribs/gnopls/internal/golang/invertifcondition.go new file mode 100644 index 00000000000..16eaaa39bd2 --- /dev/null +++ b/contribs/gnopls/internal/golang/invertifcondition.go @@ -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 golang + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/util/safetoken" +) + +// invertIfCondition is a singleFileFixFunc that inverts an if/else statement +func invertIfCondition(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, _ *types.Package, _ *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { + ifStatement, _, err := canInvertIfCondition(file, start, end) + if err != nil { + return nil, nil, err + } + + var replaceElse analysis.TextEdit + + endsWithReturn, err := endsWithReturn(ifStatement.Else) + if err != nil { + return nil, nil, err + } + + if endsWithReturn { + // Replace the whole else part with an empty line and an unindented + // version of the original if body + sourcePos := safetoken.StartPosition(fset, ifStatement.Pos()) + + indent := sourcePos.Column - 1 + if indent < 0 { + indent = 0 + } + + standaloneBodyText := ifBodyToStandaloneCode(fset, ifStatement.Body, src) + replaceElse = analysis.TextEdit{ + Pos: ifStatement.Body.Rbrace + 1, // 1 == len("}") + End: ifStatement.End(), + NewText: []byte("\n\n" + strings.Repeat("\t", indent) + standaloneBodyText), + } + } else { + // Replace the else body text with the if body text + bodyStart := safetoken.StartPosition(fset, ifStatement.Body.Lbrace) + bodyEnd := safetoken.EndPosition(fset, ifStatement.Body.Rbrace+1) // 1 == len("}") + bodyText := src[bodyStart.Offset:bodyEnd.Offset] + replaceElse = analysis.TextEdit{ + Pos: ifStatement.Else.Pos(), + End: ifStatement.Else.End(), + NewText: bodyText, + } + } + + // Replace the if text with the else text + elsePosInSource := safetoken.StartPosition(fset, ifStatement.Else.Pos()) + elseEndInSource := safetoken.EndPosition(fset, ifStatement.Else.End()) + elseText := src[elsePosInSource.Offset:elseEndInSource.Offset] + replaceBodyWithElse := analysis.TextEdit{ + Pos: ifStatement.Body.Pos(), + End: ifStatement.Body.End(), + NewText: elseText, + } + + // Replace the if condition with its inverse + inverseCondition, err := invertCondition(fset, ifStatement.Cond, src) + if err != nil { + return nil, nil, err + } + replaceConditionWithInverse := analysis.TextEdit{ + Pos: ifStatement.Cond.Pos(), + End: ifStatement.Cond.End(), + NewText: inverseCondition, + } + + // Return a SuggestedFix with just that TextEdit in there + return fset, &analysis.SuggestedFix{ + TextEdits: []analysis.TextEdit{ + replaceConditionWithInverse, + replaceBodyWithElse, + replaceElse, + }, + }, nil +} + +func endsWithReturn(elseBranch ast.Stmt) (bool, error) { + elseBlock, isBlockStatement := elseBranch.(*ast.BlockStmt) + if !isBlockStatement { + return false, fmt.Errorf("unable to figure out whether this ends with return: %T", elseBranch) + } + + if len(elseBlock.List) == 0 { + // Empty blocks don't end in returns + return false, nil + } + + lastStatement := elseBlock.List[len(elseBlock.List)-1] + + _, lastStatementIsReturn := lastStatement.(*ast.ReturnStmt) + return lastStatementIsReturn, nil +} + +// Turn { fmt.Println("Hello") } into just fmt.Println("Hello"), with one less +// level of indentation. +// +// The first line of the result will not be indented, but all of the following +// lines will. +func ifBodyToStandaloneCode(fset *token.FileSet, ifBody *ast.BlockStmt, src []byte) string { + // Get the whole body (without the surrounding braces) as a string + bodyStart := safetoken.StartPosition(fset, ifBody.Lbrace+1) // 1 == len("}") + bodyEnd := safetoken.EndPosition(fset, ifBody.Rbrace) + bodyWithoutBraces := string(src[bodyStart.Offset:bodyEnd.Offset]) + bodyWithoutBraces = strings.TrimSpace(bodyWithoutBraces) + + // Unindent + bodyWithoutBraces = strings.ReplaceAll(bodyWithoutBraces, "\n\t", "\n") + + return bodyWithoutBraces +} + +func invertCondition(fset *token.FileSet, cond ast.Expr, src []byte) ([]byte, error) { + condStart := safetoken.StartPosition(fset, cond.Pos()) + condEnd := safetoken.EndPosition(fset, cond.End()) + oldText := string(src[condStart.Offset:condEnd.Offset]) + + switch expr := cond.(type) { + case *ast.Ident, *ast.ParenExpr, *ast.CallExpr, *ast.StarExpr, *ast.IndexExpr, *ast.IndexListExpr, *ast.SelectorExpr: + newText := "!" + oldText + if oldText == "true" { + newText = "false" + } else if oldText == "false" { + newText = "true" + } + + return []byte(newText), nil + + case *ast.UnaryExpr: + if expr.Op != token.NOT { + // This should never happen + return dumbInvert(fset, cond, src), nil + } + + inverse := expr.X + if p, isParen := inverse.(*ast.ParenExpr); isParen { + // We got !(x), remove the parentheses with the ! so we get just "x" + inverse = p.X + + start := safetoken.StartPosition(fset, inverse.Pos()) + end := safetoken.EndPosition(fset, inverse.End()) + if start.Line != end.Line { + // The expression is multi-line, so we can't remove the parentheses + inverse = expr.X + } + } + + start := safetoken.StartPosition(fset, inverse.Pos()) + end := safetoken.EndPosition(fset, inverse.End()) + textWithoutNot := src[start.Offset:end.Offset] + + return textWithoutNot, nil + + case *ast.BinaryExpr: + // These inversions are unsound for floating point NaN, but that's ok. + negations := map[token.Token]string{ + token.EQL: "!=", + token.LSS: ">=", + token.GTR: "<=", + token.NEQ: "==", + token.LEQ: ">", + token.GEQ: "<", + } + + negation, negationFound := negations[expr.Op] + if !negationFound { + return invertAndOr(fset, expr, src) + } + + xPosInSource := safetoken.StartPosition(fset, expr.X.Pos()) + opPosInSource := safetoken.StartPosition(fset, expr.OpPos) + yPosInSource := safetoken.StartPosition(fset, expr.Y.Pos()) + + textBeforeOp := string(src[xPosInSource.Offset:opPosInSource.Offset]) + + oldOpWithTrailingWhitespace := string(src[opPosInSource.Offset:yPosInSource.Offset]) + newOpWithTrailingWhitespace := negation + oldOpWithTrailingWhitespace[len(expr.Op.String()):] + + textAfterOp := string(src[yPosInSource.Offset:condEnd.Offset]) + + return []byte(textBeforeOp + newOpWithTrailingWhitespace + textAfterOp), nil + } + + return dumbInvert(fset, cond, src), nil +} + +// dumbInvert is a fallback, inverting cond into !(cond). +func dumbInvert(fset *token.FileSet, expr ast.Expr, src []byte) []byte { + start := safetoken.StartPosition(fset, expr.Pos()) + end := safetoken.EndPosition(fset, expr.End()) + text := string(src[start.Offset:end.Offset]) + return []byte("!(" + text + ")") +} + +func invertAndOr(fset *token.FileSet, expr *ast.BinaryExpr, src []byte) ([]byte, error) { + if expr.Op != token.LAND && expr.Op != token.LOR { + // Neither AND nor OR, don't know how to invert this + return dumbInvert(fset, expr, src), nil + } + + oppositeOp := "&&" + if expr.Op == token.LAND { + oppositeOp = "||" + } + + xEndInSource := safetoken.EndPosition(fset, expr.X.End()) + opPosInSource := safetoken.StartPosition(fset, expr.OpPos) + whitespaceAfterBefore := src[xEndInSource.Offset:opPosInSource.Offset] + + invertedBefore, err := invertCondition(fset, expr.X, src) + if err != nil { + return nil, err + } + + invertedAfter, err := invertCondition(fset, expr.Y, src) + if err != nil { + return nil, err + } + + yPosInSource := safetoken.StartPosition(fset, expr.Y.Pos()) + + oldOpWithTrailingWhitespace := string(src[opPosInSource.Offset:yPosInSource.Offset]) + newOpWithTrailingWhitespace := oppositeOp + oldOpWithTrailingWhitespace[len(expr.Op.String()):] + + return []byte(string(invertedBefore) + string(whitespaceAfterBefore) + newOpWithTrailingWhitespace + string(invertedAfter)), nil +} + +// canInvertIfCondition reports whether we can do invert-if-condition on the +// code in the given range +func canInvertIfCondition(file *ast.File, start, end token.Pos) (*ast.IfStmt, bool, error) { + path, _ := astutil.PathEnclosingInterval(file, start, end) + for _, node := range path { + stmt, isIfStatement := node.(*ast.IfStmt) + if !isIfStatement { + continue + } + + if stmt.Else == nil { + // Can't invert conditions without else clauses + return nil, false, fmt.Errorf("else clause required") + } + + if _, hasElseIf := stmt.Else.(*ast.IfStmt); hasElseIf { + // Can't invert conditions with else-if clauses, unclear what that + // would look like + return nil, false, fmt.Errorf("else-if not supported") + } + + return stmt, true, nil + } + + return nil, false, fmt.Errorf("not an if statement") +} diff --git a/contribs/gnopls/internal/golang/known_packages.go b/contribs/gnopls/internal/golang/known_packages.go new file mode 100644 index 00000000000..3b320d4f782 --- /dev/null +++ b/contribs/gnopls/internal/golang/known_packages.go @@ -0,0 +1,137 @@ +// 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 golang + +import ( + "context" + "go/parser" + "go/token" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/imports" +) + +// KnownPackagePaths returns a new list of package paths of all known +// packages in the package graph that could potentially be imported by +// the given file. The list is ordered lexicographically, except that +// all dot-free paths (standard packages) appear before dotful ones. +// +// It is part of the gopls.list_known_packages command. +func KnownPackagePaths(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]PackagePath, error) { + // This algorithm is expressed in terms of Metadata, not Packages, + // so it doesn't cause or wait for type checking. + + current, err := NarrowestMetadataForFile(ctx, snapshot, fh.URI()) + if err != nil { + return nil, err // e.g. context cancelled + } + + // Parse the file's imports so we can compute which + // PackagePaths are imported by this specific file. + src, err := fh.Content() + if err != nil { + return nil, err + } + file, err := parser.ParseFile(token.NewFileSet(), fh.URI().Path(), src, parser.ImportsOnly) + if err != nil { + return nil, err + } + imported := make(map[PackagePath]bool) + for _, imp := range file.Imports { + if id := current.DepsByImpPath[metadata.UnquoteImportPath(imp)]; id != "" { + if mp := snapshot.Metadata(id); mp != nil { + imported[mp.PkgPath] = true + } + } + } + + // Now find candidates among all known packages. + knownPkgs, err := snapshot.AllMetadata(ctx) + if err != nil { + return nil, err + } + seen := make(map[PackagePath]bool) + for _, knownPkg := range knownPkgs { + // package main cannot be imported + if knownPkg.Name == "main" { + continue + } + // test packages cannot be imported + if knownPkg.ForTest != "" { + continue + } + // No need to import what the file already imports. + // This check is based on PackagePath, not PackageID, + // so that all test variants are filtered out too. + if imported[knownPkg.PkgPath] { + continue + } + // make sure internal packages are importable by the file + if !metadata.IsValidImport(current.PkgPath, knownPkg.PkgPath, snapshot.View().Type() != cache.GoPackagesDriverView) { + continue + } + // naive check on cyclical imports + if isDirectlyCyclical(current, knownPkg) { + continue + } + // AllMetadata may have multiple variants of a pkg. + seen[knownPkg.PkgPath] = true + } + + // Augment the set by invoking the goimports algorithm. + if err := snapshot.RunProcessEnvFunc(ctx, func(ctx context.Context, o *imports.Options) error { + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*80) + defer cancel() + var seenMu sync.Mutex + wrapped := func(ifix imports.ImportFix) { + seenMu.Lock() + defer seenMu.Unlock() + // TODO(adonovan): what if the actual package path has a vendor/ prefix? + seen[PackagePath(ifix.StmtInfo.ImportPath)] = true + } + return imports.GetAllCandidates(ctx, wrapped, "", fh.URI().Path(), string(current.Name), o.Env) + }); err != nil { + // If goimports failed, proceed with just the candidates from the metadata. + event.Error(ctx, "imports.GetAllCandidates", err) + } + + // Sort lexicographically, but with std before non-std packages. + paths := make([]PackagePath, 0, len(seen)) + for path := range seen { + paths = append(paths, path) + } + sort.Slice(paths, func(i, j int) bool { + importI, importJ := paths[i], paths[j] + iHasDot := strings.Contains(string(importI), ".") + jHasDot := strings.Contains(string(importJ), ".") + if iHasDot != jHasDot { + return jHasDot // dot-free paths (standard packages) compare less + } + return importI < importJ + }) + + return paths, nil +} + +// isDirectlyCyclical checks if imported directly imports pkg. +// It does not (yet) offer a full cyclical check because showing a user +// a list of importable packages already generates a very large list +// and having a few false positives in there could be worth the +// performance snappiness. +// +// TODO(adonovan): ensure that metadata graph is always cyclic! +// Many algorithms will get confused or even stuck in the +// presence of cycles. Then replace this function by 'false'. +func isDirectlyCyclical(pkg, imported *metadata.Package) bool { + _, ok := imported.DepsByPkgPath[pkg.PkgPath] + return ok +} diff --git a/contribs/gnopls/internal/golang/lines.go b/contribs/gnopls/internal/golang/lines.go new file mode 100644 index 00000000000..6a17e928b34 --- /dev/null +++ b/contribs/gnopls/internal/golang/lines.go @@ -0,0 +1,245 @@ +// 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 golang + +// This file defines refactorings for splitting lists of elements +// (arguments, literals, etc) across multiple lines, and joining +// them into a single line. + +import ( + "bytes" + "go/ast" + "go/token" + "go/types" + "slices" + "sort" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/util/safetoken" +) + +// canSplitLines checks whether we can split lists of elements inside +// an enclosing curly bracket/parens into separate lines. +func canSplitLines(file *ast.File, fset *token.FileSet, start, end token.Pos) (string, bool, error) { + itemType, items, comments, _, _, _ := findSplitJoinTarget(fset, file, nil, start, end) + if itemType == "" { + return "", false, nil + } + + if !canSplitJoinLines(items, comments) { + return "", false, nil + } + + for i := 1; i < len(items); i++ { + prevLine := safetoken.EndPosition(fset, items[i-1].End()).Line + curLine := safetoken.StartPosition(fset, items[i].Pos()).Line + if prevLine == curLine { + return "Split " + itemType + " into separate lines", true, nil + } + } + + return "", false, nil +} + +// canJoinLines checks whether we can join lists of elements inside an +// enclosing curly bracket/parens into a single line. +func canJoinLines(file *ast.File, fset *token.FileSet, start, end token.Pos) (string, bool, error) { + itemType, items, comments, _, _, _ := findSplitJoinTarget(fset, file, nil, start, end) + if itemType == "" { + return "", false, nil + } + + if !canSplitJoinLines(items, comments) { + return "", false, nil + } + + for i := 1; i < len(items); i++ { + prevLine := safetoken.EndPosition(fset, items[i-1].End()).Line + curLine := safetoken.StartPosition(fset, items[i].Pos()).Line + if prevLine != curLine { + return "Join " + itemType + " into one line", true, nil + } + } + + return "", false, nil +} + +// canSplitJoinLines determines whether we should split/join the lines or not. +func canSplitJoinLines(items []ast.Node, comments []*ast.CommentGroup) bool { + if len(items) <= 1 { + return false + } + + for _, cg := range comments { + if !strings.HasPrefix(cg.List[0].Text, "/*") { + return false // can't split/join lists containing "//" comments + } + } + + return true +} + +// splitLines is a singleFile fixer. +func splitLines(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, _ *types.Package, _ *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { + itemType, items, comments, indent, braceOpen, braceClose := findSplitJoinTarget(fset, file, src, start, end) + if itemType == "" { + return nil, nil, nil // no fix available + } + + return fset, processLines(fset, items, comments, src, braceOpen, braceClose, ",\n", "\n", ",\n"+indent, indent+"\t"), nil +} + +// joinLines is a singleFile fixer. +func joinLines(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, _ *types.Package, _ *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { + itemType, items, comments, _, braceOpen, braceClose := findSplitJoinTarget(fset, file, src, start, end) + if itemType == "" { + return nil, nil, nil // no fix available + } + + return fset, processLines(fset, items, comments, src, braceOpen, braceClose, ", ", "", "", ""), nil +} + +// processLines is the common operation for both split and join lines because this split/join operation is +// essentially a transformation of the separating whitespace. +func processLines(fset *token.FileSet, items []ast.Node, comments []*ast.CommentGroup, src []byte, braceOpen, braceClose token.Pos, sep, prefix, suffix, indent string) *analysis.SuggestedFix { + nodes := slices.Clone(items) + + // box *ast.CommentGroup to ast.Node for easier processing later. + for _, cg := range comments { + nodes = append(nodes, cg) + } + + // Sort to interleave comments and nodes. + sort.Slice(nodes, func(i, j int) bool { + return nodes[i].Pos() < nodes[j].Pos() + }) + + edits := []analysis.TextEdit{ + { + Pos: token.Pos(int(braceOpen) + len("{")), + End: nodes[0].Pos(), + NewText: []byte(prefix + indent), + }, + { + Pos: nodes[len(nodes)-1].End(), + End: braceClose, + NewText: []byte(suffix), + }, + } + + for i := 1; i < len(nodes); i++ { + pos, end := nodes[i-1].End(), nodes[i].Pos() + if pos > end { + // this will happen if we have a /*-style comment inside of a Field + // e.g. `a /*comment here */ int` + // + // we will ignore as we only care about finding the field delimiter. + continue + } + + // at this point, the `,` token in between 2 nodes here must be the field delimiter. + posOffset := safetoken.EndPosition(fset, pos).Offset + endOffset := safetoken.StartPosition(fset, end).Offset + if bytes.IndexByte(src[posOffset:endOffset], ',') == -1 { + // nodes[i] or nodes[i-1] is a comment hence no delimiter in between + // in such case, do nothing. + continue + } + + edits = append(edits, analysis.TextEdit{Pos: pos, End: end, NewText: []byte(sep + indent)}) + } + + return &analysis.SuggestedFix{TextEdits: edits} +} + +// findSplitJoinTarget returns the first curly bracket/parens that encloses the current cursor. +func findSplitJoinTarget(fset *token.FileSet, file *ast.File, src []byte, start, end token.Pos) (itemType string, items []ast.Node, comments []*ast.CommentGroup, indent string, open, close token.Pos) { + isCursorInside := func(nodePos, nodeEnd token.Pos) bool { + return nodePos < start && end < nodeEnd + } + + findTarget := func() (targetType string, target ast.Node, open, close token.Pos) { + path, _ := astutil.PathEnclosingInterval(file, start, end) + for _, node := range path { + switch node := node.(type) { + case *ast.FuncType: + // params or results of func signature + // Note: + // - each ast.Field (e.g. "x, y, z int") is considered a single item. + // - splitting Params and Results lists is not usually good style. + if p := node.Params; isCursorInside(p.Opening, p.Closing) { + return "parameters", p, p.Opening, p.Closing + } + if r := node.Results; r != nil && isCursorInside(r.Opening, r.Closing) { + return "results", r, r.Opening, r.Closing + } + case *ast.CallExpr: // f(a, b, c) + if isCursorInside(node.Lparen, node.Rparen) { + return "arguments", node, node.Lparen, node.Rparen + } + case *ast.CompositeLit: // T{a, b, c} + if isCursorInside(node.Lbrace, node.Rbrace) { + return "elements", node, node.Lbrace, node.Rbrace + } + } + } + + return "", nil, 0, 0 + } + + targetType, targetNode, open, close := findTarget() + if targetType == "" { + return "", nil, nil, "", 0, 0 + } + + switch node := targetNode.(type) { + case *ast.FieldList: + for _, field := range node.List { + items = append(items, field) + } + case *ast.CallExpr: + for _, arg := range node.Args { + items = append(items, arg) + } + case *ast.CompositeLit: + for _, arg := range node.Elts { + items = append(items, arg) + } + } + + // preserve comments separately as it's not part of the targetNode AST. + for _, cg := range file.Comments { + if open <= cg.Pos() && cg.Pos() < close { + comments = append(comments, cg) + } + } + + // indent is the leading whitespace before the opening curly bracket/paren. + // + // in case where we don't have access to src yet i.e. src == nil + // it's fine to return incorrect indent because we don't need it yet. + indent = "" + if len(src) > 0 { + var pos token.Pos + switch node := targetNode.(type) { + case *ast.FieldList: + pos = node.Opening + case *ast.CallExpr: + pos = node.Lparen + case *ast.CompositeLit: + pos = node.Lbrace + } + + split := bytes.Split(src, []byte("\n")) + targetLineNumber := safetoken.StartPosition(fset, pos).Line + firstLine := string(split[targetLineNumber-1]) + trimmed := strings.TrimSpace(string(firstLine)) + indent = firstLine[:strings.Index(firstLine, trimmed)] + } + + return targetType, items, comments, indent, open, close +} diff --git a/contribs/gnopls/internal/golang/linkname.go b/contribs/gnopls/internal/golang/linkname.go new file mode 100644 index 00000000000..c4ec3517b53 --- /dev/null +++ b/contribs/gnopls/internal/golang/linkname.go @@ -0,0 +1,145 @@ +// 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 golang + +import ( + "context" + "errors" + "fmt" + "go/token" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/safetoken" +) + +// ErrNoLinkname is returned by LinknameDefinition when no linkname +// directive is found at a particular position. +// As such it indicates that other definitions could be worth checking. +var ErrNoLinkname = errors.New("no linkname directive found") + +// linknameDefinition finds the definition of the linkname directive in m at pos. +// If there is no linkname directive at pos, returns ErrNoLinkname. +func linknameDefinition(ctx context.Context, snapshot *cache.Snapshot, m *protocol.Mapper, from protocol.Position) ([]protocol.Location, error) { + pkgPath, name, _ := parseLinkname(m, from) + if pkgPath == "" { + return nil, ErrNoLinkname + } + + _, pgf, pos, err := findLinkname(ctx, snapshot, PackagePath(pkgPath), name) + if err != nil { + return nil, fmt.Errorf("find linkname: %w", err) + } + loc, err := pgf.PosLocation(pos, pos+token.Pos(len(name))) + if err != nil { + return nil, fmt.Errorf("location of linkname: %w", err) + } + return []protocol.Location{loc}, nil +} + +// parseLinkname attempts to parse a go:linkname declaration at the given pos. +// If successful, it returns +// - package path referenced +// - object name referenced +// - byte offset in mapped file of the start of the link target +// of the linkname directives 2nd argument. +// +// If the position is not in the second argument of a go:linkname directive, +// or parsing fails, it returns "", "", 0. +func parseLinkname(m *protocol.Mapper, pos protocol.Position) (pkgPath, name string, targetOffset int) { + lineStart, err := m.PositionOffset(protocol.Position{Line: pos.Line, Character: 0}) + if err != nil { + return "", "", 0 + } + lineEnd, err := m.PositionOffset(protocol.Position{Line: pos.Line + 1, Character: 0}) + if err != nil { + return "", "", 0 + } + + directive := string(m.Content[lineStart:lineEnd]) + // (Assumes no leading spaces.) + if !strings.HasPrefix(directive, "//go:linkname") { + return "", "", 0 + } + // Sometimes source code (typically tests) has another + // comment after the directive, trim that away. + if i := strings.LastIndex(directive, "//"); i != 0 { + directive = strings.TrimSpace(directive[:i]) + } + + // Looking for pkgpath in '//go:linkname f pkgpath.g'. + // (We ignore 1-arg linkname directives.) + parts := strings.Fields(directive) + if len(parts) != 3 { + return "", "", 0 + } + + // Inside 2nd arg [start, end]? + // (Assumes no trailing spaces.) + offset, err := m.PositionOffset(pos) + if err != nil { + return "", "", 0 + } + end := lineStart + len(directive) + start := end - len(parts[2]) + if !(start <= offset && offset <= end) { + return "", "", 0 + } + linkname := parts[2] + + // Split the pkg path from the name. + dot := strings.LastIndexByte(linkname, '.') + if dot < 0 { + return "", "", 0 + } + + return linkname[:dot], linkname[dot+1:], start +} + +// findLinkname searches dependencies of packages containing fh for an object +// with linker name matching the given package path and name. +func findLinkname(ctx context.Context, snapshot *cache.Snapshot, pkgPath PackagePath, name string) (*cache.Package, *parsego.File, token.Pos, error) { + // Typically the linkname refers to a forward dependency + // or a reverse dependency, but in general it may refer + // to any package that is linked with this one. + var pkgMeta *metadata.Package + metas, err := snapshot.AllMetadata(ctx) + if err != nil { + return nil, nil, token.NoPos, err + } + metadata.RemoveIntermediateTestVariants(&metas) + for _, meta := range metas { + if meta.PkgPath == pkgPath { + pkgMeta = meta + break + } + } + if pkgMeta == nil { + return nil, nil, token.NoPos, fmt.Errorf("cannot find package %q", pkgPath) + } + + // When found, type check the desired package (snapshot.TypeCheck in TypecheckFull mode), + pkgs, err := snapshot.TypeCheck(ctx, pkgMeta.ID) + if err != nil { + return nil, nil, token.NoPos, err + } + pkg := pkgs[0] + + obj := pkg.Types().Scope().Lookup(name) + if obj == nil { + return nil, nil, token.NoPos, fmt.Errorf("package %q does not define %s", pkgPath, name) + } + + objURI := safetoken.StartPosition(pkg.FileSet(), obj.Pos()) + pgf, err := pkg.File(protocol.URIFromPath(objURI.Filename)) + if err != nil { + return nil, nil, token.NoPos, err + } + + return pkg, pgf, obj.Pos(), nil +} diff --git a/contribs/gnopls/internal/golang/origin.go b/contribs/gnopls/internal/golang/origin.go new file mode 100644 index 00000000000..aa77a9b3aa4 --- /dev/null +++ b/contribs/gnopls/internal/golang/origin.go @@ -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 golang + +import "go/types" + +// containsOrigin reports whether the provided object set contains an object +// with the same origin as the provided obj (which may be a synthetic object +// created during instantiation). +func containsOrigin(objSet map[types.Object]bool, obj types.Object) bool { + objOrigin := origin(obj) + for target := range objSet { + if origin(target) == objOrigin { + return true + } + } + return false +} + +func origin(obj types.Object) types.Object { + switch obj := obj.(type) { + case *types.Var: + return obj.Origin() + case *types.Func: + return obj.Origin() + } + return obj +} diff --git a/contribs/gnopls/internal/golang/pkgdoc.go b/contribs/gnopls/internal/golang/pkgdoc.go new file mode 100644 index 00000000000..ed8f1b388f0 --- /dev/null +++ b/contribs/gnopls/internal/golang/pkgdoc.go @@ -0,0 +1,889 @@ +// 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 golang + +// This file defines a simple HTML rendering of package documentation +// in imitation of the style of pkg.go.dev. +// +// The current implementation is just a starting point and a +// placeholder for a more sophisticated one. +// +// TODO(adonovan): +// - rewrite using html/template. +// Or factor with golang.org/x/pkgsite/internal/godoc/dochtml. +// - emit breadcrumbs for parent + sibling packages. +// - list promoted methods---we have type information! +// - gather Example tests, following go/doc and pkgsite. +// - add option for doc.AllDecls: show non-exported symbols too. +// - style the
  • bullets in the index as invisible. +// - add push notifications such as didChange -> reload. +// - there appears to be a maximum file size beyond which the +// "source.doc" code action is not offered. Remove that. +// - modify JS httpGET function to give a transient visual indication +// when clicking a source link that the editor is being navigated +// (in case it doesn't raise itself, like VS Code). +// - move this into a new package, golang/web, and then +// split out the various helpers without fear of polluting +// the golang package namespace? +// - show "Deprecated" chip when appropriate. + +import ( + "bytes" + "fmt" + "go/ast" + "go/doc" + "go/doc/comment" + "go/format" + "go/token" + "go/types" + "html" + "iter" + "path/filepath" + "slices" + "strings" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/protocol" + goplsastutil "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/stdlib" + "golang.org/x/tools/internal/typesinternal" +) + +// DocFragment finds the package and (optionally) symbol identified by +// the current selection, and returns the package path and the +// optional symbol URL fragment (e.g. "#Buffer.Len") for a symbol, +// along with a title for the code action. +// +// It is called once to offer the code action, and again when the +// command is executed. This is slightly inefficient but ensures that +// the title and package/symbol logic are consistent in all cases. +// +// It returns zeroes if there is nothing to see here (e.g. reference to a builtin). +func DocFragment(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (pkgpath PackagePath, fragment, title string) { + thing := thingAtPoint(pkg, pgf, start, end) + + makeTitle := func(kind string, imp *types.Package, name string) string { + title := "Browse documentation for " + kind + " " + if imp != nil && imp != pkg.Types() { + title += imp.Name() + "." + } + return title + name + } + + wholePackage := func(pkg *types.Package) (PackagePath, string, string) { + // External test packages don't have /pkg doc pages, + // so instead show the doc for the package under test. + // (This named-based heuristic is imperfect.) + if forTest := strings.TrimSuffix(pkg.Path(), "_test"); forTest != pkg.Path() { + return PackagePath(forTest), "", makeTitle("package", nil, filepath.Base(forTest)) + } + + return PackagePath(pkg.Path()), "", makeTitle("package", nil, pkg.Name()) + } + + // Conceptually, we check cases in the order: + // 1. symbol + // 2. package + // 3. enclosing + // but the logic of cases 1 and 3 are identical, hence the odd factoring. + + // Imported package? + if thing.pkg != nil && thing.symbol == nil { + return wholePackage(thing.pkg) + } + + // Symbol? + var sym types.Object + if thing.symbol != nil { + sym = thing.symbol // reference to a symbol + } else if thing.enclosing != nil { + sym = thing.enclosing // selection is within a declaration of a symbol + } + if sym == nil { + return wholePackage(pkg.Types()) // no symbol + } + + // Built-in (error.Error, append or unsafe). + // TODO(adonovan): handle builtins in /pkg viewer. + if sym.Pkg() == nil { + return "", "", "" // nothing to see here + } + pkgpath = PackagePath(sym.Pkg().Path()) + + // Unexported? Show enclosing type or package. + if !sym.Exported() { + // Unexported method of exported type? + if fn, ok := sym.(*types.Func); ok { + if recv := fn.Signature().Recv(); recv != nil { + _, named := typesinternal.ReceiverNamed(recv) + if named != nil && named.Obj().Exported() { + sym = named.Obj() + goto below + } + } + } + + return wholePackage(sym.Pkg()) + below: + } + + // Reference to symbol in external test package? + // Short-circuit: see comment in wholePackage. + if strings.HasSuffix(string(pkgpath), "_test") { + return wholePackage(pkg.Types()) + } + + // package-level symbol? + if isPackageLevel(sym) { + return pkgpath, sym.Name(), makeTitle(objectKind(sym), sym.Pkg(), sym.Name()) + } + + // Inv: sym is field or method, or local. + switch sym := sym.(type) { + case *types.Func: // => method + sig := sym.Signature() + isPtr, named := typesinternal.ReceiverNamed(sig.Recv()) + if named != nil { + if !named.Obj().Exported() { + return wholePackage(sym.Pkg()) // exported method of unexported type + } + name := fmt.Sprintf("(%s%s).%s", + strings.Repeat("*", btoi(isPtr)), // for *T + named.Obj().Name(), + sym.Name()) + fragment := named.Obj().Name() + "." + sym.Name() + return pkgpath, fragment, makeTitle("method", sym.Pkg(), name) + } + + case *types.Var: + if sym.IsField() { + // TODO(adonovan): support fields. + // The Var symbol doesn't include the struct + // type, so we need to use the logic from + // Hover. (This isn't important for + // DocFragment as fields don't have fragments, + // but it matters to the grand unification of + // Hover/Definition/DocFragment. + } + } + + // Field, non-exported method, or local declaration: + // just show current package. + return wholePackage(pkg.Types()) +} + +// thing describes the package or symbol denoted by a selection. +// +// TODO(adonovan): Hover, Definition, and References all start by +// identifying the selected object. Let's achieve a better factoring +// of the common parts using this structure, including uniform +// treatment of doc links, linkname, and suchlike. +type thing struct { + // At most one of these fields is set. + // (The 'enclosing' field is a fallback for when neither + // of the first two is set.) + symbol types.Object // referenced symbol + pkg *types.Package // referenced package + enclosing types.Object // package-level symbol or method decl enclosing selection +} + +func thingAtPoint(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) thing { + path, _ := astutil.PathEnclosingInterval(pgf.File, start, end) + + // In an import spec? + if len(path) >= 3 { // [...ImportSpec GenDecl File] + if spec, ok := path[len(path)-3].(*ast.ImportSpec); ok { + if pkgname := pkg.TypesInfo().PkgNameOf(spec); pkgname != nil { + return thing{pkg: pkgname.Imported()} + } + } + } + + // Definition or reference to symbol? + var obj types.Object + if id, ok := path[0].(*ast.Ident); ok { + obj = pkg.TypesInfo().ObjectOf(id) + + // Treat use to PkgName like ImportSpec. + if pkgname, ok := obj.(*types.PkgName); ok { + return thing{pkg: pkgname.Imported()} + } + + } else if sel, ok := path[0].(*ast.SelectorExpr); ok { + // e.g. selection is "fmt.Println" or just a portion ("mt.Prin") + obj = pkg.TypesInfo().Uses[sel.Sel] + } + if obj != nil { + return thing{symbol: obj} + } + + // Find enclosing declaration. + if n := len(path); n > 1 { + switch decl := path[n-2].(type) { + case *ast.FuncDecl: + // method? + if fn := pkg.TypesInfo().Defs[decl.Name]; fn != nil { + return thing{enclosing: fn} + } + + case *ast.GenDecl: + // path=[... Spec? GenDecl File] + for _, spec := range decl.Specs { + if n > 2 && spec == path[n-3] { + var name *ast.Ident + switch spec := spec.(type) { + case *ast.ValueSpec: + // var, const: use first name + name = spec.Names[0] + case *ast.TypeSpec: + name = spec.Name + } + if name != nil { + return thing{enclosing: pkg.TypesInfo().Defs[name]} + } + break + } + } + } + } + + return thing{} // nothing to see here +} + +// Web is an abstraction of gopls' web server. +type Web interface { + // PkgURL forms URLs of package or symbol documentation. + PkgURL(viewID string, path PackagePath, fragment string) protocol.URI + + // SrcURL forms URLs that cause the editor to open a file at a specific position. + SrcURL(filename string, line, col8 int) protocol.URI +} + +// PackageDocHTML formats the package documentation page. +// +// The posURL function returns a URL that when visited, has the side +// effect of causing gopls to direct the client editor to navigate to +// the specified file/line/column position, in UTF-8 coordinates. +func PackageDocHTML(viewID string, pkg *cache.Package, web Web) ([]byte, error) { + // We can't use doc.NewFromFiles (even with doc.PreserveAST + // mode) as it calls ast.NewPackage which assumes that each + // ast.File has an ast.Scope and resolves identifiers to + // (deprecated) ast.Objects. (This is golang/go#66290.) + // But doc.New only requires pkg.{Name,Files}, + // so we just boil it down. + // + // The only loss is doc.classifyExamples. + // TODO(adonovan): simulate that too. + fileMap := make(map[string]*ast.File) + for _, f := range pkg.Syntax() { + fileMap[pkg.FileSet().File(f.Pos()).Name()] = f + } + astpkg := &ast.Package{ + Name: pkg.Types().Name(), + Files: fileMap, + } + // PreserveAST mode only half works (golang/go#66449): it still + // mutates ASTs when filtering out non-exported symbols. + // As a workaround, enable AllDecls to suppress filtering, + // and do it ourselves. + mode := doc.PreserveAST | doc.AllDecls + docpkg := doc.New(astpkg, pkg.Types().Path(), mode) + + // Discard non-exported symbols. + // TODO(adonovan): do this conditionally, and expose option in UI. + const showUnexported = false + if !showUnexported { + var ( + unexported = func(name string) bool { return !token.IsExported(name) } + filterValues = func(slice *[]*doc.Value) { + delValue := func(v *doc.Value) bool { + v.Names = slices.DeleteFunc(v.Names, unexported) + return len(v.Names) == 0 + } + *slice = slices.DeleteFunc(*slice, delValue) + } + filterFuncs = func(funcs *[]*doc.Func) { + *funcs = slices.DeleteFunc(*funcs, func(v *doc.Func) bool { + return unexported(v.Name) + }) + } + ) + filterValues(&docpkg.Consts) + filterValues(&docpkg.Vars) + filterFuncs(&docpkg.Funcs) + docpkg.Types = slices.DeleteFunc(docpkg.Types, func(t *doc.Type) bool { + filterValues(&t.Consts) + filterValues(&t.Vars) + filterFuncs(&t.Funcs) + filterFuncs(&t.Methods) + return unexported(t.Name) + }) + } + + var docHTML func(comment string) []byte + { + // Adapt doc comment parser and printer + // to our representation of Go packages + // so that doc links (e.g. "[fmt.Println]") + // become valid links. + + printer := docpkg.Printer() + printer.DocLinkURL = func(link *comment.DocLink) string { + path := pkg.Metadata().PkgPath + if link.ImportPath != "" { + path = PackagePath(link.ImportPath) + } + fragment := link.Name + if link.Recv != "" { + fragment = link.Recv + "." + link.Name + } + return web.PkgURL(viewID, path, fragment) + } + parser := docpkg.Parser() + parser.LookupPackage = func(name string) (importPath string, ok bool) { + // Ambiguous: different files in the same + // package may have different import mappings, + // but the hook doesn't provide the file context. + // TODO(adonovan): conspire with docHTML to + // pass the doc comment's enclosing file through + // a shared variable, so that we can compute + // the correct per-file mapping. + // + // TODO(adonovan): check for PkgName.Name + // matches, but also check for + // PkgName.Imported.Namer matches, since some + // packages are typically imported under a + // non-default name (e.g. pathpkg "path") but + // may be referred to in doc links using their + // canonical name. + for _, f := range pkg.Syntax() { + for _, imp := range f.Imports { + pkgName := pkg.TypesInfo().PkgNameOf(imp) + if pkgName != nil && pkgName.Name() == name { + return pkgName.Imported().Path(), true + } + } + } + return "", false + } + parser.LookupSym = func(recv, name string) (ok bool) { + // package-level decl? + if recv == "" { + return pkg.Types().Scope().Lookup(name) != nil + } + + // method? + tname, ok := pkg.Types().Scope().Lookup(recv).(*types.TypeName) + if !ok { + return false + } + m, _, _ := types.LookupFieldOrMethod(tname.Type(), true, pkg.Types(), name) + return is[*types.Func](m) + } + docHTML = func(comment string) []byte { + return printer.HTML(parser.Parse(comment)) + } + } + + scope := pkg.Types().Scope() + escape := html.EscapeString + + title := fmt.Sprintf("%s package - %s - Gopls packages", + pkg.Types().Name(), escape(pkg.Types().Path())) + + var buf bytes.Buffer + buf.WriteString(` + + + + ` + title + ` + + + + + + +
    +\n") + fmt.Fprintf(&buf, "
    \n") + + // -- main element -- + + // nodeHTML returns HTML markup for a syntax tree. + // It replaces referring identifiers with links, + // and adds style spans for strings and comments. + nodeHTML := func(n ast.Node) string { + + // linkify returns the appropriate URL (if any) for an identifier. + linkify := func(id *ast.Ident) protocol.URI { + if obj, ok := pkg.TypesInfo().Uses[id]; ok && obj.Pkg() != nil { + // imported package name? + if pkgname, ok := obj.(*types.PkgName); ok { + // TODO(adonovan): do this for Defs of PkgName too. + return web.PkgURL(viewID, PackagePath(pkgname.Imported().Path()), "") + } + + // package-level symbol? + if obj.Parent() == obj.Pkg().Scope() { + if obj.Pkg() == pkg.Types() { + return "#" + obj.Name() // intra-package ref + } else { + return web.PkgURL(viewID, PackagePath(obj.Pkg().Path()), obj.Name()) + } + } + + // method of package-level named type? + if fn, ok := obj.(*types.Func); ok { + sig := fn.Signature() + if sig.Recv() != nil { + _, named := typesinternal.ReceiverNamed(sig.Recv()) + if named != nil { + fragment := named.Obj().Name() + "." + fn.Name() + return web.PkgURL(viewID, PackagePath(fn.Pkg().Path()), fragment) + } + } + return "" + } + + // TODO(adonovan): field of package-level named struct type. + // (Requires an index, since there's no way to + // get from Var to Named.) + } + return "" + } + + // Splice spans into HTML-escaped segments of the + // original source buffer (which is usually but not + // necessarily formatted). + // + // (For expedience we don't use the more sophisticated + // approach taken by cmd/godoc and pkgsite's render + // package, which emit the text, spans, and comments + // in one traversal of the syntax tree.) + // + // TODO(adonovan): splice styled spans around comments too. + // + // TODO(adonovan): pkgsite prints specs from grouped + // type decls like "type ( T1; T2 )" to make them + // appear as separate decls. We should too. + var buf bytes.Buffer + for _, file := range pkg.CompiledGoFiles() { + if goplsastutil.NodeContains(file.File, n.Pos()) { + pos := n.Pos() + + // emit emits source in the interval [pos:to] and updates pos. + emit := func(to token.Pos) { + // Ident and BasicLit always have a valid pos. + // (Failure means the AST has been corrupted.) + if !to.IsValid() { + bug.Reportf("invalid Pos") + } + start, err := safetoken.Offset(file.Tok, pos) + if err != nil { + bug.Reportf("invalid start Pos: %v", err) + } + end, err := safetoken.Offset(file.Tok, to) + if err != nil { + bug.Reportf("invalid end Pos: %v", err) + } + buf.WriteString(escape(string(file.Src[start:end]))) + pos = to + } + ast.Inspect(n, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.Ident: + emit(n.Pos()) + pos = n.End() + if url := linkify(n); url != "" { + fmt.Fprintf(&buf, "%s", url, escape(n.Name)) + } else { + buf.WriteString(escape(n.Name)) // plain + } + + case *ast.BasicLit: + emit(n.Pos()) + pos = n.End() + fmt.Fprintf(&buf, "%s", escape(n.Value)) + } + return true + }) + emit(n.End()) + return buf.String() + } + } + + // Original source not found. + // Format the node without adornments. + if err := format.Node(&buf, pkg.FileSet(), n); err != nil { + // e.g. BadDecl? + buf.Reset() + fmt.Fprintf(&buf, "formatting error: %v", err) + } + return escape(buf.String()) + } + + // fnString is like fn.String() except that it: + // - shows the receiver name; + // - uses space "(T) M()" not dot "(T).M()" after receiver; + // - doesn't bother with the special case for interface receivers + // since it is unreachable for the methods in go/doc. + // - elides parameters after the first three: f(a, b, c, ...). + fnString := func(fn *types.Func) string { + pkgRelative := typesinternal.NameRelativeTo(pkg.Types()) + + sig := fn.Signature() + + // Emit "func (recv T) F". + var buf bytes.Buffer + buf.WriteString("func ") + if recv := sig.Recv(); recv != nil { + buf.WriteByte('(') + if recv.Name() != "" { + buf.WriteString(recv.Name()) + buf.WriteByte(' ') + } + types.WriteType(&buf, recv.Type(), pkgRelative) + buf.WriteByte(')') + buf.WriteByte(' ') // (ObjectString uses a '.' here) + } else if pkg := fn.Pkg(); pkg != nil { + if s := pkgRelative(pkg); s != "" { + buf.WriteString(s) + buf.WriteByte('.') + } + } + buf.WriteString(fn.Name()) + + // Emit signature. + // + // Elide parameters after the third one. + // WriteSignature is too complex to fork, so we replace + // parameters 4+ with "invalid type", format, + // then post-process the string. + if sig.Params().Len() > 3 { + + // Clone each TypeParam as NewSignatureType modifies them (#67294). + cloneTparams := func(seq *types.TypeParamList) []*types.TypeParam { + slice := make([]*types.TypeParam, seq.Len()) + for i := range slice { + tparam := seq.At(i) + slice[i] = types.NewTypeParam(tparam.Obj(), tparam.Constraint()) + } + return slice + } + + sig = types.NewSignatureType( + sig.Recv(), + cloneTparams(sig.RecvTypeParams()), + cloneTparams(sig.TypeParams()), + types.NewTuple(append( + slices.Collect(tupleVariables(sig.Params()))[:3], + types.NewVar(0, nil, "", types.Typ[types.Invalid]))...), + sig.Results(), + false) // any final ...T parameter is truncated + } + types.WriteSignature(&buf, sig, pkgRelative) + return strings.ReplaceAll(buf.String(), ", invalid type)", ", ...)") + } + + fmt.Fprintf(&buf, "
    \n") + + // package name + fmt.Fprintf(&buf, "

    Package %s

    \n", pkg.Types().Name()) + + // import path + fmt.Fprintf(&buf, "
    import %q
    \n", pkg.Types().Path()) + + // link to same package in pkg.go.dev + fmt.Fprintf(&buf, "
    \n", + "https://pkg.go.dev/"+string(pkg.Types().Path())) + + // package doc + fmt.Fprintf(&buf, "
    %s
    \n", docHTML(docpkg.Doc)) + + // symbol index + fmt.Fprintf(&buf, "

    Index

    \n") + fmt.Fprintf(&buf, "
      \n") + if len(docpkg.Consts) > 0 { + fmt.Fprintf(&buf, "
    • Constants
    • \n") + } + if len(docpkg.Vars) > 0 { + fmt.Fprintf(&buf, "
    • Variables
    • \n") + } + for _, fn := range docpkg.Funcs { + obj := scope.Lookup(fn.Name).(*types.Func) + fmt.Fprintf(&buf, "
    • %s
    • \n", + obj.Name(), escape(fnString(obj))) + } + for _, doctype := range docpkg.Types { + tname := scope.Lookup(doctype.Name).(*types.TypeName) + fmt.Fprintf(&buf, "
    • type %[1]s
    • \n", + tname.Name()) + + if len(doctype.Funcs)+len(doctype.Methods) > 0 { + fmt.Fprintf(&buf, "
        \n") + + // constructors + for _, docfn := range doctype.Funcs { + obj := scope.Lookup(docfn.Name).(*types.Func) + fmt.Fprintf(&buf, "
      • %s
      • \n", + docfn.Name, escape(fnString(obj))) + } + // methods + for _, docmethod := range doctype.Methods { + method, _, _ := types.LookupFieldOrMethod(tname.Type(), true, tname.Pkg(), docmethod.Name) + fmt.Fprintf(&buf, "
      • %s
      • \n", + doctype.Name, + docmethod.Name, + escape(fnString(method.(*types.Func)))) + } + fmt.Fprintf(&buf, "
      \n") + } + } + // TODO(adonovan): add index of Examples here. + fmt.Fprintf(&buf, "
    \n") + + // constants and variables + values := func(vals []*doc.Value) { + for _, v := range vals { + // anchors + for _, name := range v.Names { + fmt.Fprintf(&buf, "\n", escape(name)) + } + + // declaration + decl2 := *v.Decl // shallow copy + decl2.Doc = nil + fmt.Fprintf(&buf, "
    %s
    \n", nodeHTML(&decl2)) + + // comment (if any) + fmt.Fprintf(&buf, "
    %s
    \n", docHTML(v.Doc)) + } + } + fmt.Fprintf(&buf, "

    Constants

    \n") + if len(docpkg.Consts) == 0 { + fmt.Fprintf(&buf, "
    (no constants)
    \n") + } else { + values(docpkg.Consts) + } + fmt.Fprintf(&buf, "

    Variables

    \n") + if len(docpkg.Vars) == 0 { + fmt.Fprintf(&buf, "
    (no variables)
    \n") + } else { + values(docpkg.Vars) + } + + // addedInHTML returns an HTML division containing the Go release version at + // which this obj became available. + addedInHTML := func(obj types.Object) string { + if sym := StdSymbolOf(obj); sym != nil && sym.Version != stdlib.Version(0) { + return fmt.Sprintf("added in %v", sym.Version) + } + return "" + } + + // package-level functions + fmt.Fprintf(&buf, "

    Functions

    \n") + // funcs emits a list of package-level functions, + // possibly organized beneath the type they construct. + funcs := func(funcs []*doc.Func) { + for _, docfn := range funcs { + obj := scope.Lookup(docfn.Name).(*types.Func) + + fmt.Fprintf(&buf, "

    func %s %s

    \n", + docfn.Name, objHTML(pkg.FileSet(), web, obj), addedInHTML(obj)) + + // decl: func F(params) results + fmt.Fprintf(&buf, "
    %s
    \n", + nodeHTML(docfn.Decl.Type)) + + // comment (if any) + fmt.Fprintf(&buf, "
    %s
    \n", docHTML(docfn.Doc)) + } + } + funcs(docpkg.Funcs) + + // types and their subelements + fmt.Fprintf(&buf, "

    Types

    \n") + for _, doctype := range docpkg.Types { + tname := scope.Lookup(doctype.Name).(*types.TypeName) + + // title and source link + fmt.Fprintf(&buf, "

    type %s %s

    \n", + doctype.Name, objHTML(pkg.FileSet(), web, tname), addedInHTML(tname)) + + // declaration + // TODO(adonovan): excise non-exported struct fields somehow. + decl2 := *doctype.Decl // shallow copy + decl2.Doc = nil + fmt.Fprintf(&buf, "
    %s
    \n", nodeHTML(&decl2)) + + // comment (if any) + fmt.Fprintf(&buf, "
    %s
    \n", docHTML(doctype.Doc)) + + // subelements + values(doctype.Consts) // constants of type T + values(doctype.Vars) // vars of type T + funcs(doctype.Funcs) // constructors of T + + // methods on T + for _, docmethod := range doctype.Methods { + method, _, _ := types.LookupFieldOrMethod(tname.Type(), true, tname.Pkg(), docmethod.Name) + fmt.Fprintf(&buf, "

    func (%s) %s %s

    \n", + doctype.Name, docmethod.Name, + docmethod.Orig, // T or *T + objHTML(pkg.FileSet(), web, method), addedInHTML(method)) + + // decl: func (x T) M(params) results + fmt.Fprintf(&buf, "
    %s
    \n", + nodeHTML(docmethod.Decl.Type)) + + // comment (if any) + fmt.Fprintf(&buf, "
    %s
    \n", + docHTML(docmethod.Doc)) + } + } + + // source files + fmt.Fprintf(&buf, "

    Source files

    \n") + for _, filename := range docpkg.Filenames { + fmt.Fprintf(&buf, "
    %s
    \n", + sourceLink(filepath.Base(filename), web.SrcURL(filename, 1, 1))) + } + + fmt.Fprintf(&buf, "
    \n") + fmt.Fprintf(&buf, "\n") + fmt.Fprintf(&buf, "\n") + + return buf.Bytes(), nil +} + +// tupleVariables returns a go1.23 iterator over the variables of a tuple type. +// +// Example: for v := range tuple.Variables() { ... } +// TODO(adonovan): use t.Variables in go1.24. +func tupleVariables(t *types.Tuple) iter.Seq[*types.Var] { + return func(yield func(v *types.Var) bool) { + for i := range t.Len() { + if !yield(t.At(i)) { + break + } + } + } +} diff --git a/contribs/gnopls/internal/golang/references.go b/contribs/gnopls/internal/golang/references.go new file mode 100644 index 00000000000..6679b45df6b --- /dev/null +++ b/contribs/gnopls/internal/golang/references.go @@ -0,0 +1,688 @@ +// 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. + +package golang + +// This file defines the 'references' query based on a serializable +// index constructed during type checking, thus avoiding the need to +// type-check packages at search time. +// +// See the ./xrefs/ subpackage for the index construction and lookup. +// +// This implementation does not intermingle objects from distinct +// calls to TypeCheck. + +import ( + "context" + "fmt" + "go/ast" + "go/token" + "go/types" + "sort" + "strings" + "sync" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/types/objectpath" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/methodsets" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/event" +) + +// References returns a list of all references (sorted with +// definitions before uses) to the object denoted by the identifier at +// the given file/position, searching the entire workspace. +func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position, includeDeclaration bool) ([]protocol.Location, error) { + references, err := references(ctx, snapshot, fh, pp, includeDeclaration) + if err != nil { + return nil, err + } + locations := make([]protocol.Location, len(references)) + for i, ref := range references { + locations[i] = ref.location + } + return locations, nil +} + +// A reference describes an identifier that refers to the same +// object as the subject of a References query. +type reference struct { + isDeclaration bool + location protocol.Location + pkgPath PackagePath // of declaring package (same for all elements of the slice) +} + +// references returns a list of all references (sorted with +// definitions before uses) to the object denoted by the identifier at +// the given file/position, searching the entire workspace. +func references(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, includeDeclaration bool) ([]reference, error) { + ctx, done := event.Start(ctx, "golang.references") + defer done() + + // Is the cursor within the package name declaration? + _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp) + if err != nil { + return nil, err + } + + var refs []reference + if inPackageName { + refs, err = packageReferences(ctx, snapshot, f.URI()) + } else { + refs, err = ordinaryReferences(ctx, snapshot, f.URI(), pp) + } + if err != nil { + return nil, err + } + + sort.Slice(refs, func(i, j int) bool { + x, y := refs[i], refs[j] + if x.isDeclaration != y.isDeclaration { + return x.isDeclaration // decls < refs + } + return protocol.CompareLocation(x.location, y.location) < 0 + }) + + // De-duplicate by location, and optionally remove declarations. + out := refs[:0] + for _, ref := range refs { + if !includeDeclaration && ref.isDeclaration { + continue + } + if len(out) == 0 || out[len(out)-1].location != ref.location { + out = append(out, ref) + } + } + refs = out + + return refs, nil +} + +// packageReferences returns a list of references to the package +// declaration of the specified name and uri by searching among the +// import declarations of all packages that directly import the target +// package. +func packageReferences(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) ([]reference, error) { + metas, err := snapshot.MetadataForFile(ctx, uri) + if err != nil { + return nil, err + } + if len(metas) == 0 { + return nil, fmt.Errorf("found no package containing %s", uri) + } + + var refs []reference + + // Find external references to the package declaration + // from each direct import of the package. + // + // The narrowest package is the most broadly imported, + // so we choose it for the external references. + // + // But if the file ends with _test.go then we need to + // find the package it is testing; there's no direct way + // to do that, so pick a file from the same package that + // doesn't end in _test.go and start over. + narrowest := metas[0] + if narrowest.ForTest != "" && strings.HasSuffix(string(uri), "_test.go") { + for _, f := range narrowest.CompiledGoFiles { + if !strings.HasSuffix(string(f), "_test.go") { + return packageReferences(ctx, snapshot, f) + } + } + // This package has no non-test files. + // Skip the search for external references. + // (Conceivably one could blank-import an empty package, but why?) + } else { + rdeps, err := snapshot.ReverseDependencies(ctx, narrowest.ID, false) // direct + if err != nil { + return nil, err + } + + // Restrict search to workspace packages. + workspace, err := snapshot.WorkspaceMetadata(ctx) + if err != nil { + return nil, err + } + workspaceMap := make(map[PackageID]*metadata.Package, len(workspace)) + for _, mp := range workspace { + workspaceMap[mp.ID] = mp + } + + for _, rdep := range rdeps { + if _, ok := workspaceMap[rdep.ID]; !ok { + continue + } + for _, uri := range rdep.CompiledGoFiles { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + f, err := snapshot.ParseGo(ctx, fh, parsego.Header) + if err != nil { + return nil, err + } + for _, imp := range f.File.Imports { + if rdep.DepsByImpPath[metadata.UnquoteImportPath(imp)] == narrowest.ID { + refs = append(refs, reference{ + isDeclaration: false, + location: mustLocation(f, imp), + pkgPath: narrowest.PkgPath, + }) + } + } + } + } + } + + // Find internal "references" to the package from + // of each package declaration in the target package itself. + // + // The widest package (possibly a test variant) has the + // greatest number of files and thus we choose it for the + // "internal" references. + widest := metas[len(metas)-1] // may include _test.go files + for _, uri := range widest.CompiledGoFiles { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + f, err := snapshot.ParseGo(ctx, fh, parsego.Header) + if err != nil { + return nil, err + } + // golang/go#66250: don't crash if the package file lacks a name. + if f.File.Name.Pos().IsValid() { + refs = append(refs, reference{ + isDeclaration: true, // (one of many) + location: mustLocation(f, f.File.Name), + pkgPath: widest.PkgPath, + }) + } + } + + return refs, nil +} + +// ordinaryReferences computes references for all ordinary objects (not package declarations). +func ordinaryReferences(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, pp protocol.Position) ([]reference, error) { + // Strategy: use the reference information computed by the + // type checker to find the declaration. First type-check this + // package to find the declaration, then type check the + // declaring package (which may be different), plus variants, + // to find local (in-package) references. + // Global references are satisfied by the index. + + // Strictly speaking, a wider package could provide a different + // declaration (e.g. because the _test.go files can change the + // meaning of a field or method selection), but the narrower + // package reports the more broadly referenced object. + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, uri) + if err != nil { + return nil, err + } + + // Find the selected object (declaration or reference). + // For struct{T}, we choose the field (Def) over the type (Use). + pos, err := pgf.PositionPos(pp) + if err != nil { + return nil, err + } + candidates, _, err := objectsAt(pkg.TypesInfo(), pgf.File, pos) + if err != nil { + return nil, err + } + + // Pick first object arbitrarily. + // The case variables of a type switch have different + // types but that difference is immaterial here. + var obj types.Object + for obj = range candidates { + break + } + if obj == nil { + return nil, ErrNoIdentFound // can't happen + } + + // nil, error, error.Error, iota, or other built-in? + if isBuiltin(obj) { + return nil, fmt.Errorf("references to builtin %q are not supported", obj.Name()) + } + + // Find metadata of all packages containing the object's defining file. + // This may include the query pkg, and possibly other variants. + declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos()) + declURI := protocol.URIFromPath(declPosn.Filename) + variants, err := snapshot.MetadataForFile(ctx, declURI) + if err != nil { + return nil, err + } + if len(variants) == 0 { + return nil, fmt.Errorf("no packages for file %q", declURI) // can't happen + } + // (variants must include ITVs for reverse dependency computation below.) + + // Is object exported? + // If so, compute scope and targets of the global search. + var ( + globalScope = make(map[PackageID]*metadata.Package) // (excludes ITVs) + globalTargets map[PackagePath]map[objectpath.Path]unit + expansions = make(map[PackageID]unit) // packages that caused search expansion + ) + // TODO(adonovan): what about generic functions? Need to consider both + // uninstantiated and instantiated. The latter have no objectpath. Use Origin? + if path, err := objectpath.For(obj); err == nil && obj.Exported() { + pkgPath := variants[0].PkgPath // (all variants have same package path) + globalTargets = map[PackagePath]map[objectpath.Path]unit{ + pkgPath: {path: {}}, // primary target + } + + // Compute set of (non-ITV) workspace packages. + // We restrict references to this subset. + workspace, err := snapshot.WorkspaceMetadata(ctx) + if err != nil { + return nil, err + } + workspaceMap := make(map[PackageID]*metadata.Package, len(workspace)) + workspaceIDs := make([]PackageID, 0, len(workspace)) + for _, mp := range workspace { + workspaceMap[mp.ID] = mp + workspaceIDs = append(workspaceIDs, mp.ID) + } + + // addRdeps expands the global scope to include the + // reverse dependencies of the specified package. + addRdeps := func(id PackageID, transitive bool) error { + rdeps, err := snapshot.ReverseDependencies(ctx, id, transitive) + if err != nil { + return err + } + for rdepID, rdep := range rdeps { + // Skip non-workspace packages. + // + // This means we also skip any expansion of the + // search that might be caused by a non-workspace + // package, possibly causing us to miss references + // to the expanded target set from workspace packages. + // + // TODO(adonovan): don't skip those expansions. + // The challenge is how to so without type-checking + // a lot of non-workspace packages not covered by + // the initial workspace load. + if _, ok := workspaceMap[rdepID]; !ok { + continue + } + + globalScope[rdepID] = rdep + } + return nil + } + + // How far need we search? + // For package-level objects, we need only search the direct importers. + // For fields and methods, we must search transitively. + transitive := obj.Pkg().Scope().Lookup(obj.Name()) != obj + + // The scope is the union of rdeps of each variant. + // (Each set is disjoint so there's no benefit to + // combining the metadata graph traversals.) + for _, mp := range variants { + if err := addRdeps(mp.ID, transitive); err != nil { + return nil, err + } + } + + // Is object a method? + // + // If so, expand the search so that the targets include + // all methods that correspond to it through interface + // satisfaction, and the scope includes the rdeps of + // the package that declares each corresponding type. + // + // 'expansions' records the packages that declared + // such types. + if recv := effectiveReceiver(obj); recv != nil { + if err := expandMethodSearch(ctx, snapshot, workspaceIDs, obj.(*types.Func), recv, addRdeps, globalTargets, expansions); err != nil { + return nil, err + } + } + } + + // The search functions will call report(loc) for each hit. + var ( + refsMu sync.Mutex + refs []reference + ) + report := func(loc protocol.Location, isDecl bool) { + ref := reference{ + isDeclaration: isDecl, + location: loc, + pkgPath: pkg.Metadata().PkgPath, + } + refsMu.Lock() + refs = append(refs, ref) + refsMu.Unlock() + } + + // Loop over the variants of the declaring package, + // and perform both the local (in-package) and global + // (cross-package) searches, in parallel. + // + // TODO(adonovan): opt: support LSP reference streaming. See: + // - https://github.com/microsoft/vscode-languageserver-node/pull/164 + // - https://github.com/microsoft/language-server-protocol/pull/182 + // + // Careful: this goroutine must not return before group.Wait. + var group errgroup.Group + + // Compute local references for each variant. + // The target objects are identified by (URI, offset). + for _, mp := range variants { + // We want the ordinary importable package, + // plus any test-augmented variants, since + // declarations in _test.go files may change + // the reference of a selection, or even a + // field into a method or vice versa. + // + // But we don't need intermediate test variants, + // as their local references will be covered + // already by other variants. + if mp.IsIntermediateTestVariant() { + continue + } + mp := mp + group.Go(func() error { + // TODO(adonovan): opt: batch these TypeChecks. + pkgs, err := snapshot.TypeCheck(ctx, mp.ID) + if err != nil { + return err + } + pkg := pkgs[0] + + // Find the declaration of the corresponding + // object in this package based on (URI, offset). + pgf, err := pkg.File(declURI) + if err != nil { + return err + } + pos, err := safetoken.Pos(pgf.Tok, declPosn.Offset) + if err != nil { + return err + } + objects, _, err := objectsAt(pkg.TypesInfo(), pgf.File, pos) + if err != nil { + return err // unreachable? (probably caught earlier) + } + + // Report the locations of the declaration(s). + // TODO(adonovan): what about for corresponding methods? Add tests. + for _, node := range objects { + report(mustLocation(pgf, node), true) + } + + // Convert targets map to set. + targets := make(map[types.Object]bool) + for obj := range objects { + targets[obj] = true + } + + return localReferences(pkg, targets, true, report) + }) + } + + // Also compute local references within packages that declare + // corresponding methods (see above), which expand the global search. + // The target objects are identified by (PkgPath, objectpath). + for id := range expansions { + id := id + group.Go(func() error { + // TODO(adonovan): opt: batch these TypeChecks. + pkgs, err := snapshot.TypeCheck(ctx, id) + if err != nil { + return err + } + pkg := pkgs[0] + + targets := make(map[types.Object]bool) + for objpath := range globalTargets[pkg.Metadata().PkgPath] { + obj, err := objectpath.Object(pkg.Types(), objpath) + if err != nil { + // No such object, because it was + // declared only in the test variant. + continue + } + targets[obj] = true + } + + // Don't include corresponding types or methods + // since expansions did that already, and we don't + // want (e.g.) concrete -> interface -> concrete. + const correspond = false + return localReferences(pkg, targets, correspond, report) + }) + } + + // Compute global references for selected reverse dependencies. + group.Go(func() error { + var globalIDs []PackageID + for id := range globalScope { + globalIDs = append(globalIDs, id) + } + indexes, err := snapshot.References(ctx, globalIDs...) + if err != nil { + return err + } + for _, index := range indexes { + for _, loc := range index.Lookup(globalTargets) { + report(loc, false) + } + } + return nil + }) + + if err := group.Wait(); err != nil { + return nil, err + } + return refs, nil +} + +// expandMethodSearch expands the scope and targets of a global search +// for an exported method to include all methods in the workspace +// that correspond to it through interface satisfaction. +// +// Each package that declares a corresponding type is added to +// expansions so that we can also find local references to the type +// within the package, which of course requires type checking. +// +// The scope is expanded by a sequence of calls (not concurrent) to addRdeps. +// +// recv is the method's effective receiver type, for method-set computations. +func expandMethodSearch(ctx context.Context, snapshot *cache.Snapshot, workspaceIDs []PackageID, method *types.Func, recv types.Type, addRdeps func(id PackageID, transitive bool) error, targets map[PackagePath]map[objectpath.Path]unit, expansions map[PackageID]unit) error { + // Compute the method-set fingerprint used as a key to the global search. + key, hasMethods := methodsets.KeyOf(recv) + if !hasMethods { + // The query object was method T.m, but methodset(T)={}: + // this indicates that ill-typed T has conflicting fields and methods. + // Rather than bug-report (#67978), treat the empty method set at face value. + return nil + } + // Search the methodset index of each package in the workspace. + indexes, err := snapshot.MethodSets(ctx, workspaceIDs...) + if err != nil { + return err + } + var mu sync.Mutex // guards addRdeps, targets, expansions + var group errgroup.Group + for i, index := range indexes { + i := i + index := index + group.Go(func() error { + // Consult index for matching methods. + results := index.Search(key, method.Name()) + if len(results) == 0 { + return nil + } + + // We have discovered one or more corresponding types. + id := workspaceIDs[i] + + mu.Lock() + defer mu.Unlock() + + // Expand global search scope to include rdeps of this pkg. + if err := addRdeps(id, true); err != nil { + return err + } + + // Mark this package so that we search within it for + // local references to the additional types/methods. + expansions[id] = unit{} + + // Add each corresponding method the to set of global search targets. + for _, res := range results { + methodPkg := PackagePath(res.PkgPath) + opaths, ok := targets[methodPkg] + if !ok { + opaths = make(map[objectpath.Path]unit) + targets[methodPkg] = opaths + } + opaths[res.ObjectPath] = unit{} + } + return nil + }) + } + return group.Wait() +} + +// localReferences traverses syntax and reports each reference to one +// of the target objects, or (if correspond is set) an object that +// corresponds to one of them via interface satisfaction. +func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspond bool, report func(loc protocol.Location, isDecl bool)) error { + // If we're searching for references to a method optionally + // broaden the search to include references to corresponding + // methods of mutually assignable receiver types. + // (We use a slice, but objectsAt never returns >1 methods.) + var methodRecvs []types.Type + var methodName string // name of an arbitrary target, iff a method + if correspond { + for obj := range targets { + if t := effectiveReceiver(obj); t != nil { + methodRecvs = append(methodRecvs, t) + methodName = obj.Name() + } + } + } + + // matches reports whether obj either is or corresponds to a target. + // (Correspondence is defined as usual for interface methods.) + matches := func(obj types.Object) bool { + if containsOrigin(targets, obj) { + return true + } + if methodRecvs != nil && obj.Name() == methodName { + if orecv := effectiveReceiver(obj); orecv != nil { + for _, mrecv := range methodRecvs { + if concreteImplementsIntf(orecv, mrecv) { + return true + } + } + } + } + return false + } + + // Scan through syntax looking for uses of one of the target objects. + for _, pgf := range pkg.CompiledGoFiles() { + ast.Inspect(pgf.File, func(n ast.Node) bool { + if id, ok := n.(*ast.Ident); ok { + if obj, ok := pkg.TypesInfo().Uses[id]; ok && matches(obj) { + report(mustLocation(pgf, id), false) + } + } + return true + }) + } + return nil +} + +// effectiveReceiver returns the effective receiver type for method-set +// comparisons for obj, if it is a method, or nil otherwise. +func effectiveReceiver(obj types.Object) types.Type { + if fn, ok := obj.(*types.Func); ok { + if recv := fn.Signature().Recv(); recv != nil { + return methodsets.EnsurePointer(recv.Type()) + } + } + return nil +} + +// objectsAt returns the non-empty set of objects denoted (def or use) +// by the specified position within a file syntax tree, or an error if +// none were found. +// +// The result may contain more than one element because all case +// variables of a type switch appear to be declared at the same +// position. +// +// Each object is mapped to the syntax node that was treated as an +// identifier, which is not always an ast.Ident. The second component +// of the result is the innermost node enclosing pos. +// +// TODO(adonovan): factor in common with referencedObject. +func objectsAt(info *types.Info, file *ast.File, pos token.Pos) (map[types.Object]ast.Node, ast.Node, error) { + path := pathEnclosingObjNode(file, pos) + if path == nil { + return nil, nil, ErrNoIdentFound + } + + targets := make(map[types.Object]ast.Node) + + switch leaf := path[0].(type) { + case *ast.Ident: + // If leaf represents an implicit type switch object or the type + // switch "assign" variable, expand to all of the type switch's + // implicit objects. + if implicits, _ := typeSwitchImplicits(info, path); len(implicits) > 0 { + for _, obj := range implicits { + targets[obj] = leaf + } + } else { + // For struct{T}, we prefer the defined field Var over the used TypeName. + obj := info.ObjectOf(leaf) + if obj == nil { + return nil, nil, fmt.Errorf("%w for %q", errNoObjectFound, leaf.Name) + } + targets[obj] = leaf + } + case *ast.ImportSpec: + // Look up the implicit *types.PkgName. + obj := info.Implicits[leaf] + if obj == nil { + return nil, nil, fmt.Errorf("%w for import %s", errNoObjectFound, metadata.UnquoteImportPath(leaf)) + } + targets[obj] = leaf + } + + if len(targets) == 0 { + return nil, nil, fmt.Errorf("objectAt: internal error: no targets") // can't happen + } + return targets, path[0], nil +} + +// mustLocation reports the location interval a syntax node, +// which must belong to m.File. +// +// Safe for use only by references and implementations. +func mustLocation(pgf *parsego.File, n ast.Node) protocol.Location { + loc, err := pgf.NodeLocation(n) + if err != nil { + panic(err) // can't happen in references or implementations + } + return loc +} diff --git a/contribs/gnopls/internal/golang/rename.go b/contribs/gnopls/internal/golang/rename.go new file mode 100644 index 00000000000..12d9d283915 --- /dev/null +++ b/contribs/gnopls/internal/golang/rename.go @@ -0,0 +1,1438 @@ +// 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. + +package golang + +// TODO(adonovan): +// +// - method of generic concrete type -> arbitrary instances of same +// +// - make satisfy work across packages. +// +// - tests, tests, tests: +// - play with renamings in the k8s tree. +// - generics +// - error cases (e.g. conflicts) +// - renaming a symbol declared in the module cache +// (currently proceeds with half of the renaming!) +// - make sure all tests have both a local and a cross-package analogue. +// - look at coverage +// - special cases: embedded fields, interfaces, test variants, +// function-local things with uppercase names; +// packages with type errors (currently 'satisfy' rejects them), +// package with missing imports; +// +// - measure performance in k8s. +// +// - The original gorename tool assumed well-typedness, but the gopls feature +// does no such check (which actually makes it much more useful). +// Audit to ensure it is safe on ill-typed code. +// +// - Generics support was no doubt buggy before but incrementalization +// may have exacerbated it. If the problem were just about objects, +// defs and uses it would be fairly simple, but type assignability +// comes into play in the 'satisfy' check for method renamings. +// De-instantiating Vector[int] to Vector[T] changes its type. +// We need to come up with a theory for the satisfy check that +// works with generics, and across packages. We currently have no +// simple way to pass types between packages (think: objectpath for +// types), though presumably exportdata could be pressed into service. +// +// - FileID-based de-duplication of edits to different URIs for the same file. + +import ( + "context" + "errors" + "fmt" + "go/ast" + "go/token" + "go/types" + "path" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/types/objectpath" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/typesinternal" + "golang.org/x/tools/refactor/satisfy" +) + +// A renamer holds state of a single call to renameObj, which renames +// an object (or several coupled objects) within a single type-checked +// syntax package. +type renamer struct { + pkg *cache.Package // the syntax package in which the renaming is applied + objsToUpdate map[types.Object]bool // records progress of calls to check + conflicts []string + from, to string + satisfyConstraints map[satisfy.Constraint]bool + msets typeutil.MethodSetCache + changeMethods bool +} + +// A PrepareItem holds the result of a "prepare rename" operation: +// the source range and value of a selected identifier. +type PrepareItem struct { + Range protocol.Range + Text string +} + +// PrepareRename searches for a valid renaming at position pp. +// +// The returned usererr is intended to be displayed to the user to explain why +// the prepare fails. Probably we could eliminate the redundancy in returning +// two errors, but for now this is done defensively. +func PrepareRename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position) (_ *PrepareItem, usererr, err error) { + ctx, done := event.Start(ctx, "golang.PrepareRename") + defer done() + + // Is the cursor within the package name declaration? + if pgf, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp); err != nil { + return nil, err, err + } else if inPackageName { + item, err := prepareRenamePackageName(ctx, snapshot, pgf) + return item, err, err + } + + // Ordinary (non-package) renaming. + // + // Type-check the current package, locate the reference at the position, + // validate the object, and report its name and range. + // + // TODO(adonovan): in all cases below, we return usererr=nil, + // which means we return (nil, nil) at the protocol + // layer. This seems like a bug, or at best an exploitation of + // knowledge of VSCode-specific behavior. Can we avoid that? + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, f.URI()) + if err != nil { + return nil, nil, err + } + pos, err := pgf.PositionPos(pp) + if err != nil { + return nil, nil, err + } + targets, node, err := objectsAt(pkg.TypesInfo(), pgf.File, pos) + if err != nil { + return nil, nil, err + } + var obj types.Object + for obj = range targets { + break // pick one arbitrarily + } + if err := checkRenamable(obj); err != nil { + return nil, nil, err + } + rng, err := pgf.NodeRange(node) + if err != nil { + return nil, nil, err + } + if _, isImport := node.(*ast.ImportSpec); isImport { + // We're not really renaming the import path. + rng.End = rng.Start + } + return &PrepareItem{ + Range: rng, + Text: obj.Name(), + }, nil, nil +} + +func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf *parsego.File) (*PrepareItem, error) { + // Does the client support file renaming? + fileRenameSupported := false + for _, op := range snapshot.Options().SupportedResourceOperations { + if op == protocol.Rename { + fileRenameSupported = true + break + } + } + if !fileRenameSupported { + return nil, errors.New("can't rename package: LSP client does not support file renaming") + } + + // Check validity of the metadata for the file's containing package. + meta, err := NarrowestMetadataForFile(ctx, snapshot, pgf.URI) + if err != nil { + return nil, err + } + if meta.Name == "main" { + return nil, fmt.Errorf("can't rename package \"main\"") + } + if strings.HasSuffix(string(meta.Name), "_test") { + return nil, fmt.Errorf("can't rename x_test packages") + } + if meta.Module == nil { + return nil, fmt.Errorf("can't rename package: missing module information for package %q", meta.PkgPath) + } + if meta.Module.Path == string(meta.PkgPath) { + return nil, fmt.Errorf("can't rename package: package path %q is the same as module path %q", meta.PkgPath, meta.Module.Path) + } + + // Return the location of the package declaration. + rng, err := pgf.NodeRange(pgf.File.Name) + if err != nil { + return nil, err + } + return &PrepareItem{ + Range: rng, + Text: string(meta.Name), + }, nil +} + +func checkRenamable(obj types.Object) error { + switch obj := obj.(type) { + case *types.Var: + if obj.Embedded() { + return fmt.Errorf("can't rename embedded fields: rename the type directly or name the field") + } + case *types.Builtin, *types.Nil: + return fmt.Errorf("%s is built in and cannot be renamed", obj.Name()) + } + if obj.Pkg() == nil || obj.Pkg().Path() == "unsafe" { + // e.g. error.Error, unsafe.Pointer + return fmt.Errorf("%s is built in and cannot be renamed", obj.Name()) + } + if obj.Name() == "_" { + return errors.New("can't rename \"_\"") + } + return nil +} + +// Rename returns a map of TextEdits for each file modified when renaming a +// given identifier within a package and a boolean value of true for renaming +// package and false otherwise. +func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, newName string) (map[protocol.DocumentURI][]protocol.TextEdit, bool, error) { + ctx, done := event.Start(ctx, "golang.Rename") + defer done() + + if !isValidIdentifier(newName) { + return nil, false, fmt.Errorf("invalid identifier to rename: %q", newName) + } + + // Cursor within package name declaration? + _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp) + if err != nil { + return nil, false, err + } + + var editMap map[protocol.DocumentURI][]diff.Edit + if inPackageName { + editMap, err = renamePackageName(ctx, snapshot, f, PackageName(newName)) + } else { + editMap, err = renameOrdinary(ctx, snapshot, f, pp, newName) + } + if err != nil { + return nil, false, err + } + + // Convert edits to protocol form. + result := make(map[protocol.DocumentURI][]protocol.TextEdit) + for uri, edits := range editMap { + // Sort and de-duplicate edits. + // + // Overlapping edits may arise in local renamings (due + // to type switch implicits) and globals ones (due to + // processing multiple package variants). + // + // We assume renaming produces diffs that are all + // replacements (no adjacent insertions that might + // become reordered) and that are either identical or + // non-overlapping. + diff.SortEdits(edits) + filtered := edits[:0] + for i, edit := range edits { + if i == 0 || edit != filtered[len(filtered)-1] { + filtered = append(filtered, edit) + } + } + edits = filtered + + // TODO(adonovan): the logic above handles repeat edits to the + // same file URI (e.g. as a member of package p and p_test) but + // is not sufficient to handle file-system level aliasing arising + // from symbolic or hard links. For that, we should use a + // robustio-FileID-keyed map. + // See https://go.dev/cl/457615 for example. + // This really occurs in practice, e.g. kubernetes has + // vendor/k8s.io/kubectl -> ../../staging/src/k8s.io/kubectl. + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return nil, false, err + } + data, err := fh.Content() + if err != nil { + return nil, false, err + } + m := protocol.NewMapper(uri, data) + textedits, err := protocol.EditsFromDiffEdits(m, edits) + if err != nil { + return nil, false, err + } + result[uri] = textedits + } + + return result, inPackageName, nil +} + +// renameOrdinary renames an ordinary (non-package) name throughout the workspace. +func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, newName string) (map[protocol.DocumentURI][]diff.Edit, error) { + // Type-check the referring package and locate the object(s). + // + // Unlike NarrowestPackageForFile, this operation prefers the + // widest variant as, for non-exported identifiers, it is the + // only package we need. (In case you're wondering why + // 'references' doesn't also want the widest variant: it + // computes the union across all variants.) + var targets map[types.Object]ast.Node + var pkg *cache.Package + { + mps, err := snapshot.MetadataForFile(ctx, f.URI()) + if err != nil { + return nil, err + } + metadata.RemoveIntermediateTestVariants(&mps) + if len(mps) == 0 { + return nil, fmt.Errorf("no package metadata for file %s", f.URI()) + } + widest := mps[len(mps)-1] // widest variant may include _test.go files + pkgs, err := snapshot.TypeCheck(ctx, widest.ID) + if err != nil { + return nil, err + } + pkg = pkgs[0] + pgf, err := pkg.File(f.URI()) + if err != nil { + return nil, err // "can't happen" + } + pos, err := pgf.PositionPos(pp) + if err != nil { + return nil, err + } + objects, _, err := objectsAt(pkg.TypesInfo(), pgf.File, pos) + if err != nil { + return nil, err + } + targets = objects + } + + // Pick a representative object arbitrarily. + // (All share the same name, pos, and kind.) + var obj types.Object + for obj = range targets { + break + } + if obj.Name() == newName { + return nil, fmt.Errorf("old and new names are the same: %s", newName) + } + if err := checkRenamable(obj); err != nil { + return nil, err + } + + // Find objectpath, if object is exported ("" otherwise). + var declObjPath objectpath.Path + if obj.Exported() { + // objectpath.For requires the origin of a generic function or type, not an + // instantiation (a bug?). + // + // Note that unlike Funcs, TypeNames are always canonical (they are "left" + // of the type parameters, unlike methods). + switch obj.(type) { // avoid "obj :=" since cases reassign the var + case *types.TypeName: + if _, ok := aliases.Unalias(obj.Type()).(*types.TypeParam); ok { + // As with capitalized function parameters below, type parameters are + // local. + goto skipObjectPath + } + case *types.Func: + obj = obj.(*types.Func).Origin() + case *types.Var: + // TODO(adonovan): do vars need the origin treatment too? (issue #58462) + + // Function parameter and result vars that are (unusually) + // capitalized are technically exported, even though they + // cannot be referenced, because they may affect downstream + // error messages. But we can safely treat them as local. + // + // This is not merely an optimization: the renameExported + // operation gets confused by such vars. It finds them from + // objectpath, the classifies them as local vars, but as + // they came from export data they lack syntax and the + // correct scope tree (issue #61294). + if !obj.(*types.Var).IsField() && !isPackageLevel(obj) { + goto skipObjectPath + } + } + if path, err := objectpath.For(obj); err == nil { + declObjPath = path + } + skipObjectPath: + } + + // Nonexported? Search locally. + if declObjPath == "" { + var objects []types.Object + for obj := range targets { + objects = append(objects, obj) + } + editMap, _, err := renameObjects(newName, pkg, objects...) + return editMap, err + } + + // Exported: search globally. + // + // For exported package-level var/const/func/type objects, the + // search scope is just the direct importers. + // + // For exported fields and methods, the scope is the + // transitive rdeps. (The exportedness of the field's struct + // or method's receiver is irrelevant.) + transitive := false + switch obj := obj.(type) { + case *types.TypeName: + // Renaming an exported package-level type + // requires us to inspect all transitive rdeps + // in the event that the type is embedded. + // + // TODO(adonovan): opt: this is conservative + // but inefficient. Instead, expand the scope + // of the search only if we actually encounter + // an embedding of the type, and only then to + // the rdeps of the embedding package. + if obj.Parent() == obj.Pkg().Scope() { + transitive = true + } + + case *types.Var: + if obj.IsField() { + transitive = true // field + } + + // TODO(adonovan): opt: process only packages that + // contain a reference (xrefs) to the target field. + + case *types.Func: + if obj.Signature().Recv() != nil { + transitive = true // method + } + + // It's tempting to optimize by skipping + // packages that don't contain a reference to + // the method in the xrefs index, but we still + // need to apply the satisfy check to those + // packages to find assignment statements that + // might expands the scope of the renaming. + } + + // Type-check all the packages to inspect. + declURI := protocol.URIFromPath(pkg.FileSet().File(obj.Pos()).Name()) + pkgs, err := typeCheckReverseDependencies(ctx, snapshot, declURI, transitive) + if err != nil { + return nil, err + } + + // Apply the renaming to the (initial) object. + declPkgPath := PackagePath(obj.Pkg().Path()) + return renameExported(pkgs, declPkgPath, declObjPath, newName) +} + +// typeCheckReverseDependencies returns the type-checked packages for +// the reverse dependencies of all packages variants containing +// file declURI. The packages are in some topological order. +// +// It includes all variants (even intermediate test variants) for the +// purposes of computing reverse dependencies, but discards ITVs for +// the actual renaming work. +// +// (This neglects obscure edge cases where a _test.go file changes the +// selectors used only in an ITV, but life is short. Also sin must be +// punished.) +func typeCheckReverseDependencies(ctx context.Context, snapshot *cache.Snapshot, declURI protocol.DocumentURI, transitive bool) ([]*cache.Package, error) { + variants, err := snapshot.MetadataForFile(ctx, declURI) + if err != nil { + return nil, err + } + // variants must include ITVs for the reverse dependency + // computation, but they are filtered out before we typecheck. + allRdeps := make(map[PackageID]*metadata.Package) + for _, variant := range variants { + rdeps, err := snapshot.ReverseDependencies(ctx, variant.ID, transitive) + if err != nil { + return nil, err + } + allRdeps[variant.ID] = variant // include self + for id, meta := range rdeps { + allRdeps[id] = meta + } + } + var ids []PackageID + for id, meta := range allRdeps { + if meta.IsIntermediateTestVariant() { + continue + } + ids = append(ids, id) + } + + // Sort the packages into some topological order of the + // (unfiltered) metadata graph. + metadata.SortPostOrder(snapshot, ids) + + // Dependencies must be visited first since they can expand + // the search set. Ideally we would process the (filtered) set + // of packages in the parallel postorder of the snapshot's + // (unfiltered) metadata graph, but this is quite tricky + // without a good graph abstraction. + // + // For now, we visit packages sequentially in order of + // ascending height, like an inverted breadth-first search. + // + // Type checking is by far the dominant cost, so + // overlapping it with renaming may not be worthwhile. + return snapshot.TypeCheck(ctx, ids...) +} + +// renameExported renames the object denoted by (pkgPath, objPath) +// within the specified packages, along with any other objects that +// must be renamed as a consequence. The slice of packages must be +// topologically ordered. +func renameExported(pkgs []*cache.Package, declPkgPath PackagePath, declObjPath objectpath.Path, newName string) (map[protocol.DocumentURI][]diff.Edit, error) { + + // A target is a name for an object that is stable across types.Packages. + type target struct { + pkg PackagePath + obj objectpath.Path + } + + // Populate the initial set of target objects. + // This set may grow as we discover the consequences of each renaming. + // + // TODO(adonovan): strictly, each cone of reverse dependencies + // of a single variant should have its own target map that + // monotonically expands as we go up the import graph, because + // declarations in test files can alter the set of + // package-level names and change the meaning of field and + // method selectors. So if we parallelize the graph + // visitation (see above), we should also compute the targets + // as a union of dependencies. + // + // Or we could decide that the logic below is fast enough not + // to need parallelism. In small measurements so far the + // type-checking step is about 95% and the renaming only 5%. + targets := map[target]bool{{declPkgPath, declObjPath}: true} + + // Apply the renaming operation to each package. + allEdits := make(map[protocol.DocumentURI][]diff.Edit) + for _, pkg := range pkgs { + + // Resolved target objects within package pkg. + var objects []types.Object + for t := range targets { + p := pkg.DependencyTypes(t.pkg) + if p == nil { + continue // indirect dependency of no consequence + } + obj, err := objectpath.Object(p, t.obj) + if err != nil { + // Possibly a method or an unexported type + // that is not reachable through export data? + // See https://github.com/golang/go/issues/60789. + // + // TODO(adonovan): it seems unsatisfactory that Object + // should return an error for a "valid" path. Perhaps + // we should define such paths as invalid and make + // objectpath.For compute reachability? + // Would that be a compatible change? + continue + } + objects = append(objects, obj) + } + if len(objects) == 0 { + continue // no targets of consequence to this package + } + + // Apply the renaming. + editMap, moreObjects, err := renameObjects(newName, pkg, objects...) + if err != nil { + return nil, err + } + + // It is safe to concatenate the edits as they are non-overlapping + // (or identical, in which case they will be de-duped by Rename). + for uri, edits := range editMap { + allEdits[uri] = append(allEdits[uri], edits...) + } + + // Expand the search set? + for obj := range moreObjects { + objpath, err := objectpath.For(obj) + if err != nil { + continue // not exported + } + target := target{PackagePath(obj.Pkg().Path()), objpath} + targets[target] = true + + // TODO(adonovan): methods requires dynamic + // programming of the product targets x + // packages as any package might add a new + // target (from a forward dep) as a + // consequence, and any target might imply a + // new set of rdeps. See golang/go#58461. + } + } + + return allEdits, nil +} + +// renamePackageName renames package declarations, imports, and go.mod files. +func renamePackageName(ctx context.Context, s *cache.Snapshot, f file.Handle, newName PackageName) (map[protocol.DocumentURI][]diff.Edit, error) { + // Rename the package decl and all imports. + renamingEdits, err := renamePackage(ctx, s, f, newName) + if err != nil { + return nil, err + } + + // Update the last component of the file's enclosing directory. + oldBase := filepath.Dir(f.URI().Path()) + newPkgDir := filepath.Join(filepath.Dir(oldBase), string(newName)) + + // Update any affected replace directives in go.mod files. + // TODO(adonovan): extract into its own function. + // + // Get all workspace modules. + // TODO(adonovan): should this operate on all go.mod files, + // irrespective of whether they are included in the workspace? + modFiles := s.View().ModFiles() + for _, m := range modFiles { + fh, err := s.ReadFile(ctx, m) + if err != nil { + return nil, err + } + pm, err := s.ParseMod(ctx, fh) + if err != nil { + return nil, err + } + + modFileDir := filepath.Dir(pm.URI.Path()) + affectedReplaces := []*modfile.Replace{} + + // Check if any replace directives need to be fixed + for _, r := range pm.File.Replace { + if !strings.HasPrefix(r.New.Path, "/") && !strings.HasPrefix(r.New.Path, "./") && !strings.HasPrefix(r.New.Path, "../") { + continue + } + + replacedPath := r.New.Path + if strings.HasPrefix(r.New.Path, "./") || strings.HasPrefix(r.New.Path, "../") { + replacedPath = filepath.Join(modFileDir, r.New.Path) + } + + // TODO: Is there a risk of converting a '\' delimited replacement to a '/' delimited replacement? + if !strings.HasPrefix(filepath.ToSlash(replacedPath)+"/", filepath.ToSlash(oldBase)+"/") { + continue // not affected by the package renaming + } + + affectedReplaces = append(affectedReplaces, r) + } + + if len(affectedReplaces) == 0 { + continue + } + copied, err := modfile.Parse("", pm.Mapper.Content, nil) + if err != nil { + return nil, err + } + + for _, r := range affectedReplaces { + replacedPath := r.New.Path + if strings.HasPrefix(r.New.Path, "./") || strings.HasPrefix(r.New.Path, "../") { + replacedPath = filepath.Join(modFileDir, r.New.Path) + } + + suffix := strings.TrimPrefix(replacedPath, oldBase) + + newReplacedPath, err := filepath.Rel(modFileDir, newPkgDir+suffix) + if err != nil { + return nil, err + } + + newReplacedPath = filepath.ToSlash(newReplacedPath) + + if !strings.HasPrefix(newReplacedPath, "/") && !strings.HasPrefix(newReplacedPath, "../") { + newReplacedPath = "./" + newReplacedPath + } + + if err := copied.AddReplace(r.Old.Path, "", newReplacedPath, ""); err != nil { + return nil, err + } + } + + copied.Cleanup() + newContent, err := copied.Format() + if err != nil { + return nil, err + } + + // Calculate the edits to be made due to the change. + edits := diff.Bytes(pm.Mapper.Content, newContent) + renamingEdits[pm.URI] = append(renamingEdits[pm.URI], edits...) + } + + return renamingEdits, nil +} + +// renamePackage computes all workspace edits required to rename the package +// described by the given metadata, to newName, by renaming its package +// directory. +// +// It updates package clauses and import paths for the renamed package as well +// as any other packages affected by the directory renaming among all packages +// known to the snapshot. +func renamePackage(ctx context.Context, s *cache.Snapshot, f file.Handle, newName PackageName) (map[protocol.DocumentURI][]diff.Edit, error) { + if strings.HasSuffix(string(newName), "_test") { + return nil, fmt.Errorf("cannot rename to _test package") + } + + // We need metadata for the relevant package and module paths. + // These should be the same for all packages containing the file. + meta, err := NarrowestMetadataForFile(ctx, s, f.URI()) + if err != nil { + return nil, err + } + + oldPkgPath := meta.PkgPath + if meta.Module == nil { + return nil, fmt.Errorf("cannot rename package: missing module information for package %q", meta.PkgPath) + } + modulePath := PackagePath(meta.Module.Path) + if modulePath == oldPkgPath { + return nil, fmt.Errorf("cannot rename package: module path %q is the same as the package path, so renaming the package directory would have no effect", modulePath) + } + + newPathPrefix := path.Join(path.Dir(string(oldPkgPath)), string(newName)) + + // We must inspect all packages, not just direct importers, + // because we also rename subpackages, which may be unrelated. + // (If the renamed package imports a subpackage it may require + // edits to both its package and import decls.) + allMetadata, err := s.AllMetadata(ctx) + if err != nil { + return nil, err + } + + // Rename package and import declarations in all relevant packages. + edits := make(map[protocol.DocumentURI][]diff.Edit) + for _, mp := range allMetadata { + // Special case: x_test packages for the renamed package will not have the + // package path as a dir prefix, but still need their package clauses + // renamed. + if mp.PkgPath == oldPkgPath+"_test" { + if err := renamePackageClause(ctx, mp, s, newName+"_test", edits); err != nil { + return nil, err + } + continue + } + + // Subtle: check this condition before checking for valid module info + // below, because we should not fail this operation if unrelated packages + // lack module info. + if !strings.HasPrefix(string(mp.PkgPath)+"/", string(oldPkgPath)+"/") { + continue // not affected by the package renaming + } + + if mp.Module == nil { + // This check will always fail under Bazel. + return nil, fmt.Errorf("cannot rename package: missing module information for package %q", mp.PkgPath) + } + + if modulePath != PackagePath(mp.Module.Path) { + continue // don't edit imports if nested package and renaming package have different module paths + } + + // Renaming a package consists of changing its import path and package name. + suffix := strings.TrimPrefix(string(mp.PkgPath), string(oldPkgPath)) + newPath := newPathPrefix + suffix + + pkgName := mp.Name + if mp.PkgPath == oldPkgPath { + pkgName = newName + + if err := renamePackageClause(ctx, mp, s, newName, edits); err != nil { + return nil, err + } + } + + imp := ImportPath(newPath) // TODO(adonovan): what if newPath has vendor/ prefix? + if err := renameImports(ctx, s, mp, imp, pkgName, edits); err != nil { + return nil, err + } + } + + return edits, nil +} + +// renamePackageClause computes edits renaming the package clause of files in +// the package described by the given metadata, to newName. +// +// Edits are written into the edits map. +func renamePackageClause(ctx context.Context, mp *metadata.Package, snapshot *cache.Snapshot, newName PackageName, edits map[protocol.DocumentURI][]diff.Edit) error { + // Rename internal references to the package in the renaming package. + for _, uri := range mp.CompiledGoFiles { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return err + } + f, err := snapshot.ParseGo(ctx, fh, parsego.Header) + if err != nil { + return err + } + if f.File.Name == nil { + continue // no package declaration + } + + edit, err := posEdit(f.Tok, f.File.Name.Pos(), f.File.Name.End(), string(newName)) + if err != nil { + return err + } + edits[f.URI] = append(edits[f.URI], edit) + } + + return nil +} + +// renameImports computes the set of edits to imports resulting from renaming +// the package described by the given metadata, to a package with import path +// newPath and name newName. +// +// Edits are written into the edits map. +func renameImports(ctx context.Context, snapshot *cache.Snapshot, mp *metadata.Package, newPath ImportPath, newName PackageName, allEdits map[protocol.DocumentURI][]diff.Edit) error { + rdeps, err := snapshot.ReverseDependencies(ctx, mp.ID, false) // find direct importers + if err != nil { + return err + } + + // Pass 1: rename import paths in import declarations. + needsTypeCheck := make(map[PackageID][]protocol.DocumentURI) + for _, rdep := range rdeps { + if rdep.IsIntermediateTestVariant() { + continue // for renaming, these variants are redundant + } + + for _, uri := range rdep.CompiledGoFiles { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return err + } + f, err := snapshot.ParseGo(ctx, fh, parsego.Header) + if err != nil { + return err + } + if f.File.Name == nil { + continue // no package declaration + } + for _, imp := range f.File.Imports { + if rdep.DepsByImpPath[metadata.UnquoteImportPath(imp)] != mp.ID { + continue // not the import we're looking for + } + + // If the import does not explicitly specify + // a local name, then we need to invoke the + // type checker to locate references to update. + // + // TODO(adonovan): is this actually true? + // Renaming an import with a local name can still + // cause conflicts: shadowing of built-ins, or of + // package-level decls in the same or another file. + if imp.Name == nil { + needsTypeCheck[rdep.ID] = append(needsTypeCheck[rdep.ID], uri) + } + + // Create text edit for the import path (string literal). + edit, err := posEdit(f.Tok, imp.Path.Pos(), imp.Path.End(), strconv.Quote(string(newPath))) + if err != nil { + return err + } + allEdits[uri] = append(allEdits[uri], edit) + } + } + } + + // If the imported package's name hasn't changed, + // we don't need to rename references within each file. + if newName == mp.Name { + return nil + } + + // Pass 2: rename local name (types.PkgName) of imported + // package throughout one or more files of the package. + ids := make([]PackageID, 0, len(needsTypeCheck)) + for id := range needsTypeCheck { + ids = append(ids, id) + } + pkgs, err := snapshot.TypeCheck(ctx, ids...) + if err != nil { + return err + } + for i, id := range ids { + pkg := pkgs[i] + for _, uri := range needsTypeCheck[id] { + f, err := pkg.File(uri) + if err != nil { + return err + } + for _, imp := range f.File.Imports { + if imp.Name != nil { + continue // has explicit local name + } + if rdeps[id].DepsByImpPath[metadata.UnquoteImportPath(imp)] != mp.ID { + continue // not the import we're looking for + } + + pkgname := pkg.TypesInfo().Implicits[imp].(*types.PkgName) + + pkgScope := pkg.Types().Scope() + fileScope := pkg.TypesInfo().Scopes[f.File] + + localName := string(newName) + try := 0 + + // Keep trying with fresh names until one succeeds. + // + // TODO(adonovan): fix: this loop is not sufficient to choose a name + // that is guaranteed to be conflict-free; renameObj may still fail. + // So the retry loop should be around renameObj, and we shouldn't + // bother with scopes here. + for fileScope.Lookup(localName) != nil || pkgScope.Lookup(localName) != nil { + try++ + localName = fmt.Sprintf("%s%d", newName, try) + } + + // renameObj detects various conflicts, including: + // - new name conflicts with a package-level decl in this file; + // - new name hides a package-level decl in another file that + // is actually referenced in this file; + // - new name hides a built-in that is actually referenced + // in this file; + // - a reference in this file to the old package name would + // become shadowed by an intervening declaration that + // uses the new name. + // It returns the edits if no conflict was detected. + editMap, _, err := renameObjects(localName, pkg, pkgname) + if err != nil { + return err + } + + // If the chosen local package name matches the package's + // new name, delete the change that would have inserted + // an explicit local name, which is always the lexically + // first change. + if localName == string(newName) { + edits, ok := editMap[uri] + if !ok { + return fmt.Errorf("internal error: no changes for %s", uri) + } + diff.SortEdits(edits) + editMap[uri] = edits[1:] + } + for uri, edits := range editMap { + allEdits[uri] = append(allEdits[uri], edits...) + } + } + } + } + return nil +} + +// renameObjects computes the edits to the type-checked syntax package pkg +// required to rename a set of target objects to newName. +// +// It also returns the set of objects that were found (due to +// corresponding methods and embedded fields) to require renaming as a +// consequence of the requested renamings. +// +// It returns an error if the renaming would cause a conflict. +func renameObjects(newName string, pkg *cache.Package, targets ...types.Object) (map[protocol.DocumentURI][]diff.Edit, map[types.Object]bool, error) { + r := renamer{ + pkg: pkg, + objsToUpdate: make(map[types.Object]bool), + from: targets[0].Name(), + to: newName, + } + + // A renaming initiated at an interface method indicates the + // intention to rename abstract and concrete methods as needed + // to preserve assignability. + // TODO(adonovan): pull this into the caller. + for _, obj := range targets { + if obj, ok := obj.(*types.Func); ok { + recv := obj.Signature().Recv() + if recv != nil && types.IsInterface(recv.Type().Underlying()) { + r.changeMethods = true + break + } + } + } + + // Check that the renaming of the identifier is ok. + for _, obj := range targets { + r.check(obj) + if len(r.conflicts) > 0 { + // Stop at first error. + return nil, nil, fmt.Errorf("%s", strings.Join(r.conflicts, "\n")) + } + } + + editMap, err := r.update() + if err != nil { + return nil, nil, err + } + + // Remove initial targets so that only 'consequences' remain. + for _, obj := range targets { + delete(r.objsToUpdate, obj) + } + return editMap, r.objsToUpdate, nil +} + +// Rename all references to the target objects. +func (r *renamer) update() (map[protocol.DocumentURI][]diff.Edit, error) { + result := make(map[protocol.DocumentURI][]diff.Edit) + + // shouldUpdate reports whether obj is one of (or an + // instantiation of one of) the target objects. + shouldUpdate := func(obj types.Object) bool { + return containsOrigin(r.objsToUpdate, obj) + } + + // Find all identifiers in the package that define or use a + // renamed object. We iterate over info as it is more efficient + // than calling ast.Inspect for each of r.pkg.CompiledGoFiles(). + type item struct { + node ast.Node // Ident, ImportSpec (obj=PkgName), or CaseClause (obj=Var) + obj types.Object + isDef bool + } + var items []item + info := r.pkg.TypesInfo() + for id, obj := range info.Uses { + if shouldUpdate(obj) { + items = append(items, item{id, obj, false}) + } + } + for id, obj := range info.Defs { + if shouldUpdate(obj) { + items = append(items, item{id, obj, true}) + } + } + for node, obj := range info.Implicits { + if shouldUpdate(obj) { + switch node.(type) { + case *ast.ImportSpec, *ast.CaseClause: + items = append(items, item{node, obj, true}) + } + } + } + sort.Slice(items, func(i, j int) bool { + return items[i].node.Pos() < items[j].node.Pos() + }) + + // Update each identifier, and its doc comment if it is a declaration. + for _, item := range items { + pgf, ok := enclosingFile(r.pkg, item.node.Pos()) + if !ok { + bug.Reportf("edit does not belong to syntax of package %q", r.pkg) + continue + } + + // Renaming a types.PkgName may result in the addition or removal of an identifier, + // so we deal with this separately. + if pkgName, ok := item.obj.(*types.PkgName); ok && item.isDef { + edit, err := r.updatePkgName(pgf, pkgName) + if err != nil { + return nil, err + } + result[pgf.URI] = append(result[pgf.URI], edit) + continue + } + + // Workaround the unfortunate lack of a Var object + // for x in "switch x := expr.(type) {}" by adjusting + // the case clause to the switch ident. + // This may result in duplicate edits, but we de-dup later. + if _, ok := item.node.(*ast.CaseClause); ok { + path, _ := astutil.PathEnclosingInterval(pgf.File, item.obj.Pos(), item.obj.Pos()) + item.node = path[0].(*ast.Ident) + } + + // Replace the identifier with r.to. + edit, err := posEdit(pgf.Tok, item.node.Pos(), item.node.End(), r.to) + if err != nil { + return nil, err + } + + result[pgf.URI] = append(result[pgf.URI], edit) + + if !item.isDef { // uses do not have doc comments to update. + continue + } + + doc := docComment(pgf, item.node.(*ast.Ident)) + if doc == nil { + continue + } + + // Perform the rename in doc comments declared in the original package. + // go/parser strips out \r\n returns from the comment text, so go + // line-by-line through the comment text to get the correct positions. + docRegexp := regexp.MustCompile(`\b` + r.from + `\b`) // valid identifier => valid regexp + for _, comment := range doc.List { + if isDirective(comment.Text) { + continue + } + // TODO(adonovan): why are we looping over lines? + // Just run the loop body once over the entire multiline comment. + lines := strings.Split(comment.Text, "\n") + tokFile := pgf.Tok + commentLine := safetoken.Line(tokFile, comment.Pos()) + uri := protocol.URIFromPath(tokFile.Name()) + for i, line := range lines { + lineStart := comment.Pos() + if i > 0 { + lineStart = tokFile.LineStart(commentLine + i) + } + for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) { + edit, err := posEdit(tokFile, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]), r.to) + if err != nil { + return nil, err // can't happen + } + result[uri] = append(result[uri], edit) + } + } + } + } + + docLinkEdits, err := r.updateCommentDocLinks() + if err != nil { + return nil, err + } + for uri, edits := range docLinkEdits { + result[uri] = append(result[uri], edits...) + } + + return result, nil +} + +// updateCommentDocLinks updates each doc comment in the package +// that refers to one of the renamed objects using a doc link +// (https://golang.org/doc/comment#doclinks) such as "[pkg.Type.Method]". +func (r *renamer) updateCommentDocLinks() (map[protocol.DocumentURI][]diff.Edit, error) { + result := make(map[protocol.DocumentURI][]diff.Edit) + var docRenamers []*docLinkRenamer + for obj := range r.objsToUpdate { + if _, ok := obj.(*types.PkgName); ok { + // The dot package name will not be referenced + if obj.Name() == "." { + continue + } + + docRenamers = append(docRenamers, &docLinkRenamer{ + isDep: false, + isPkgOrType: true, + file: r.pkg.FileSet().File(obj.Pos()), + regexp: docLinkPattern("", "", obj.Name(), true), + to: r.to, + }) + continue + } + if !obj.Exported() { + continue + } + recvName := "" + // Doc links can reference only exported package-level objects + // and methods of exported package-level named types. + if !isPackageLevel(obj) { + obj, isFunc := obj.(*types.Func) + if !isFunc { + continue + } + recv := obj.Signature().Recv() + if recv == nil { + continue + } + _, named := typesinternal.ReceiverNamed(recv) + if named == nil { + continue + } + // Doc links can't reference interface methods. + if types.IsInterface(named.Underlying()) { + continue + } + name := named.Origin().Obj() + if !name.Exported() || !isPackageLevel(name) { + continue + } + recvName = name.Name() + } + + // Qualify objects from other packages. + pkgName := "" + if r.pkg.Types() != obj.Pkg() { + pkgName = obj.Pkg().Name() + } + _, isTypeName := obj.(*types.TypeName) + docRenamers = append(docRenamers, &docLinkRenamer{ + isDep: r.pkg.Types() != obj.Pkg(), + isPkgOrType: isTypeName, + packagePath: obj.Pkg().Path(), + packageName: pkgName, + recvName: recvName, + objName: obj.Name(), + regexp: docLinkPattern(pkgName, recvName, obj.Name(), isTypeName), + to: r.to, + }) + } + for _, pgf := range r.pkg.CompiledGoFiles() { + for _, d := range docRenamers { + edits, err := d.update(pgf) + if err != nil { + return nil, err + } + if len(edits) > 0 { + result[pgf.URI] = append(result[pgf.URI], edits...) + } + } + } + return result, nil +} + +// docLinkPattern returns a regular expression that matches doclinks in comments. +// It has one submatch that indicates the symbol to be updated. +func docLinkPattern(pkgName, recvName, objName string, isPkgOrType bool) *regexp.Regexp { + // The doc link may contain a leading star, e.g. [*bytes.Buffer]. + pattern := `\[\*?` + if pkgName != "" { + pattern += pkgName + `\.` + } + if recvName != "" { + pattern += recvName + `\.` + } + // The first submatch is object name. + pattern += `(` + objName + `)` + // If the object is a *types.TypeName or *types.PkgName, also need + // match the objects referenced by them, so add `(\.\w+)*`. + if isPkgOrType { + pattern += `(?:\.\w+)*` + } + // There are two type of link in comments: + // 1. url link. e.g. [text]: url + // 2. doc link. e.g. [pkg.Name] + // in order to only match the doc link, add `([^:]|$)` in the end. + pattern += `\](?:[^:]|$)` + + return regexp.MustCompile(pattern) +} + +// A docLinkRenamer renames doc links of forms such as these: +// +// [Func] +// [pkg.Func] +// [RecvType.Method] +// [*Type] +// [*pkg.Type] +// [*pkg.RecvType.Method] +type docLinkRenamer struct { + isDep bool // object is from a dependency package + isPkgOrType bool // object is *types.PkgName or *types.TypeName + packagePath string + packageName string // e.g. "pkg" + recvName string // e.g. "RecvType" + objName string // e.g. "Func", "Type", "Method" + to string // new name + regexp *regexp.Regexp + + file *token.File // enclosing file, if renaming *types.PkgName +} + +// update updates doc links in the package level comments. +func (r *docLinkRenamer) update(pgf *parsego.File) (result []diff.Edit, err error) { + if r.file != nil && r.file != pgf.Tok { + return nil, nil + } + pattern := r.regexp + // If the object is in dependency package, + // the imported name in the file may be different from the original package name + if r.isDep { + for _, spec := range pgf.File.Imports { + importPath, _ := strconv.Unquote(spec.Path.Value) + if importPath == r.packagePath { + // Ignore blank imports + if spec.Name == nil || spec.Name.Name == "_" || spec.Name.Name == "." { + continue + } + if spec.Name.Name != r.packageName { + pattern = docLinkPattern(spec.Name.Name, r.recvName, r.objName, r.isPkgOrType) + } + break + } + } + } + + var edits []diff.Edit + updateDocLinks := func(doc *ast.CommentGroup) error { + if doc != nil { + for _, c := range doc.List { + for _, locs := range pattern.FindAllStringSubmatchIndex(c.Text, -1) { + // The first submatch is the object name, so the locs[2:4] is the index of object name. + edit, err := posEdit(pgf.Tok, c.Pos()+token.Pos(locs[2]), c.Pos()+token.Pos(locs[3]), r.to) + if err != nil { + return err + } + edits = append(edits, edit) + } + } + } + return nil + } + + // Update package doc comments. + err = updateDocLinks(pgf.File.Doc) + if err != nil { + return nil, err + } + for _, decl := range pgf.File.Decls { + var doc *ast.CommentGroup + switch decl := decl.(type) { + case *ast.GenDecl: + doc = decl.Doc + case *ast.FuncDecl: + doc = decl.Doc + } + err = updateDocLinks(doc) + if err != nil { + return nil, err + } + } + return edits, nil +} + +// docComment returns the doc for an identifier within the specified file. +func docComment(pgf *parsego.File, id *ast.Ident) *ast.CommentGroup { + nodes, _ := astutil.PathEnclosingInterval(pgf.File, id.Pos(), id.End()) + for _, node := range nodes { + switch decl := node.(type) { + case *ast.FuncDecl: + return decl.Doc + case *ast.Field: + return decl.Doc + case *ast.GenDecl: + return decl.Doc + // For {Type,Value}Spec, if the doc on the spec is absent, + // search for the enclosing GenDecl + case *ast.TypeSpec: + if decl.Doc != nil { + return decl.Doc + } + case *ast.ValueSpec: + if decl.Doc != nil { + return decl.Doc + } + case *ast.Ident: + case *ast.AssignStmt: + // *ast.AssignStmt doesn't have an associated comment group. + // So, we try to find a comment just before the identifier. + + // Try to find a comment group only for short variable declarations (:=). + if decl.Tok != token.DEFINE { + return nil + } + + identLine := safetoken.Line(pgf.Tok, id.Pos()) + for _, comment := range nodes[len(nodes)-1].(*ast.File).Comments { + if comment.Pos() > id.Pos() { + // Comment is after the identifier. + continue + } + + lastCommentLine := safetoken.Line(pgf.Tok, comment.End()) + if lastCommentLine+1 == identLine { + return comment + } + } + default: + return nil + } + } + return nil +} + +// updatePkgName returns the updates to rename a pkgName in the import spec by +// only modifying the package name portion of the import declaration. +func (r *renamer) updatePkgName(pgf *parsego.File, pkgName *types.PkgName) (diff.Edit, error) { + // Modify ImportSpec syntax to add or remove the Name as needed. + path, _ := astutil.PathEnclosingInterval(pgf.File, pkgName.Pos(), pkgName.Pos()) + if len(path) < 2 { + return diff.Edit{}, fmt.Errorf("no path enclosing interval for %s", pkgName.Name()) + } + spec, ok := path[1].(*ast.ImportSpec) + if !ok { + return diff.Edit{}, fmt.Errorf("failed to update PkgName for %s", pkgName.Name()) + } + + newText := "" + if pkgName.Imported().Name() != r.to { + newText = r.to + " " + } + + // Replace the portion (possibly empty) of the spec before the path: + // local "path" or "path" + // -> <- -><- + return posEdit(pgf.Tok, spec.Pos(), spec.Path.Pos(), newText) +} + +// parsePackageNameDecl is a convenience function that parses and +// returns the package name declaration of file fh, and reports +// whether the position ppos lies within it. +// +// Note: also used by references. +func parsePackageNameDecl(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, ppos protocol.Position) (*parsego.File, bool, error) { + pgf, err := snapshot.ParseGo(ctx, fh, parsego.Header) + if err != nil { + return nil, false, err + } + // Careful: because we used parsego.Header, + // pgf.Pos(ppos) may be beyond EOF => (0, err). + pos, _ := pgf.PositionPos(ppos) + return pgf, pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.End(), nil +} + +// enclosingFile returns the CompiledGoFile of pkg that contains the specified position. +func enclosingFile(pkg *cache.Package, pos token.Pos) (*parsego.File, bool) { + for _, pgf := range pkg.CompiledGoFiles() { + if pgf.File.Pos() <= pos && pos <= pgf.File.End() { + return pgf, true + } + } + return nil, false +} + +// posEdit returns an edit to replace the (start, end) range of tf with 'new'. +func posEdit(tf *token.File, start, end token.Pos, new string) (diff.Edit, error) { + startOffset, endOffset, err := safetoken.Offsets(tf, start, end) + if err != nil { + return diff.Edit{}, err + } + return diff.Edit{Start: startOffset, End: endOffset, New: new}, nil +} diff --git a/contribs/gnopls/internal/golang/rename_check.go b/contribs/gnopls/internal/golang/rename_check.go new file mode 100644 index 00000000000..574ea8dbea7 --- /dev/null +++ b/contribs/gnopls/internal/golang/rename_check.go @@ -0,0 +1,960 @@ +// 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. +// +// Taken from golang.org/x/tools/refactor/rename. + +package golang + +// This file defines the conflict-checking portion of the rename operation. +// +// The renamer works on a single package of type-checked syntax, and +// is called in parallel for all necessary packages in the workspace, +// possibly up to the transitive reverse dependencies of the +// declaration. Finally the union of all edits and errors is computed. +// +// Renaming one object may entail renaming of others. For example: +// +// - An embedded field couples a Var (field) and a TypeName. +// So, renaming either one requires renaming the other. +// If the initial object is an embedded field, we must add its +// TypeName (and its enclosing package) to the renaming set; +// this is easily discovered at the outset. +// +// Conversely, if the initial object is a TypeName, we must observe +// whether any of its references (from directly importing packages) +// is coincident with an embedded field Var and, if so, initiate a +// renaming of it. +// +// - A method of an interface type is coupled to all corresponding +// methods of types that are assigned to the interface (as +// discovered by the 'satisfy' pass). As a matter of usability, we +// require that such renamings be initiated from the interface +// method, not the concrete method. + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "path/filepath" + "reflect" + "strings" + "unicode" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" + "golang.org/x/tools/refactor/satisfy" +) + +// errorf reports an error (e.g. conflict) and prevents file modification. +func (r *renamer) errorf(pos token.Pos, format string, args ...interface{}) { + // Conflict error messages in the old gorename tool (whence this + // logic originated) contain rich information associated with + // multiple source lines, such as: + // + // p/a.go:1:2: renaming "x" to "y" here + // p/b.go:3:4: \t would cause this reference to "y" + // p/c.go:5:5: \t to become shadowed by this intervening declaration. + // + // Unfortunately LSP provides no means to transmit the + // structure of this error, so we format the positions briefly + // using dir/file.go where dir is the base name of the parent + // directory. + + var conflict strings.Builder + + // Add prefix of (truncated) position. + if pos != token.NoPos { + // TODO(adonovan): skip position of first error if it is + // on the same line as the renaming itself. + posn := safetoken.StartPosition(r.pkg.FileSet(), pos).String() + segments := strings.Split(filepath.ToSlash(posn), "/") + if n := len(segments); n > 2 { + segments = segments[n-2:] + } + posn = strings.Join(segments, "/") + fmt.Fprintf(&conflict, "%s:", posn) + + if !strings.HasPrefix(format, "\t") { + conflict.WriteByte(' ') + } + } + + fmt.Fprintf(&conflict, format, args...) + r.conflicts = append(r.conflicts, conflict.String()) +} + +// check performs safety checks of the renaming of the 'from' object to r.to. +func (r *renamer) check(from types.Object) { + if r.objsToUpdate[from] { + return + } + r.objsToUpdate[from] = true + + // NB: order of conditions is important. + if from_, ok := from.(*types.PkgName); ok { + r.checkInFileBlock(from_) + } else if from_, ok := from.(*types.Label); ok { + r.checkLabel(from_) + } else if isPackageLevel(from) { + r.checkInPackageBlock(from) + } else if v, ok := from.(*types.Var); ok && v.IsField() { + r.checkStructField(v) + } else if f, ok := from.(*types.Func); ok && recv(f) != nil { + r.checkMethod(f) + } else if isLocal(from) { + r.checkInLexicalScope(from) + } else { + r.errorf(from.Pos(), "unexpected %s object %q (please report a bug)\n", + objectKind(from), from) + } +} + +// checkInFileBlock performs safety checks for renames of objects in the file block, +// i.e. imported package names. +func (r *renamer) checkInFileBlock(from *types.PkgName) { + // Check import name is not "init". + if r.to == "init" { + r.errorf(from.Pos(), "%q is not a valid imported package name", r.to) + } + + // Check for conflicts between file and package block. + if prev := from.Pkg().Scope().Lookup(r.to); prev != nil { + r.errorf(from.Pos(), "renaming this %s %q to %q would conflict", + objectKind(from), from.Name(), r.to) + r.errorf(prev.Pos(), "\twith this package member %s", + objectKind(prev)) + return // since checkInPackageBlock would report redundant errors + } + + // Check for conflicts in lexical scope. + r.checkInLexicalScope(from) +} + +// checkInPackageBlock performs safety checks for renames of +// func/var/const/type objects in the package block. +func (r *renamer) checkInPackageBlock(from types.Object) { + // Check that there are no references to the name from another + // package if the renaming would make it unexported. + if typ := r.pkg.Types(); typ != from.Pkg() && ast.IsExported(r.from) && !ast.IsExported(r.to) { + if id := someUse(r.pkg.TypesInfo(), from); id != nil { + r.checkExport(id, typ, from) + } + } + + // Check that in the package block, "init" is a function, and never referenced. + if r.to == "init" { + kind := objectKind(from) + if kind == "func" { + // Reject if intra-package references to it exist. + for id, obj := range r.pkg.TypesInfo().Uses { + if obj == from { + r.errorf(from.Pos(), + "renaming this func %q to %q would make it a package initializer", + from.Name(), r.to) + r.errorf(id.Pos(), "\tbut references to it exist") + break + } + } + } else { + r.errorf(from.Pos(), "you cannot have a %s at package level named %q", + kind, r.to) + } + } + + // In the declaring package, check for conflicts between the + // package block and all file blocks. + if from.Pkg() == r.pkg.Types() { + for _, f := range r.pkg.Syntax() { + fileScope := r.pkg.TypesInfo().Scopes[f] + if fileScope == nil { + continue // type error? (golang/go#40835) + } + b, prev := fileScope.LookupParent(r.to, token.NoPos) + if b == fileScope { + r.errorf(from.Pos(), "renaming this %s %q to %q would conflict", objectKind(from), from.Name(), r.to) + r.errorf(prev.Pos(), "\twith this %s", objectKind(prev)) + return // since checkInPackageBlock would report redundant errors + } + } + } + + // Check for conflicts in lexical scope. + r.checkInLexicalScope(from) +} + +// checkInLexicalScope performs safety checks that a renaming does not +// change the lexical reference structure of the specified package. +// +// For objects in lexical scope, there are three kinds of conflicts: +// same-, sub-, and super-block conflicts. We will illustrate all three +// using this example: +// +// var x int +// var z int +// +// func f(y int) { +// print(x) +// print(y) +// } +// +// Renaming x to z encounters a "same-block conflict", because an object +// with the new name already exists, defined in the same lexical block +// as the old object. +// +// Renaming x to y encounters a "sub-block conflict", because there exists +// a reference to x from within (what would become) a hole in its scope. +// The definition of y in an (inner) sub-block would cast a shadow in +// the scope of the renamed variable. +// +// Renaming y to x encounters a "super-block conflict". This is the +// converse situation: there is an existing definition of the new name +// (x) in an (enclosing) super-block, and the renaming would create a +// hole in its scope, within which there exist references to it. The +// new name shadows the existing definition of x in the super-block. +// +// Removing the old name (and all references to it) is always safe, and +// requires no checks. +func (r *renamer) checkInLexicalScope(from types.Object) { + b := from.Parent() // the block defining the 'from' object + if b != nil { + toBlock, to := b.LookupParent(r.to, from.Parent().End()) + if toBlock == b { + // same-block conflict + r.errorf(from.Pos(), "renaming this %s %q to %q", + objectKind(from), from.Name(), r.to) + r.errorf(to.Pos(), "\tconflicts with %s in same block", + objectKind(to)) + return + } else if toBlock != nil { + // Check for super-block conflict. + // The name r.to is defined in a superblock. + // Is that name referenced from within this block? + forEachLexicalRef(r.pkg, to, func(id *ast.Ident, block *types.Scope) bool { + _, obj := block.LookupParent(from.Name(), id.Pos()) + if obj == from { + // super-block conflict + r.errorf(from.Pos(), "renaming this %s %q to %q", + objectKind(from), from.Name(), r.to) + r.errorf(id.Pos(), "\twould shadow this reference") + r.errorf(to.Pos(), "\tto the %s declared here", + objectKind(to)) + return false // stop + } + return true + }) + } + } + // Check for sub-block conflict. + // Is there an intervening definition of r.to between + // the block defining 'from' and some reference to it? + forEachLexicalRef(r.pkg, from, func(id *ast.Ident, block *types.Scope) bool { + // Find the block that defines the found reference. + // It may be an ancestor. + fromBlock, _ := block.LookupParent(from.Name(), id.Pos()) + // See what r.to would resolve to in the same scope. + toBlock, to := block.LookupParent(r.to, id.Pos()) + if to != nil { + // sub-block conflict + if deeper(toBlock, fromBlock) { + r.errorf(from.Pos(), "renaming this %s %q to %q", + objectKind(from), from.Name(), r.to) + r.errorf(id.Pos(), "\twould cause this reference to become shadowed") + r.errorf(to.Pos(), "\tby this intervening %s definition", + objectKind(to)) + return false // stop + } + } + return true + }) + + // Renaming a type that is used as an embedded field + // requires renaming the field too. e.g. + // type T int // if we rename this to U.. + // var s struct {T} + // print(s.T) // ...this must change too + if _, ok := from.(*types.TypeName); ok { + for id, obj := range r.pkg.TypesInfo().Uses { + if obj == from { + if field := r.pkg.TypesInfo().Defs[id]; field != nil { + r.check(field) + } + } + } + } +} + +// deeper reports whether block x is lexically deeper than y. +func deeper(x, y *types.Scope) bool { + if x == y || x == nil { + return false + } else if y == nil { + return true + } else { + return deeper(x.Parent(), y.Parent()) + } +} + +// Scope and Position +// +// Consider a function f declared as: +// +// func f[T *U, U *T](p, q T) (r, s U) { var ( v T; w = v ); type (t *t; u t) } +// ^ ^ ^ ^ ^ ^ +/// {T,U} {p,q,r,s} v w t u +// +// All objects {T, U, p, q, r, s, local} belong to the same lexical +// block, the function scope, which is found in types.Info.Scopes +// for f's FuncType. (A function body's BlockStmt does not have +// an associated scope; only nested BlockStmts do.) +// +// The effective scope of each object is different: +// +// - The type parameters T and U, whose constraints may refer to each +// other, all have a scope that starts at the beginning of the +// FuncDecl.Type.Func token. +// +// - The parameter and result variables {p,q,r,s} can reference the +// type parameters but not each other, so their scopes all start at +// the end of the FuncType. +// (Prior to go1.22 it was--incorrectly--unset; see #64295). +// Beware also that Scope.Innermost does not currently work correctly for +// type parameters: it returns the scope of the package, not the function. +// +// - Each const or var {v,w} declared within the function body has a +// scope that begins at the end of its ValueSpec, or after the +// AssignStmt for a var declared by ":=". +// +// - Each type {t,u} in the body has a scope that that begins at +// the start of the TypeSpec, so they can be self-recursive +// but--unlike package-level types--not mutually recursive. + +// forEachLexicalRef calls fn(id, block) for each identifier id in package +// pkg that is a reference to obj in lexical scope. block is the +// lexical block enclosing the reference. If fn returns false the +// iteration is terminated and findLexicalRefs returns false. +func forEachLexicalRef(pkg *cache.Package, obj types.Object, fn func(id *ast.Ident, block *types.Scope) bool) bool { + ok := true + var stack []ast.Node + + var visit func(n ast.Node) bool + visit = func(n ast.Node) bool { + if n == nil { + stack = stack[:len(stack)-1] // pop + return false + } + if !ok { + return false // bail out + } + + stack = append(stack, n) // push + switch n := n.(type) { + case *ast.Ident: + if pkg.TypesInfo().Uses[n] == obj { + block := enclosingBlock(pkg.TypesInfo(), stack) + if !fn(n, block) { + ok = false + } + } + return visit(nil) // pop stack + + case *ast.SelectorExpr: + // don't visit n.Sel + ast.Inspect(n.X, visit) + return visit(nil) // pop stack, don't descend + + case *ast.CompositeLit: + // Handle recursion ourselves for struct literals + // so we don't visit field identifiers. + tv, ok := pkg.TypesInfo().Types[n] + if !ok { + return visit(nil) // pop stack, don't descend + } + if is[*types.Struct](typeparams.CoreType(typeparams.Deref(tv.Type))) { + if n.Type != nil { + ast.Inspect(n.Type, visit) + } + for _, elt := range n.Elts { + if kv, ok := elt.(*ast.KeyValueExpr); ok { + ast.Inspect(kv.Value, visit) + } else { + ast.Inspect(elt, visit) + } + } + return visit(nil) // pop stack, don't descend + } + } + return true + } + + for _, f := range pkg.Syntax() { + ast.Inspect(f, visit) + if len(stack) != 0 { + panic(stack) + } + if !ok { + break + } + } + return ok +} + +// enclosingBlock returns the innermost block logically enclosing the +// specified AST node (an ast.Ident), specified in the form of a path +// from the root of the file, [file...n]. +func enclosingBlock(info *types.Info, stack []ast.Node) *types.Scope { + for i := range stack { + n := stack[len(stack)-1-i] + // For some reason, go/types always associates a + // function's scope with its FuncType. + // See comments about scope above. + switch f := n.(type) { + case *ast.FuncDecl: + n = f.Type + case *ast.FuncLit: + n = f.Type + } + if b := info.Scopes[n]; b != nil { + return b + } + } + panic("no Scope for *ast.File") +} + +func (r *renamer) checkLabel(label *types.Label) { + // Check there are no identical labels in the function's label block. + // (Label blocks don't nest, so this is easy.) + if prev := label.Parent().Lookup(r.to); prev != nil { + r.errorf(label.Pos(), "renaming this label %q to %q", label.Name(), prev.Name()) + r.errorf(prev.Pos(), "\twould conflict with this one") + } +} + +// checkStructField checks that the field renaming will not cause +// conflicts at its declaration, or ambiguity or changes to any selection. +func (r *renamer) checkStructField(from *types.Var) { + // If this is the declaring package, check that the struct + // declaration is free of field conflicts, and field/method + // conflicts. + // + // go/types offers no easy way to get from a field (or interface + // method) to its declaring struct (or interface), so we must + // ascend the AST. + if pgf, ok := enclosingFile(r.pkg, from.Pos()); ok { + path, _ := astutil.PathEnclosingInterval(pgf.File, from.Pos(), from.Pos()) + // path matches this pattern: + // [Ident SelectorExpr? StarExpr? Field FieldList StructType ParenExpr* ... File] + + // Ascend to FieldList. + var i int + for { + if _, ok := path[i].(*ast.FieldList); ok { + break + } + i++ + } + i++ + tStruct := path[i].(*ast.StructType) + i++ + // Ascend past parens (unlikely). + for { + _, ok := path[i].(*ast.ParenExpr) + if !ok { + break + } + i++ + } + if spec, ok := path[i].(*ast.TypeSpec); ok { + // This struct is also a named type. + // We must check for direct (non-promoted) field/field + // and method/field conflicts. + named := r.pkg.TypesInfo().Defs[spec.Name].Type() + prev, indices, _ := types.LookupFieldOrMethod(named, true, r.pkg.Types(), r.to) + if len(indices) == 1 { + r.errorf(from.Pos(), "renaming this field %q to %q", + from.Name(), r.to) + r.errorf(prev.Pos(), "\twould conflict with this %s", + objectKind(prev)) + return // skip checkSelections to avoid redundant errors + } + } else { + // This struct is not a named type. + // We need only check for direct (non-promoted) field/field conflicts. + T := r.pkg.TypesInfo().Types[tStruct].Type.Underlying().(*types.Struct) + for i := 0; i < T.NumFields(); i++ { + if prev := T.Field(i); prev.Name() == r.to { + r.errorf(from.Pos(), "renaming this field %q to %q", + from.Name(), r.to) + r.errorf(prev.Pos(), "\twould conflict with this field") + return // skip checkSelections to avoid redundant errors + } + } + } + } + + // Renaming an anonymous field requires renaming the type too. e.g. + // print(s.T) // if we rename T to U, + // type T int // this and + // var s struct {T} // this must change too. + if from.Anonymous() { + if named, ok := from.Type().(*types.Named); ok { + r.check(named.Obj()) + } else if named, ok := aliases.Unalias(typesinternal.Unpointer(from.Type())).(*types.Named); ok { + r.check(named.Obj()) + } + } + + // Check integrity of existing (field and method) selections. + r.checkSelections(from) +} + +// checkSelections checks that all uses and selections that resolve to +// the specified object would continue to do so after the renaming. +func (r *renamer) checkSelections(from types.Object) { + pkg := r.pkg + typ := pkg.Types() + { + if id := someUse(pkg.TypesInfo(), from); id != nil { + if !r.checkExport(id, typ, from) { + return + } + } + + for syntax, sel := range pkg.TypesInfo().Selections { + // There may be extant selections of only the old + // name or only the new name, so we must check both. + // (If neither, the renaming is sound.) + // + // In both cases, we wish to compare the lengths + // of the implicit field path (Selection.Index) + // to see if the renaming would change it. + // + // If a selection that resolves to 'from', when renamed, + // would yield a path of the same or shorter length, + // this indicates ambiguity or a changed referent, + // analogous to same- or sub-block lexical conflict. + // + // If a selection using the name 'to' would + // yield a path of the same or shorter length, + // this indicates ambiguity or shadowing, + // analogous to same- or super-block lexical conflict. + + // TODO(adonovan): fix: derive from Types[syntax.X].Mode + // TODO(adonovan): test with pointer, value, addressable value. + isAddressable := true + + if sel.Obj() == from { + if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), r.to); obj != nil { + // Renaming this existing selection of + // 'from' may block access to an existing + // type member named 'to'. + delta := len(indices) - len(sel.Index()) + if delta > 0 { + continue // no ambiguity + } + r.selectionConflict(from, delta, syntax, obj) + return + } + } else if sel.Obj().Name() == r.to { + if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), from.Name()); obj == from { + // Renaming 'from' may cause this existing + // selection of the name 'to' to change + // its meaning. + delta := len(indices) - len(sel.Index()) + if delta > 0 { + continue // no ambiguity + } + r.selectionConflict(from, -delta, syntax, sel.Obj()) + return + } + } + } + } +} + +func (r *renamer) selectionConflict(from types.Object, delta int, syntax *ast.SelectorExpr, obj types.Object) { + r.errorf(from.Pos(), "renaming this %s %q to %q", + objectKind(from), from.Name(), r.to) + + switch { + case delta < 0: + // analogous to sub-block conflict + r.errorf(syntax.Sel.Pos(), + "\twould change the referent of this selection") + r.errorf(obj.Pos(), "\tof this %s", objectKind(obj)) + case delta == 0: + // analogous to same-block conflict + r.errorf(syntax.Sel.Pos(), + "\twould make this reference ambiguous") + r.errorf(obj.Pos(), "\twith this %s", objectKind(obj)) + case delta > 0: + // analogous to super-block conflict + r.errorf(syntax.Sel.Pos(), + "\twould shadow this selection") + r.errorf(obj.Pos(), "\tof the %s declared here", + objectKind(obj)) + } +} + +// checkMethod performs safety checks for renaming a method. +// There are three hazards: +// - declaration conflicts +// - selection ambiguity/changes +// - entailed renamings of assignable concrete/interface types. +// +// We reject renamings initiated at concrete methods if it would +// change the assignability relation. For renamings of abstract +// methods, we rename all methods transitively coupled to it via +// assignability. +func (r *renamer) checkMethod(from *types.Func) { + // e.g. error.Error + if from.Pkg() == nil { + r.errorf(from.Pos(), "you cannot rename built-in method %s", from) + return + } + + // ASSIGNABILITY: We reject renamings of concrete methods that + // would break a 'satisfy' constraint; but renamings of abstract + // methods are allowed to proceed, and we rename affected + // concrete and abstract methods as necessary. It is the + // initial method that determines the policy. + + // Check for conflict at point of declaration. + // Check to ensure preservation of assignability requirements. + R := recv(from).Type() + if types.IsInterface(R) { + // Abstract method + + // declaration + prev, _, _ := types.LookupFieldOrMethod(R, false, from.Pkg(), r.to) + if prev != nil { + r.errorf(from.Pos(), "renaming this interface method %q to %q", + from.Name(), r.to) + r.errorf(prev.Pos(), "\twould conflict with this method") + return + } + + // Check all interfaces that embed this one for + // declaration conflicts too. + { + // Start with named interface types (better errors) + for _, obj := range r.pkg.TypesInfo().Defs { + if obj, ok := obj.(*types.TypeName); ok && types.IsInterface(obj.Type()) { + f, _, _ := types.LookupFieldOrMethod( + obj.Type(), false, from.Pkg(), from.Name()) + if f == nil { + continue + } + t, _, _ := types.LookupFieldOrMethod( + obj.Type(), false, from.Pkg(), r.to) + if t == nil { + continue + } + r.errorf(from.Pos(), "renaming this interface method %q to %q", + from.Name(), r.to) + r.errorf(t.Pos(), "\twould conflict with this method") + r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name()) + } + } + + // Now look at all literal interface types (includes named ones again). + for e, tv := range r.pkg.TypesInfo().Types { + if e, ok := e.(*ast.InterfaceType); ok { + _ = e + _ = tv.Type.(*types.Interface) + // TODO(adonovan): implement same check as above. + } + } + } + + // assignability + // + // Find the set of concrete or abstract methods directly + // coupled to abstract method 'from' by some + // satisfy.Constraint, and rename them too. + for key := range r.satisfy() { + // key = (lhs, rhs) where lhs is always an interface. + + lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name()) + if lsel == nil { + continue + } + rmethods := r.msets.MethodSet(key.RHS) + rsel := rmethods.Lookup(from.Pkg(), from.Name()) + if rsel == nil { + continue + } + + // If both sides have a method of this name, + // and one of them is m, the other must be coupled. + var coupled *types.Func + switch from { + case lsel.Obj(): + coupled = rsel.Obj().(*types.Func) + case rsel.Obj(): + coupled = lsel.Obj().(*types.Func) + default: + continue + } + + // We must treat concrete-to-interface + // constraints like an implicit selection C.f of + // each interface method I.f, and check that the + // renaming leaves the selection unchanged and + // unambiguous. + // + // Fun fact: the implicit selection of C.f + // type I interface{f()} + // type C struct{I} + // func (C) g() + // var _ I = C{} // here + // yields abstract method I.f. This can make error + // messages less than obvious. + // + if !types.IsInterface(key.RHS) { + // The logic below was derived from checkSelections. + + rtosel := rmethods.Lookup(from.Pkg(), r.to) + if rtosel != nil { + rto := rtosel.Obj().(*types.Func) + delta := len(rsel.Index()) - len(rtosel.Index()) + if delta < 0 { + continue // no ambiguity + } + + // TODO(adonovan): record the constraint's position. + keyPos := token.NoPos + + r.errorf(from.Pos(), "renaming this method %q to %q", + from.Name(), r.to) + if delta == 0 { + // analogous to same-block conflict + r.errorf(keyPos, "\twould make the %s method of %s invoked via interface %s ambiguous", + r.to, key.RHS, key.LHS) + r.errorf(rto.Pos(), "\twith (%s).%s", + recv(rto).Type(), r.to) + } else { + // analogous to super-block conflict + r.errorf(keyPos, "\twould change the %s method of %s invoked via interface %s", + r.to, key.RHS, key.LHS) + r.errorf(coupled.Pos(), "\tfrom (%s).%s", + recv(coupled).Type(), r.to) + r.errorf(rto.Pos(), "\tto (%s).%s", + recv(rto).Type(), r.to) + } + return // one error is enough + } + } + + if !r.changeMethods { + // This should be unreachable. + r.errorf(from.Pos(), "internal error: during renaming of abstract method %s", from) + r.errorf(coupled.Pos(), "\tchangedMethods=false, coupled method=%s", coupled) + r.errorf(from.Pos(), "\tPlease file a bug report") + return + } + + // Rename the coupled method to preserve assignability. + r.check(coupled) + } + } else { + // Concrete method + + // declaration + prev, indices, _ := types.LookupFieldOrMethod(R, true, from.Pkg(), r.to) + if prev != nil && len(indices) == 1 { + r.errorf(from.Pos(), "renaming this method %q to %q", + from.Name(), r.to) + r.errorf(prev.Pos(), "\twould conflict with this %s", + objectKind(prev)) + return + } + + // assignability + // + // Find the set of abstract methods coupled to concrete + // method 'from' by some satisfy.Constraint, and rename + // them too. + // + // Coupling may be indirect, e.g. I.f <-> C.f via type D. + // + // type I interface {f()} + // type C int + // type (C) f() + // type D struct{C} + // var _ I = D{} + // + for key := range r.satisfy() { + // key = (lhs, rhs) where lhs is always an interface. + if types.IsInterface(key.RHS) { + continue + } + rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name()) + if rsel == nil || rsel.Obj() != from { + continue // rhs does not have the method + } + lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name()) + if lsel == nil { + continue + } + imeth := lsel.Obj().(*types.Func) + + // imeth is the abstract method (e.g. I.f) + // and key.RHS is the concrete coupling type (e.g. D). + if !r.changeMethods { + r.errorf(from.Pos(), "renaming this method %q to %q", + from.Name(), r.to) + var pos token.Pos + var iface string + + I := recv(imeth).Type() + if named, ok := aliases.Unalias(I).(*types.Named); ok { + pos = named.Obj().Pos() + iface = "interface " + named.Obj().Name() + } else { + pos = from.Pos() + iface = I.String() + } + r.errorf(pos, "\twould make %s no longer assignable to %s", + key.RHS, iface) + r.errorf(imeth.Pos(), "\t(rename %s.%s if you intend to change both types)", + I, from.Name()) + return // one error is enough + } + + // Rename the coupled interface method to preserve assignability. + r.check(imeth) + } + } + + // Check integrity of existing (field and method) selections. + // We skip this if there were errors above, to avoid redundant errors. + r.checkSelections(from) +} + +func (r *renamer) checkExport(id *ast.Ident, pkg *types.Package, from types.Object) bool { + // Reject cross-package references if r.to is unexported. + // (Such references may be qualified identifiers or field/method + // selections.) + if !ast.IsExported(r.to) && pkg != from.Pkg() { + r.errorf(from.Pos(), + "renaming %q to %q would make it unexported", + from.Name(), r.to) + r.errorf(id.Pos(), "\tbreaking references from packages such as %q", + pkg.Path()) + return false + } + return true +} + +// satisfy returns the set of interface satisfaction constraints. +func (r *renamer) satisfy() map[satisfy.Constraint]bool { + if r.satisfyConstraints == nil { + // Compute on demand: it's expensive. + var f satisfy.Finder + pkg := r.pkg + { + // From satisfy.Finder documentation: + // + // The package must be free of type errors, and + // info.{Defs,Uses,Selections,Types} must have been populated by the + // type-checker. + // + // Only proceed if all packages have no errors. + if len(pkg.ParseErrors()) > 0 || len(pkg.TypeErrors()) > 0 { + r.errorf(token.NoPos, // we don't have a position for this error. + "renaming %q to %q not possible because %q has errors", + r.from, r.to, pkg.Metadata().PkgPath) + return nil + } + f.Find(pkg.TypesInfo(), pkg.Syntax()) + } + r.satisfyConstraints = f.Result + } + return r.satisfyConstraints +} + +// -- helpers ---------------------------------------------------------- + +// recv returns the method's receiver. +func recv(meth *types.Func) *types.Var { + return meth.Signature().Recv() +} + +// someUse returns an arbitrary use of obj within info. +func someUse(info *types.Info, obj types.Object) *ast.Ident { + for id, o := range info.Uses { + if o == obj { + return id + } + } + return nil +} + +func objectKind(obj types.Object) string { + if obj == nil { + return "nil object" + } + switch obj := obj.(type) { + case *types.PkgName: + return "imported package name" + case *types.TypeName: + return "type" + case *types.Var: + if obj.IsField() { + return "field" + } + case *types.Func: + if recv(obj) != nil { + return "method" + } + } + // label, func, var, const + return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types.")) +} + +// NB: for renamings, blank is not considered valid. +func isValidIdentifier(id string) bool { + if id == "" || id == "_" { + return false + } + for i, r := range id { + if !isLetter(r) && (i == 0 || !isDigit(r)) { + return false + } + } + return token.Lookup(id) == token.IDENT +} + +// isLocal reports whether obj is local to some function. +// Precondition: not a struct field or interface method. +func isLocal(obj types.Object) bool { + // [... 5=stmt 4=func 3=file 2=pkg 1=universe] + var depth int + for scope := obj.Parent(); scope != nil; scope = scope.Parent() { + depth++ + } + return depth >= 4 +} + +func isPackageLevel(obj types.Object) bool { + if obj == nil { + return false + } + return obj.Pkg().Scope().Lookup(obj.Name()) == obj +} + +// -- Plundered from go/scanner: --------------------------------------- + +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) +} + +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) +} diff --git a/contribs/gnopls/internal/golang/semtok.go b/contribs/gnopls/internal/golang/semtok.go new file mode 100644 index 00000000000..0da38bbaa2b --- /dev/null +++ b/contribs/gnopls/internal/golang/semtok.go @@ -0,0 +1,999 @@ +// 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 golang + +// This file defines the Semantic Tokens operation for Go source. + +import ( + "bytes" + "context" + "errors" + "fmt" + "go/ast" + "go/token" + "go/types" + "log" + "path/filepath" + "regexp" + "strings" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/semtok" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/event" +) + +// semDebug enables comprehensive logging of decisions +// (gopls semtok foo.go > /dev/null shows log output). +// It should never be true in checked-in code. +const semDebug = false + +func SemanticTokens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng *protocol.Range) (*protocol.SemanticTokens, error) { + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) + if err != nil { + return nil, err + } + + // Select range. + var start, end token.Pos + if rng != nil { + var err error + start, end, err = pgf.RangePos(*rng) + if err != nil { + return nil, err // e.g. invalid range + } + } else { + tok := pgf.Tok + start, end = tok.Pos(0), tok.Pos(tok.Size()) // entire file + } + + // Reject full semantic token requests for large files. + // + // The LSP says that errors for the semantic token requests + // should only be returned for exceptions (a word not + // otherwise defined). This code treats a too-large file as an + // exception. On parse errors, the code does what it can. + const maxFullFileSize = 100000 + if int(end-start) > maxFullFileSize { + return nil, fmt.Errorf("semantic tokens: range %s too large (%d > %d)", + fh.URI().Path(), end-start, maxFullFileSize) + } + + tv := tokenVisitor{ + ctx: ctx, + metadataSource: snapshot, + metadata: pkg.Metadata(), + info: pkg.TypesInfo(), + fset: pkg.FileSet(), + pkg: pkg, + pgf: pgf, + start: start, + end: end, + } + tv.visit() + return &protocol.SemanticTokens{ + Data: semtok.Encode( + tv.tokens, + snapshot.Options().NoSemanticString, + snapshot.Options().NoSemanticNumber, + snapshot.Options().SemanticTypes, + snapshot.Options().SemanticMods), + ResultID: time.Now().String(), // for delta requests, but we've never seen any + }, nil +} + +type tokenVisitor struct { + // inputs + ctx context.Context // for event logging + metadataSource metadata.Source // used to resolve imports + metadata *metadata.Package + info *types.Info + fset *token.FileSet + pkg *cache.Package + pgf *parsego.File + start, end token.Pos // range of interest + + // working state + stack []ast.Node // path from root of the syntax tree + tokens []semtok.Token // computed sequence of semantic tokens +} + +func (tv *tokenVisitor) visit() { + f := tv.pgf.File + // may not be in range, but harmless + tv.token(f.Package, len("package"), semtok.TokKeyword, nil) + if f.Name != nil { + tv.token(f.Name.NamePos, len(f.Name.Name), semtok.TokNamespace, nil) + } + for _, decl := range f.Decls { + // Only look at the decls that overlap the range. + if decl.End() <= tv.start || decl.Pos() >= tv.end { + continue + } + ast.Inspect(decl, tv.inspect) + } + + // Scan all files for imported pkgs, ignore the ambiguous pkg. + // This is to be consistent with the behavior in [go/doc]: https://pkg.go.dev/pkg/go/doc. + importByName := make(map[string]*types.PkgName) + for _, pgf := range tv.pkg.CompiledGoFiles() { + for _, imp := range pgf.File.Imports { + if obj := tv.pkg.TypesInfo().PkgNameOf(imp); obj != nil { + if old, ok := importByName[obj.Name()]; ok { + if old != nil && old.Imported() != obj.Imported() { + importByName[obj.Name()] = nil // nil => ambiguous across files + } + continue + } + importByName[obj.Name()] = obj + } + } + } + + for _, cg := range f.Comments { + for _, c := range cg.List { + // Only look at the comment that overlap the range. + if c.End() <= tv.start || c.Pos() >= tv.end { + continue + } + tv.comment(c, importByName) + } + } +} + +// Matches (for example) "[F]", "[*p.T]", "[p.T.M]" +// unless followed by a colon (exclude url link, e.g. "[go]: https://go.dev"). +// The first group is reference name. e.g. The first group of "[*p.T.M]" is "p.T.M". +var docLinkRegex = regexp.MustCompile(`\[\*?([\pL_][\pL_0-9]*(\.[\pL_][\pL_0-9]*){0,2})](?:[^:]|$)`) + +// comment emits semantic tokens for a comment. +// If the comment contains doc links or "go:" directives, +// it emits a separate token for each link or directive and +// each comment portion between them. +func (tv *tokenVisitor) comment(c *ast.Comment, importByName map[string]*types.PkgName) { + if strings.HasPrefix(c.Text, "//go:") { + tv.godirective(c) + return + } + + pkgScope := tv.pkg.Types().Scope() + // lookupObjects interprets the name in various forms + // (X, p.T, p.T.M, etc) and return the list of symbols + // denoted by each identifier in the dotted list. + lookupObjects := func(name string) (objs []types.Object) { + scope := pkgScope + if pkg, suffix, ok := strings.Cut(name, "."); ok { + if obj, _ := importByName[pkg]; obj != nil { + objs = append(objs, obj) + scope = obj.Imported().Scope() + name = suffix + } + } + + if recv, method, ok := strings.Cut(name, "."); ok { + obj, ok := scope.Lookup(recv).(*types.TypeName) + if !ok { + return nil + } + objs = append(objs, obj) + t, ok := obj.Type().(*types.Named) + if !ok { + return nil + } + m, _, _ := types.LookupFieldOrMethod(t, true, tv.pkg.Types(), method) + if m == nil { + return nil + } + objs = append(objs, m) + return objs + } else { + obj := scope.Lookup(name) + if obj == nil { + return nil + } + if _, ok := obj.(*types.PkgName); !ok && !obj.Exported() { + return nil + } + objs = append(objs, obj) + return objs + + } + } + + tokenTypeByObject := func(obj types.Object) (semtok.TokenType, []string) { + switch obj.(type) { + case *types.PkgName: + return semtok.TokNamespace, nil + case *types.Func: + return semtok.TokFunction, nil + case *types.TypeName: + return semtok.TokType, appendTypeModifiers(nil, obj) + case *types.Const, *types.Var: + return semtok.TokVariable, nil + default: + return semtok.TokComment, nil + } + } + + pos := c.Pos() + for _, line := range strings.Split(c.Text, "\n") { + last := 0 + + for _, idx := range docLinkRegex.FindAllStringSubmatchIndex(line, -1) { + // The first group is the reference name. e.g. "X", "p.T", "p.T.M". + name := line[idx[2]:idx[3]] + if objs := lookupObjects(name); len(objs) > 0 { + if last < idx[2] { + tv.token(pos+token.Pos(last), idx[2]-last, semtok.TokComment, nil) + } + offset := pos + token.Pos(idx[2]) + for i, obj := range objs { + if i > 0 { + tv.token(offset, len("."), semtok.TokComment, nil) + offset += token.Pos(len(".")) + } + id, rest, _ := strings.Cut(name, ".") + name = rest + tok, mods := tokenTypeByObject(obj) + tv.token(offset, len(id), tok, mods) + offset += token.Pos(len(id)) + } + last = idx[3] + } + } + if last != len(c.Text) { + tv.token(pos+token.Pos(last), len(line)-last, semtok.TokComment, nil) + } + pos += token.Pos(len(line) + 1) + } +} + +// token emits a token of the specified extent and semantics. +func (tv *tokenVisitor) token(start token.Pos, length int, typ semtok.TokenType, modifiers []string) { + if !start.IsValid() { + return + } + if length <= 0 { + return // vscode doesn't like 0-length Tokens + } + end := start + token.Pos(length) + if start >= tv.end || end <= tv.start { + return + } + // want a line and column from start (in LSP coordinates). Ignore line directives. + rng, err := tv.pgf.PosRange(start, end) + if err != nil { + event.Error(tv.ctx, "failed to convert to range", err) + return + } + if rng.End.Line != rng.Start.Line { + // this happens if users are typing at the end of the file, but report nothing + return + } + tv.tokens = append(tv.tokens, semtok.Token{ + Line: rng.Start.Line, + Start: rng.Start.Character, + Len: rng.End.Character - rng.Start.Character, // (on same line) + Type: typ, + Modifiers: modifiers, + }) +} + +// strStack converts the stack to a string, for debugging and error messages. +func (tv *tokenVisitor) strStack() string { + msg := []string{"["} + for i := len(tv.stack) - 1; i >= 0; i-- { + n := tv.stack[i] + msg = append(msg, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast.")) + } + if len(tv.stack) > 0 { + pos := tv.stack[len(tv.stack)-1].Pos() + if _, err := safetoken.Offset(tv.pgf.Tok, pos); err != nil { + msg = append(msg, fmt.Sprintf("invalid position %v for %s", pos, tv.pgf.URI)) + } else { + posn := safetoken.Position(tv.pgf.Tok, pos) + msg = append(msg, fmt.Sprintf("(%s:%d,col:%d)", + filepath.Base(posn.Filename), posn.Line, posn.Column)) + } + } + msg = append(msg, "]") + return strings.Join(msg, " ") +} + +// srcLine returns the source text for n (truncated at first newline). +func (tv *tokenVisitor) srcLine(n ast.Node) string { + file := tv.pgf.Tok + line := safetoken.Line(file, n.Pos()) + start, err := safetoken.Offset(file, file.LineStart(line)) + if err != nil { + return "" + } + end := start + for ; end < len(tv.pgf.Src) && tv.pgf.Src[end] != '\n'; end++ { + + } + return string(tv.pgf.Src[start:end]) +} + +func (tv *tokenVisitor) inspect(n ast.Node) (descend bool) { + if n == nil { + tv.stack = tv.stack[:len(tv.stack)-1] // pop + return true + } + tv.stack = append(tv.stack, n) // push + defer func() { + if !descend { + tv.stack = tv.stack[:len(tv.stack)-1] // pop + } + }() + + switch n := n.(type) { + case *ast.ArrayType: + case *ast.AssignStmt: + tv.token(n.TokPos, len(n.Tok.String()), semtok.TokOperator, nil) + case *ast.BasicLit: + if strings.Contains(n.Value, "\n") { + // has to be a string. + tv.multiline(n.Pos(), n.End(), semtok.TokString) + break + } + what := semtok.TokNumber + if n.Kind == token.STRING { + what = semtok.TokString + } + tv.token(n.Pos(), len(n.Value), what, nil) + case *ast.BinaryExpr: + tv.token(n.OpPos, len(n.Op.String()), semtok.TokOperator, nil) + case *ast.BlockStmt: + case *ast.BranchStmt: + tv.token(n.TokPos, len(n.Tok.String()), semtok.TokKeyword, nil) + if n.Label != nil { + tv.token(n.Label.Pos(), len(n.Label.Name), semtok.TokLabel, nil) + } + case *ast.CallExpr: + if n.Ellipsis.IsValid() { + tv.token(n.Ellipsis, len("..."), semtok.TokOperator, nil) + } + case *ast.CaseClause: + iam := "case" + if n.List == nil { + iam = "default" + } + tv.token(n.Case, len(iam), semtok.TokKeyword, nil) + case *ast.ChanType: + // chan | chan <- | <- chan + switch { + case n.Arrow == token.NoPos: + tv.token(n.Begin, len("chan"), semtok.TokKeyword, nil) + case n.Arrow == n.Begin: + tv.token(n.Arrow, 2, semtok.TokOperator, nil) + pos := tv.findKeyword("chan", n.Begin+2, n.Value.Pos()) + tv.token(pos, len("chan"), semtok.TokKeyword, nil) + case n.Arrow != n.Begin: + tv.token(n.Begin, len("chan"), semtok.TokKeyword, nil) + tv.token(n.Arrow, 2, semtok.TokOperator, nil) + } + case *ast.CommClause: + length := len("case") + if n.Comm == nil { + length = len("default") + } + tv.token(n.Case, length, semtok.TokKeyword, nil) + case *ast.CompositeLit: + case *ast.DeclStmt: + case *ast.DeferStmt: + tv.token(n.Defer, len("defer"), semtok.TokKeyword, nil) + case *ast.Ellipsis: + tv.token(n.Ellipsis, len("..."), semtok.TokOperator, nil) + case *ast.EmptyStmt: + case *ast.ExprStmt: + case *ast.Field: + case *ast.FieldList: + case *ast.ForStmt: + tv.token(n.For, len("for"), semtok.TokKeyword, nil) + case *ast.FuncDecl: + case *ast.FuncLit: + case *ast.FuncType: + if n.Func != token.NoPos { + tv.token(n.Func, len("func"), semtok.TokKeyword, nil) + } + case *ast.GenDecl: + tv.token(n.TokPos, len(n.Tok.String()), semtok.TokKeyword, nil) + case *ast.GoStmt: + tv.token(n.Go, len("go"), semtok.TokKeyword, nil) + case *ast.Ident: + tv.ident(n) + case *ast.IfStmt: + tv.token(n.If, len("if"), semtok.TokKeyword, nil) + if n.Else != nil { + // x.Body.End() or x.Body.End()+1, not that it matters + pos := tv.findKeyword("else", n.Body.End(), n.Else.Pos()) + tv.token(pos, len("else"), semtok.TokKeyword, nil) + } + case *ast.ImportSpec: + tv.importSpec(n) + return false + case *ast.IncDecStmt: + tv.token(n.TokPos, len(n.Tok.String()), semtok.TokOperator, nil) + case *ast.IndexExpr: + case *ast.IndexListExpr: + case *ast.InterfaceType: + tv.token(n.Interface, len("interface"), semtok.TokKeyword, nil) + case *ast.KeyValueExpr: + case *ast.LabeledStmt: + tv.token(n.Label.Pos(), len(n.Label.Name), semtok.TokLabel, []string{"definition"}) + case *ast.MapType: + tv.token(n.Map, len("map"), semtok.TokKeyword, nil) + case *ast.ParenExpr: + case *ast.RangeStmt: + tv.token(n.For, len("for"), semtok.TokKeyword, nil) + // x.TokPos == token.NoPos is legal (for range foo {}) + offset := n.TokPos + if offset == token.NoPos { + offset = n.For + } + pos := tv.findKeyword("range", offset, n.X.Pos()) + tv.token(pos, len("range"), semtok.TokKeyword, nil) + case *ast.ReturnStmt: + tv.token(n.Return, len("return"), semtok.TokKeyword, nil) + case *ast.SelectStmt: + tv.token(n.Select, len("select"), semtok.TokKeyword, nil) + case *ast.SelectorExpr: + case *ast.SendStmt: + tv.token(n.Arrow, len("<-"), semtok.TokOperator, nil) + case *ast.SliceExpr: + case *ast.StarExpr: + tv.token(n.Star, len("*"), semtok.TokOperator, nil) + case *ast.StructType: + tv.token(n.Struct, len("struct"), semtok.TokKeyword, nil) + case *ast.SwitchStmt: + tv.token(n.Switch, len("switch"), semtok.TokKeyword, nil) + case *ast.TypeAssertExpr: + if n.Type == nil { + pos := tv.findKeyword("type", n.Lparen, n.Rparen) + tv.token(pos, len("type"), semtok.TokKeyword, nil) + } + case *ast.TypeSpec: + case *ast.TypeSwitchStmt: + tv.token(n.Switch, len("switch"), semtok.TokKeyword, nil) + case *ast.UnaryExpr: + tv.token(n.OpPos, len(n.Op.String()), semtok.TokOperator, nil) + case *ast.ValueSpec: + // things only seen with parsing or type errors, so ignore them + case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt: + return false + // not going to see these + case *ast.File, *ast.Package: + tv.errorf("implement %T %s", n, safetoken.Position(tv.pgf.Tok, n.Pos())) + // other things we knowingly ignore + case *ast.Comment, *ast.CommentGroup: + return false + default: + tv.errorf("failed to implement %T", n) + } + return true +} + +// appendTypeModifiers appends optional modifiers that describe the top-level +// type constructor of obj.Type(): "pointer", "map", etc. +func appendTypeModifiers(mods []string, obj types.Object) []string { + switch t := obj.Type().Underlying().(type) { + case *types.Interface: + mods = append(mods, "interface") + case *types.Struct: + mods = append(mods, "struct") + case *types.Signature: + mods = append(mods, "signature") + case *types.Pointer: + mods = append(mods, "pointer") + case *types.Array: + mods = append(mods, "array") + case *types.Map: + mods = append(mods, "map") + case *types.Slice: + mods = append(mods, "slice") + case *types.Chan: + mods = append(mods, "chan") + case *types.Basic: + mods = append(mods, "defaultLibrary") + switch t.Kind() { + case types.Invalid: + mods = append(mods, "invalid") + case types.String: + mods = append(mods, "string") + case types.Bool: + mods = append(mods, "bool") + case types.UnsafePointer: + mods = append(mods, "pointer") + default: + if t.Info()&types.IsNumeric != 0 { + mods = append(mods, "number") + } + } + } + return mods +} + +func (tv *tokenVisitor) ident(id *ast.Ident) { + var obj types.Object + + // emit emits a token for the identifier's extent. + emit := func(tok semtok.TokenType, modifiers ...string) { + tv.token(id.Pos(), len(id.Name), tok, modifiers) + if semDebug { + q := "nil" + if obj != nil { + q = fmt.Sprintf("%T", obj.Type()) // e.g. "*types.Map" + } + log.Printf(" use %s/%T/%s got %s %v (%s)", + id.Name, obj, q, tok, modifiers, tv.strStack()) + } + } + + // definition? + obj = tv.info.Defs[id] + if obj != nil { + if tok, modifiers := tv.definitionFor(id, obj); tok != "" { + emit(tok, modifiers...) + } else if semDebug { + log.Printf(" for %s/%T/%T got '' %v (%s)", + id.Name, obj, obj.Type(), modifiers, tv.strStack()) + } + return + } + + // use? + obj = tv.info.Uses[id] + switch obj := obj.(type) { + case *types.Builtin: + emit(semtok.TokFunction, "defaultLibrary") + case *types.Const: + if is[*types.Named](obj.Type()) && + (id.Name == "iota" || id.Name == "true" || id.Name == "false") { + emit(semtok.TokVariable, "readonly", "defaultLibrary") + } else { + emit(semtok.TokVariable, "readonly") + } + case *types.Func: + emit(semtok.TokFunction) + case *types.Label: + // Labels are reliably covered by the syntax traversal. + case *types.Nil: + // nil is a predeclared identifier + emit(semtok.TokVariable, "readonly", "defaultLibrary") + case *types.PkgName: + emit(semtok.TokNamespace) + case *types.TypeName: // could be a TypeParam + if is[*types.TypeParam](aliases.Unalias(obj.Type())) { + emit(semtok.TokTypeParam) + } else { + emit(semtok.TokType, appendTypeModifiers(nil, obj)...) + } + case *types.Var: + if is[*types.Signature](aliases.Unalias(obj.Type())) { + emit(semtok.TokFunction) + } else if tv.isParam(obj.Pos()) { + // variable, unless use.pos is the pos of a Field in an ancestor FuncDecl + // or FuncLit and then it's a parameter + emit(semtok.TokParameter) + } else { + emit(semtok.TokVariable) + } + case nil: + if tok, modifiers := tv.unkIdent(id); tok != "" { + emit(tok, modifiers...) + } + default: + panic(obj) + } +} + +// isParam reports whether the position is that of a parameter name of +// an enclosing function. +func (tv *tokenVisitor) isParam(pos token.Pos) bool { + for i := len(tv.stack) - 1; i >= 0; i-- { + switch n := tv.stack[i].(type) { + case *ast.FuncDecl: + for _, f := range n.Type.Params.List { + for _, id := range f.Names { + if id.Pos() == pos { + return true + } + } + } + case *ast.FuncLit: + for _, f := range n.Type.Params.List { + for _, id := range f.Names { + if id.Pos() == pos { + return true + } + } + } + } + } + return false +} + +// unkIdent handles identifiers with no types.Object (neither use nor +// def), use the parse stack. +// A lot of these only happen when the package doesn't compile, +// but in that case it is all best-effort from the parse tree. +func (tv *tokenVisitor) unkIdent(id *ast.Ident) (semtok.TokenType, []string) { + def := []string{"definition"} + n := len(tv.stack) - 2 // parent of Ident; stack is [File ... Ident] + if n < 0 { + tv.errorf("no stack") // can't happen + return "", nil + } + switch parent := tv.stack[n].(type) { + case *ast.BinaryExpr, *ast.UnaryExpr, *ast.ParenExpr, *ast.StarExpr, + *ast.IncDecStmt, *ast.SliceExpr, *ast.ExprStmt, *ast.IndexExpr, + *ast.ReturnStmt, *ast.ChanType, *ast.SendStmt, + *ast.ForStmt, // possibly incomplete + *ast.IfStmt, /* condition */ + *ast.KeyValueExpr, // either key or value + *ast.IndexListExpr: + return semtok.TokVariable, nil + case *ast.Ellipsis: + return semtok.TokType, nil + case *ast.CaseClause: + if n-2 >= 0 && is[ast.TypeSwitchStmt](tv.stack[n-2]) { + return semtok.TokType, nil + } + return semtok.TokVariable, nil + case *ast.ArrayType: + if id == parent.Len { + // or maybe a Type Param, but we can't just from the parse tree + return semtok.TokVariable, nil + } else { + return semtok.TokType, nil + } + case *ast.MapType: + return semtok.TokType, nil + case *ast.CallExpr: + if id == parent.Fun { + return semtok.TokFunction, nil + } + return semtok.TokVariable, nil + case *ast.SwitchStmt: + return semtok.TokVariable, nil + case *ast.TypeAssertExpr: + if id == parent.X { + return semtok.TokVariable, nil + } else if id == parent.Type { + return semtok.TokType, nil + } + case *ast.ValueSpec: + for _, p := range parent.Names { + if p == id { + return semtok.TokVariable, def + } + } + for _, p := range parent.Values { + if p == id { + return semtok.TokVariable, nil + } + } + return semtok.TokType, nil + case *ast.SelectorExpr: // e.ti.Selections[nd] is nil, so no help + if n-1 >= 0 { + if ce, ok := tv.stack[n-1].(*ast.CallExpr); ok { + // ... CallExpr SelectorExpr Ident (_.x()) + if ce.Fun == parent && parent.Sel == id { + return semtok.TokFunction, nil + } + } + } + return semtok.TokVariable, nil + case *ast.AssignStmt: + for _, p := range parent.Lhs { + // x := ..., or x = ... + if p == id { + if parent.Tok != token.DEFINE { + def = nil + } + return semtok.TokVariable, def // '_' in _ = ... + } + } + // RHS, = x + return semtok.TokVariable, nil + case *ast.TypeSpec: // it's a type if it is either the Name or the Type + if id == parent.Type { + def = nil + } + return semtok.TokType, def + case *ast.Field: + // ident could be type in a field, or a method in an interface type, or a variable + if id == parent.Type { + return semtok.TokType, nil + } + if n > 2 && + is[*ast.InterfaceType](tv.stack[n-2]) && + is[*ast.FieldList](tv.stack[n-1]) { + + return semtok.TokMethod, def + } + return semtok.TokVariable, nil + case *ast.LabeledStmt: + if id == parent.Label { + return semtok.TokLabel, def + } + case *ast.BranchStmt: + if id == parent.Label { + return semtok.TokLabel, nil + } + case *ast.CompositeLit: + if parent.Type == id { + return semtok.TokType, nil + } + return semtok.TokVariable, nil + case *ast.RangeStmt: + if parent.Tok != token.DEFINE { + def = nil + } + return semtok.TokVariable, def + case *ast.FuncDecl: + return semtok.TokFunction, def + default: + tv.errorf("%T unexpected: %s %s%q", parent, id.Name, tv.strStack(), tv.srcLine(id)) + } + return "", nil +} + +func isDeprecated(n *ast.CommentGroup) bool { + if n != nil { + for _, c := range n.List { + if strings.HasPrefix(c.Text, "// Deprecated") { + return true + } + } + } + return false +} + +// definitionFor handles a defining identifier. +func (tv *tokenVisitor) definitionFor(id *ast.Ident, obj types.Object) (semtok.TokenType, []string) { + // The definition of a types.Label cannot be found by + // ascending the syntax tree, and doing so will reach the + // FuncDecl, causing us to misinterpret the label as a + // parameter (#65494). + // + // However, labels are reliably covered by the syntax + // traversal, so we don't need to use type information. + if is[*types.Label](obj) { + return "", nil + } + + // PJW: look into replacing these syntactic tests with types more generally + modifiers := []string{"definition"} + for i := len(tv.stack) - 1; i >= 0; i-- { + switch ancestor := tv.stack[i].(type) { + case *ast.AssignStmt, *ast.RangeStmt: + if id.Name == "_" { + return "", nil // not really a variable + } + return semtok.TokVariable, modifiers + case *ast.GenDecl: + if isDeprecated(ancestor.Doc) { + modifiers = append(modifiers, "deprecated") + } + if ancestor.Tok == token.CONST { + modifiers = append(modifiers, "readonly") + } + return semtok.TokVariable, modifiers + case *ast.FuncDecl: + // If x is immediately under a FuncDecl, it is a function or method + if i == len(tv.stack)-2 { + if isDeprecated(ancestor.Doc) { + modifiers = append(modifiers, "deprecated") + } + if ancestor.Recv != nil { + return semtok.TokMethod, modifiers + } + return semtok.TokFunction, modifiers + } + // if x < ... < FieldList < FuncDecl, this is the receiver, a variable + // PJW: maybe not. it might be a typeparameter in the type of the receiver + if is[*ast.FieldList](tv.stack[i+1]) { + if is[*types.TypeName](obj) { + return semtok.TokTypeParam, modifiers + } + return semtok.TokVariable, nil + } + // if x < ... < FieldList < FuncType < FuncDecl, this is a param + return semtok.TokParameter, modifiers + case *ast.FuncType: + if isTypeParam(id, ancestor) { + return semtok.TokTypeParam, modifiers + } + return semtok.TokParameter, modifiers + case *ast.InterfaceType: + return semtok.TokMethod, modifiers + case *ast.TypeSpec: + // GenDecl/Typespec/FuncType/FieldList/Field/Ident + // (type A func(b uint64)) (err error) + // b and err should not be semtok.TokType, but semtok.TokVariable + // and in GenDecl/TpeSpec/StructType/FieldList/Field/Ident + // (type A struct{b uint64} + // but on type B struct{C}), C is a type, but is not being defined. + // GenDecl/TypeSpec/FieldList/Field/Ident is a typeParam + if is[*ast.FieldList](tv.stack[i+1]) { + return semtok.TokTypeParam, modifiers + } + fldm := tv.stack[len(tv.stack)-2] + if fld, ok := fldm.(*ast.Field); ok { + // if len(fld.names) == 0 this is a semtok.TokType, being used + if len(fld.Names) == 0 { + return semtok.TokType, appendTypeModifiers(nil, obj) + } + return semtok.TokVariable, modifiers + } + return semtok.TokType, appendTypeModifiers(modifiers, obj) + } + } + // can't happen + tv.errorf("failed to find the decl for %s", safetoken.Position(tv.pgf.Tok, id.Pos())) + return "", nil +} + +func isTypeParam(id *ast.Ident, t *ast.FuncType) bool { + if tp := t.TypeParams; tp != nil { + for _, p := range tp.List { + for _, n := range p.Names { + if id == n { + return true + } + } + } + } + return false +} + +// multiline emits a multiline token (`string` or /*comment*/). +func (tv *tokenVisitor) multiline(start, end token.Pos, tok semtok.TokenType) { + // TODO(adonovan): test with non-ASCII. + + f := tv.fset.File(start) + // the hard part is finding the lengths of lines. include the \n + length := func(line int) int { + n := f.LineStart(line) + if line >= f.LineCount() { + return f.Size() - int(n) + } + return int(f.LineStart(line+1) - n) + } + spos := safetoken.StartPosition(tv.fset, start) + epos := safetoken.EndPosition(tv.fset, end) + sline := spos.Line + eline := epos.Line + // first line is from spos.Column to end + tv.token(start, length(sline)-spos.Column, tok, nil) // leng(sline)-1 - (spos.Column-1) + for i := sline + 1; i < eline; i++ { + // intermediate lines are from 1 to end + tv.token(f.LineStart(i), length(i)-1, tok, nil) // avoid the newline + } + // last line is from 1 to epos.Column + tv.token(f.LineStart(eline), epos.Column-1, tok, nil) // columns are 1-based +} + +// findKeyword returns the position of a keyword by searching within +// the specified range, for when it cannot be exactly known from the AST. +// It returns NoPos if the keyword was not present in the source due to parse error. +func (tv *tokenVisitor) findKeyword(keyword string, start, end token.Pos) token.Pos { + // TODO(adonovan): use safetoken.Offset. + offset := int(start) - tv.pgf.Tok.Base() + last := int(end) - tv.pgf.Tok.Base() + buf := tv.pgf.Src + idx := bytes.Index(buf[offset:last], []byte(keyword)) + if idx < 0 { + // Ill-formed code may form syntax trees without their usual tokens. + // For example, "type _ <-<-chan int" parses as <-chan (chan int), + // with two nested ChanTypes but only one chan keyword. + return token.NoPos + } + return start + token.Pos(idx) +} + +func (tv *tokenVisitor) importSpec(spec *ast.ImportSpec) { + // a local package name or the last component of the Path + if spec.Name != nil { + name := spec.Name.String() + if name != "_" && name != "." { + tv.token(spec.Name.Pos(), len(name), semtok.TokNamespace, nil) + } + return // don't mark anything for . or _ + } + importPath := metadata.UnquoteImportPath(spec) + if importPath == "" { + return + } + // Import strings are implementation defined. Try to match with parse information. + depID := tv.metadata.DepsByImpPath[importPath] + if depID == "" { + return + } + depMD := tv.metadataSource.Metadata(depID) + if depMD == nil { + // unexpected, but impact is that maybe some import is not colored + return + } + // Check whether the original literal contains the package's declared name. + j := strings.LastIndex(spec.Path.Value, string(depMD.Name)) + if j < 0 { + // Package name does not match import path, so there is nothing to report. + return + } + // Report virtual declaration at the position of the substring. + start := spec.Path.Pos() + token.Pos(j) + tv.token(start, len(depMD.Name), semtok.TokNamespace, nil) +} + +// errorf logs an error and reports a bug. +func (tv *tokenVisitor) errorf(format string, args ...any) { + msg := fmt.Sprintf(format, args...) + bug.Report(msg) + event.Error(tv.ctx, tv.strStack(), errors.New(msg)) +} + +var godirectives = map[string]struct{}{ + // https://pkg.go.dev/cmd/compile + "noescape": {}, + "uintptrescapes": {}, + "noinline": {}, + "norace": {}, + "nosplit": {}, + "linkname": {}, + + // https://pkg.go.dev/go/build + "build": {}, + "binary-only-package": {}, + "embed": {}, +} + +// Tokenize godirective at the start of the comment c, if any, and the surrounding comment. +// If there is any failure, emits the entire comment as a TokComment token. +// Directives are highlighted as-is, even if used incorrectly. Typically there are +// dedicated analyzers that will warn about misuse. +func (tv *tokenVisitor) godirective(c *ast.Comment) { + // First check if '//go:directive args...' is a valid directive. + directive, args, _ := strings.Cut(c.Text, " ") + kind, _ := stringsCutPrefix(directive, "//go:") + if _, ok := godirectives[kind]; !ok { + // Unknown 'go:' directive. + tv.token(c.Pos(), len(c.Text), semtok.TokComment, nil) + return + } + + // Make the 'go:directive' part stand out, the rest is comments. + tv.token(c.Pos(), len("//"), semtok.TokComment, nil) + + directiveStart := c.Pos() + token.Pos(len("//")) + tv.token(directiveStart, len(directive[len("//"):]), semtok.TokNamespace, nil) + + if len(args) > 0 { + tailStart := c.Pos() + token.Pos(len(directive)+len(" ")) + tv.token(tailStart, len(args), semtok.TokComment, nil) + } +} + +// Go 1.20 strings.CutPrefix. +func stringsCutPrefix(s, prefix string) (after string, found bool) { + if !strings.HasPrefix(s, prefix) { + return s, false + } + return s[len(prefix):], true +} + +func is[T any](x any) bool { + _, ok := x.(T) + return ok +} diff --git a/contribs/gnopls/internal/golang/signature_help.go b/contribs/gnopls/internal/golang/signature_help.go new file mode 100644 index 00000000000..26cb92c643b --- /dev/null +++ b/contribs/gnopls/internal/golang/signature_help.go @@ -0,0 +1,233 @@ +// Copyright 2018 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 golang + +import ( + "context" + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/typesutil" + "golang.org/x/tools/internal/event" +) + +// SignatureHelp returns information about the signature of the innermost +// function call enclosing the position, or nil if there is none. +// On success it also returns the parameter index of the position. +func SignatureHelp(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.SignatureInformation, int, error) { + ctx, done := event.Start(ctx, "golang.SignatureHelp") + defer done() + + // We need full type-checking here, as we must type-check function bodies in + // order to provide signature help at the requested position. + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) + if err != nil { + return nil, 0, fmt.Errorf("getting file for SignatureHelp: %w", err) + } + pos, err := pgf.PositionPos(position) + if err != nil { + return nil, 0, err + } + // Find a call expression surrounding the query position. + var callExpr *ast.CallExpr + path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos) + if path == nil { + return nil, 0, fmt.Errorf("cannot find node enclosing position") + } + info := pkg.TypesInfo() + var fnval ast.Expr +loop: + for i, node := range path { + switch node := node.(type) { + case *ast.Ident: + // If the selected text is a function/method Ident orSelectorExpr, + // even one not in function call position, + // show help for its signature. Example: + // once.Do(initialize⁁) + // should show help for initialize, not once.Do. + if t := info.TypeOf(node); t != nil && + info.Defs[node] == nil && + is[*types.Signature](t.Underlying()) { + if sel, ok := path[i+1].(*ast.SelectorExpr); ok && sel.Sel == node { + fnval = sel // e.g. fmt.Println⁁ + } else { + fnval = node + } + break loop + } + case *ast.CallExpr: + if pos >= node.Lparen && pos <= node.Rparen { + callExpr = node + fnval = callExpr.Fun + break loop + } + case *ast.FuncLit, *ast.FuncType: + // The user is within an anonymous function, + // which may be the parameter to the *ast.CallExpr. + // Don't show signature help in this case. + return nil, 0, nil + case *ast.BasicLit: + if node.Kind == token.STRING { + // golang/go#43397: don't offer signature help when the user is typing + // in a string literal. Most LSP clients use ( or , as trigger + // characters, but within a string literal these should not trigger + // signature help (and it can be annoying when this happens after + // you've already dismissed the help!). + return nil, 0, nil + } + } + + } + + if fnval == nil { + return nil, 0, nil + } + + // Get the type information for the function being called. + var sig *types.Signature + if tv, ok := info.Types[fnval]; !ok { + return nil, 0, fmt.Errorf("cannot get type for Fun %[1]T (%[1]v)", fnval) + } else if tv.IsType() { + return nil, 0, nil // a conversion, not a call + } else if sig, ok = tv.Type.Underlying().(*types.Signature); !ok { + return nil, 0, fmt.Errorf("call operand is not a func or type: %[1]T (%[1]v)", fnval) + } + // Inv: sig != nil + + qf := typesutil.FileQualifier(pgf.File, pkg.Types(), info) + + // Get the object representing the function, if available. + // There is no object in certain cases such as calling a function returned by + // a function (e.g. "foo()()"). + var obj types.Object + switch t := fnval.(type) { + case *ast.Ident: + obj = info.ObjectOf(t) + case *ast.SelectorExpr: + obj = info.ObjectOf(t.Sel) + } + if obj != nil && isBuiltin(obj) { + // function? + if obj, ok := obj.(*types.Builtin); ok { + return builtinSignature(ctx, snapshot, callExpr, obj.Name(), pos) + } + + // method (only error.Error)? + if fn, ok := obj.(*types.Func); ok && fn.Name() == "Error" { + return &protocol.SignatureInformation{ + Label: "Error()", + Documentation: stringToSigInfoDocumentation("Error returns the error message.", snapshot.Options()), + }, 0, nil + } + + return nil, 0, bug.Errorf("call to unexpected built-in %v (%T)", obj, obj) + } + + activeParam := 0 + if callExpr != nil { + // only return activeParam when CallExpr + // because we don't modify arguments when get function signature only + activeParam = activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos) + } + + var ( + name string + comment *ast.CommentGroup + ) + if obj != nil { + d, err := HoverDocForObject(ctx, snapshot, pkg.FileSet(), obj) + if err != nil { + return nil, 0, err + } + name = obj.Name() + comment = d + } else { + name = "func" + } + mq := MetadataQualifierForFile(snapshot, pgf.File, pkg.Metadata()) + s, err := NewSignature(ctx, snapshot, pkg, sig, comment, qf, mq) + if err != nil { + return nil, 0, err + } + paramInfo := make([]protocol.ParameterInformation, 0, len(s.params)) + for _, p := range s.params { + paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p}) + } + return &protocol.SignatureInformation{ + Label: name + s.Format(), + Documentation: stringToSigInfoDocumentation(s.doc, snapshot.Options()), + Parameters: paramInfo, + }, activeParam, nil +} + +func builtinSignature(ctx context.Context, snapshot *cache.Snapshot, callExpr *ast.CallExpr, name string, pos token.Pos) (*protocol.SignatureInformation, int, error) { + sig, err := NewBuiltinSignature(ctx, snapshot, name) + if err != nil { + return nil, 0, err + } + paramInfo := make([]protocol.ParameterInformation, 0, len(sig.params)) + for _, p := range sig.params { + paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p}) + } + activeParam := activeParameter(callExpr, len(sig.params), sig.variadic, pos) + return &protocol.SignatureInformation{ + Label: sig.name + sig.Format(), + Documentation: stringToSigInfoDocumentation(sig.doc, snapshot.Options()), + Parameters: paramInfo, + }, activeParam, nil +} + +func activeParameter(callExpr *ast.CallExpr, numParams int, variadic bool, pos token.Pos) (activeParam int) { + if len(callExpr.Args) == 0 { + return 0 + } + // First, check if the position is even in the range of the arguments. + start, end := callExpr.Lparen, callExpr.Rparen + if !(start <= pos && pos <= end) { + return 0 + } + for _, expr := range callExpr.Args { + if start == token.NoPos { + start = expr.Pos() + } + end = expr.End() + if start <= pos && pos <= end { + break + } + // Don't advance the active parameter for the last parameter of a variadic function. + if !variadic || activeParam < numParams-1 { + activeParam++ + } + start = expr.Pos() + 1 // to account for commas + } + return activeParam +} + +func stringToSigInfoDocumentation(s string, options *settings.Options) *protocol.Or_SignatureInformation_documentation { + v := s + k := protocol.PlainText + if options.PreferredContentFormat == protocol.Markdown { + v = CommentToMarkdown(s, options) + // whether or not content is newline terminated may not matter for LSP clients, + // but our tests expect trailing newlines to be stripped. + v = strings.TrimSuffix(v, "\n") // TODO(pjw): change the golden files + k = protocol.Markdown + } + return &protocol.Or_SignatureInformation_documentation{ + Value: protocol.MarkupContent{ + Kind: k, + Value: v, + }, + } +} diff --git a/contribs/gnopls/internal/golang/snapshot.go b/contribs/gnopls/internal/golang/snapshot.go new file mode 100644 index 00000000000..c381c962d08 --- /dev/null +++ b/contribs/gnopls/internal/golang/snapshot.go @@ -0,0 +1,98 @@ +// Copyright 2018 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 golang + +import ( + "context" + "fmt" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/protocol" +) + +// NarrowestMetadataForFile returns metadata for the narrowest package +// (the one with the fewest files) that encloses the specified file. +// The result may be a test variant, but never an intermediate test variant. +func NarrowestMetadataForFile(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) (*metadata.Package, error) { + mps, err := snapshot.MetadataForFile(ctx, uri) + if err != nil { + return nil, err + } + metadata.RemoveIntermediateTestVariants(&mps) + if len(mps) == 0 { + return nil, fmt.Errorf("no package metadata for file %s", uri) + } + return mps[0], nil +} + +// NarrowestPackageForFile is a convenience function that selects the narrowest +// non-ITV package to which this file belongs, type-checks it in the requested +// mode (full or workspace), and returns it, along with the parse tree of that +// file. +// +// The "narrowest" package is the one with the fewest number of files that +// includes the given file. This solves the problem of test variants, as the +// test will have more files than the non-test package. +// +// An intermediate test variant (ITV) package has identical source to a regular +// package but resolves imports differently. gopls should never need to +// type-check them. +// +// Type-checking is expensive. Call snapshot.ParseGo if all you need is a parse +// tree, or snapshot.MetadataForFile if you only need metadata. +func NarrowestPackageForFile(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) (*cache.Package, *parsego.File, error) { + return selectPackageForFile(ctx, snapshot, uri, func(metas []*metadata.Package) *metadata.Package { return metas[0] }) +} + +// WidestPackageForFile is a convenience function that selects the widest +// non-ITV package to which this file belongs, type-checks it in the requested +// mode (full or workspace), and returns it, along with the parse tree of that +// file. +// +// The "widest" package is the one with the most number of files that includes +// the given file. Which is the test variant if one exists. +// +// An intermediate test variant (ITV) package has identical source to a regular +// package but resolves imports differently. gopls should never need to +// type-check them. +// +// Type-checking is expensive. Call snapshot.ParseGo if all you need is a parse +// tree, or snapshot.MetadataForFile if you only need metadata. +func WidestPackageForFile(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) (*cache.Package, *parsego.File, error) { + return selectPackageForFile(ctx, snapshot, uri, func(metas []*metadata.Package) *metadata.Package { return metas[len(metas)-1] }) +} + +func selectPackageForFile(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, selector func([]*metadata.Package) *metadata.Package) (*cache.Package, *parsego.File, error) { + mps, err := snapshot.MetadataForFile(ctx, uri) + if err != nil { + return nil, nil, err + } + metadata.RemoveIntermediateTestVariants(&mps) + if len(mps) == 0 { + return nil, nil, fmt.Errorf("no package metadata for file %s", uri) + } + mp := selector(mps) + pkgs, err := snapshot.TypeCheck(ctx, mp.ID) + if err != nil { + return nil, nil, err + } + pkg := pkgs[0] + pgf, err := pkg.File(uri) + if err != nil { + return nil, nil, err // "can't happen" + } + return pkg, pgf, err +} + +type ( + PackageID = metadata.PackageID + PackagePath = metadata.PackagePath + PackageName = metadata.PackageName + ImportPath = metadata.ImportPath +) + +type unit = struct{} diff --git a/contribs/gnopls/internal/golang/stub.go b/contribs/gnopls/internal/golang/stub.go new file mode 100644 index 00000000000..db405631c9e --- /dev/null +++ b/contribs/gnopls/internal/golang/stub.go @@ -0,0 +1,334 @@ +// 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 golang + +import ( + "bytes" + "context" + "fmt" + "go/format" + "go/parser" + "go/token" + "go/types" + "io" + pathpkg "path" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/analysis/stubmethods" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/tokeninternal" +) + +// stubMethodsFixer returns a suggested fix to declare the missing +// methods of the concrete type that is assigned to an interface type +// at the cursor position. +func stubMethodsFixer(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) { + nodes, _ := astutil.PathEnclosingInterval(pgf.File, start, end) + si := stubmethods.GetStubInfo(pkg.FileSet(), pkg.TypesInfo(), nodes, start) + if si == nil { + return nil, nil, fmt.Errorf("nil interface request") + } + + // A function-local type cannot be stubbed + // since there's nowhere to put the methods. + conc := si.Concrete.Obj() + if conc.Parent() != conc.Pkg().Scope() { + return nil, nil, fmt.Errorf("local type %q cannot be stubbed", conc.Name()) + } + + // Parse the file declaring the concrete type. + // + // Beware: declPGF is not necessarily covered by pkg.FileSet() or si.Fset. + declPGF, _, err := parseFull(ctx, snapshot, si.Fset, conc.Pos()) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse file %q declaring implementation type: %w", declPGF.URI, err) + } + if declPGF.Fixed() { + return nil, nil, fmt.Errorf("file contains parse errors: %s", declPGF.URI) + } + + // Find metadata for the concrete type's declaring package + // as we'll need its import mapping. + declMeta := findFileInDeps(snapshot, pkg.Metadata(), declPGF.URI) + if declMeta == nil { + return nil, nil, bug.Errorf("can't find metadata for file %s among dependencies of %s", declPGF.URI, pkg) + } + + // Record all direct methods of the current object + concreteFuncs := make(map[string]struct{}) + for i := 0; i < si.Concrete.NumMethods(); i++ { + concreteFuncs[si.Concrete.Method(i).Name()] = struct{}{} + } + + // Find subset of interface methods that the concrete type lacks. + ifaceType := si.Interface.Type().Underlying().(*types.Interface) + + type missingFn struct { + fn *types.Func + needSubtle string + } + + var ( + missing []missingFn + concreteStruct, isStruct = si.Concrete.Origin().Underlying().(*types.Struct) + ) + + for i := 0; i < ifaceType.NumMethods(); i++ { + imethod := ifaceType.Method(i) + cmethod, index, _ := types.LookupFieldOrMethod(si.Concrete, si.Pointer, imethod.Pkg(), imethod.Name()) + if cmethod == nil { + missing = append(missing, missingFn{fn: imethod}) + continue + } + + if _, ok := cmethod.(*types.Var); ok { + // len(LookupFieldOrMethod.index) = 1 => conflict, >1 => shadow. + return nil, nil, fmt.Errorf("adding method %s.%s would conflict with (or shadow) existing field", + conc.Name(), imethod.Name()) + } + + if _, exist := concreteFuncs[imethod.Name()]; exist { + if !types.Identical(cmethod.Type(), imethod.Type()) { + return nil, nil, fmt.Errorf("method %s.%s already exists but has the wrong type: got %s, want %s", + conc.Name(), imethod.Name(), cmethod.Type(), imethod.Type()) + } + continue + } + + mf := missingFn{fn: imethod} + if isStruct && len(index) > 0 { + field := concreteStruct.Field(index[0]) + + fn := field.Name() + if is[*types.Pointer](field.Type()) { + fn = "*" + fn + } + + mf.needSubtle = fmt.Sprintf("// Subtle: this method shadows the method (%s).%s of %s.%s.\n", fn, imethod.Name(), si.Concrete.Obj().Name(), field.Name()) + } + + missing = append(missing, mf) + } + if len(missing) == 0 { + return nil, nil, fmt.Errorf("no missing methods found") + } + + // Build import environment for the declaring file. + // (typesutil.FileQualifier works only for complete + // import mappings, and requires types.) + importEnv := make(map[ImportPath]string) // value is local name + for _, imp := range declPGF.File.Imports { + importPath := metadata.UnquoteImportPath(imp) + var name string + if imp.Name != nil { + name = imp.Name.Name + if name == "_" { + continue + } else if name == "." { + name = "" // see types.Qualifier + } + } else { + // Use the correct name from the metadata of the imported + // package---not a guess based on the import path. + mp := snapshot.Metadata(declMeta.DepsByImpPath[importPath]) + if mp == nil { + continue // can't happen? + } + name = string(mp.Name) + } + importEnv[importPath] = name // latest alias wins + } + + // Create a package name qualifier that uses the + // locally appropriate imported package name. + // It records any needed new imports. + // TODO(adonovan): factor with golang.FormatVarType? + // + // Prior to CL 469155 this logic preserved any renaming + // imports from the file that declares the interface + // method--ostensibly the preferred name for imports of + // frequently renamed packages such as protobufs. + // Now we use the package's declared name. If this turns out + // to be a mistake, then use parseHeader(si.iface.Pos()). + // + type newImport struct{ name, importPath string } + var newImports []newImport // for AddNamedImport + qual := func(pkg *types.Package) string { + // TODO(adonovan): don't ignore vendor prefix. + // + // Ignore the current package import. + if pkg.Path() == conc.Pkg().Path() { + return "" + } + + importPath := ImportPath(pkg.Path()) + name, ok := importEnv[importPath] + if !ok { + // Insert new import using package's declared name. + // + // TODO(adonovan): resolve conflict between declared + // name and existing file-level (declPGF.File.Imports) + // or package-level (si.Concrete.Pkg.Scope) decls by + // generating a fresh name. + name = pkg.Name() + importEnv[importPath] = name + new := newImport{importPath: string(importPath)} + // For clarity, use a renaming import whenever the + // local name does not match the path's last segment. + if name != pathpkg.Base(trimVersionSuffix(new.importPath)) { + new.name = name + } + newImports = append(newImports, new) + } + return name + } + + // Format interface name (used only in a comment). + iface := si.Interface.Name() + if ipkg := si.Interface.Pkg(); ipkg != nil && ipkg != conc.Pkg() { + iface = ipkg.Name() + "." + iface + } + + // Pointer receiver? + var star string + if si.Pointer { + star = "*" + } + + // If there are any that have named receiver, choose the first one. + // Otherwise, use lowercase for the first letter of the object. + rn := strings.ToLower(si.Concrete.Obj().Name()[0:1]) + for i := 0; i < si.Concrete.NumMethods(); i++ { + if recv := si.Concrete.Method(i).Signature().Recv(); recv.Name() != "" { + rn = recv.Name() + break + } + } + + // Check for receiver name conflicts + checkRecvName := func(tuple *types.Tuple) bool { + for i := 0; i < tuple.Len(); i++ { + if rn == tuple.At(i).Name() { + return true + } + } + return false + } + + // Format the new methods. + var newMethods bytes.Buffer + + for index := range missing { + mrn := rn + " " + sig := missing[index].fn.Signature() + if checkRecvName(sig.Params()) || checkRecvName(sig.Results()) { + mrn = "" + } + + fmt.Fprintf(&newMethods, `// %s implements %s. +%sfunc (%s%s%s%s) %s%s { + panic("unimplemented") +} +`, + missing[index].fn.Name(), + iface, + missing[index].needSubtle, + mrn, + star, + si.Concrete.Obj().Name(), + FormatTypeParams(si.Concrete.TypeParams()), + missing[index].fn.Name(), + strings.TrimPrefix(types.TypeString(missing[index].fn.Type(), qual), "func")) + } + + // Compute insertion point for new methods: + // after the top-level declaration enclosing the (package-level) type. + insertOffset, err := safetoken.Offset(declPGF.Tok, declPGF.File.End()) + if err != nil { + return nil, nil, bug.Errorf("internal error: end position outside file bounds: %v", err) + } + concOffset, err := safetoken.Offset(si.Fset.File(conc.Pos()), conc.Pos()) + if err != nil { + return nil, nil, bug.Errorf("internal error: finding type decl offset: %v", err) + } + for _, decl := range declPGF.File.Decls { + declEndOffset, err := safetoken.Offset(declPGF.Tok, decl.End()) + if err != nil { + return nil, nil, bug.Errorf("internal error: finding decl offset: %v", err) + } + if declEndOffset > concOffset { + insertOffset = declEndOffset + break + } + } + + // Splice the new methods into the file content. + var buf bytes.Buffer + input := declPGF.Mapper.Content // unfixed content of file + buf.Write(input[:insertOffset]) + buf.WriteByte('\n') + io.Copy(&buf, &newMethods) + buf.Write(input[insertOffset:]) + + // Re-parse the file. + fset := token.NewFileSet() + newF, err := parser.ParseFile(fset, declPGF.URI.Path(), buf.Bytes(), parser.ParseComments) + if err != nil { + return nil, nil, fmt.Errorf("could not reparse file: %w", err) + } + + // Splice the new imports into the syntax tree. + for _, imp := range newImports { + astutil.AddNamedImport(fset, newF, imp.name, imp.importPath) + } + + // Pretty-print. + var output bytes.Buffer + if err := format.Node(&output, fset, newF); err != nil { + return nil, nil, fmt.Errorf("format.Node: %w", err) + } + + // Report the diff. + diffs := diff.Bytes(input, output.Bytes()) + return tokeninternal.FileSetFor(declPGF.Tok), // edits use declPGF.Tok + &analysis.SuggestedFix{TextEdits: diffToTextEdits(declPGF.Tok, diffs)}, + nil +} + +// diffToTextEdits converts diff (offset-based) edits to analysis (token.Pos) form. +func diffToTextEdits(tok *token.File, diffs []diff.Edit) []analysis.TextEdit { + edits := make([]analysis.TextEdit, 0, len(diffs)) + for _, edit := range diffs { + edits = append(edits, analysis.TextEdit{ + Pos: tok.Pos(edit.Start), + End: tok.Pos(edit.End), + NewText: []byte(edit.New), + }) + } + return edits +} + +// trimVersionSuffix removes a trailing "/v2" (etc) suffix from a module path. +// +// This is only a heuristic as to the package's declared name, and +// should only be used for stylistic decisions, such as whether it +// would be clearer to use an explicit local name in the import +// because the declared name differs from the result of this function. +// When the name matters for correctness, look up the imported +// package's Metadata.Name. +func trimVersionSuffix(path string) string { + dir, base := pathpkg.Split(path) + if len(base) > 1 && base[0] == 'v' && strings.Trim(base[1:], "0123456789") == "" { + return dir // sans "/v2" + } + return path +} diff --git a/contribs/gnopls/internal/golang/symbols.go b/contribs/gnopls/internal/golang/symbols.go new file mode 100644 index 00000000000..35959c2de7a --- /dev/null +++ b/contribs/gnopls/internal/golang/symbols.go @@ -0,0 +1,230 @@ +// 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. + +package golang + +import ( + "context" + "fmt" + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func DocumentSymbols(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.DocumentSymbol, error) { + ctx, done := event.Start(ctx, "golang.DocumentSymbols") + defer done() + + pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full) + if err != nil { + return nil, fmt.Errorf("getting file for DocumentSymbols: %w", err) + } + + // Build symbols for file declarations. When encountering a declaration with + // errors (typically because positions are invalid), we skip the declaration + // entirely. VS Code fails to show any symbols if one of the top-level + // symbols is missing position information. + var symbols []protocol.DocumentSymbol + for _, decl := range pgf.File.Decls { + switch decl := decl.(type) { + case *ast.FuncDecl: + if decl.Name.Name == "_" { + continue + } + fs, err := funcSymbol(pgf.Mapper, pgf.Tok, decl) + if err == nil { + // If function is a method, prepend the type of the method. + if decl.Recv != nil && len(decl.Recv.List) > 0 { + fs.Name = fmt.Sprintf("(%s).%s", types.ExprString(decl.Recv.List[0].Type), fs.Name) + } + symbols = append(symbols, fs) + } + case *ast.GenDecl: + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.TypeSpec: + if spec.Name.Name == "_" { + continue + } + ts, err := typeSymbol(pgf.Mapper, pgf.Tok, spec) + if err == nil { + symbols = append(symbols, ts) + } + case *ast.ValueSpec: + for _, name := range spec.Names { + if name.Name == "_" { + continue + } + vs, err := varSymbol(pgf.Mapper, pgf.Tok, spec, name, decl.Tok == token.CONST) + if err == nil { + symbols = append(symbols, vs) + } + } + } + } + } + } + return symbols, nil +} + +func funcSymbol(m *protocol.Mapper, tf *token.File, decl *ast.FuncDecl) (protocol.DocumentSymbol, error) { + s := protocol.DocumentSymbol{ + Name: decl.Name.Name, + Kind: protocol.Function, + } + if decl.Recv != nil { + s.Kind = protocol.Method + } + var err error + s.Range, err = m.NodeRange(tf, decl) + if err != nil { + return protocol.DocumentSymbol{}, err + } + s.SelectionRange, err = m.NodeRange(tf, decl.Name) + if err != nil { + return protocol.DocumentSymbol{}, err + } + s.Detail = types.ExprString(decl.Type) + return s, nil +} + +func typeSymbol(m *protocol.Mapper, tf *token.File, spec *ast.TypeSpec) (protocol.DocumentSymbol, error) { + s := protocol.DocumentSymbol{ + Name: spec.Name.Name, + } + var err error + s.Range, err = m.NodeRange(tf, spec) + if err != nil { + return protocol.DocumentSymbol{}, err + } + s.SelectionRange, err = m.NodeRange(tf, spec.Name) + if err != nil { + return protocol.DocumentSymbol{}, err + } + s.Kind, s.Detail, s.Children = typeDetails(m, tf, spec.Type) + return s, nil +} + +func typeDetails(m *protocol.Mapper, tf *token.File, typExpr ast.Expr) (kind protocol.SymbolKind, detail string, children []protocol.DocumentSymbol) { + switch typExpr := typExpr.(type) { + case *ast.StructType: + kind = protocol.Struct + children = fieldListSymbols(m, tf, typExpr.Fields, protocol.Field) + if len(children) > 0 { + detail = "struct{...}" + } else { + detail = "struct{}" + } + + // Find interface methods and embedded types. + case *ast.InterfaceType: + kind = protocol.Interface + children = fieldListSymbols(m, tf, typExpr.Methods, protocol.Method) + if len(children) > 0 { + detail = "interface{...}" + } else { + detail = "interface{}" + } + + case *ast.FuncType: + kind = protocol.Function + detail = types.ExprString(typExpr) + + default: + kind = protocol.Class // catch-all, for cases where we don't know the kind syntactically + detail = types.ExprString(typExpr) + } + return +} + +func fieldListSymbols(m *protocol.Mapper, tf *token.File, fields *ast.FieldList, fieldKind protocol.SymbolKind) []protocol.DocumentSymbol { + if fields == nil { + return nil + } + + var symbols []protocol.DocumentSymbol + for _, field := range fields.List { + detail, children := "", []protocol.DocumentSymbol(nil) + if field.Type != nil { + _, detail, children = typeDetails(m, tf, field.Type) + } + if len(field.Names) == 0 { // embedded interface or struct field + // By default, use the formatted type details as the name of this field. + // This handles potentially invalid syntax, as well as type embeddings in + // interfaces. + child := protocol.DocumentSymbol{ + Name: detail, + Kind: protocol.Field, // consider all embeddings to be fields + Children: children, + } + + // If the field is a valid embedding, promote the type name to field + // name. + selection := field.Type + if id := embeddedIdent(field.Type); id != nil { + child.Name = id.Name + child.Detail = detail + selection = id + } + + if rng, err := m.NodeRange(tf, field.Type); err == nil { + child.Range = rng + } + if rng, err := m.NodeRange(tf, selection); err == nil { + child.SelectionRange = rng + } + + symbols = append(symbols, child) + } else { + for _, name := range field.Names { + child := protocol.DocumentSymbol{ + Name: name.Name, + Kind: fieldKind, + Detail: detail, + Children: children, + } + + if rng, err := m.NodeRange(tf, field); err == nil { + child.Range = rng + } + if rng, err := m.NodeRange(tf, name); err == nil { + child.SelectionRange = rng + } + + symbols = append(symbols, child) + } + } + + } + return symbols +} + +func varSymbol(m *protocol.Mapper, tf *token.File, spec *ast.ValueSpec, name *ast.Ident, isConst bool) (protocol.DocumentSymbol, error) { + s := protocol.DocumentSymbol{ + Name: name.Name, + Kind: protocol.Variable, + } + if isConst { + s.Kind = protocol.Constant + } + var err error + s.Range, err = m.NodeRange(tf, spec) + if err != nil { + return protocol.DocumentSymbol{}, err + } + s.SelectionRange, err = m.NodeRange(tf, name) + if err != nil { + return protocol.DocumentSymbol{}, err + } + if spec.Type != nil { // type may be missing from the syntax + _, s.Detail, s.Children = typeDetails(m, tf, spec.Type) + } + return s, nil +} diff --git a/contribs/gnopls/internal/golang/type_definition.go b/contribs/gnopls/internal/golang/type_definition.go new file mode 100644 index 00000000000..a396793e48a --- /dev/null +++ b/contribs/gnopls/internal/golang/type_definition.go @@ -0,0 +1,53 @@ +// 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 golang + +import ( + "context" + "fmt" + "go/token" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +// TypeDefinition handles the textDocument/typeDefinition request for Go files. +func TypeDefinition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Location, error) { + ctx, done := event.Start(ctx, "golang.TypeDefinition") + defer done() + + pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI()) + if err != nil { + return nil, err + } + pos, err := pgf.PositionPos(position) + if err != nil { + return nil, err + } + + // TODO(rfindley): handle type switch implicits correctly here: if the user + // jumps to the type definition of x in x := y.(type), it makes sense to jump + // to the type of y. + _, obj, _ := referencedObject(pkg, pgf, pos) + if obj == nil { + return nil, nil + } + + tname := typeToObject(obj.Type()) + if tname == nil { + return nil, fmt.Errorf("no type definition for %s", obj.Name()) + } + if isBuiltin(tname) { + return nil, nil // built-ins (error, comparable) have no position + } + + loc, err := mapPosition(ctx, pkg.FileSet(), snapshot, tname.Pos(), tname.Pos()+token.Pos(len(tname.Name()))) + if err != nil { + return nil, err + } + return []protocol.Location{loc}, nil +} diff --git a/contribs/gnopls/internal/golang/types_format.go b/contribs/gnopls/internal/golang/types_format.go new file mode 100644 index 00000000000..41828244e11 --- /dev/null +++ b/contribs/gnopls/internal/golang/types_format.go @@ -0,0 +1,546 @@ +// 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 golang + +import ( + "bytes" + "context" + "fmt" + "go/ast" + "go/doc" + "go/printer" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/tokeninternal" + "golang.org/x/tools/internal/typeparams" +) + +// FormatType returns the detail and kind for a types.Type. +func FormatType(typ types.Type, qf types.Qualifier) (detail string, kind protocol.CompletionItemKind) { + typ = typ.Underlying() + if types.IsInterface(typ) { + detail = "interface{...}" + kind = protocol.InterfaceCompletion + } else if _, ok := typ.(*types.Struct); ok { + detail = "struct{...}" + kind = protocol.StructCompletion + } else { + detail = types.TypeString(typ, qf) + kind = protocol.ClassCompletion + } + return detail, kind +} + +type signature struct { + name, doc string + typeParams, params, results []string + variadic bool + needResultParens bool +} + +func (s *signature) Format() string { + var b strings.Builder + b.WriteByte('(') + for i, p := range s.params { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(p) + } + b.WriteByte(')') + + // Add space between parameters and results. + if len(s.results) > 0 { + b.WriteByte(' ') + } + if s.needResultParens { + b.WriteByte('(') + } + for i, r := range s.results { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(r) + } + if s.needResultParens { + b.WriteByte(')') + } + return b.String() +} + +func (s *signature) TypeParams() []string { + return s.typeParams +} + +func (s *signature) Params() []string { + return s.params +} + +// NewBuiltinSignature returns signature for the builtin object with a given +// name, if a builtin object with the name exists. +func NewBuiltinSignature(ctx context.Context, s *cache.Snapshot, name string) (*signature, error) { + builtin, err := s.BuiltinFile(ctx) + if err != nil { + return nil, err + } + obj := builtin.File.Scope.Lookup(name) + if obj == nil { + return nil, fmt.Errorf("no builtin object for %s", name) + } + decl, ok := obj.Decl.(*ast.FuncDecl) + if !ok { + return nil, fmt.Errorf("no function declaration for builtin: %s", name) + } + if decl.Type == nil { + return nil, fmt.Errorf("no type for builtin decl %s", decl.Name) + } + var variadic bool + if decl.Type.Params.List != nil { + numParams := len(decl.Type.Params.List) + lastParam := decl.Type.Params.List[numParams-1] + if _, ok := lastParam.Type.(*ast.Ellipsis); ok { + variadic = true + } + } + fset := tokeninternal.FileSetFor(builtin.Tok) + params, _ := formatFieldList(ctx, fset, decl.Type.Params, variadic) + results, needResultParens := formatFieldList(ctx, fset, decl.Type.Results, false) + d := decl.Doc.Text() + switch s.Options().HoverKind { + case settings.SynopsisDocumentation: + d = doc.Synopsis(d) + case settings.NoDocumentation: + d = "" + } + return &signature{ + doc: d, + name: name, + needResultParens: needResultParens, + params: params, + results: results, + variadic: variadic, + }, nil +} + +// replacer replaces some synthetic "type classes" used in the builtin file +// with their most common constituent type. +var replacer = strings.NewReplacer( + `ComplexType`, `complex128`, + `FloatType`, `float64`, + `IntegerType`, `int`, +) + +func formatFieldList(ctx context.Context, fset *token.FileSet, list *ast.FieldList, variadic bool) ([]string, bool) { + if list == nil { + return nil, false + } + var writeResultParens bool + var result []string + for i := 0; i < len(list.List); i++ { + if i >= 1 { + writeResultParens = true + } + p := list.List[i] + cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4} + b := &bytes.Buffer{} + if err := cfg.Fprint(b, fset, p.Type); err != nil { + event.Error(ctx, fmt.Sprintf("error printing type %s", types.ExprString(p.Type)), err) + continue + } + typ := replacer.Replace(b.String()) + if len(p.Names) == 0 { + result = append(result, typ) + } + for _, name := range p.Names { + if name.Name != "" { + if i == 0 { + writeResultParens = true + } + result = append(result, fmt.Sprintf("%s %s", name.Name, typ)) + } else { + result = append(result, typ) + } + } + } + if variadic { + result[len(result)-1] = strings.Replace(result[len(result)-1], "[]", "...", 1) + } + return result, writeResultParens +} + +// FormatTypeParams turns TypeParamList into its Go representation, such as: +// [T, Y]. Note that it does not print constraints as this is mainly used for +// formatting type params in method receivers. +func FormatTypeParams(tparams *types.TypeParamList) string { + if tparams == nil || tparams.Len() == 0 { + return "" + } + var buf bytes.Buffer + buf.WriteByte('[') + for i := 0; i < tparams.Len(); i++ { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(tparams.At(i).Obj().Name()) + } + buf.WriteByte(']') + return buf.String() +} + +// NewSignature returns formatted signature for a types.Signature struct. +func NewSignature(ctx context.Context, s *cache.Snapshot, pkg *cache.Package, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier, mq MetadataQualifier) (*signature, error) { + var tparams []string + tpList := sig.TypeParams() + for i := 0; i < tpList.Len(); i++ { + tparam := tpList.At(i) + // TODO: is it possible to reuse the logic from FormatVarType here? + s := tparam.Obj().Name() + " " + tparam.Constraint().String() + tparams = append(tparams, s) + } + + params := make([]string, 0, sig.Params().Len()) + for i := 0; i < sig.Params().Len(); i++ { + el := sig.Params().At(i) + typ, err := FormatVarType(ctx, s, pkg, el, qf, mq) + if err != nil { + return nil, err + } + if sig.Variadic() && i == sig.Params().Len()-1 { + typ = strings.Replace(typ, "[]", "...", 1) + } + p := typ + if el.Name() != "" { + p = el.Name() + " " + typ + } + params = append(params, p) + } + + var needResultParens bool + results := make([]string, 0, sig.Results().Len()) + for i := 0; i < sig.Results().Len(); i++ { + if i >= 1 { + needResultParens = true + } + el := sig.Results().At(i) + typ, err := FormatVarType(ctx, s, pkg, el, qf, mq) + if err != nil { + return nil, err + } + if el.Name() == "" { + results = append(results, typ) + } else { + if i == 0 { + needResultParens = true + } + results = append(results, el.Name()+" "+typ) + } + } + var d string + if comment != nil { + d = comment.Text() + } + switch s.Options().HoverKind { + case settings.SynopsisDocumentation: + d = doc.Synopsis(d) + case settings.NoDocumentation: + d = "" + } + return &signature{ + doc: d, + typeParams: tparams, + params: params, + results: results, + variadic: sig.Variadic(), + needResultParens: needResultParens, + }, nil +} + +// We look for 'invalidTypeString' to determine if we can use the fast path for +// FormatVarType. +var invalidTypeString = types.Typ[types.Invalid].String() + +// FormatVarType formats a *types.Var, accounting for type aliases. +// To do this, it looks in the AST of the file in which the object is declared. +// On any errors, it always falls back to types.TypeString. +// +// TODO(rfindley): this function could return the actual name used in syntax, +// for better parameter names. +func FormatVarType(ctx context.Context, snapshot *cache.Snapshot, srcpkg *cache.Package, obj *types.Var, qf types.Qualifier, mq MetadataQualifier) (string, error) { + typeString := types.TypeString(obj.Type(), qf) + // Fast path: if the type string does not contain 'invalid type', we no + // longer need to do any special handling, thanks to materialized aliases in + // Go 1.23+. + // + // Unfortunately, due to the handling of invalid types, we can't quite delete + // the rather complicated preexisting logic of FormatVarType--it isn't an + // acceptable regression to start printing "invalid type" in completion or + // signature help. strings.Contains is conservative: the type string of a + // valid type may actually contain "invalid type" (due to struct tags or + // field formatting), but such cases should be exceedingly rare. + if !strings.Contains(typeString, invalidTypeString) { + return typeString, nil + } + + // TODO(rfindley): This looks wrong. The previous comment said: + // "If the given expr refers to a type parameter, then use the + // object's Type instead of the type parameter declaration. This helps + // format the instantiated type as opposed to the original undeclared + // generic type". + // + // But of course, if obj is a type param, we are formatting a generic type + // and not an instantiated type. Handling for instantiated types must be done + // at a higher level. + // + // Left this during refactoring in order to preserve pre-existing logic. + if typeparams.IsTypeParam(obj.Type()) { + return typeString, nil + } + + if isBuiltin(obj) { + // This is defensive, though it is extremely unlikely we'll ever have a + // builtin var. + return typeString, nil + } + + // TODO(rfindley): parsing to produce candidates can be costly; consider + // using faster methods. + targetpgf, pos, err := parseFull(ctx, snapshot, srcpkg.FileSet(), obj.Pos()) + if err != nil { + return "", err // e.g. ctx cancelled + } + + targetMeta := findFileInDeps(snapshot, srcpkg.Metadata(), targetpgf.URI) + if targetMeta == nil { + // If we have an object from type-checking, it should exist in a file in + // the forward transitive closure. + return "", bug.Errorf("failed to find file %q in deps of %q", targetpgf.URI, srcpkg.Metadata().ID) + } + + decl, spec, field := findDeclInfo([]*ast.File{targetpgf.File}, pos) + + // We can't handle type parameters correctly, so we fall back on TypeString + // for parameterized decls. + if decl, _ := decl.(*ast.FuncDecl); decl != nil { + if decl.Type.TypeParams.NumFields() > 0 { + return typeString, nil // in generic function + } + if decl.Recv != nil && len(decl.Recv.List) > 0 { + rtype := decl.Recv.List[0].Type + if e, ok := rtype.(*ast.StarExpr); ok { + rtype = e.X + } + if x, _, _, _ := typeparams.UnpackIndexExpr(rtype); x != nil { + return typeString, nil // in method of generic type + } + } + } + if spec, _ := spec.(*ast.TypeSpec); spec != nil && spec.TypeParams.NumFields() > 0 { + return typeString, nil // in generic type decl + } + + if field == nil { + // TODO(rfindley): we should never reach here from an ordinary var, so + // should probably return an error here. + return typeString, nil + } + expr := field.Type + + rq := requalifier(snapshot, targetpgf.File, targetMeta, mq) + + // The type names in the AST may not be correctly qualified. + // Determine the package name to use based on the package that originated + // the query and the package in which the type is declared. + // We then qualify the value by cloning the AST node and editing it. + expr = qualifyTypeExpr(expr, rq) + + // If the request came from a different package than the one in which the + // types are defined, we may need to modify the qualifiers. + return formatNodeFile(targetpgf.Tok, expr), nil +} + +// qualifyTypeExpr clones the type expression expr after re-qualifying type +// names using the given function, which accepts the current syntactic +// qualifier (possibly "" for unqualified idents), and returns a new qualifier +// (again, possibly "" if the identifier should be unqualified). +// +// The resulting expression may be inaccurate: without type-checking we don't +// properly account for "." imported identifiers or builtins. +// +// TODO(rfindley): add many more tests for this function. +func qualifyTypeExpr(expr ast.Expr, qf func(string) string) ast.Expr { + switch expr := expr.(type) { + case *ast.ArrayType: + return &ast.ArrayType{ + Lbrack: expr.Lbrack, + Elt: qualifyTypeExpr(expr.Elt, qf), + Len: expr.Len, + } + + case *ast.BinaryExpr: + if expr.Op != token.OR { + return expr + } + return &ast.BinaryExpr{ + X: qualifyTypeExpr(expr.X, qf), + OpPos: expr.OpPos, + Op: expr.Op, + Y: qualifyTypeExpr(expr.Y, qf), + } + + case *ast.ChanType: + return &ast.ChanType{ + Arrow: expr.Arrow, + Begin: expr.Begin, + Dir: expr.Dir, + Value: qualifyTypeExpr(expr.Value, qf), + } + + case *ast.Ellipsis: + return &ast.Ellipsis{ + Ellipsis: expr.Ellipsis, + Elt: qualifyTypeExpr(expr.Elt, qf), + } + + case *ast.FuncType: + return &ast.FuncType{ + Func: expr.Func, + Params: qualifyFieldList(expr.Params, qf), + Results: qualifyFieldList(expr.Results, qf), + } + + case *ast.Ident: + // Unqualified type (builtin, package local, or dot-imported). + + // Don't qualify names that look like builtins. + // + // Without type-checking this may be inaccurate. It could be made accurate + // by doing syntactic object resolution for the entire package, but that + // does not seem worthwhile and we generally want to avoid using + // ast.Object, which may be inaccurate. + if obj := types.Universe.Lookup(expr.Name); obj != nil { + return expr + } + + newName := qf("") + if newName != "" { + return &ast.SelectorExpr{ + X: &ast.Ident{ + NamePos: expr.Pos(), + Name: newName, + }, + Sel: expr, + } + } + return expr + + case *ast.IndexExpr: + return &ast.IndexExpr{ + X: qualifyTypeExpr(expr.X, qf), + Lbrack: expr.Lbrack, + Index: qualifyTypeExpr(expr.Index, qf), + Rbrack: expr.Rbrack, + } + + case *ast.IndexListExpr: + indices := make([]ast.Expr, len(expr.Indices)) + for i, idx := range expr.Indices { + indices[i] = qualifyTypeExpr(idx, qf) + } + return &ast.IndexListExpr{ + X: qualifyTypeExpr(expr.X, qf), + Lbrack: expr.Lbrack, + Indices: indices, + Rbrack: expr.Rbrack, + } + + case *ast.InterfaceType: + return &ast.InterfaceType{ + Interface: expr.Interface, + Methods: qualifyFieldList(expr.Methods, qf), + Incomplete: expr.Incomplete, + } + + case *ast.MapType: + return &ast.MapType{ + Map: expr.Map, + Key: qualifyTypeExpr(expr.Key, qf), + Value: qualifyTypeExpr(expr.Value, qf), + } + + case *ast.ParenExpr: + return &ast.ParenExpr{ + Lparen: expr.Lparen, + Rparen: expr.Rparen, + X: qualifyTypeExpr(expr.X, qf), + } + + case *ast.SelectorExpr: + if id, ok := expr.X.(*ast.Ident); ok { + // qualified type + newName := qf(id.Name) + if newName == "" { + return expr.Sel + } + return &ast.SelectorExpr{ + X: &ast.Ident{ + NamePos: id.NamePos, + Name: newName, + }, + Sel: expr.Sel, + } + } + return expr + + case *ast.StarExpr: + return &ast.StarExpr{ + Star: expr.Star, + X: qualifyTypeExpr(expr.X, qf), + } + + case *ast.StructType: + return &ast.StructType{ + Struct: expr.Struct, + Fields: qualifyFieldList(expr.Fields, qf), + Incomplete: expr.Incomplete, + } + + default: + return expr + } +} + +func qualifyFieldList(fl *ast.FieldList, qf func(string) string) *ast.FieldList { + if fl == nil { + return nil + } + if fl.List == nil { + return &ast.FieldList{ + Closing: fl.Closing, + Opening: fl.Opening, + } + } + list := make([]*ast.Field, 0, len(fl.List)) + for _, f := range fl.List { + list = append(list, &ast.Field{ + Comment: f.Comment, + Doc: f.Doc, + Names: f.Names, + Tag: f.Tag, + Type: qualifyTypeExpr(f.Type, qf), + }) + } + return &ast.FieldList{ + Closing: fl.Closing, + Opening: fl.Opening, + List: list, + } +} diff --git a/contribs/gnopls/internal/golang/util.go b/contribs/gnopls/internal/golang/util.go new file mode 100644 index 00000000000..18f72421a64 --- /dev/null +++ b/contribs/gnopls/internal/golang/util.go @@ -0,0 +1,365 @@ +// 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. + +package golang + +import ( + "context" + "go/ast" + "go/printer" + "go/token" + "go/types" + "regexp" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/tokeninternal" +) + +// IsGenerated gets and reads the file denoted by uri and reports +// whether it contains a "generated file" comment as described at +// https://golang.org/s/generatedcode. +// +// TODO(adonovan): opt: this function does too much. +// Move snapshot.ReadFile into the caller (most of which have already done it). +func IsGenerated(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) bool { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return false + } + pgf, err := snapshot.ParseGo(ctx, fh, parsego.Header) + if err != nil { + return false + } + for _, commentGroup := range pgf.File.Comments { + for _, comment := range commentGroup.List { + if matched := generatedRx.MatchString(comment.Text); matched { + // Check if comment is at the beginning of the line in source. + if safetoken.Position(pgf.Tok, comment.Slash).Column == 1 { + return true + } + } + } + } + return false +} + +// adjustedObjEnd returns the end position of obj, possibly modified for +// package names. +// +// TODO(rfindley): eliminate this function, by inlining it at callsites where +// it makes sense. +func adjustedObjEnd(obj types.Object) token.Pos { + nameLen := len(obj.Name()) + if pkgName, ok := obj.(*types.PkgName); ok { + // An imported Go package has a package-local, unqualified name. + // When the name matches the imported package name, there is no + // identifier in the import spec with the local package name. + // + // For example: + // import "go/ast" // name "ast" matches package name + // import a "go/ast" // name "a" does not match package name + // + // When the identifier does not appear in the source, have the range + // of the object be the import path, including quotes. + if pkgName.Imported().Name() == pkgName.Name() { + nameLen = len(pkgName.Imported().Path()) + len(`""`) + } + } + return obj.Pos() + token.Pos(nameLen) +} + +// Matches cgo generated comment as well as the proposed standard: +// +// https://golang.org/s/generatedcode +var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`) + +// FormatNode returns the "pretty-print" output for an ast node. +func FormatNode(fset *token.FileSet, n ast.Node) string { + var buf strings.Builder + if err := printer.Fprint(&buf, fset, n); err != nil { + // TODO(rfindley): we should use bug.Reportf here. + // We encounter this during completion.resolveInvalid. + return "" + } + return buf.String() +} + +// formatNodeFile is like FormatNode, but requires only the token.File for the +// syntax containing the given ast node. +func formatNodeFile(file *token.File, n ast.Node) string { + fset := tokeninternal.FileSetFor(file) + return FormatNode(fset, n) +} + +// findFileInDeps finds package metadata containing URI in the transitive +// dependencies of m. When using the Go command, the answer is unique. +func findFileInDeps(s metadata.Source, mp *metadata.Package, uri protocol.DocumentURI) *metadata.Package { + seen := make(map[PackageID]bool) + var search func(*metadata.Package) *metadata.Package + search = func(mp *metadata.Package) *metadata.Package { + if seen[mp.ID] { + return nil + } + seen[mp.ID] = true + for _, cgf := range mp.CompiledGoFiles { + if cgf == uri { + return mp + } + } + for _, dep := range mp.DepsByPkgPath { + mp := s.Metadata(dep) + if mp == nil { + bug.Reportf("nil metadata for %q", dep) + continue + } + if found := search(mp); found != nil { + return found + } + } + return nil + } + return search(mp) +} + +// CollectScopes returns all scopes in an ast path, ordered as innermost scope +// first. +func CollectScopes(info *types.Info, path []ast.Node, pos token.Pos) []*types.Scope { + // scopes[i], where i import path mapping. + inverseDeps := make(map[PackageID]PackagePath) + for path, id := range mp.DepsByPkgPath { + inverseDeps[id] = path + } + importsByPkgPath := make(map[PackagePath]ImportPath) // best import paths by pkgPath + for impPath, id := range mp.DepsByImpPath { + if id == "" { + continue + } + pkgPath := inverseDeps[id] + _, hasPath := importsByPkgPath[pkgPath] + _, hasImp := localNames[impPath] + // In rare cases, there may be multiple import paths with the same package + // path. In such scenarios, prefer an import path that already exists in + // the file. + if !hasPath || hasImp { + importsByPkgPath[pkgPath] = impPath + } + } + + return func(pkgName PackageName, impPath ImportPath, pkgPath PackagePath) string { + // If supplied, translate the package path to an import path in the source + // package. + if pkgPath != "" { + if srcImp := importsByPkgPath[pkgPath]; srcImp != "" { + impPath = srcImp + } + if pkgPath == mp.PkgPath { + return "" + } + } + if localName, ok := localNames[impPath]; ok && impPath != "" { + return localName + } + if pkgName != "" { + return string(pkgName) + } + idx := strings.LastIndexByte(string(impPath), '/') + return string(impPath[idx+1:]) + } +} + +// importInfo collects information about the import specified by imp, +// extracting its file-local name, package name, import path, and package path. +// +// If metadata is missing for the import, the resulting package name and +// package path may be empty, and the file local name may be guessed based on +// the import path. +// +// Note: previous versions of this helper used a PackageID->PackagePath map +// extracted from m, for extracting package path even in the case where +// metadata for a dep was missing. This should not be necessary, as we should +// always have metadata for IDs contained in DepsByPkgPath. +func importInfo(s metadata.Source, imp *ast.ImportSpec, mp *metadata.Package) (string, PackageName, ImportPath, PackagePath) { + var ( + name string // local name + pkgName PackageName + impPath = metadata.UnquoteImportPath(imp) + pkgPath PackagePath + ) + + // If the import has a local name, use it. + if imp.Name != nil { + name = imp.Name.Name + } + + // Try to find metadata for the import. If successful and there is no local + // name, the package name is the local name. + if depID := mp.DepsByImpPath[impPath]; depID != "" { + if depMP := s.Metadata(depID); depMP != nil { + if name == "" { + name = string(depMP.Name) + } + pkgName = depMP.Name + pkgPath = depMP.PkgPath + } + } + + // If the local name is still unknown, guess it based on the import path. + if name == "" { + idx := strings.LastIndexByte(string(impPath), '/') + name = string(impPath[idx+1:]) + } + return name, pkgName, impPath, pkgPath +} + +// isDirective reports whether c is a comment directive. +// +// Copied and adapted from go/src/go/ast/ast.go. +func isDirective(c string) bool { + if len(c) < 3 { + return false + } + if c[1] != '/' { + return false + } + //-style comment (no newline at the end) + c = c[2:] + if len(c) == 0 { + // empty line + return false + } + // "//line " is a line directive. + // (The // has been removed.) + if strings.HasPrefix(c, "line ") { + return true + } + + // "//[a-z0-9]+:[a-z0-9]" + // (The // has been removed.) + colon := strings.Index(c, ":") + if colon <= 0 || colon+1 >= len(c) { + return false + } + for i := 0; i <= colon+1; i++ { + if i == colon { + continue + } + b := c[i] + if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { + return false + } + } + return true +} + +// embeddedIdent returns the type name identifier for an embedding x, if x in a +// valid embedding. Otherwise, it returns nil. +// +// Spec: An embedded field must be specified as a type name T or as a pointer +// to a non-interface type name *T +func embeddedIdent(x ast.Expr) *ast.Ident { + if star, ok := x.(*ast.StarExpr); ok { + x = star.X + } + switch ix := x.(type) { // check for instantiated receivers + case *ast.IndexExpr: + x = ix.X + case *ast.IndexListExpr: + x = ix.X + } + switch x := x.(type) { + case *ast.Ident: + return x + case *ast.SelectorExpr: + if _, ok := x.X.(*ast.Ident); ok { + return x.Sel + } + } + return nil +} + +// An importFunc is an implementation of the single-method +// types.Importer interface based on a function value. +type ImporterFunc func(path string) (*types.Package, error) + +func (f ImporterFunc) Import(path string) (*types.Package, error) { return f(path) } + +// isBuiltin reports whether obj is a built-in symbol (e.g. append, iota, error.Error, unsafe.Slice). +// All other symbols have a valid position and a valid package. +func isBuiltin(obj types.Object) bool { return !obj.Pos().IsValid() } + +// btoi returns int(b) as proposed in #64825. +func btoi(b bool) int { + if b { + return 1 + } else { + return 0 + } +} diff --git a/contribs/gnopls/internal/golang/workspace_symbol.go b/contribs/gnopls/internal/golang/workspace_symbol.go new file mode 100644 index 00000000000..c80174c78fd --- /dev/null +++ b/contribs/gnopls/internal/golang/workspace_symbol.go @@ -0,0 +1,526 @@ +// 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 golang + +import ( + "context" + "fmt" + "path/filepath" + "runtime" + "sort" + "strings" + "unicode" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/fuzzy" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/event" +) + +// maxSymbols defines the maximum number of symbol results that should ever be +// sent in response to a client. +const maxSymbols = 100 + +// WorkspaceSymbols matches symbols across all views using the given query, +// according to the match semantics parameterized by matcherType and style. +// +// The workspace symbol method is defined in the spec as follows: +// +// The workspace symbol request is sent from the client to the server to +// list project-wide symbols matching the query string. +// +// It is unclear what "project-wide" means here, but given the parameters of +// workspace/symbol do not include any workspace identifier, then it has to be +// assumed that "project-wide" means "across all workspaces". Hence why +// WorkspaceSymbols receives the views []View. +// +// However, it then becomes unclear what it would mean to call WorkspaceSymbols +// with a different configured SymbolMatcher per View. Therefore we assume that +// Session level configuration will define the SymbolMatcher to be used for the +// WorkspaceSymbols method. +func WorkspaceSymbols(ctx context.Context, matcher settings.SymbolMatcher, style settings.SymbolStyle, snapshots []*cache.Snapshot, query string) ([]protocol.SymbolInformation, error) { + ctx, done := event.Start(ctx, "golang.WorkspaceSymbols") + defer done() + if query == "" { + return nil, nil + } + + var s symbolizer + switch style { + case settings.DynamicSymbols: + s = dynamicSymbolMatch + case settings.FullyQualifiedSymbols: + s = fullyQualifiedSymbolMatch + case settings.PackageQualifiedSymbols: + s = packageSymbolMatch + default: + panic(fmt.Errorf("unknown symbol style: %v", style)) + } + + return collectSymbols(ctx, snapshots, matcher, s, query) +} + +// A matcherFunc returns the index and score of a symbol match. +// +// See the comment for symbolCollector for more information. +type matcherFunc func(chunks []string) (int, float64) + +// A symbolizer returns the best symbol match for a name with pkg, according to +// some heuristic. The symbol name is passed as the slice nameParts of logical +// name pieces. For example, for myType.field the caller can pass either +// []string{"myType.field"} or []string{"myType.", "field"}. +// +// See the comment for symbolCollector for more information. +// +// The space argument is an empty slice with spare capacity that may be used +// to allocate the result. +type symbolizer func(space []string, name string, pkg *metadata.Package, m matcherFunc) ([]string, float64) + +func fullyQualifiedSymbolMatch(space []string, name string, pkg *metadata.Package, matcher matcherFunc) ([]string, float64) { + if _, score := dynamicSymbolMatch(space, name, pkg, matcher); score > 0 { + return append(space, string(pkg.PkgPath), ".", name), score + } + return nil, 0 +} + +func dynamicSymbolMatch(space []string, name string, pkg *metadata.Package, matcher matcherFunc) ([]string, float64) { + if metadata.IsCommandLineArguments(pkg.ID) { + // command-line-arguments packages have a non-sensical package path, so + // just use their package name. + return packageSymbolMatch(space, name, pkg, matcher) + } + + var score float64 + + endsInPkgName := strings.HasSuffix(string(pkg.PkgPath), string(pkg.Name)) + + // If the package path does not end in the package name, we need to check the + // package-qualified symbol as an extra pass first. + if !endsInPkgName { + pkgQualified := append(space, string(pkg.Name), ".", name) + idx, score := matcher(pkgQualified) + nameStart := len(pkg.Name) + 1 + if score > 0 { + // If our match is contained entirely within the unqualified portion, + // just return that. + if idx >= nameStart { + return append(space, name), score + } + // Lower the score for matches that include the package name. + return pkgQualified, score * 0.8 + } + } + + // Now try matching the fully qualified symbol. + fullyQualified := append(space, string(pkg.PkgPath), ".", name) + idx, score := matcher(fullyQualified) + + // As above, check if we matched just the unqualified symbol name. + nameStart := len(pkg.PkgPath) + 1 + if idx >= nameStart { + return append(space, name), score + } + + // If our package path ends in the package name, we'll have skipped the + // initial pass above, so check if we matched just the package-qualified + // name. + if endsInPkgName && idx >= 0 { + pkgStart := len(pkg.PkgPath) - len(pkg.Name) + if idx >= pkgStart { + return append(space, string(pkg.Name), ".", name), score + } + } + + // Our match was not contained within the unqualified or package qualified + // symbol. Return the fully qualified symbol but discount the score. + return fullyQualified, score * 0.6 +} + +func packageSymbolMatch(space []string, name string, pkg *metadata.Package, matcher matcherFunc) ([]string, float64) { + qualified := append(space, string(pkg.Name), ".", name) + if _, s := matcher(qualified); s > 0 { + return qualified, s + } + return nil, 0 +} + +func buildMatcher(matcher settings.SymbolMatcher, query string) matcherFunc { + switch matcher { + case settings.SymbolFuzzy: + return parseQuery(query, newFuzzyMatcher) + case settings.SymbolFastFuzzy: + return parseQuery(query, func(query string) matcherFunc { + return fuzzy.NewSymbolMatcher(query).Match + }) + case settings.SymbolCaseSensitive: + return matchExact(query) + case settings.SymbolCaseInsensitive: + q := strings.ToLower(query) + exact := matchExact(q) + wrapper := []string{""} + return func(chunks []string) (int, float64) { + s := strings.Join(chunks, "") + wrapper[0] = strings.ToLower(s) + return exact(wrapper) + } + } + panic(fmt.Errorf("unknown symbol matcher: %v", matcher)) +} + +func newFuzzyMatcher(query string) matcherFunc { + fm := fuzzy.NewMatcher(query) + return func(chunks []string) (int, float64) { + score := float64(fm.ScoreChunks(chunks)) + ranges := fm.MatchedRanges() + if len(ranges) > 0 { + return ranges[0], score + } + return -1, score + } +} + +// parseQuery parses a field-separated symbol query, extracting the special +// characters listed below, and returns a matcherFunc corresponding to the AND +// of all field queries. +// +// Special characters: +// +// ^ match exact prefix +// $ match exact suffix +// ' match exact +// +// In all three of these special queries, matches are 'smart-cased', meaning +// they are case sensitive if the symbol query contains any upper-case +// characters, and case insensitive otherwise. +func parseQuery(q string, newMatcher func(string) matcherFunc) matcherFunc { + fields := strings.Fields(q) + if len(fields) == 0 { + return func([]string) (int, float64) { return -1, 0 } + } + var funcs []matcherFunc + for _, field := range fields { + var f matcherFunc + switch { + case strings.HasPrefix(field, "^"): + prefix := field[1:] + f = smartCase(prefix, func(chunks []string) (int, float64) { + s := strings.Join(chunks, "") + if strings.HasPrefix(s, prefix) { + return 0, 1 + } + return -1, 0 + }) + case strings.HasPrefix(field, "'"): + exact := field[1:] + f = smartCase(exact, matchExact(exact)) + case strings.HasSuffix(field, "$"): + suffix := field[0 : len(field)-1] + f = smartCase(suffix, func(chunks []string) (int, float64) { + s := strings.Join(chunks, "") + if strings.HasSuffix(s, suffix) { + return len(s) - len(suffix), 1 + } + return -1, 0 + }) + default: + f = newMatcher(field) + } + funcs = append(funcs, f) + } + if len(funcs) == 1 { + return funcs[0] + } + return comboMatcher(funcs).match +} + +func matchExact(exact string) matcherFunc { + return func(chunks []string) (int, float64) { + s := strings.Join(chunks, "") + if idx := strings.LastIndex(s, exact); idx >= 0 { + return idx, 1 + } + return -1, 0 + } +} + +// smartCase returns a matcherFunc that is case-sensitive if q contains any +// upper-case characters, and case-insensitive otherwise. +func smartCase(q string, m matcherFunc) matcherFunc { + insensitive := strings.ToLower(q) == q + wrapper := []string{""} + return func(chunks []string) (int, float64) { + s := strings.Join(chunks, "") + if insensitive { + s = strings.ToLower(s) + } + wrapper[0] = s + return m(wrapper) + } +} + +type comboMatcher []matcherFunc + +func (c comboMatcher) match(chunks []string) (int, float64) { + score := 1.0 + first := 0 + for _, f := range c { + idx, s := f(chunks) + if idx < first { + first = idx + } + score *= s + } + return first, score +} + +// collectSymbols calls snapshot.Symbols to walk the syntax trees of +// all files in the views' current snapshots, and returns a sorted, +// scored list of symbols that best match the parameters. +// +// How it matches symbols is parameterized by two interfaces: +// - A matcherFunc determines how well a string symbol matches a query. It +// returns a non-negative score indicating the quality of the match. A score +// of zero indicates no match. +// - A symbolizer determines how we extract the symbol for an object. This +// enables the 'symbolStyle' configuration option. +func collectSymbols(ctx context.Context, snapshots []*cache.Snapshot, matcherType settings.SymbolMatcher, symbolizer symbolizer, query string) ([]protocol.SymbolInformation, error) { + // Extract symbols from all files. + var work []symbolFile + var roots []string + seen := make(map[protocol.DocumentURI]bool) + // TODO(adonovan): opt: parallelize this loop? How often is len > 1? + for _, snapshot := range snapshots { + // Use the root view URIs for determining (lexically) + // whether a URI is in any open workspace. + folderURI := snapshot.Folder() + roots = append(roots, strings.TrimRight(string(folderURI), "/")) + + filters := snapshot.Options().DirectoryFilters + filterer := cache.NewFilterer(filters) + folder := filepath.ToSlash(folderURI.Path()) + + workspaceOnly := true + if snapshot.Options().SymbolScope == settings.AllSymbolScope { + workspaceOnly = false + } + symbols, err := snapshot.Symbols(ctx, workspaceOnly) + if err != nil { + return nil, err + } + + for uri, syms := range symbols { + norm := filepath.ToSlash(uri.Path()) + nm := strings.TrimPrefix(norm, folder) + if filterer.Disallow(nm) { + continue + } + // Only scan each file once. + if seen[uri] { + continue + } + meta, err := NarrowestMetadataForFile(ctx, snapshot, uri) + if err != nil { + event.Error(ctx, fmt.Sprintf("missing metadata for %q", uri), err) + continue + } + seen[uri] = true + work = append(work, symbolFile{uri, meta, syms}) + } + } + + // Match symbols in parallel. + // Each worker has its own symbolStore, + // which we merge at the end. + nmatchers := runtime.GOMAXPROCS(-1) // matching is CPU bound + results := make(chan *symbolStore) + for i := 0; i < nmatchers; i++ { + go func(i int) { + matcher := buildMatcher(matcherType, query) + store := new(symbolStore) + // Assign files to workers in round-robin fashion. + for j := i; j < len(work); j += nmatchers { + matchFile(store, symbolizer, matcher, roots, work[j]) + } + results <- store + }(i) + } + + // Gather and merge results as they arrive. + var unified symbolStore + for i := 0; i < nmatchers; i++ { + store := <-results + for _, syms := range store.res { + unified.store(syms) + } + } + return unified.results(), nil +} + +// symbolFile holds symbol information for a single file. +type symbolFile struct { + uri protocol.DocumentURI + mp *metadata.Package + syms []cache.Symbol +} + +// matchFile scans a symbol file and adds matching symbols to the store. +func matchFile(store *symbolStore, symbolizer symbolizer, matcher matcherFunc, roots []string, i symbolFile) { + space := make([]string, 0, 3) + for _, sym := range i.syms { + symbolParts, score := symbolizer(space, sym.Name, i.mp, matcher) + + // Check if the score is too low before applying any downranking. + if store.tooLow(score) { + continue + } + + // Factors to apply to the match score for the purpose of downranking + // results. + // + // These numbers were crudely calibrated based on trial-and-error using a + // small number of sample queries. Adjust as necessary. + // + // All factors are multiplicative, meaning if more than one applies they are + // multiplied together. + const ( + // nonWorkspaceFactor is applied to symbols outside the workspace. + // Developers are less likely to want to jump to code that they + // are not actively working on. + nonWorkspaceFactor = 0.5 + // nonWorkspaceUnexportedFactor is applied to unexported symbols outside + // the workspace. Since one wouldn't usually jump to unexported + // symbols to understand a package API, they are particularly irrelevant. + nonWorkspaceUnexportedFactor = 0.5 + // every field or method nesting level to access the field decreases + // the score by a factor of 1.0 - depth*depthFactor, up to a depth of + // 3. + // + // Use a small constant here, as this exists mostly to break ties + // (e.g. given a type Foo and a field x.Foo, prefer Foo). + depthFactor = 0.01 + ) + + startWord := true + exported := true + depth := 0.0 + for _, r := range sym.Name { + if startWord && !unicode.IsUpper(r) { + exported = false + } + if r == '.' { + startWord = true + depth++ + } else { + startWord = false + } + } + + // TODO(rfindley): use metadata to determine if the file is in a workspace + // package, rather than this heuristic. + inWorkspace := false + for _, root := range roots { + if strings.HasPrefix(string(i.uri), root) { + inWorkspace = true + break + } + } + + // Apply downranking based on workspace position. + if !inWorkspace { + score *= nonWorkspaceFactor + if !exported { + score *= nonWorkspaceUnexportedFactor + } + } + + // Apply downranking based on symbol depth. + if depth > 3 { + depth = 3 + } + score *= 1.0 - depth*depthFactor + + if store.tooLow(score) { + continue + } + + si := symbolInformation{ + score: score, + symbol: strings.Join(symbolParts, ""), + kind: sym.Kind, + uri: i.uri, + rng: sym.Range, + container: string(i.mp.PkgPath), + } + store.store(si) + } +} + +type symbolStore struct { + res [maxSymbols]symbolInformation +} + +// store inserts si into the sorted results, if si has a high enough score. +func (sc *symbolStore) store(si symbolInformation) { + if sc.tooLow(si.score) { + return + } + insertAt := sort.Search(len(sc.res), func(i int) bool { + // Sort by score, then symbol length, and finally lexically. + if sc.res[i].score != si.score { + return sc.res[i].score < si.score + } + if len(sc.res[i].symbol) != len(si.symbol) { + return len(sc.res[i].symbol) > len(si.symbol) + } + return sc.res[i].symbol > si.symbol + }) + if insertAt < len(sc.res)-1 { + copy(sc.res[insertAt+1:], sc.res[insertAt:len(sc.res)-1]) + } + sc.res[insertAt] = si +} + +func (sc *symbolStore) tooLow(score float64) bool { + return score <= sc.res[len(sc.res)-1].score +} + +func (sc *symbolStore) results() []protocol.SymbolInformation { + var res []protocol.SymbolInformation + for _, si := range sc.res { + if si.score <= 0 { + return res + } + res = append(res, si.asProtocolSymbolInformation()) + } + return res +} + +// symbolInformation is a cut-down version of protocol.SymbolInformation that +// allows struct values of this type to be used as map keys. +type symbolInformation struct { + score float64 + symbol string + container string + kind protocol.SymbolKind + uri protocol.DocumentURI + rng protocol.Range +} + +// asProtocolSymbolInformation converts s to a protocol.SymbolInformation value. +// +// TODO: work out how to handle tags if/when they are needed. +func (s symbolInformation) asProtocolSymbolInformation() protocol.SymbolInformation { + return protocol.SymbolInformation{ + Name: s.symbol, + Kind: s.kind, + Location: protocol.Location{ + URI: s.uri, + Range: s.rng, + }, + ContainerName: s.container, + } +} diff --git a/contribs/gnopls/internal/golang/workspace_symbol_test.go b/contribs/gnopls/internal/golang/workspace_symbol_test.go new file mode 100644 index 00000000000..4982b767754 --- /dev/null +++ b/contribs/gnopls/internal/golang/workspace_symbol_test.go @@ -0,0 +1,138 @@ +// 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 golang + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/cache" +) + +func TestParseQuery(t *testing.T) { + tests := []struct { + query, s string + wantMatch bool + }{ + {"", "anything", false}, + {"any", "anything", true}, + {"any$", "anything", false}, + {"ing$", "anything", true}, + {"ing$", "anythinG", true}, + {"inG$", "anything", false}, + {"^any", "anything", true}, + {"^any", "Anything", true}, + {"^Any", "anything", false}, + {"at", "anything", true}, + // TODO: this appears to be a bug in the fuzzy matching algorithm. 'At' + // should cause a case-sensitive match. + // {"At", "anything", false}, + {"At", "Anything", true}, + {"'yth", "Anything", true}, + {"'yti", "Anything", false}, + {"'any 'thing", "Anything", true}, + {"anythn nythg", "Anything", true}, + {"ntx", "Anything", false}, + {"anythn", "anything", true}, + {"ing", "anything", true}, + {"anythn nythgx", "anything", false}, + } + + for _, test := range tests { + matcher := parseQuery(test.query, newFuzzyMatcher) + if _, score := matcher([]string{test.s}); score > 0 != test.wantMatch { + t.Errorf("parseQuery(%q) match for %q: %.2g, want match: %t", test.query, test.s, score, test.wantMatch) + } + } +} + +func TestFiltererDisallow(t *testing.T) { + tests := []struct { + filters []string + included []string + excluded []string + }{ + { + []string{"+**/c.go"}, + []string{"a/c.go", "a/b/c.go"}, + []string{}, + }, + { + []string{"+a/**/c.go"}, + []string{"a/b/c.go", "a/b/d/c.go", "a/c.go"}, + []string{}, + }, + { + []string{"-a/c.go", "+a/**"}, + []string{"a/c.go"}, + []string{}, + }, + { + []string{"+a/**/c.go", "-**/c.go"}, + []string{}, + []string{"a/b/c.go"}, + }, + { + []string{"+a/**/c.go", "-a/**"}, + []string{}, + []string{"a/b/c.go"}, + }, + { + []string{"+**/c.go", "-a/**/c.go"}, + []string{}, + []string{"a/b/c.go"}, + }, + { + []string{"+foobar", "-foo"}, + []string{"foobar", "foobar/a"}, + []string{"foo", "foo/a"}, + }, + { + []string{"+", "-"}, + []string{}, + []string{"foobar", "foobar/a", "foo", "foo/a"}, + }, + { + []string{"-", "+"}, + []string{"foobar", "foobar/a", "foo", "foo/a"}, + []string{}, + }, + { + []string{"-a/**/b/**/c.go"}, + []string{}, + []string{"a/x/y/z/b/f/g/h/c.go"}, + }, + // tests for unsupported glob operators + { + []string{"+**/c.go", "-a/*/c.go"}, + []string{"a/b/c.go"}, + []string{}, + }, + { + []string{"+**/c.go", "-a/?/c.go"}, + []string{"a/b/c.go"}, + []string{}, + }, + { + []string{"-b"}, // should only filter paths prefixed with the "b" directory + []string{"a/b/c.go", "bb"}, + []string{"b/c/d.go", "b"}, + }, + } + + for _, test := range tests { + filterer := cache.NewFilterer(test.filters) + for _, inc := range test.included { + if filterer.Disallow(inc) { + t.Errorf("Filters %v excluded %v, wanted included", test.filters, inc) + } + } + + for _, exc := range test.excluded { + if !filterer.Disallow(exc) { + t.Errorf("Filters %v included %v, wanted excluded", test.filters, exc) + } + } + } +} diff --git a/contribs/gnopls/internal/label/keys.go b/contribs/gnopls/internal/label/keys.go new file mode 100644 index 00000000000..1ef3b1786e5 --- /dev/null +++ b/contribs/gnopls/internal/label/keys.go @@ -0,0 +1,37 @@ +// 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. + +// Package label provides common labels used to annotate gopls log messages +// and events. +package label + +import "golang.org/x/tools/internal/event/keys" + +var ( + File = keys.NewString("file", "") + Directory = keys.New("directory", "") + URI = keys.New("URI", "") + Package = keys.NewString("package", "") // sorted comma-separated list of Package IDs + Query = keys.New("query", "") + ViewID = keys.NewString("view_id", "") + Snapshot = keys.NewUInt64("snapshot", "") + Operation = keys.NewString("operation", "") + Duration = keys.New("duration", "Elapsed time") + + Position = keys.New("position", "") + PackageCount = keys.NewInt("packages", "") + Files = keys.New("files", "") + Port = keys.NewInt("port", "") + + NewServer = keys.NewString("new_server", "A new server was added") + EndServer = keys.NewString("end_server", "A server was shut down") + + ServerID = keys.NewString("server", "The server ID an event is related to") + Logfile = keys.NewString("logfile", "") + DebugAddress = keys.NewString("debug_address", "") + GoplsPath = keys.NewString("gopls_path", "") + ClientID = keys.NewString("client_id", "") + + Level = keys.NewInt("level", "The logging level") +) diff --git a/contribs/gnopls/internal/licenses/gen-licenses.sh b/contribs/gnopls/internal/licenses/gen-licenses.sh new file mode 100755 index 00000000000..a39f87ce845 --- /dev/null +++ b/contribs/gnopls/internal/licenses/gen-licenses.sh @@ -0,0 +1,38 @@ +#!/bin/bash -eu + +# 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. + +set -o pipefail + +output=$1 +tempfile=$(mktemp) +cd $(dirname $0) + +cat > $tempfile <> $tempfile + echo >> $tempfile + sed 's/^-- / &/' $dir/$license >> $tempfile + echo >> $tempfile +done + +echo "\`" >> $tempfile +mv $tempfile $output diff --git a/contribs/gnopls/internal/licenses/licenses.go b/contribs/gnopls/internal/licenses/licenses.go new file mode 100644 index 00000000000..e8c5ba9c691 --- /dev/null +++ b/contribs/gnopls/internal/licenses/licenses.go @@ -0,0 +1,146 @@ +// 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. + +//go:generate ./gen-licenses.sh licenses.go +package licenses + +const Text = ` +-- github.com/BurntSushi/toml COPYING -- + +The MIT License (MIT) + +Copyright (c) 2013 TOML authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +-- github.com/google/go-cmp LICENSE -- + +Copyright (c) 2017 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. + +-- honnef.co/go/tools LICENSE -- + +Copyright (c) 2016 Dominik Honnef + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-- mvdan.cc/gofumpt LICENSE -- + +Copyright (c) 2019, Daniel Martí. 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 the copyright holder 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. + +-- mvdan.cc/xurls/v2 LICENSE -- + +Copyright (c) 2015, Daniel Martí. 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 the copyright holder 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. + +` diff --git a/contribs/gnopls/internal/licenses/licenses_test.go b/contribs/gnopls/internal/licenses/licenses_test.go new file mode 100644 index 00000000000..00b6b6c94f7 --- /dev/null +++ b/contribs/gnopls/internal/licenses/licenses_test.go @@ -0,0 +1,47 @@ +// 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 licenses_test + +import ( + "bytes" + "os" + "os/exec" + "runtime" + "testing" + + "golang.org/x/tools/internal/testenv" +) + +func TestLicenses(t *testing.T) { + // License text differs for older Go versions because staticcheck or gofumpt + // isn't supported for those versions, and this fails for unknown, unrelated + // reasons on Kokoro legacy CI. + testenv.NeedsGo1Point(t, 21) + + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + t.Skip("generating licenses only works on Unixes") + } + tmp, err := os.CreateTemp("", "") + if err != nil { + t.Fatal(err) + } + tmp.Close() + + if out, err := exec.Command("./gen-licenses.sh", tmp.Name()).CombinedOutput(); err != nil { + t.Fatalf("generating licenses failed: %q, %v", out, err) + } + + got, err := os.ReadFile(tmp.Name()) + if err != nil { + t.Fatal(err) + } + want, err := os.ReadFile("licenses.go") + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, want) { + t.Error("combined license text needs updating. Run: `go generate ./internal/licenses` from the gopls module.") + } +} diff --git a/contribs/gnopls/internal/lsprpc/autostart_default.go b/contribs/gnopls/internal/lsprpc/autostart_default.go new file mode 100644 index 00000000000..a170b56203c --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/autostart_default.go @@ -0,0 +1,38 @@ +// 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 lsprpc + +import ( + "fmt" + "os/exec" +) + +var ( + daemonize = func(*exec.Cmd) {} + autoNetworkAddress = autoNetworkAddressDefault + verifyRemoteOwnership = verifyRemoteOwnershipDefault +) + +func runRemote(cmd *exec.Cmd) error { + daemonize(cmd) + if err := cmd.Start(); err != nil { + return fmt.Errorf("starting remote gopls: %w", err) + } + return nil +} + +// autoNetworkAddressDefault returns the default network and address for the +// automatically-started gopls remote. See autostart_posix.go for more +// information. +func autoNetworkAddressDefault(goplsPath, id string) (network string, address string) { + if id != "" { + panic("identified remotes are not supported on windows") + } + return "tcp", "localhost:37374" +} + +func verifyRemoteOwnershipDefault(network, address string) (bool, error) { + return true, nil +} diff --git a/contribs/gnopls/internal/lsprpc/autostart_posix.go b/contribs/gnopls/internal/lsprpc/autostart_posix.go new file mode 100644 index 00000000000..6aeac3ec70d --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/autostart_posix.go @@ -0,0 +1,96 @@ +// 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. + +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build darwin dragonfly freebsd linux netbsd openbsd solaris + +package lsprpc + +import ( + "crypto/sha256" + "errors" + "fmt" + "log" + "os" + "os/exec" + "os/user" + "path/filepath" + "strconv" + "syscall" +) + +func init() { + daemonize = daemonizePosix + autoNetworkAddress = autoNetworkAddressPosix + verifyRemoteOwnership = verifyRemoteOwnershipPosix +} + +func daemonizePosix(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setsid: true, + } +} + +// autoNetworkAddressPosix resolves an id on the 'auto' pseduo-network to a +// real network and address. On unix, this uses unix domain sockets. +func autoNetworkAddressPosix(goplsPath, id string) (network string, address string) { + // Especially when doing local development or testing, it's important that + // the remote gopls instance we connect to is running the same binary as our + // forwarder. So we encode a short hash of the binary path into the daemon + // socket name. If possible, we also include the buildid in this hash, to + // account for long-running processes where the binary has been subsequently + // rebuilt. + h := sha256.New() + cmd := exec.Command("go", "tool", "buildid", goplsPath) + cmd.Stdout = h + var pathHash []byte + if err := cmd.Run(); err == nil { + pathHash = h.Sum(nil) + } else { + log.Printf("error getting current buildid: %v", err) + sum := sha256.Sum256([]byte(goplsPath)) + pathHash = sum[:] + } + shortHash := fmt.Sprintf("%x", pathHash)[:6] + user := os.Getenv("USER") + if user == "" { + user = "shared" + } + basename := filepath.Base(goplsPath) + idComponent := "" + if id != "" { + idComponent = "-" + id + } + runtimeDir := os.TempDir() + if xdg := os.Getenv("XDG_RUNTIME_DIR"); xdg != "" { + runtimeDir = xdg + } + return "unix", filepath.Join(runtimeDir, fmt.Sprintf("%s-%s-daemon.%s%s", basename, shortHash, user, idComponent)) +} + +func verifyRemoteOwnershipPosix(network, address string) (bool, error) { + if network != "unix" { + return true, nil + } + fi, err := os.Stat(address) + if err != nil { + if os.IsNotExist(err) { + return true, nil + } + return false, fmt.Errorf("checking socket owner: %w", err) + } + stat, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return false, errors.New("fi.Sys() is not a Stat_t") + } + user, err := user.Current() + if err != nil { + return false, fmt.Errorf("checking current user: %w", err) + } + uid, err := strconv.ParseUint(user.Uid, 10, 32) + if err != nil { + return false, fmt.Errorf("parsing current UID: %w", err) + } + return stat.Uid == uint32(uid), nil +} diff --git a/contribs/gnopls/internal/lsprpc/binder.go b/contribs/gnopls/internal/lsprpc/binder.go new file mode 100644 index 00000000000..708e0ad6afe --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/binder.go @@ -0,0 +1,5 @@ +// 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 lsprpc diff --git a/contribs/gnopls/internal/lsprpc/binder_test.go b/contribs/gnopls/internal/lsprpc/binder_test.go new file mode 100644 index 00000000000..042056e7777 --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/binder_test.go @@ -0,0 +1,199 @@ +// 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 lsprpc_test + +import ( + "context" + "regexp" + "strings" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/protocol" + jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" + + . "golang.org/x/tools/gopls/internal/lsprpc" +) + +// ServerBinder binds incoming connections to a new server. +type ServerBinder struct { + newServer ServerFunc +} + +func NewServerBinder(newServer ServerFunc) *ServerBinder { + return &ServerBinder{newServer: newServer} +} + +// streamServer used to have this method, but it was never used. +// TODO(adonovan): figure out whether we need any of this machinery +// and, if not, delete it. In the meantime, it's better that it sit +// in the test package with all the other mothballed machinery +// than in the production code where it would couple streamServer +// and ServerBinder. +/* +func (s *streamServer) Binder() *ServerBinder { + newServer := func(ctx context.Context, client protocol.ClientCloser) protocol.Server { + session := cache.NewSession(ctx, s.cache) + svr := s.serverForTest + if svr == nil { + options := settings.DefaultOptions(s.optionsOverrides) + svr = server.New(session, client, options) + if instance := debug.GetInstance(ctx); instance != nil { + instance.AddService(svr, session) + } + } + return svr + } + return NewServerBinder(newServer) +} +*/ + +func (b *ServerBinder) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions { + client := protocol.ClientDispatcherV2(conn) + server := b.newServer(ctx, client) + serverHandler := protocol.ServerHandlerV2(server) + // Wrap the server handler to inject the client into each request context, so + // that log events are reflected back to the client. + wrapped := jsonrpc2_v2.HandlerFunc(func(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) { + ctx = protocol.WithClient(ctx, client) + return serverHandler.Handle(ctx, req) + }) + preempter := &Canceler{ + Conn: conn, + } + return jsonrpc2_v2.ConnectionOptions{ + Handler: wrapped, + Preempter: preempter, + } +} + +type TestEnv struct { + Conns []*jsonrpc2_v2.Connection + Servers []*jsonrpc2_v2.Server +} + +func (e *TestEnv) Shutdown(t *testing.T) { + for _, s := range e.Servers { + s.Shutdown() + } + for _, c := range e.Conns { + if err := c.Close(); err != nil { + t.Error(err) + } + } + for _, s := range e.Servers { + if err := s.Wait(); err != nil { + t.Error(err) + } + } +} + +func (e *TestEnv) serve(ctx context.Context, t *testing.T, server jsonrpc2_v2.Binder) (jsonrpc2_v2.Listener, *jsonrpc2_v2.Server) { + l, err := jsonrpc2_v2.NetPipeListener(ctx) + if err != nil { + t.Fatal(err) + } + s := jsonrpc2_v2.NewServer(ctx, l, server) + e.Servers = append(e.Servers, s) + return l, s +} + +func (e *TestEnv) dial(ctx context.Context, t *testing.T, dialer jsonrpc2_v2.Dialer, client jsonrpc2_v2.Binder, forwarded bool) *jsonrpc2_v2.Connection { + if forwarded { + l, _ := e.serve(ctx, t, NewForwardBinder(dialer)) + dialer = l.Dialer() + } + conn, err := jsonrpc2_v2.Dial(ctx, dialer, client) + if err != nil { + t.Fatal(err) + } + e.Conns = append(e.Conns, conn) + return conn +} + +func staticClientBinder(client protocol.Client) jsonrpc2_v2.Binder { + f := func(context.Context, protocol.Server) protocol.Client { return client } + return NewClientBinder(f) +} + +func staticServerBinder(server protocol.Server) jsonrpc2_v2.Binder { + f := func(ctx context.Context, client protocol.ClientCloser) protocol.Server { + return server + } + return NewServerBinder(f) +} + +func TestClientLoggingV2(t *testing.T) { + ctx := context.Background() + + for name, forwarded := range map[string]bool{ + "forwarded": true, + "standalone": false, + } { + t.Run(name, func(t *testing.T) { + client := FakeClient{Logs: make(chan string, 10)} + env := new(TestEnv) + defer env.Shutdown(t) + l, _ := env.serve(ctx, t, staticServerBinder(PingServer{})) + conn := env.dial(ctx, t, l.Dialer(), staticClientBinder(client), forwarded) + + if err := protocol.ServerDispatcherV2(conn).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{}); err != nil { + t.Errorf("DidOpen: %v", err) + } + select { + case got := <-client.Logs: + want := "ping" + matched, err := regexp.MatchString(want, got) + if err != nil { + t.Fatal(err) + } + if !matched { + t.Errorf("got log %q, want a log containing %q", got, want) + } + case <-time.After(1 * time.Second): + t.Error("timeout waiting for client log") + } + }) + } +} + +func TestRequestCancellationV2(t *testing.T) { + ctx := context.Background() + + for name, forwarded := range map[string]bool{ + "forwarded": true, + "standalone": false, + } { + t.Run(name, func(t *testing.T) { + server := WaitableServer{ + Started: make(chan struct{}), + Completed: make(chan error), + } + env := new(TestEnv) + defer env.Shutdown(t) + l, _ := env.serve(ctx, t, staticServerBinder(server)) + client := FakeClient{Logs: make(chan string, 10)} + conn := env.dial(ctx, t, l.Dialer(), staticClientBinder(client), forwarded) + + sd := protocol.ServerDispatcherV2(conn) + ctx, cancel := context.WithCancel(ctx) + + result := make(chan error) + go func() { + _, err := sd.Hover(ctx, &protocol.HoverParams{}) + result <- err + }() + // Wait for the Hover request to start. + <-server.Started + cancel() + if err := <-result; err == nil { + t.Error("nil error for cancelled Hover(), want non-nil") + } + if err := <-server.Completed; err == nil || !strings.Contains(err.Error(), "cancelled hover") { + t.Errorf("Hover(): unexpected server-side error %v", err) + } + }) + } +} diff --git a/contribs/gnopls/internal/lsprpc/commandinterceptor_test.go b/contribs/gnopls/internal/lsprpc/commandinterceptor_test.go new file mode 100644 index 00000000000..7c83ef993f0 --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/commandinterceptor_test.go @@ -0,0 +1,61 @@ +// 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 lsprpc_test + +import ( + "context" + "encoding/json" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" + + . "golang.org/x/tools/gopls/internal/lsprpc" +) + +func CommandInterceptor(command string, run func(*protocol.ExecuteCommandParams) (interface{}, error)) Middleware { + return BindHandler(func(delegate jsonrpc2_v2.Handler) jsonrpc2_v2.Handler { + return jsonrpc2_v2.HandlerFunc(func(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) { + if req.Method == "workspace/executeCommand" { + var params protocol.ExecuteCommandParams + if err := json.Unmarshal(req.Params, ¶ms); err == nil { + if params.Command == command { + return run(¶ms) + } + } + } + + return delegate.Handle(ctx, req) + }) + }) +} + +func TestCommandInterceptor(t *testing.T) { + const command = "foo" + caught := false + intercept := func(_ *protocol.ExecuteCommandParams) (interface{}, error) { + caught = true + return map[string]interface{}{}, nil + } + + ctx := context.Background() + env := new(TestEnv) + defer env.Shutdown(t) + mw := CommandInterceptor(command, intercept) + l, _ := env.serve(ctx, t, mw(noopBinder)) + conn := env.dial(ctx, t, l.Dialer(), noopBinder, false) + + params := &protocol.ExecuteCommandParams{ + Command: command, + } + var res interface{} + err := conn.Call(ctx, "workspace/executeCommand", params).Await(ctx, &res) + if err != nil { + t.Fatal(err) + } + if !caught { + t.Errorf("workspace/executeCommand was not intercepted") + } +} diff --git a/contribs/gnopls/internal/lsprpc/dialer.go b/contribs/gnopls/internal/lsprpc/dialer.go new file mode 100644 index 00000000000..a5f038df9f1 --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/dialer.go @@ -0,0 +1,114 @@ +// 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 lsprpc + +import ( + "context" + "fmt" + "io" + "net" + "os" + "os/exec" + "time" + + "golang.org/x/tools/internal/event" +) + +// autoNetwork is the pseudo network type used to signal that gopls should use +// automatic discovery to resolve a remote address. +const autoNetwork = "auto" + +// An autoDialer is a jsonrpc2 dialer that understands the 'auto' network. +type autoDialer struct { + network, addr string // the 'real' network and address + isAuto bool // whether the server is on the 'auto' network + + executable string + argFunc func(network, addr string) []string +} + +func newAutoDialer(rawAddr string, argFunc func(network, addr string) []string) (*autoDialer, error) { + d := autoDialer{ + argFunc: argFunc, + } + d.network, d.addr = ParseAddr(rawAddr) + if d.network == autoNetwork { + d.isAuto = true + bin, err := os.Executable() + if err != nil { + return nil, fmt.Errorf("getting executable: %w", err) + } + d.executable = bin + d.network, d.addr = autoNetworkAddress(bin, d.addr) + } + return &d, nil +} + +// Dial implements the jsonrpc2.Dialer interface. +func (d *autoDialer) Dial(ctx context.Context) (io.ReadWriteCloser, error) { + conn, err := d.dialNet(ctx) + return conn, err +} + +// TODO(rFindley): remove this once we no longer need to integrate with v1 of +// the jsonrpc2 package. +func (d *autoDialer) dialNet(ctx context.Context) (net.Conn, error) { + // Attempt to verify that we own the remote. This is imperfect, but if we can + // determine that the remote is owned by a different user, we should fail. + ok, err := verifyRemoteOwnership(d.network, d.addr) + if err != nil { + // If the ownership check itself failed, we fail open but log an error to + // the user. + event.Error(ctx, "unable to check daemon socket owner, failing open", err) + } else if !ok { + // We successfully checked that the socket is not owned by us, we fail + // closed. + return nil, fmt.Errorf("socket %q is owned by a different user", d.addr) + } + const dialTimeout = 1 * time.Second + // Try dialing our remote once, in case it is already running. + netConn, err := net.DialTimeout(d.network, d.addr, dialTimeout) + if err == nil { + return netConn, nil + } + if d.isAuto && d.argFunc != nil { + if d.network == "unix" { + // Sometimes the socketfile isn't properly cleaned up when the server + // shuts down. Since we have already tried and failed to dial this + // address, it should *usually* be safe to remove the socket before + // binding to the address. + // TODO(rfindley): there is probably a race here if multiple server + // instances are simultaneously starting up. + if _, err := os.Stat(d.addr); err == nil { + if err := os.Remove(d.addr); err != nil { + return nil, fmt.Errorf("removing remote socket file: %w", err) + } + } + } + args := d.argFunc(d.network, d.addr) + cmd := exec.Command(d.executable, args...) + if err := runRemote(cmd); err != nil { + return nil, err + } + } + + const retries = 5 + // It can take some time for the newly started server to bind to our address, + // so we retry for a bit. + for retry := 0; retry < retries; retry++ { + startDial := time.Now() + netConn, err = net.DialTimeout(d.network, d.addr, dialTimeout) + if err == nil { + return netConn, nil + } + event.Log(ctx, fmt.Sprintf("failed attempt #%d to connect to remote: %v\n", retry+2, err)) + // In case our failure was a fast-failure, ensure we wait at least + // f.dialTimeout before trying again. + if retry != retries-1 { + time.Sleep(dialTimeout - time.Since(startDial)) + } + } + return nil, fmt.Errorf("dialing remote: %w", err) +} diff --git a/contribs/gnopls/internal/lsprpc/export_test.go b/contribs/gnopls/internal/lsprpc/export_test.go new file mode 100644 index 00000000000..509129870dc --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/export_test.go @@ -0,0 +1,142 @@ +// 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 lsprpc + +// This file defines things (and opens backdoors) needed only by tests. + +import ( + "context" + "encoding/json" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" + jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" + "golang.org/x/tools/internal/xcontext" +) + +const HandshakeMethod = handshakeMethod + +// A ServerFunc is used to construct an LSP server for a given client. +type ServerFunc func(context.Context, protocol.ClientCloser) protocol.Server + +type Canceler struct { + Conn *jsonrpc2_v2.Connection +} + +func (c *Canceler) Preempt(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) { + if req.Method != "$/cancelRequest" { + return nil, jsonrpc2_v2.ErrNotHandled + } + var params protocol.CancelParams + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + return nil, fmt.Errorf("%w: %v", jsonrpc2_v2.ErrParse, err) + } + var id jsonrpc2_v2.ID + switch raw := params.ID.(type) { + case float64: + id = jsonrpc2_v2.Int64ID(int64(raw)) + case string: + id = jsonrpc2_v2.StringID(raw) + default: + return nil, fmt.Errorf("%w: invalid ID type %T", jsonrpc2_v2.ErrParse, params.ID) + } + c.Conn.Cancel(id) + return nil, nil +} + +type ForwardBinder struct { + dialer jsonrpc2_v2.Dialer + onBind func(*jsonrpc2_v2.Connection) +} + +func NewForwardBinder(dialer jsonrpc2_v2.Dialer) *ForwardBinder { + return &ForwardBinder{ + dialer: dialer, + } +} + +func (b *ForwardBinder) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection) (opts jsonrpc2_v2.ConnectionOptions) { + client := protocol.ClientDispatcherV2(conn) + clientBinder := NewClientBinder(func(context.Context, protocol.Server) protocol.Client { return client }) + + serverConn, err := jsonrpc2_v2.Dial(context.Background(), b.dialer, clientBinder) + if err != nil { + return jsonrpc2_v2.ConnectionOptions{ + Handler: jsonrpc2_v2.HandlerFunc(func(context.Context, *jsonrpc2_v2.Request) (interface{}, error) { + return nil, fmt.Errorf("%w: %v", jsonrpc2_v2.ErrInternal, err) + }), + } + } + + if b.onBind != nil { + b.onBind(serverConn) + } + server := protocol.ServerDispatcherV2(serverConn) + preempter := &Canceler{ + Conn: conn, + } + detached := xcontext.Detach(ctx) + go func() { + conn.Wait() + if err := serverConn.Close(); err != nil { + event.Log(detached, fmt.Sprintf("closing remote connection: %v", err)) + } + }() + return jsonrpc2_v2.ConnectionOptions{ + Handler: protocol.ServerHandlerV2(server), + Preempter: preempter, + } +} + +func NewClientBinder(newClient ClientFunc) *clientBinder { + return &clientBinder{newClient} +} + +// A ClientFunc is used to construct an LSP client for a given server. +type ClientFunc func(context.Context, protocol.Server) protocol.Client + +// clientBinder binds an LSP client to an incoming connection. +type clientBinder struct { + newClient ClientFunc +} + +func (b *clientBinder) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions { + server := protocol.ServerDispatcherV2(conn) + client := b.newClient(ctx, server) + return jsonrpc2_v2.ConnectionOptions{ + Handler: protocol.ClientHandlerV2(client), + } +} + +// HandlerMiddleware is a middleware that only modifies the jsonrpc2 handler. +type HandlerMiddleware func(jsonrpc2_v2.Handler) jsonrpc2_v2.Handler + +// BindHandler transforms a HandlerMiddleware into a Middleware. +func BindHandler(hmw HandlerMiddleware) Middleware { + return Middleware(func(binder jsonrpc2_v2.Binder) jsonrpc2_v2.Binder { + return BinderFunc(func(ctx context.Context, conn *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions { + opts := binder.Bind(ctx, conn) + opts.Handler = hmw(opts.Handler) + return opts + }) + }) +} + +// The BinderFunc type adapts a bind function to implement the jsonrpc2.Binder +// interface. +type BinderFunc func(ctx context.Context, conn *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions + +func (f BinderFunc) Bind(ctx context.Context, conn *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions { + return f(ctx, conn) +} + +// Middleware defines a transformation of jsonrpc2 Binders, that may be +// composed to build jsonrpc2 servers. +type Middleware func(jsonrpc2_v2.Binder) jsonrpc2_v2.Binder + +var GetGoEnv = getGoEnv + +type StreamServer = streamServer diff --git a/contribs/gnopls/internal/lsprpc/goenv.go b/contribs/gnopls/internal/lsprpc/goenv.go new file mode 100644 index 00000000000..52ec08ff7eb --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/goenv.go @@ -0,0 +1,34 @@ +// 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 lsprpc + +import ( + "context" + "encoding/json" + "fmt" + + "golang.org/x/tools/internal/gocommand" +) + +func getGoEnv(ctx context.Context, env map[string]interface{}) (map[string]string, error) { + var runEnv []string + for k, v := range env { + runEnv = append(runEnv, fmt.Sprintf("%s=%s", k, v)) + } + runner := gocommand.Runner{} + output, err := runner.Run(ctx, gocommand.Invocation{ + Verb: "env", + Args: []string{"-json"}, + Env: runEnv, + }) + if err != nil { + return nil, err + } + envmap := make(map[string]string) + if err := json.Unmarshal(output.Bytes(), &envmap); err != nil { + return nil, err + } + return envmap, nil +} diff --git a/contribs/gnopls/internal/lsprpc/goenv_test.go b/contribs/gnopls/internal/lsprpc/goenv_test.go new file mode 100644 index 00000000000..6c41540fafb --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/goenv_test.go @@ -0,0 +1,133 @@ +// 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 lsprpc_test + +import ( + "context" + "encoding/json" + "fmt" + "os" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" + jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" + "golang.org/x/tools/internal/testenv" + + . "golang.org/x/tools/gopls/internal/lsprpc" +) + +func GoEnvMiddleware() (Middleware, error) { + return BindHandler(func(delegate jsonrpc2_v2.Handler) jsonrpc2_v2.Handler { + return jsonrpc2_v2.HandlerFunc(func(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) { + if req.Method == "initialize" { + if err := addGoEnvToInitializeRequestV2(ctx, req); err != nil { + event.Error(ctx, "adding go env to initialize", err) + } + } + return delegate.Handle(ctx, req) + }) + }), nil +} + +// This function is almost identical to addGoEnvToInitializeRequest in lsprpc.go. +// Make changes in parallel. +func addGoEnvToInitializeRequestV2(ctx context.Context, req *jsonrpc2_v2.Request) error { + var params protocol.ParamInitialize + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + return err + } + var opts map[string]interface{} + switch v := params.InitializationOptions.(type) { + case nil: + opts = make(map[string]interface{}) + case map[string]interface{}: + opts = v + default: + return fmt.Errorf("unexpected type for InitializationOptions: %T", v) + } + envOpt, ok := opts["env"] + if !ok { + envOpt = make(map[string]interface{}) + } + env, ok := envOpt.(map[string]interface{}) + if !ok { + return fmt.Errorf("env option is %T, expected a map", envOpt) + } + goenv, err := GetGoEnv(ctx, env) + if err != nil { + return err + } + // We don't want to propagate GOWORK unless explicitly set since that could mess with + // path inference during cmd/go invocations, see golang/go#51825. + _, goworkSet := os.LookupEnv("GOWORK") + for govar, value := range goenv { + if govar == "GOWORK" && !goworkSet { + continue + } + env[govar] = value + } + opts["env"] = env + params.InitializationOptions = opts + raw, err := json.Marshal(params) + if err != nil { + return fmt.Errorf("marshaling updated options: %v", err) + } + req.Params = json.RawMessage(raw) + return nil +} + +type initServer struct { + protocol.Server + + params *protocol.ParamInitialize +} + +func (s *initServer) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { + s.params = params + return &protocol.InitializeResult{}, nil +} + +func TestGoEnvMiddleware(t *testing.T) { + testenv.NeedsTool(t, "go") + + ctx := context.Background() + + server := &initServer{} + env := new(TestEnv) + defer env.Shutdown(t) + l, _ := env.serve(ctx, t, staticServerBinder(server)) + mw, err := GoEnvMiddleware() + if err != nil { + t.Fatal(err) + } + binder := mw(NewForwardBinder(l.Dialer())) + l, _ = env.serve(ctx, t, binder) + conn := env.dial(ctx, t, l.Dialer(), noopBinder, true) + dispatch := protocol.ServerDispatcherV2(conn) + initParams := &protocol.ParamInitialize{} + initParams.InitializationOptions = map[string]interface{}{ + "env": map[string]interface{}{ + "GONOPROXY": "example.com", + }, + } + if _, err := dispatch.Initialize(ctx, initParams); err != nil { + t.Fatal(err) + } + + if server.params == nil { + t.Fatalf("initialize params are unset") + } + envOpts := server.params.InitializationOptions.(map[string]interface{})["env"].(map[string]interface{}) + + // Check for an arbitrary Go variable. It should be set. + if _, ok := envOpts["GOPRIVATE"]; !ok { + t.Errorf("Go environment variable GOPRIVATE unset in initialization options") + } + // Check that the variable present in our user config was not overwritten. + if got, want := envOpts["GONOPROXY"], "example.com"; got != want { + t.Errorf("GONOPROXY=%q, want %q", got, want) + } +} diff --git a/contribs/gnopls/internal/lsprpc/lsprpc.go b/contribs/gnopls/internal/lsprpc/lsprpc.go new file mode 100644 index 00000000000..b77557c9a4b --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/lsprpc.go @@ -0,0 +1,533 @@ +// 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 lsprpc implements a jsonrpc2.StreamServer that may be used to +// serve the LSP on a jsonrpc2 channel. +package lsprpc + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/server" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/jsonrpc2" +) + +// Unique identifiers for client/server. +var serverIndex int64 + +// The streamServer type is a jsonrpc2.streamServer that handles incoming +// streams as a new LSP session, using a shared cache. +type streamServer struct { + cache *cache.Cache + // daemon controls whether or not to log new connections. + daemon bool + + // optionsOverrides is passed to newly created sessions. + optionsOverrides func(*settings.Options) + + // serverForTest may be set to a test fake for testing. + serverForTest protocol.Server +} + +// NewStreamServer creates a StreamServer using the shared cache. If +// withTelemetry is true, each session is instrumented with telemetry that +// records RPC statistics. +func NewStreamServer(cache *cache.Cache, daemon bool, optionsFunc func(*settings.Options)) jsonrpc2.StreamServer { + return &streamServer{cache: cache, daemon: daemon, optionsOverrides: optionsFunc} +} + +// ServeStream implements the jsonrpc2.StreamServer interface, by handling +// incoming streams using a new lsp server. +func (s *streamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error { + client := protocol.ClientDispatcher(conn) + session := cache.NewSession(ctx, s.cache) + svr := s.serverForTest + if svr == nil { + options := settings.DefaultOptions(s.optionsOverrides) + svr = server.New(session, client, options) + if instance := debug.GetInstance(ctx); instance != nil { + instance.AddService(svr, session) + } + } + // Clients may or may not send a shutdown message. Make sure the server is + // shut down. + // TODO(rFindley): this shutdown should perhaps be on a disconnected context. + defer func() { + if err := svr.Shutdown(ctx); err != nil { + event.Error(ctx, "error shutting down", err) + } + }() + executable, err := os.Executable() + if err != nil { + log.Printf("error getting gopls path: %v", err) + executable = "" + } + ctx = protocol.WithClient(ctx, client) + conn.Go(ctx, + protocol.Handlers( + handshaker(session, executable, s.daemon, + protocol.ServerHandler(svr, + jsonrpc2.MethodNotFound)))) + if s.daemon { + log.Printf("Session %s: connected", session.ID()) + defer log.Printf("Session %s: exited", session.ID()) + } + <-conn.Done() + return conn.Err() +} + +// A forwarder is a jsonrpc2.StreamServer that handles an LSP stream by +// forwarding it to a remote. This is used when the gopls process started by +// the editor is in the `-remote` mode, which means it finds and connects to a +// separate gopls daemon. In these cases, we still want the forwarder gopls to +// be instrumented with telemetry, and want to be able to in some cases hijack +// the jsonrpc2 connection with the daemon. +type forwarder struct { + dialer *autoDialer + + mu sync.Mutex + // Hold on to the server connection so that we can redo the handshake if any + // information changes. + serverConn jsonrpc2.Conn + serverID string +} + +// NewForwarder creates a new forwarder (a [jsonrpc2.StreamServer]), +// ready to forward connections to the +// remote server specified by rawAddr. If provided and rawAddr indicates an +// 'automatic' address (starting with 'auto;'), argFunc may be used to start a +// remote server for the auto-discovered address. +func NewForwarder(rawAddr string, argFunc func(network, address string) []string) (jsonrpc2.StreamServer, error) { + dialer, err := newAutoDialer(rawAddr, argFunc) + if err != nil { + return nil, err + } + fwd := &forwarder{ + dialer: dialer, + } + return fwd, nil +} + +// QueryServerState returns a JSON-encodable struct describing the state of the named server. +func QueryServerState(ctx context.Context, addr string) (any, error) { + serverConn, err := dialRemote(ctx, addr) + if err != nil { + return nil, err + } + var state serverState + if err := protocol.Call(ctx, serverConn, sessionsMethod, nil, &state); err != nil { + return nil, fmt.Errorf("querying server state: %w", err) + } + return &state, nil +} + +// dialRemote is used for making calls into the gopls daemon. addr should be a +// URL, possibly on the synthetic 'auto' network (e.g. tcp://..., unix://..., +// or auto://...). +func dialRemote(ctx context.Context, addr string) (jsonrpc2.Conn, error) { + network, address := ParseAddr(addr) + if network == autoNetwork { + gp, err := os.Executable() + if err != nil { + return nil, fmt.Errorf("getting gopls path: %w", err) + } + network, address = autoNetworkAddress(gp, address) + } + netConn, err := net.DialTimeout(network, address, 5*time.Second) + if err != nil { + return nil, fmt.Errorf("dialing remote: %w", err) + } + serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn)) + serverConn.Go(ctx, jsonrpc2.MethodNotFound) + return serverConn, nil +} + +// ExecuteCommand connects to the named server, sends it a +// workspace/executeCommand request (with command 'id' and arguments +// JSON encoded in 'request'), and populates the result variable. +func ExecuteCommand(ctx context.Context, addr string, id string, request, result any) error { + serverConn, err := dialRemote(ctx, addr) + if err != nil { + return err + } + args, err := command.MarshalArgs(request) + if err != nil { + return err + } + params := protocol.ExecuteCommandParams{ + Command: id, + Arguments: args, + } + return protocol.Call(ctx, serverConn, "workspace/executeCommand", params, result) +} + +// ServeStream dials the forwarder remote and binds the remote to serve the LSP +// on the incoming stream. +func (f *forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) error { + client := protocol.ClientDispatcher(clientConn) + + netConn, err := f.dialer.dialNet(ctx) + if err != nil { + return fmt.Errorf("forwarder: connecting to remote: %w", err) + } + serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn)) + server := protocol.ServerDispatcher(serverConn) + + // Forward between connections. + serverConn.Go(ctx, + protocol.Handlers( + protocol.ClientHandler(client, + jsonrpc2.MethodNotFound))) + + // Don't run the clientConn yet, so that we can complete the handshake before + // processing any client messages. + + // Do a handshake with the server instance to exchange debug information. + index := atomic.AddInt64(&serverIndex, 1) + f.mu.Lock() + f.serverConn = serverConn + f.serverID = strconv.FormatInt(index, 10) + f.mu.Unlock() + f.handshake(ctx) + clientConn.Go(ctx, + protocol.Handlers( + f.handler( + protocol.ServerHandler(server, + jsonrpc2.MethodNotFound)))) + + select { + case <-serverConn.Done(): + clientConn.Close() + case <-clientConn.Done(): + serverConn.Close() + } + + err = nil + if serverConn.Err() != nil { + err = fmt.Errorf("remote disconnected: %v", serverConn.Err()) + } else if clientConn.Err() != nil { + err = fmt.Errorf("client disconnected: %v", clientConn.Err()) + } + event.Log(ctx, fmt.Sprintf("forwarder: exited with error: %v", err)) + return err +} + +// TODO(rfindley): remove this handshaking in favor of middleware. +func (f *forwarder) handshake(ctx context.Context) { + // This call to os.Executable is redundant, and will be eliminated by the + // transition to the V2 API. + goplsPath, err := os.Executable() + if err != nil { + event.Error(ctx, "getting executable for handshake", err) + goplsPath = "" + } + var ( + hreq = handshakeRequest{ + ServerID: f.serverID, + GoplsPath: goplsPath, + } + hresp handshakeResponse + ) + if di := debug.GetInstance(ctx); di != nil { + hreq.Logfile = di.Logfile + hreq.DebugAddr = di.ListenedDebugAddress() + } + if err := protocol.Call(ctx, f.serverConn, handshakeMethod, hreq, &hresp); err != nil { + // TODO(rfindley): at some point in the future we should return an error + // here. Handshakes have become functional in nature. + event.Error(ctx, "forwarder: gopls handshake failed", err) + } + if hresp.GoplsPath != goplsPath { + event.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", goplsPath, hresp.GoplsPath)) + } + event.Log(ctx, "New server", + label.NewServer.Of(f.serverID), + label.Logfile.Of(hresp.Logfile), + label.DebugAddress.Of(hresp.DebugAddr), + label.GoplsPath.Of(hresp.GoplsPath), + label.ClientID.Of(hresp.SessionID), + ) +} + +func ConnectToRemote(ctx context.Context, addr string) (net.Conn, error) { + dialer, err := newAutoDialer(addr, nil) + if err != nil { + return nil, err + } + return dialer.dialNet(ctx) +} + +// handler intercepts messages to the daemon to enrich them with local +// information. +func (f *forwarder) handler(handler jsonrpc2.Handler) jsonrpc2.Handler { + return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error { + // Intercept certain messages to add special handling. + switch r.Method() { + case "initialize": + if newr, err := addGoEnvToInitializeRequest(ctx, r); err == nil { + r = newr + } else { + log.Printf("unable to add local env to initialize request: %v", err) + } + case "workspace/executeCommand": + var params protocol.ExecuteCommandParams + if err := json.Unmarshal(r.Params(), ¶ms); err == nil { + if params.Command == command.StartDebugging.String() { + var args command.DebuggingArgs + if err := command.UnmarshalArgs(params.Arguments, &args); err == nil { + reply = f.replyWithDebugAddress(ctx, reply, args) + } else { + event.Error(ctx, "unmarshaling debugging args", err) + } + } + } else { + event.Error(ctx, "intercepting executeCommand request", err) + } + } + // The gopls workspace environment defaults to the process environment in + // which gopls daemon was started. To avoid discrepancies in Go environment + // between the editor and daemon, inject any unset variables in `go env` + // into the options sent by initialize. + // + // See also golang.org/issue/37830. + return handler(ctx, reply, r) + } +} + +// addGoEnvToInitializeRequest builds a new initialize request in which we set +// any environment variables output by `go env` and not already present in the +// request. +// +// It returns an error if r is not an initialize request, or is otherwise +// malformed. +func addGoEnvToInitializeRequest(ctx context.Context, r jsonrpc2.Request) (jsonrpc2.Request, error) { + var params protocol.ParamInitialize + if err := json.Unmarshal(r.Params(), ¶ms); err != nil { + return nil, err + } + var opts map[string]interface{} + switch v := params.InitializationOptions.(type) { + case nil: + opts = make(map[string]interface{}) + case map[string]interface{}: + opts = v + default: + return nil, fmt.Errorf("unexpected type for InitializationOptions: %T", v) + } + envOpt, ok := opts["env"] + if !ok { + envOpt = make(map[string]interface{}) + } + env, ok := envOpt.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf(`env option is %T, expected a map`, envOpt) + } + goenv, err := getGoEnv(ctx, env) + if err != nil { + return nil, err + } + // We don't want to propagate GOWORK unless explicitly set since that could mess with + // path inference during cmd/go invocations, see golang/go#51825. + _, goworkSet := os.LookupEnv("GOWORK") + for govar, value := range goenv { + if govar == "GOWORK" && !goworkSet { + continue + } + env[govar] = value + } + opts["env"] = env + params.InitializationOptions = opts + call, ok := r.(*jsonrpc2.Call) + if !ok { + return nil, fmt.Errorf("%T is not a *jsonrpc2.Call", r) + } + return jsonrpc2.NewCall(call.ID(), "initialize", params) +} + +func (f *forwarder) replyWithDebugAddress(outerCtx context.Context, r jsonrpc2.Replier, args command.DebuggingArgs) jsonrpc2.Replier { + di := debug.GetInstance(outerCtx) + if di == nil { + event.Log(outerCtx, "no debug instance to start") + return r + } + return func(ctx context.Context, result interface{}, outerErr error) error { + if outerErr != nil { + return r(ctx, result, outerErr) + } + // Enrich the result with our own debugging information. Since we're an + // intermediary, the jsonrpc2 package has deserialized the result into + // maps, by default. Re-do the unmarshalling. + raw, err := json.Marshal(result) + if err != nil { + event.Error(outerCtx, "marshaling intermediate command result", err) + return r(ctx, result, err) + } + var modified command.DebuggingResult + if err := json.Unmarshal(raw, &modified); err != nil { + event.Error(outerCtx, "unmarshaling intermediate command result", err) + return r(ctx, result, err) + } + addr := args.Addr + if addr == "" { + addr = "localhost:0" + } + addr, err = di.Serve(outerCtx, addr) + if err != nil { + event.Error(outerCtx, "starting debug server", err) + return r(ctx, result, outerErr) + } + urls := []string{"http://" + addr} + modified.URLs = append(urls, modified.URLs...) + go f.handshake(ctx) + return r(ctx, modified, nil) + } +} + +// A handshakeRequest identifies a client to the LSP server. +type handshakeRequest struct { + // ServerID is the ID of the server on the client. This should usually be 0. + ServerID string `json:"serverID"` + // Logfile is the location of the clients log file. + Logfile string `json:"logfile"` + // DebugAddr is the client debug address. + DebugAddr string `json:"debugAddr"` + // GoplsPath is the path to the Gopls binary running the current client + // process. + GoplsPath string `json:"goplsPath"` +} + +// A handshakeResponse is returned by the LSP server to tell the LSP client +// information about its session. +type handshakeResponse struct { + // SessionID is the server session associated with the client. + SessionID string `json:"sessionID"` + // Logfile is the location of the server logs. + Logfile string `json:"logfile"` + // DebugAddr is the server debug address. + DebugAddr string `json:"debugAddr"` + // GoplsPath is the path to the Gopls binary running the current server + // process. + GoplsPath string `json:"goplsPath"` +} + +// clientSession identifies a current client LSP session on the server. Note +// that it looks similar to handshakeResposne, but in fact 'Logfile' and +// 'DebugAddr' now refer to the client. +type clientSession struct { + SessionID string `json:"sessionID"` + Logfile string `json:"logfile"` + DebugAddr string `json:"debugAddr"` +} + +// serverState holds information about the gopls daemon process, including its +// debug information and debug information of all of its current connected +// clients. +type serverState struct { + Logfile string `json:"logfile"` + DebugAddr string `json:"debugAddr"` + GoplsPath string `json:"goplsPath"` + CurrentClientID string `json:"currentClientID"` + Clients []clientSession `json:"clients"` +} + +const ( + handshakeMethod = "gopls/handshake" + sessionsMethod = "gopls/sessions" +) + +func handshaker(session *cache.Session, goplsPath string, logHandshakes bool, handler jsonrpc2.Handler) jsonrpc2.Handler { + return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error { + switch r.Method() { + case handshakeMethod: + // We log.Printf in this handler, rather than event.Log when we want logs + // to go to the daemon log rather than being reflected back to the + // client. + var req handshakeRequest + if err := json.Unmarshal(r.Params(), &req); err != nil { + if logHandshakes { + log.Printf("Error processing handshake for session %s: %v", session.ID(), err) + } + sendError(ctx, reply, err) + return nil + } + if logHandshakes { + log.Printf("Session %s: got handshake. Logfile: %q, Debug addr: %q", session.ID(), req.Logfile, req.DebugAddr) + } + event.Log(ctx, "Handshake session update", + cache.KeyUpdateSession.Of(session), + label.DebugAddress.Of(req.DebugAddr), + label.Logfile.Of(req.Logfile), + label.ServerID.Of(req.ServerID), + label.GoplsPath.Of(req.GoplsPath), + ) + resp := handshakeResponse{ + SessionID: session.ID(), + GoplsPath: goplsPath, + } + if di := debug.GetInstance(ctx); di != nil { + resp.Logfile = di.Logfile + resp.DebugAddr = di.ListenedDebugAddress() + } + return reply(ctx, resp, nil) + + case sessionsMethod: + resp := serverState{ + GoplsPath: goplsPath, + CurrentClientID: session.ID(), + } + if di := debug.GetInstance(ctx); di != nil { + resp.Logfile = di.Logfile + resp.DebugAddr = di.ListenedDebugAddress() + for _, c := range di.State.Clients() { + resp.Clients = append(resp.Clients, clientSession{ + SessionID: c.Session.ID(), + Logfile: c.Logfile, + DebugAddr: c.DebugAddress, + }) + } + } + return reply(ctx, resp, nil) + } + return handler(ctx, reply, r) + } +} + +func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) { + err = fmt.Errorf("%v: %w", err, jsonrpc2.ErrParse) + if err := reply(ctx, nil, err); err != nil { + event.Error(ctx, "", err) + } +} + +// ParseAddr parses the address of a gopls remote. +// TODO(rFindley): further document this syntax, and allow URI-style remote +// addresses such as "auto://...". +func ParseAddr(listen string) (network string, address string) { + // Allow passing just -remote=auto, as a shorthand for using automatic remote + // resolution. + if listen == autoNetwork { + return autoNetwork, "" + } + if parts := strings.SplitN(listen, ";", 2); len(parts) == 2 { + return parts[0], parts[1] + } + return "tcp", listen +} diff --git a/contribs/gnopls/internal/lsprpc/lsprpc_test.go b/contribs/gnopls/internal/lsprpc/lsprpc_test.go new file mode 100644 index 00000000000..c4ccab71a3e --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/lsprpc_test.go @@ -0,0 +1,375 @@ +// 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 lsprpc + +import ( + "context" + "encoding/json" + "errors" + "regexp" + "strings" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/jsonrpc2/servertest" + "golang.org/x/tools/internal/testenv" +) + +type FakeClient struct { + protocol.Client + + Logs chan string +} + +func (c FakeClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error { + c.Logs <- params.Message + return nil +} + +// fakeServer is intended to be embedded in the test fakes below, to trivially +// implement Shutdown. +type fakeServer struct { + protocol.Server +} + +func (fakeServer) Shutdown(ctx context.Context) error { + return nil +} + +type PingServer struct{ fakeServer } + +func (s PingServer) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { + event.Log(ctx, "ping") + return nil +} + +func TestClientLogging(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + server := PingServer{} + client := FakeClient{Logs: make(chan string, 10)} + + ctx = debug.WithInstance(ctx, "") + ss := NewStreamServer(cache.New(nil), false, nil).(*StreamServer) + ss.serverForTest = server + ts := servertest.NewPipeServer(ss, nil) + defer checkClose(t, ts.Close) + cc := ts.Connect(ctx) + cc.Go(ctx, protocol.ClientHandler(client, jsonrpc2.MethodNotFound)) + + if err := protocol.ServerDispatcher(cc).DidOpen(ctx, &protocol.DidOpenTextDocumentParams{}); err != nil { + t.Errorf("DidOpen: %v", err) + } + + select { + case got := <-client.Logs: + want := "ping" + matched, err := regexp.MatchString(want, got) + if err != nil { + t.Fatal(err) + } + if !matched { + t.Errorf("got log %q, want a log containing %q", got, want) + } + case <-time.After(1 * time.Second): + t.Error("timeout waiting for client log") + } +} + +// WaitableServer instruments LSP request so that we can control their timing. +// The requests chosen are arbitrary: we simply needed one that blocks, and +// another that doesn't. +type WaitableServer struct { + fakeServer + + Started chan struct{} + Completed chan error +} + +func (s WaitableServer) Hover(ctx context.Context, _ *protocol.HoverParams) (_ *protocol.Hover, err error) { + s.Started <- struct{}{} + defer func() { + s.Completed <- err + }() + select { + case <-ctx.Done(): + return nil, errors.New("cancelled hover") + case <-time.After(10 * time.Second): + } + return &protocol.Hover{}, nil +} + +func (s WaitableServer) ResolveCompletionItem(_ context.Context, item *protocol.CompletionItem) (*protocol.CompletionItem, error) { + return item, nil +} + +func checkClose(t *testing.T, closer func() error) { + t.Helper() + if err := closer(); err != nil { + t.Errorf("closing: %v", err) + } +} + +func setupForwarding(ctx context.Context, t *testing.T, s protocol.Server) (direct, forwarded servertest.Connector, cleanup func()) { + t.Helper() + serveCtx := debug.WithInstance(ctx, "") + ss := NewStreamServer(cache.New(nil), false, nil).(*StreamServer) + ss.serverForTest = s + tsDirect := servertest.NewTCPServer(serveCtx, ss, nil) + + forwarder, err := NewForwarder("tcp;"+tsDirect.Addr, nil) + if err != nil { + t.Fatal(err) + } + tsForwarded := servertest.NewPipeServer(forwarder, nil) + return tsDirect, tsForwarded, func() { + checkClose(t, tsDirect.Close) + checkClose(t, tsForwarded.Close) + } +} + +func TestRequestCancellation(t *testing.T) { + ctx := context.Background() + server := WaitableServer{ + Started: make(chan struct{}), + Completed: make(chan error), + } + tsDirect, tsForwarded, cleanup := setupForwarding(ctx, t, server) + defer cleanup() + tests := []struct { + serverType string + ts servertest.Connector + }{ + {"direct", tsDirect}, + {"forwarder", tsForwarded}, + } + + for _, test := range tests { + t.Run(test.serverType, func(t *testing.T) { + cc := test.ts.Connect(ctx) + sd := protocol.ServerDispatcher(cc) + cc.Go(ctx, + protocol.Handlers( + jsonrpc2.MethodNotFound)) + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + + result := make(chan error) + go func() { + _, err := sd.Hover(ctx, &protocol.HoverParams{}) + result <- err + }() + // Wait for the Hover request to start. + <-server.Started + cancel() + if err := <-result; err == nil { + t.Error("nil error for cancelled Hover(), want non-nil") + } + if err := <-server.Completed; err == nil || !strings.Contains(err.Error(), "cancelled hover") { + t.Errorf("Hover(): unexpected server-side error %v", err) + } + }) + } +} + +const exampleProgram = ` +-- go.mod -- +module mod + +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println("Hello World.") +}` + +func TestDebugInfoLifecycle(t *testing.T) { + sb, err := fake.NewSandbox(&fake.SandboxConfig{Files: fake.UnpackTxt(exampleProgram)}) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := sb.Close(); err != nil { + // TODO(golang/go#38490): we can't currently make this an error because + // it fails on Windows: the workspace directory is still locked by a + // separate Go process. + // Once we have a reliable way to wait for proper shutdown, make this an + // error. + t.Logf("closing workspace failed: %v", err) + } + }() + + baseCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + clientCtx := debug.WithInstance(baseCtx, "") + serverCtx := debug.WithInstance(baseCtx, "") + + cache := cache.New(nil) + ss := NewStreamServer(cache, false, nil) + tsBackend := servertest.NewTCPServer(serverCtx, ss, nil) + + forwarder, err := NewForwarder("tcp;"+tsBackend.Addr, nil) + if err != nil { + t.Fatal(err) + } + tsForwarder := servertest.NewPipeServer(forwarder, nil) + + ed1, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(clientCtx, tsForwarder, fake.ClientHooks{}) + if err != nil { + t.Fatal(err) + } + defer ed1.Close(clientCtx) + ed2, err := fake.NewEditor(sb, fake.EditorConfig{}).Connect(baseCtx, tsBackend, fake.ClientHooks{}) + if err != nil { + t.Fatal(err) + } + defer ed2.Close(baseCtx) + + serverDebug := debug.GetInstance(serverCtx) + if got, want := len(serverDebug.State.Clients()), 2; got != want { + t.Errorf("len(server:Clients) = %d, want %d", got, want) + } + if got, want := len(serverDebug.State.Sessions()), 2; got != want { + t.Errorf("len(server:Sessions) = %d, want %d", got, want) + } + clientDebug := debug.GetInstance(clientCtx) + if got, want := len(clientDebug.State.Servers()), 1; got != want { + t.Errorf("len(client:Servers) = %d, want %d", got, want) + } + // Close one of the connections to verify that the client and session were + // dropped. + if err := ed1.Close(clientCtx); err != nil { + t.Fatal(err) + } + /*TODO: at this point we have verified the editor is closed + However there is no way currently to wait for all associated go routines to + go away, and we need to wait for those to trigger the client drop + for now we just give it a little bit of time, but we need to fix this + in a principled way + */ + start := time.Now() + delay := time.Millisecond + const maxWait = time.Second + for len(serverDebug.State.Clients()) > 1 { + if time.Since(start) > maxWait { + break + } + time.Sleep(delay) + delay *= 2 + } + if got, want := len(serverDebug.State.Clients()), 1; got != want { + t.Errorf("len(server:Clients) = %d, want %d", got, want) + } + if got, want := len(serverDebug.State.Sessions()), 1; got != want { + t.Errorf("len(server:Sessions()) = %d, want %d", got, want) + } +} + +type initServer struct { + fakeServer + + params *protocol.ParamInitialize +} + +func (s *initServer) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { + s.params = params + return &protocol.InitializeResult{}, nil +} + +func TestEnvForwarding(t *testing.T) { + testenv.NeedsTool(t, "go") + + ctx := context.Background() + + server := &initServer{} + _, tsForwarded, cleanup := setupForwarding(ctx, t, server) + defer cleanup() + + conn := tsForwarded.Connect(ctx) + conn.Go(ctx, jsonrpc2.MethodNotFound) + dispatch := protocol.ServerDispatcher(conn) + initParams := &protocol.ParamInitialize{} + initParams.InitializationOptions = map[string]interface{}{ + "env": map[string]interface{}{ + "GONOPROXY": "example.com", + }, + } + _, err := dispatch.Initialize(ctx, initParams) + if err != nil { + t.Fatal(err) + } + if server.params == nil { + t.Fatalf("initialize params are unset") + } + env := server.params.InitializationOptions.(map[string]interface{})["env"].(map[string]interface{}) + + // Check for an arbitrary Go variable. It should be set. + if _, ok := env["GOPRIVATE"]; !ok { + t.Errorf("Go environment variable GOPRIVATE unset in initialization options") + } + // Check that the variable present in our user config was not overwritten. + if v := env["GONOPROXY"]; v != "example.com" { + t.Errorf("GONOPROXY environment variable was overwritten") + } +} + +func TestListenParsing(t *testing.T) { + tests := []struct { + input, wantNetwork, wantAddr string + }{ + {"127.0.0.1:0", "tcp", "127.0.0.1:0"}, + {"unix;/tmp/sock", "unix", "/tmp/sock"}, + {"auto", "auto", ""}, + {"auto;foo", "auto", "foo"}, + } + + for _, test := range tests { + gotNetwork, gotAddr := ParseAddr(test.input) + if gotNetwork != test.wantNetwork { + t.Errorf("network = %q, want %q", gotNetwork, test.wantNetwork) + } + if gotAddr != test.wantAddr { + t.Errorf("addr = %q, want %q", gotAddr, test.wantAddr) + } + } +} + +// For #59479, verify that empty slices are serialized as []. +func TestEmptySlices(t *testing.T) { + // The LSP would prefer that empty slices be sent as [] rather than null. + const bad = `{"a":null}` + const good = `{"a":[]}` + var x struct { + A []string `json:"a"` + } + buf, _ := json.Marshal(x) + if string(buf) != bad { + // uninitialized is ezpected to give null + t.Errorf("unexpectedly got %s, want %s", buf, bad) + } + x.A = make([]string, 0) + buf, _ = json.Marshal(x) + if string(buf) != good { + // expect [] + t.Errorf("unexpectedly got %s, want %s", buf, good) + } + x.A = []string{} + buf, _ = json.Marshal(x) + if string(buf) != good { + // expect [] + t.Errorf("unexpectedly got %s, want %s", buf, good) + } +} diff --git a/contribs/gnopls/internal/lsprpc/middleware_test.go b/contribs/gnopls/internal/lsprpc/middleware_test.go new file mode 100644 index 00000000000..526c7343b78 --- /dev/null +++ b/contribs/gnopls/internal/lsprpc/middleware_test.go @@ -0,0 +1,223 @@ +// 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 lsprpc_test + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sync" + "testing" + "time" + + . "golang.org/x/tools/gopls/internal/lsprpc" + "golang.org/x/tools/internal/event" + jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" +) + +var noopBinder = BinderFunc(func(context.Context, *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions { + return jsonrpc2_v2.ConnectionOptions{} +}) + +func TestHandshakeMiddleware(t *testing.T) { + sh := &Handshaker{ + metadata: metadata{ + "answer": 42, + }, + } + ctx := context.Background() + env := new(TestEnv) + defer env.Shutdown(t) + l, _ := env.serve(ctx, t, sh.Middleware(noopBinder)) + conn := env.dial(ctx, t, l.Dialer(), noopBinder, false) + ch := &Handshaker{ + metadata: metadata{ + "question": 6 * 9, + }, + } + + check := func(connected bool) error { + clients := sh.Peers() + servers := ch.Peers() + want := 0 + if connected { + want = 1 + } + if got := len(clients); got != want { + return fmt.Errorf("got %d clients on the server, want %d", got, want) + } + if got := len(servers); got != want { + return fmt.Errorf("got %d servers on the client, want %d", got, want) + } + if !connected { + return nil + } + client := clients[0] + server := servers[0] + if _, ok := client.Metadata["question"]; !ok { + return errors.New("no client metadata") + } + if _, ok := server.Metadata["answer"]; !ok { + return errors.New("no server metadata") + } + if client.LocalID != server.RemoteID { + return fmt.Errorf("client.LocalID == %d, server.PeerID == %d", client.LocalID, server.RemoteID) + } + if client.RemoteID != server.LocalID { + return fmt.Errorf("client.PeerID == %d, server.LocalID == %d", client.RemoteID, server.LocalID) + } + return nil + } + + if err := check(false); err != nil { + t.Fatalf("before handshake: %v", err) + } + ch.ClientHandshake(ctx, conn) + if err := check(true); err != nil { + t.Fatalf("after handshake: %v", err) + } + conn.Close() + // Wait for up to ~2s for connections to get cleaned up. + delay := 25 * time.Millisecond + for retries := 3; retries >= 0; retries-- { + time.Sleep(delay) + err := check(false) + if err == nil { + return + } + if retries == 0 { + t.Fatalf("after closing connection: %v", err) + } + delay *= 4 + } +} + +// Handshaker handles both server and client handshaking over jsonrpc2 v2. +// To instrument server-side handshaking, use Handshaker.Middleware. +// To instrument client-side handshaking, call +// Handshaker.ClientHandshake for any new client-side connections. +type Handshaker struct { + // metadata will be shared with peers via handshaking. + metadata metadata + + mu sync.Mutex + prevID int64 + peers map[int64]PeerInfo +} + +// metadata holds arbitrary data transferred between jsonrpc2 peers. +type metadata map[string]any + +// PeerInfo holds information about a peering between jsonrpc2 servers. +type PeerInfo struct { + // RemoteID is the identity of the current server on its peer. + RemoteID int64 + + // LocalID is the identity of the peer on the server. + LocalID int64 + + // IsClient reports whether the peer is a client. If false, the peer is a + // server. + IsClient bool + + // Metadata holds arbitrary information provided by the peer. + Metadata metadata +} + +// Peers returns the peer info this handshaker knows about by way of either the +// server-side handshake middleware, or client-side handshakes. +func (h *Handshaker) Peers() []PeerInfo { + h.mu.Lock() + defer h.mu.Unlock() + + var c []PeerInfo + for _, v := range h.peers { + c = append(c, v) + } + return c +} + +// Middleware is a jsonrpc2 middleware function to augment connection binding +// to handle the handshake method, and record disconnections. +func (h *Handshaker) Middleware(inner jsonrpc2_v2.Binder) jsonrpc2_v2.Binder { + return BinderFunc(func(ctx context.Context, conn *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions { + opts := inner.Bind(ctx, conn) + + localID := h.nextID() + info := &PeerInfo{ + RemoteID: localID, + Metadata: h.metadata, + } + + // Wrap the delegated handler to accept the handshake. + delegate := opts.Handler + opts.Handler = jsonrpc2_v2.HandlerFunc(func(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) { + if req.Method == HandshakeMethod { + var peerInfo PeerInfo + if err := json.Unmarshal(req.Params, &peerInfo); err != nil { + return nil, fmt.Errorf("%w: unmarshaling client info: %v", jsonrpc2_v2.ErrInvalidParams, err) + } + peerInfo.LocalID = localID + peerInfo.IsClient = true + h.recordPeer(peerInfo) + return info, nil + } + return delegate.Handle(ctx, req) + }) + + // Record the dropped client. + go h.cleanupAtDisconnect(conn, localID) + + return opts + }) +} + +// ClientHandshake performs a client-side handshake with the server at the +// other end of conn, recording the server's peer info and watching for conn's +// disconnection. +func (h *Handshaker) ClientHandshake(ctx context.Context, conn *jsonrpc2_v2.Connection) { + localID := h.nextID() + info := &PeerInfo{ + RemoteID: localID, + Metadata: h.metadata, + } + + call := conn.Call(ctx, HandshakeMethod, info) + var serverInfo PeerInfo + if err := call.Await(ctx, &serverInfo); err != nil { + event.Error(ctx, "performing handshake", err) + return + } + serverInfo.LocalID = localID + h.recordPeer(serverInfo) + + go h.cleanupAtDisconnect(conn, localID) +} + +func (h *Handshaker) nextID() int64 { + h.mu.Lock() + defer h.mu.Unlock() + + h.prevID++ + return h.prevID +} + +func (h *Handshaker) cleanupAtDisconnect(conn *jsonrpc2_v2.Connection, peerID int64) { + conn.Wait() + + h.mu.Lock() + defer h.mu.Unlock() + delete(h.peers, peerID) +} + +func (h *Handshaker) recordPeer(info PeerInfo) { + h.mu.Lock() + defer h.mu.Unlock() + if h.peers == nil { + h.peers = make(map[int64]PeerInfo) + } + h.peers[info.LocalID] = info +} diff --git a/contribs/gnopls/internal/mod/code_lens.go b/contribs/gnopls/internal/mod/code_lens.go new file mode 100644 index 00000000000..f80063625ff --- /dev/null +++ b/contribs/gnopls/internal/mod/code_lens.go @@ -0,0 +1,172 @@ +// 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 mod + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" +) + +// CodeLensSources returns the sources of code lenses for go.mod files. +func CodeLensSources() map[settings.CodeLensSource]cache.CodeLensSourceFunc { + return map[settings.CodeLensSource]cache.CodeLensSourceFunc{ + settings.CodeLensUpgradeDependency: upgradeLenses, // commands: CheckUpgrades, UpgradeDependency + settings.CodeLensTidy: tidyLens, // commands: Tidy + settings.CodeLensVendor: vendorLens, // commands: Vendor + settings.CodeLensRunGovulncheck: vulncheckLenses, // commands: RunGovulncheck + } +} + +func upgradeLenses(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil || pm.File == nil { + return nil, err + } + uri := fh.URI() + reset := command.NewResetGoModDiagnosticsCommand("Reset go.mod diagnostics", command.ResetGoModDiagnosticsArgs{URIArg: command.URIArg{URI: uri}}) + // Put the `Reset go.mod diagnostics` codelens on the module statement. + modrng, err := moduleStmtRange(fh, pm) + if err != nil { + return nil, err + } + lenses := []protocol.CodeLens{{Range: modrng, Command: reset}} + if len(pm.File.Require) == 0 { + // Nothing to upgrade. + return lenses, nil + } + var requires []string + for _, req := range pm.File.Require { + requires = append(requires, req.Mod.Path) + } + checkUpgrade := command.NewCheckUpgradesCommand("Check for upgrades", command.CheckUpgradesArgs{ + URI: uri, + Modules: requires, + }) + upgradeTransitive := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{ + URI: uri, + AddRequire: false, + GoCmdArgs: []string{"-d", "-u", "-t", "./..."}, + }) + upgradeDirect := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{ + URI: uri, + AddRequire: false, + GoCmdArgs: append([]string{"-d"}, requires...), + }) + + // Put the upgrade code lenses above the first require block or statement. + rng, err := firstRequireRange(fh, pm) + if err != nil { + return nil, err + } + + return append(lenses, []protocol.CodeLens{ + {Range: rng, Command: checkUpgrade}, + {Range: rng, Command: upgradeTransitive}, + {Range: rng, Command: upgradeDirect}, + }...), nil +} + +func tidyLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil || pm.File == nil { + return nil, err + } + uri := fh.URI() + cmd := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: []protocol.DocumentURI{uri}}) + rng, err := moduleStmtRange(fh, pm) + if err != nil { + return nil, err + } + return []protocol.CodeLens{{ + Range: rng, + Command: cmd, + }}, nil +} + +func vendorLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil || pm.File == nil { + return nil, err + } + if len(pm.File.Require) == 0 { + // Nothing to vendor. + return nil, nil + } + rng, err := moduleStmtRange(fh, pm) + if err != nil { + return nil, err + } + title := "Create vendor directory" + uri := fh.URI() + cmd := command.NewVendorCommand(title, command.URIArg{URI: uri}) + // Change the message depending on whether or not the module already has a + // vendor directory. + vendorDir := filepath.Join(filepath.Dir(fh.URI().Path()), "vendor") + if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() { + title = "Sync vendor directory" + } + return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil +} + +func moduleStmtRange(fh file.Handle, pm *cache.ParsedModule) (protocol.Range, error) { + if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil { + return protocol.Range{}, fmt.Errorf("no module statement in %s", fh.URI()) + } + syntax := pm.File.Module.Syntax + return pm.Mapper.OffsetRange(syntax.Start.Byte, syntax.End.Byte) +} + +// firstRequireRange returns the range for the first "require" in the given +// go.mod file. This is either a require block or an individual require line. +func firstRequireRange(fh file.Handle, pm *cache.ParsedModule) (protocol.Range, error) { + if len(pm.File.Require) == 0 { + return protocol.Range{}, fmt.Errorf("no requires in the file %s", fh.URI()) + } + var start, end modfile.Position + for _, stmt := range pm.File.Syntax.Stmt { + if b, ok := stmt.(*modfile.LineBlock); ok && len(b.Token) == 1 && b.Token[0] == "require" { + start, end = b.Span() + break + } + } + + firstRequire := pm.File.Require[0].Syntax + if start.Byte == 0 || firstRequire.Start.Byte < start.Byte { + start, end = firstRequire.Start, firstRequire.End + } + return pm.Mapper.OffsetRange(start.Byte, end.Byte) +} + +func vulncheckLenses(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.CodeLens, error) { + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil || pm.File == nil { + return nil, err + } + // Place the codelenses near the module statement. + // A module may not have the require block, + // but vulnerabilities can exist in standard libraries. + uri := fh.URI() + rng, err := moduleStmtRange(fh, pm) + if err != nil { + return nil, err + } + + vulncheck := command.NewRunGovulncheckCommand("Run govulncheck", command.VulncheckArgs{ + URI: uri, + Pattern: "./...", + }) + return []protocol.CodeLens{ + {Range: rng, Command: vulncheck}, + }, nil +} diff --git a/contribs/gnopls/internal/mod/diagnostics.go b/contribs/gnopls/internal/mod/diagnostics.go new file mode 100644 index 00000000000..8da69313e49 --- /dev/null +++ b/contribs/gnopls/internal/mod/diagnostics.go @@ -0,0 +1,545 @@ +// 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. + +// Package mod provides core features related to go.mod file +// handling for use by Go editors and tools. +package mod + +import ( + "context" + "fmt" + "runtime" + "sort" + "strings" + "sync" + + "golang.org/x/mod/modfile" + "golang.org/x/mod/semver" + "golang.org/x/sync/errgroup" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" + "golang.org/x/tools/internal/event" +) + +// ParseDiagnostics returns diagnostics from parsing the go.mod files in the workspace. +func ParseDiagnostics(ctx context.Context, snapshot *cache.Snapshot) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { + ctx, done := event.Start(ctx, "mod.Diagnostics", snapshot.Labels()...) + defer done() + + return collectDiagnostics(ctx, snapshot, ModParseDiagnostics) +} + +// Diagnostics returns diagnostics from running go mod tidy. +func TidyDiagnostics(ctx context.Context, snapshot *cache.Snapshot) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { + ctx, done := event.Start(ctx, "mod.Diagnostics", snapshot.Labels()...) + defer done() + + return collectDiagnostics(ctx, snapshot, ModTidyDiagnostics) +} + +// UpgradeDiagnostics returns upgrade diagnostics for the modules in the +// workspace with known upgrades. +func UpgradeDiagnostics(ctx context.Context, snapshot *cache.Snapshot) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { + ctx, done := event.Start(ctx, "mod.UpgradeDiagnostics", snapshot.Labels()...) + defer done() + + return collectDiagnostics(ctx, snapshot, ModUpgradeDiagnostics) +} + +// VulnerabilityDiagnostics returns vulnerability diagnostics for the active modules in the +// workspace with known vulnerabilities. +func VulnerabilityDiagnostics(ctx context.Context, snapshot *cache.Snapshot) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { + ctx, done := event.Start(ctx, "mod.VulnerabilityDiagnostics", snapshot.Labels()...) + defer done() + + return collectDiagnostics(ctx, snapshot, ModVulnerabilityDiagnostics) +} + +func collectDiagnostics(ctx context.Context, snapshot *cache.Snapshot, diagFn func(context.Context, *cache.Snapshot, file.Handle) ([]*cache.Diagnostic, error)) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { + g, ctx := errgroup.WithContext(ctx) + cpulimit := runtime.GOMAXPROCS(0) + g.SetLimit(cpulimit) + + var mu sync.Mutex + reports := make(map[protocol.DocumentURI][]*cache.Diagnostic) + + for _, uri := range snapshot.View().ModFiles() { + uri := uri + g.Go(func() error { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return err + } + diagnostics, err := diagFn(ctx, snapshot, fh) + if err != nil { + return err + } + for _, d := range diagnostics { + mu.Lock() + reports[d.URI] = append(reports[fh.URI()], d) + mu.Unlock() + } + return nil + }) + } + + if err := g.Wait(); err != nil { + return nil, err + } + return reports, nil +} + +// ModParseDiagnostics reports diagnostics from parsing the mod file. +func ModParseDiagnostics(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) (diagnostics []*cache.Diagnostic, err error) { + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil { + if pm == nil || len(pm.ParseErrors) == 0 { + return nil, err + } + return pm.ParseErrors, nil + } + return nil, nil +} + +// ModTidyDiagnostics reports diagnostics from running go mod tidy. +func ModTidyDiagnostics(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]*cache.Diagnostic, error) { + pm, err := snapshot.ParseMod(ctx, fh) // memoized + if err != nil { + return nil, nil // errors reported by ModDiagnostics above + } + + tidied, err := snapshot.ModTidy(ctx, pm) + if err != nil { + if err != cache.ErrNoModOnDisk && !strings.Contains(err.Error(), "GOPROXY=off") { + // TODO(rfindley): the check for ErrNoModOnDisk was historically determined + // to be benign, but may date back to the time when the Go command did not + // have overlay support. + // + // See if we can pass the overlay to the Go command, and eliminate this guard.. + + // TODO(golang/go#56395): remove the arbitrary suppression of the mod + // tidy error when GOPROXY=off. The true fix for this noisy log message + // is to fix the mod tidy diagnostics. + event.Error(ctx, fmt.Sprintf("tidy: diagnosing %s", pm.URI), err) + } + return nil, nil + } + return tidied.Diagnostics, nil +} + +// ModUpgradeDiagnostics adds upgrade quick fixes for individual modules if the upgrades +// are recorded in the view. +func ModUpgradeDiagnostics(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) (upgradeDiagnostics []*cache.Diagnostic, err error) { + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil { + // Don't return an error if there are parse error diagnostics to be shown, but also do not + // continue since we won't be able to show the upgrade diagnostics. + if pm != nil && len(pm.ParseErrors) != 0 { + return nil, nil + } + return nil, err + } + + upgrades := snapshot.ModuleUpgrades(fh.URI()) + for _, req := range pm.File.Require { + ver, ok := upgrades[req.Mod.Path] + if !ok || req.Mod.Version == ver { + continue + } + rng, err := pm.Mapper.OffsetRange(req.Syntax.Start.Byte, req.Syntax.End.Byte) + if err != nil { + return nil, err + } + // Upgrade to the exact version we offer the user, not the most recent. + title := fmt.Sprintf("%s%v", upgradeCodeActionPrefix, ver) + cmd := command.NewUpgradeDependencyCommand(title, command.DependencyArgs{ + URI: fh.URI(), + AddRequire: false, + GoCmdArgs: []string{req.Mod.Path + "@" + ver}, + }) + upgradeDiagnostics = append(upgradeDiagnostics, &cache.Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityInformation, + Source: cache.UpgradeNotification, + Message: fmt.Sprintf("%v can be upgraded", req.Mod.Path), + SuggestedFixes: []cache.SuggestedFix{cache.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, + }) + } + + return upgradeDiagnostics, nil +} + +const upgradeCodeActionPrefix = "Upgrade to " + +// ModVulnerabilityDiagnostics adds diagnostics for vulnerabilities in individual modules +// if the vulnerability is recorded in the view. +func ModVulnerabilityDiagnostics(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) (vulnDiagnostics []*cache.Diagnostic, err error) { + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil { + // Don't return an error if there are parse error diagnostics to be shown, but also do not + // continue since we won't be able to show the vulnerability diagnostics. + if pm != nil && len(pm.ParseErrors) != 0 { + return nil, nil + } + return nil, err + } + + diagSource := cache.Govulncheck + vs := snapshot.Vulnerabilities(fh.URI())[fh.URI()] + if vs == nil && snapshot.Options().Vulncheck == settings.ModeVulncheckImports { + vs, err = snapshot.ModVuln(ctx, fh.URI()) + if err != nil { + return nil, err + } + diagSource = cache.Vulncheck + } + if vs == nil || len(vs.Findings) == 0 { + return nil, nil + } + + suggestRunOrResetGovulncheck, err := suggestGovulncheckAction(diagSource == cache.Govulncheck, fh.URI()) + if err != nil { + // must not happen + return nil, err // TODO: bug report + } + vulnsByModule := make(map[string][]*govulncheck.Finding) + + for _, finding := range vs.Findings { + if vuln, typ := foundVuln(finding); typ == vulnCalled || typ == vulnImported { + vulnsByModule[vuln.Module] = append(vulnsByModule[vuln.Module], finding) + } + } + for _, req := range pm.File.Require { + mod := req.Mod.Path + findings := vulnsByModule[mod] + if len(findings) == 0 { + continue + } + // note: req.Syntax is the line corresponding to 'require', which means + // req.Syntax.Start can point to the beginning of the "require" keyword + // for a single line require (e.g. "require golang.org/x/mod v0.0.0"). + start := req.Syntax.Start.Byte + if len(req.Syntax.Token) == 3 { + start += len("require ") + } + rng, err := pm.Mapper.OffsetRange(start, req.Syntax.End.Byte) + if err != nil { + return nil, err + } + // Map affecting vulns to 'warning' level diagnostics, + // others to 'info' level diagnostics. + // Fixes will include only the upgrades for warning level diagnostics. + var warningFixes, infoFixes []cache.SuggestedFix + var warningSet, infoSet = map[string]bool{}, map[string]bool{} + for _, finding := range findings { + // It is possible that the source code was changed since the last + // govulncheck run and information in the `vulns` info is stale. + // For example, imagine that a user is in the middle of updating + // problematic modules detected by the govulncheck run by applying + // quick fixes. Stale diagnostics can be confusing and prevent the + // user from quickly locating the next module to fix. + // Ideally we should rerun the analysis with the updated module + // dependencies or any other code changes, but we are not yet + // in the position of automatically triggering the analysis + // (govulncheck can take a while). We also don't know exactly what + // part of source code was changed since `vulns` was computed. + // As a heuristic, we assume that a user upgrades the affecting + // module to the version with the fix or the latest one, and if the + // version in the require statement is equal to or higher than the + // fixed version, skip generating a diagnostic about the vulnerability. + // Eventually, the user has to rerun govulncheck. + if finding.FixedVersion != "" && semver.IsValid(req.Mod.Version) && semver.Compare(finding.FixedVersion, req.Mod.Version) <= 0 { + continue + } + switch _, typ := foundVuln(finding); typ { + case vulnImported: + infoSet[finding.OSV] = true + case vulnCalled: + warningSet[finding.OSV] = true + } + // Upgrade to the exact version we offer the user, not the most recent. + if fixedVersion := finding.FixedVersion; semver.IsValid(fixedVersion) && semver.Compare(req.Mod.Version, fixedVersion) < 0 { + cmd := getUpgradeCodeAction(fh, req, fixedVersion) + sf := cache.SuggestedFixFromCommand(cmd, protocol.QuickFix) + switch _, typ := foundVuln(finding); typ { + case vulnImported: + infoFixes = append(infoFixes, sf) + case vulnCalled: + warningFixes = append(warningFixes, sf) + } + } + } + + if len(warningSet) == 0 && len(infoSet) == 0 { + continue + } + // Remove affecting osvs from the non-affecting osv list if any. + if len(warningSet) > 0 { + for k := range infoSet { + if warningSet[k] { + delete(infoSet, k) + } + } + } + // Add an upgrade for module@latest. + // TODO(suzmue): verify if latest is the same as fixedVersion. + latest := getUpgradeCodeAction(fh, req, "latest") + sf := cache.SuggestedFixFromCommand(latest, protocol.QuickFix) + if len(warningFixes) > 0 { + warningFixes = append(warningFixes, sf) + } + if len(infoFixes) > 0 { + infoFixes = append(infoFixes, sf) + } + if len(warningSet) > 0 { + warning := sortedKeys(warningSet) + warningFixes = append(warningFixes, suggestRunOrResetGovulncheck) + vulnDiagnostics = append(vulnDiagnostics, &cache.Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityWarning, + Source: diagSource, + Message: getVulnMessage(req.Mod.Path, warning, true, diagSource == cache.Govulncheck), + SuggestedFixes: warningFixes, + }) + } + if len(infoSet) > 0 { + info := sortedKeys(infoSet) + infoFixes = append(infoFixes, suggestRunOrResetGovulncheck) + vulnDiagnostics = append(vulnDiagnostics, &cache.Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityInformation, + Source: diagSource, + Message: getVulnMessage(req.Mod.Path, info, false, diagSource == cache.Govulncheck), + SuggestedFixes: infoFixes, + }) + } + } + + // TODO(hyangah): place this diagnostic on the `go` directive or `toolchain` directive + // after https://go.dev/issue/57001. + const diagnoseStdLib = false + + // If diagnosing the stdlib, add standard library vulnerability diagnostics + // on the module declaration. + // + // Only proceed if we have a valid module declaration on which to position + // the diagnostics. + if diagnoseStdLib && pm.File.Module != nil && pm.File.Module.Syntax != nil { + // Add standard library vulnerabilities. + stdlibVulns := vulnsByModule["stdlib"] + if len(stdlibVulns) == 0 { + return vulnDiagnostics, nil + } + + // Put the standard library diagnostic on the module declaration. + rng, err := pm.Mapper.OffsetRange(pm.File.Module.Syntax.Start.Byte, pm.File.Module.Syntax.End.Byte) + if err != nil { + return vulnDiagnostics, nil // TODO: bug report + } + + var warningSet, infoSet = map[string]bool{}, map[string]bool{} + for _, finding := range stdlibVulns { + switch _, typ := foundVuln(finding); typ { + case vulnImported: + infoSet[finding.OSV] = true + case vulnCalled: + warningSet[finding.OSV] = true + } + } + if len(warningSet) > 0 { + warning := sortedKeys(warningSet) + fixes := []cache.SuggestedFix{suggestRunOrResetGovulncheck} + vulnDiagnostics = append(vulnDiagnostics, &cache.Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityWarning, + Source: diagSource, + Message: getVulnMessage("go", warning, true, diagSource == cache.Govulncheck), + SuggestedFixes: fixes, + }) + + // remove affecting osvs from the non-affecting osv list if any. + for k := range infoSet { + if warningSet[k] { + delete(infoSet, k) + } + } + } + if len(infoSet) > 0 { + info := sortedKeys(infoSet) + fixes := []cache.SuggestedFix{suggestRunOrResetGovulncheck} + vulnDiagnostics = append(vulnDiagnostics, &cache.Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityInformation, + Source: diagSource, + Message: getVulnMessage("go", info, false, diagSource == cache.Govulncheck), + SuggestedFixes: fixes, + }) + } + } + + return vulnDiagnostics, nil +} + +type vulnFindingType int + +const ( + vulnUnknown vulnFindingType = iota + vulnCalled + vulnImported + vulnRequired +) + +// foundVuln returns the frame info describing discovered vulnerable symbol/package/module +// and how this vulnerability affects the analyzed package or module. +func foundVuln(finding *govulncheck.Finding) (*govulncheck.Frame, vulnFindingType) { + // finding.Trace is sorted from the imported vulnerable symbol to + // the entry point in the callstack. + // If Function is set, then Package must be set. Module will always be set. + // If Function is set it was found in the call graph, otherwise if Package is set + // it was found in the import graph, otherwise it was found in the require graph. + // See the documentation of govulncheck.Finding. + if len(finding.Trace) == 0 { // this shouldn't happen, but just in case... + return nil, vulnUnknown + } + vuln := finding.Trace[0] + if vuln.Package == "" { + return vuln, vulnRequired + } + if vuln.Function == "" { + return vuln, vulnImported + } + return vuln, vulnCalled +} + +func sortedKeys(m map[string]bool) []string { + ret := make([]string, 0, len(m)) + for k := range m { + ret = append(ret, k) + } + sort.Strings(ret) + return ret +} + +// suggestGovulncheckAction returns a code action that suggests either run govulncheck +// for more accurate investigation (if the present vulncheck diagnostics are based on +// analysis less accurate than govulncheck) or reset the existing govulncheck result +// (if the present vulncheck diagnostics are already based on govulncheck run). +func suggestGovulncheckAction(fromGovulncheck bool, uri protocol.DocumentURI) (cache.SuggestedFix, error) { + if fromGovulncheck { + resetVulncheck := command.NewResetGoModDiagnosticsCommand("Reset govulncheck result", command.ResetGoModDiagnosticsArgs{ + URIArg: command.URIArg{URI: uri}, + DiagnosticSource: string(cache.Govulncheck), + }) + return cache.SuggestedFixFromCommand(resetVulncheck, protocol.QuickFix), nil + } + vulncheck := command.NewRunGovulncheckCommand("Run govulncheck to verify", command.VulncheckArgs{ + URI: uri, + Pattern: "./...", + }) + return cache.SuggestedFixFromCommand(vulncheck, protocol.QuickFix), nil +} + +func getVulnMessage(mod string, vulns []string, used, fromGovulncheck bool) string { + var b strings.Builder + if used { + switch len(vulns) { + case 1: + fmt.Fprintf(&b, "%v has a vulnerability used in the code: %v.", mod, vulns[0]) + default: + fmt.Fprintf(&b, "%v has vulnerabilities used in the code: %v.", mod, strings.Join(vulns, ", ")) + } + } else { + if fromGovulncheck { + switch len(vulns) { + case 1: + fmt.Fprintf(&b, "%v has a vulnerability %v that is not used in the code.", mod, vulns[0]) + default: + fmt.Fprintf(&b, "%v has known vulnerabilities %v that are not used in the code.", mod, strings.Join(vulns, ", ")) + } + } else { + switch len(vulns) { + case 1: + fmt.Fprintf(&b, "%v has a vulnerability %v.", mod, vulns[0]) + default: + fmt.Fprintf(&b, "%v has known vulnerabilities %v.", mod, strings.Join(vulns, ", ")) + } + } + } + return b.String() +} + +// href returns the url for the vulnerability information. +// Eventually we should retrieve the url embedded in the osv.Entry. +// While vuln.go.dev is under development, this always returns +// the page in pkg.go.dev. +func href(vulnID string) string { + return fmt.Sprintf("https://pkg.go.dev/vuln/%s", vulnID) +} + +func getUpgradeCodeAction(fh file.Handle, req *modfile.Require, version string) *protocol.Command { + return command.NewUpgradeDependencyCommand(upgradeTitle(version), command.DependencyArgs{ + URI: fh.URI(), + AddRequire: false, + GoCmdArgs: []string{req.Mod.Path + "@" + version}, + }) +} + +func upgradeTitle(fixedVersion string) string { + title := fmt.Sprintf("%s%v", upgradeCodeActionPrefix, fixedVersion) + return title +} + +// SelectUpgradeCodeActions takes a list of code actions for a required module +// and returns a more selective list of upgrade code actions, +// where the code actions have been deduped. Code actions unrelated to upgrade +// are deduplicated by the name. +func SelectUpgradeCodeActions(actions []protocol.CodeAction) []protocol.CodeAction { + if len(actions) <= 1 { + return actions // return early if no sorting necessary + } + var versionedUpgrade, latestUpgrade, resetAction protocol.CodeAction + var chosenVersionedUpgrade string + var selected []protocol.CodeAction + + seenTitles := make(map[string]bool) + + for _, action := range actions { + if strings.HasPrefix(action.Title, upgradeCodeActionPrefix) { + if v := getUpgradeVersion(action); v == "latest" && latestUpgrade.Title == "" { + latestUpgrade = action + } else if versionedUpgrade.Title == "" || semver.Compare(v, chosenVersionedUpgrade) > 0 { + chosenVersionedUpgrade = v + versionedUpgrade = action + } + } else if strings.HasPrefix(action.Title, "Reset govulncheck") { + resetAction = action + } else if !seenTitles[action.Command.Title] { + seenTitles[action.Command.Title] = true + selected = append(selected, action) + } + } + if versionedUpgrade.Title != "" { + selected = append(selected, versionedUpgrade) + } + if latestUpgrade.Title != "" { + selected = append(selected, latestUpgrade) + } + if resetAction.Title != "" { + selected = append(selected, resetAction) + } + return selected +} + +func getUpgradeVersion(p protocol.CodeAction) string { + return strings.TrimPrefix(p.Title, upgradeCodeActionPrefix) +} diff --git a/contribs/gnopls/internal/mod/format.go b/contribs/gnopls/internal/mod/format.go new file mode 100644 index 00000000000..14408393969 --- /dev/null +++ b/contribs/gnopls/internal/mod/format.go @@ -0,0 +1,32 @@ +// 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 mod + +import ( + "context" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/event" +) + +func Format(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.TextEdit, error) { + ctx, done := event.Start(ctx, "mod.Format") + defer done() + + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil { + return nil, err + } + formatted, err := pm.File.Format() + if err != nil { + return nil, err + } + // Calculate the edits to be made due to the change. + diffs := diff.Bytes(pm.Mapper.Content, formatted) + return protocol.EditsFromDiffEdits(pm.Mapper, diffs) +} diff --git a/contribs/gnopls/internal/mod/hover.go b/contribs/gnopls/internal/mod/hover.go new file mode 100644 index 00000000000..458c5ce67d5 --- /dev/null +++ b/contribs/gnopls/internal/mod/hover.go @@ -0,0 +1,380 @@ +// 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 mod + +import ( + "bytes" + "context" + "fmt" + "sort" + "strings" + + "golang.org/x/mod/modfile" + "golang.org/x/mod/semver" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/vulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/osv" + "golang.org/x/tools/internal/event" +) + +func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) { + var found bool + for _, uri := range snapshot.View().ModFiles() { + if fh.URI() == uri { + found = true + break + } + } + + // We only provide hover information for the view's go.mod files. + if !found { + return nil, nil + } + + ctx, done := event.Start(ctx, "mod.Hover") + defer done() + + // Get the position of the cursor. + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil { + return nil, fmt.Errorf("getting modfile handle: %w", err) + } + offset, err := pm.Mapper.PositionOffset(position) + if err != nil { + return nil, fmt.Errorf("computing cursor position: %w", err) + } + + // If the cursor position is on a module statement + if hover, ok := hoverOnModuleStatement(ctx, pm, offset, snapshot, fh); ok { + return hover, nil + } + return hoverOnRequireStatement(ctx, pm, offset, snapshot, fh) +} + +func hoverOnRequireStatement(ctx context.Context, pm *cache.ParsedModule, offset int, snapshot *cache.Snapshot, fh file.Handle) (*protocol.Hover, error) { + // Confirm that the cursor is at the position of a require statement. + var req *modfile.Require + var startOffset, endOffset int + for _, r := range pm.File.Require { + dep := []byte(r.Mod.Path) + s, e := r.Syntax.Start.Byte, r.Syntax.End.Byte + i := bytes.Index(pm.Mapper.Content[s:e], dep) + if i == -1 { + continue + } + // Shift the start position to the location of the + // dependency within the require statement. + startOffset, endOffset = s+i, e + if startOffset <= offset && offset <= endOffset { + req = r + break + } + } + // TODO(hyangah): find position for info about vulnerabilities in Go + + // The cursor position is not on a require statement. + if req == nil { + return nil, nil + } + + // Get the vulnerability info. + fromGovulncheck := true + vs := snapshot.Vulnerabilities(fh.URI())[fh.URI()] + if vs == nil && snapshot.Options().Vulncheck == settings.ModeVulncheckImports { + var err error + vs, err = snapshot.ModVuln(ctx, fh.URI()) + if err != nil { + return nil, err + } + fromGovulncheck = false + } + affecting, nonaffecting, osvs := lookupVulns(vs, req.Mod.Path, req.Mod.Version) + + // Get the `go mod why` results for the given file. + why, err := snapshot.ModWhy(ctx, fh) + if err != nil { + return nil, err + } + explanation, ok := why[req.Mod.Path] + if !ok { + return nil, nil + } + + // Get the range to highlight for the hover. + // TODO(hyangah): adjust the hover range to include the version number + // to match the diagnostics' range. + rng, err := pm.Mapper.OffsetRange(startOffset, endOffset) + if err != nil { + return nil, err + } + options := snapshot.Options() + isPrivate := snapshot.IsGoPrivatePath(req.Mod.Path) + header := formatHeader(req.Mod.Path, options) + explanation = formatExplanation(explanation, req, options, isPrivate) + vulns := formatVulnerabilities(affecting, nonaffecting, osvs, options, fromGovulncheck) + + return &protocol.Hover{ + Contents: protocol.MarkupContent{ + Kind: options.PreferredContentFormat, + Value: header + vulns + explanation, + }, + Range: rng, + }, nil +} + +func hoverOnModuleStatement(ctx context.Context, pm *cache.ParsedModule, offset int, snapshot *cache.Snapshot, fh file.Handle) (*protocol.Hover, bool) { + module := pm.File.Module + if module == nil { + return nil, false // no module stmt + } + if offset < module.Syntax.Start.Byte || offset > module.Syntax.End.Byte { + return nil, false // cursor not in module stmt + } + + rng, err := pm.Mapper.OffsetRange(module.Syntax.Start.Byte, module.Syntax.End.Byte) + if err != nil { + return nil, false + } + fromGovulncheck := true + vs := snapshot.Vulnerabilities(fh.URI())[fh.URI()] + + if vs == nil && snapshot.Options().Vulncheck == settings.ModeVulncheckImports { + vs, err = snapshot.ModVuln(ctx, fh.URI()) + if err != nil { + return nil, false + } + fromGovulncheck = false + } + modpath := "stdlib" + goVersion := snapshot.View().GoVersionString() + affecting, nonaffecting, osvs := lookupVulns(vs, modpath, goVersion) + options := snapshot.Options() + vulns := formatVulnerabilities(affecting, nonaffecting, osvs, options, fromGovulncheck) + + return &protocol.Hover{ + Contents: protocol.MarkupContent{ + Kind: options.PreferredContentFormat, + Value: vulns, + }, + Range: rng, + }, true +} + +func formatHeader(modpath string, options *settings.Options) string { + var b strings.Builder + // Write the heading as an H3. + b.WriteString("#### " + modpath) + if options.PreferredContentFormat == protocol.Markdown { + b.WriteString("\n\n") + } else { + b.WriteRune('\n') + } + return b.String() +} + +func lookupVulns(vulns *vulncheck.Result, modpath, version string) (affecting, nonaffecting []*govulncheck.Finding, osvs map[string]*osv.Entry) { + if vulns == nil || len(vulns.Entries) == 0 { + return nil, nil, nil + } + for _, finding := range vulns.Findings { + vuln, typ := foundVuln(finding) + if vuln.Module != modpath { + continue + } + // It is possible that the source code was changed since the last + // govulncheck run and information in the `vulns` info is stale. + // For example, imagine that a user is in the middle of updating + // problematic modules detected by the govulncheck run by applying + // quick fixes. Stale diagnostics can be confusing and prevent the + // user from quickly locating the next module to fix. + // Ideally we should rerun the analysis with the updated module + // dependencies or any other code changes, but we are not yet + // in the position of automatically triggering the analysis + // (govulncheck can take a while). We also don't know exactly what + // part of source code was changed since `vulns` was computed. + // As a heuristic, we assume that a user upgrades the affecting + // module to the version with the fix or the latest one, and if the + // version in the require statement is equal to or higher than the + // fixed version, skip the vulnerability information in the hover. + // Eventually, the user has to rerun govulncheck. + if finding.FixedVersion != "" && semver.IsValid(version) && semver.Compare(finding.FixedVersion, version) <= 0 { + continue + } + switch typ { + case vulnCalled: + affecting = append(affecting, finding) + case vulnImported: + nonaffecting = append(nonaffecting, finding) + } + } + + // Remove affecting elements from nonaffecting. + // An OSV entry can appear in both lists if an OSV entry covers + // multiple packages imported but not all vulnerable symbols are used. + // The current wording of hover message doesn't clearly + // present this case well IMO, so let's skip reporting nonaffecting. + if len(affecting) > 0 && len(nonaffecting) > 0 { + affectingSet := map[string]bool{} + for _, f := range affecting { + affectingSet[f.OSV] = true + } + n := 0 + for _, v := range nonaffecting { + if !affectingSet[v.OSV] { + nonaffecting[n] = v + n++ + } + } + nonaffecting = nonaffecting[:n] + } + sort.Slice(nonaffecting, func(i, j int) bool { return nonaffecting[i].OSV < nonaffecting[j].OSV }) + sort.Slice(affecting, func(i, j int) bool { return affecting[i].OSV < affecting[j].OSV }) + return affecting, nonaffecting, vulns.Entries +} + +func fixedVersion(fixed string) string { + if fixed == "" { + return "No fix is available." + } + return "Fixed in " + fixed + "." +} + +func formatVulnerabilities(affecting, nonaffecting []*govulncheck.Finding, osvs map[string]*osv.Entry, options *settings.Options, fromGovulncheck bool) string { + if len(osvs) == 0 || (len(affecting) == 0 && len(nonaffecting) == 0) { + return "" + } + byOSV := func(findings []*govulncheck.Finding) map[string][]*govulncheck.Finding { + m := make(map[string][]*govulncheck.Finding) + for _, f := range findings { + m[f.OSV] = append(m[f.OSV], f) + } + return m + } + affectingByOSV := byOSV(affecting) + nonaffectingByOSV := byOSV(nonaffecting) + + // TODO(hyangah): can we use go templates to generate hover messages? + // Then, we can use a different template for markdown case. + useMarkdown := options.PreferredContentFormat == protocol.Markdown + + var b strings.Builder + + if len(affectingByOSV) > 0 { + // TODO(hyangah): make the message more eyecatching (icon/codicon/color) + if len(affectingByOSV) == 1 { + fmt.Fprintf(&b, "\n**WARNING:** Found %d reachable vulnerability.\n", len(affectingByOSV)) + } else { + fmt.Fprintf(&b, "\n**WARNING:** Found %d reachable vulnerabilities.\n", len(affectingByOSV)) + } + } + for id, findings := range affectingByOSV { + fix := fixedVersion(findings[0].FixedVersion) + pkgs := vulnerablePkgsInfo(findings, useMarkdown) + osvEntry := osvs[id] + + if useMarkdown { + fmt.Fprintf(&b, "- [**%v**](%v) %v%v\n%v\n", id, href(id), osvEntry.Summary, pkgs, fix) + } else { + fmt.Fprintf(&b, " - [%v] %v (%v) %v%v\n", id, osvEntry.Summary, href(id), pkgs, fix) + } + } + if len(nonaffecting) > 0 { + if fromGovulncheck { + fmt.Fprintf(&b, "\n**Note:** The project imports packages with known vulnerabilities, but does not call the vulnerable code.\n") + } else { + fmt.Fprintf(&b, "\n**Note:** The project imports packages with known vulnerabilities. Use `govulncheck` to check if the project uses vulnerable symbols.\n") + } + } + for k, findings := range nonaffectingByOSV { + fix := fixedVersion(findings[0].FixedVersion) + pkgs := vulnerablePkgsInfo(findings, useMarkdown) + osvEntry := osvs[k] + + if useMarkdown { + fmt.Fprintf(&b, "- [%v](%v) %v%v\n%v\n", k, href(k), osvEntry.Summary, pkgs, fix) + } else { + fmt.Fprintf(&b, " - [%v] %v (%v) %v\n%v\n", k, osvEntry.Summary, href(k), pkgs, fix) + } + } + b.WriteString("\n") + return b.String() +} + +func vulnerablePkgsInfo(findings []*govulncheck.Finding, useMarkdown bool) string { + var b strings.Builder + seen := map[string]bool{} + for _, f := range findings { + p := f.Trace[0].Package + if !seen[p] { + seen[p] = true + if useMarkdown { + b.WriteString("\n * `") + } else { + b.WriteString("\n ") + } + b.WriteString(p) + if useMarkdown { + b.WriteString("`") + } + } + } + return b.String() +} + +func formatExplanation(text string, req *modfile.Require, options *settings.Options, isPrivate bool) string { + text = strings.TrimSuffix(text, "\n") + splt := strings.Split(text, "\n") + length := len(splt) + + var b strings.Builder + + // If the explanation is 2 lines, then it is of the form: + // # golang.org/x/text/encoding + // (main module does not need package golang.org/x/text/encoding) + if length == 2 { + b.WriteString(splt[1]) + return b.String() + } + + imp := splt[length-1] // import path + reference := imp + // See golang/go#36998: don't link to modules matching GOPRIVATE. + if !isPrivate && options.PreferredContentFormat == protocol.Markdown { + target := imp + if strings.ToLower(options.LinkTarget) == "pkg.go.dev" { + target = strings.Replace(target, req.Mod.Path, req.Mod.String(), 1) + } + reference = fmt.Sprintf("[%s](%s)", imp, cache.BuildLink(options.LinkTarget, target, "")) + } + b.WriteString("This module is necessary because " + reference + " is imported in") + + // If the explanation is 3 lines, then it is of the form: + // # golang.org/x/tools + // modtest + // golang.org/x/tools/go/packages + if length == 3 { + msg := fmt.Sprintf(" `%s`.", splt[1]) + b.WriteString(msg) + return b.String() + } + + // If the explanation is more than 3 lines, then it is of the form: + // # golang.org/x/text/language + // rsc.io/quote + // rsc.io/sampler + // golang.org/x/text/language + b.WriteString(":\n```text") + dash := "" + for _, imp := range splt[1 : length-1] { + dash += "-" + b.WriteString("\n" + dash + " " + imp) + } + b.WriteString("\n```") + return b.String() +} diff --git a/contribs/gnopls/internal/mod/inlayhint.go b/contribs/gnopls/internal/mod/inlayhint.go new file mode 100644 index 00000000000..73286be4be6 --- /dev/null +++ b/contribs/gnopls/internal/mod/inlayhint.go @@ -0,0 +1,104 @@ +// 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 mod + +import ( + "context" + "fmt" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" +) + +func InlayHint(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, _ protocol.Range) ([]protocol.InlayHint, error) { + // Inlay hints are enabled if the client supports them. + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil { + return nil, err + } + + // Compare the version of the module used in the snapshot's + // metadata (i.e. the solution to the MVS constraints computed + // by go list) with the version requested by the module, in + // both cases, taking replaces into account. Produce an + // InlayHint when the version of the module is not the one + // used. + + replaces := make(map[string]*modfile.Replace) + for _, x := range pm.File.Replace { + replaces[x.Old.Path] = x + } + + requires := make(map[string]*modfile.Require) + for _, x := range pm.File.Require { + requires[x.Mod.Path] = x + } + + am, err := snapshot.AllMetadata(ctx) + if err != nil { + return nil, err + } + + var ans []protocol.InlayHint + seen := make(map[string]bool) + for _, meta := range am { + if meta.Module == nil || seen[meta.Module.Path] { + continue + } + seen[meta.Module.Path] = true + metaVersion := meta.Module.Version + if meta.Module.Replace != nil { + metaVersion = meta.Module.Replace.Version + } + // These versions can be blank, as in gopls/go.mod's local replace + if oldrepl, ok := replaces[meta.Module.Path]; ok && oldrepl.New.Version != metaVersion { + ih := genHint(oldrepl.Syntax, oldrepl.New.Version, metaVersion, pm.Mapper) + if ih != nil { + ans = append(ans, *ih) + } + } else if oldreq, ok := requires[meta.Module.Path]; ok && oldreq.Mod.Version != metaVersion { + // maybe it was replaced: + if _, ok := replaces[meta.Module.Path]; ok { + continue + } + ih := genHint(oldreq.Syntax, oldreq.Mod.Version, metaVersion, pm.Mapper) + if ih != nil { + ans = append(ans, *ih) + } + } + } + return ans, nil +} + +func genHint(mline *modfile.Line, oldVersion, newVersion string, m *protocol.Mapper) *protocol.InlayHint { + x := mline.End.Byte // the parser has removed trailing whitespace and comments (see modfile_test.go) + x -= len(mline.Token[len(mline.Token)-1]) + line, err := m.OffsetPosition(x) + if err != nil { + return nil + } + part := protocol.InlayHintLabelPart{ + Value: newVersion, + Tooltip: &protocol.OrPTooltipPLabel{ + Value: fmt.Sprintf("The build selects version %s rather than go.mod's version %s.", newVersion, oldVersion), + }, + } + rng, err := m.OffsetRange(x, mline.End.Byte) + if err != nil { + return nil + } + te := protocol.TextEdit{ + Range: rng, + NewText: newVersion, + } + return &protocol.InlayHint{ + Position: line, + Label: []protocol.InlayHintLabelPart{part}, + Kind: protocol.Parameter, + PaddingRight: true, + TextEdits: []protocol.TextEdit{te}, + } +} diff --git a/contribs/gnopls/internal/progress/progress.go b/contribs/gnopls/internal/progress/progress.go new file mode 100644 index 00000000000..e35c0fe19dc --- /dev/null +++ b/contribs/gnopls/internal/progress/progress.go @@ -0,0 +1,292 @@ +// 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. + +// The progress package defines utilities for reporting the progress +// of long-running operations using features of the LSP client +// interface such as Progress and ShowMessage. +package progress + +import ( + "context" + "fmt" + "io" + "math/rand" + "strconv" + "strings" + "sync" + + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/xcontext" +) + +// NewTracker returns a new Tracker that reports progress to the +// specified client. +func NewTracker(client protocol.Client) *Tracker { + return &Tracker{ + client: client, + inProgress: make(map[protocol.ProgressToken]*WorkDone), + } +} + +// A Tracker reports the progress of a long-running operation to an LSP client. +type Tracker struct { + client protocol.Client + supportsWorkDoneProgress bool + + mu sync.Mutex + inProgress map[protocol.ProgressToken]*WorkDone +} + +// SetSupportsWorkDoneProgress sets whether the client supports "work done" +// progress reporting. It must be set before using the tracker. +// +// TODO(rfindley): fix this broken initialization pattern. +// Also: do we actually need the fall-back progress behavior using ShowMessage? +// Surely ShowMessage notifications are too noisy to be worthwhile. +func (t *Tracker) SetSupportsWorkDoneProgress(b bool) { + t.supportsWorkDoneProgress = b +} + +// SupportsWorkDoneProgress reports whether the tracker supports work done +// progress reporting. +func (t *Tracker) SupportsWorkDoneProgress() bool { + return t.supportsWorkDoneProgress +} + +// Start notifies the client of work being done on the server. It uses either +// ShowMessage RPCs or $/progress messages, depending on the capabilities of +// the client. The returned WorkDone handle may be used to report incremental +// progress, and to report work completion. In particular, it is an error to +// call start and not call end(...) on the returned WorkDone handle. +// +// If token is empty, a token will be randomly generated. +// +// The progress item is considered cancellable if the given cancel func is +// non-nil. In this case, cancel is called when the work done +// +// Example: +// +// func Generate(ctx) (err error) { +// ctx, cancel := context.WithCancel(ctx) +// defer cancel() +// work := s.progress.start(ctx, "generate", "running go generate", cancel) +// defer func() { +// if err != nil { +// work.end(ctx, fmt.Sprintf("generate failed: %v", err)) +// } else { +// work.end(ctx, "done") +// } +// }() +// // Do the work... +// } +func (t *Tracker) Start(ctx context.Context, title, message string, token protocol.ProgressToken, cancel func()) *WorkDone { + ctx = xcontext.Detach(ctx) // progress messages should not be cancelled + wd := &WorkDone{ + client: t.client, + token: token, + cancel: cancel, + } + if !t.supportsWorkDoneProgress { + // Previous iterations of this fallback attempted to retain cancellation + // support by using ShowMessageCommand with a 'Cancel' button, but this is + // not ideal as the 'Cancel' dialog stays open even after the command + // completes. + // + // Just show a simple message. Clients can implement workDone progress + // reporting to get cancellation support. + if err := wd.client.ShowMessage(ctx, &protocol.ShowMessageParams{ + Type: protocol.Log, + Message: message, + }); err != nil { + event.Error(ctx, "showing start message for "+title, err) + } + return wd + } + if wd.token == nil { + token = strconv.FormatInt(rand.Int63(), 10) + err := wd.client.WorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{ + Token: token, + }) + if err != nil { + wd.err = err + event.Error(ctx, "starting work for "+title, err) + return wd + } + wd.token = token + } + // At this point we have a token that the client knows about. Store the token + // before starting work. + t.mu.Lock() + t.inProgress[wd.token] = wd + t.mu.Unlock() + wd.cleanup = func() { + t.mu.Lock() + delete(t.inProgress, token) + t.mu.Unlock() + } + err := wd.client.Progress(ctx, &protocol.ProgressParams{ + Token: wd.token, + Value: &protocol.WorkDoneProgressBegin{ + Kind: "begin", + Cancellable: wd.cancel != nil, + Message: message, + Title: title, + }, + }) + if err != nil { + event.Error(ctx, "progress begin", err) + } + return wd +} + +func (t *Tracker) Cancel(token protocol.ProgressToken) error { + t.mu.Lock() + defer t.mu.Unlock() + wd, ok := t.inProgress[token] + if !ok { + return fmt.Errorf("token %q not found in progress", token) + } + if wd.cancel == nil { + return fmt.Errorf("work %q is not cancellable", token) + } + wd.doCancel() + return nil +} + +// WorkDone represents a unit of work that is reported to the client via the +// progress API. +type WorkDone struct { + client protocol.Client + // If token is nil, this workDone object uses the ShowMessage API, rather + // than $/progress. + token protocol.ProgressToken + // err is set if progress reporting is broken for some reason (for example, + // if there was an initial error creating a token). + err error + + cancelMu sync.Mutex + cancelled bool + cancel func() + + cleanup func() +} + +func (wd *WorkDone) Token() protocol.ProgressToken { + return wd.token +} + +func (wd *WorkDone) doCancel() { + wd.cancelMu.Lock() + defer wd.cancelMu.Unlock() + if !wd.cancelled { + wd.cancel() + } +} + +// Report reports an update on WorkDone report back to the client. +func (wd *WorkDone) Report(ctx context.Context, message string, percentage float64) { + ctx = xcontext.Detach(ctx) // progress messages should not be cancelled + if wd == nil { + return + } + wd.cancelMu.Lock() + cancelled := wd.cancelled + wd.cancelMu.Unlock() + if cancelled { + return + } + if wd.err != nil || wd.token == nil { + // Not using the workDone API, so we do nothing. It would be far too spammy + // to send incremental messages. + return + } + message = strings.TrimSuffix(message, "\n") + err := wd.client.Progress(ctx, &protocol.ProgressParams{ + Token: wd.token, + Value: &protocol.WorkDoneProgressReport{ + Kind: "report", + // Note that in the LSP spec, the value of Cancellable may be changed to + // control whether the cancel button in the UI is enabled. Since we don't + // yet use this feature, the value is kept constant here. + Cancellable: wd.cancel != nil, + Message: message, + Percentage: uint32(percentage), + }, + }) + if err != nil { + event.Error(ctx, "reporting progress", err) + } +} + +// End reports a workdone completion back to the client. +func (wd *WorkDone) End(ctx context.Context, message string) { + ctx = xcontext.Detach(ctx) // progress messages should not be cancelled + if wd == nil { + return + } + var err error + switch { + case wd.err != nil: + // There is a prior error. + case wd.token == nil: + // We're falling back to message-based reporting. + err = wd.client.ShowMessage(ctx, &protocol.ShowMessageParams{ + Type: protocol.Info, + Message: message, + }) + default: + err = wd.client.Progress(ctx, &protocol.ProgressParams{ + Token: wd.token, + Value: &protocol.WorkDoneProgressEnd{ + Kind: "end", + Message: message, + }, + }) + } + if err != nil { + event.Error(ctx, "ending work", err) + } + if wd.cleanup != nil { + wd.cleanup() + } +} + +// NewEventWriter returns an [io.Writer] that calls the context's +// event printer for each data payload, wrapping it with the +// operation=generate tag to distinguish its logs from others. +func NewEventWriter(ctx context.Context, operation string) io.Writer { + return &eventWriter{ctx: ctx, operation: operation} +} + +type eventWriter struct { + ctx context.Context + operation string +} + +func (ew *eventWriter) Write(p []byte) (n int, err error) { + event.Log(ew.ctx, string(p), label.Operation.Of(ew.operation)) + return len(p), nil +} + +// NewWorkDoneWriter wraps a WorkDone handle to provide a Writer interface, +// so that workDone reporting can more easily be hooked into commands. +func NewWorkDoneWriter(ctx context.Context, wd *WorkDone) io.Writer { + return &workDoneWriter{ctx: ctx, wd: wd} +} + +// workDoneWriter wraps a workDone handle to provide a Writer interface, +// so that workDone reporting can more easily be hooked into commands. +type workDoneWriter struct { + // In order to implement the io.Writer interface, we must close over ctx. + ctx context.Context + wd *WorkDone +} + +func (wdw *workDoneWriter) Write(p []byte) (n int, err error) { + wdw.wd.Report(wdw.ctx, string(p), 0) + // Don't fail just because of a failure to report progress. + return len(p), nil +} diff --git a/contribs/gnopls/internal/progress/progress_test.go b/contribs/gnopls/internal/progress/progress_test.go new file mode 100644 index 00000000000..642103ae025 --- /dev/null +++ b/contribs/gnopls/internal/progress/progress_test.go @@ -0,0 +1,161 @@ +// 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 progress + +import ( + "context" + "fmt" + "sync" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +type fakeClient struct { + protocol.Client + + token protocol.ProgressToken + + mu sync.Mutex + created, begun, reported, messages, ended int +} + +func (c *fakeClient) checkToken(token protocol.ProgressToken) { + if token == nil { + panic("nil token in progress message") + } + if c.token != nil && c.token != token { + panic(fmt.Errorf("invalid token in progress message: got %v, want %v", token, c.token)) + } +} + +func (c *fakeClient) WorkDoneProgressCreate(ctx context.Context, params *protocol.WorkDoneProgressCreateParams) error { + c.mu.Lock() + defer c.mu.Unlock() + c.checkToken(params.Token) + c.created++ + return nil +} + +func (c *fakeClient) Progress(ctx context.Context, params *protocol.ProgressParams) error { + c.mu.Lock() + defer c.mu.Unlock() + c.checkToken(params.Token) + switch params.Value.(type) { + case *protocol.WorkDoneProgressBegin: + c.begun++ + case *protocol.WorkDoneProgressReport: + c.reported++ + case *protocol.WorkDoneProgressEnd: + c.ended++ + default: + panic(fmt.Errorf("unknown progress value %T", params.Value)) + } + return nil +} + +func (c *fakeClient) ShowMessage(context.Context, *protocol.ShowMessageParams) error { + c.mu.Lock() + defer c.mu.Unlock() + c.messages++ + return nil +} + +func setup() (context.Context, *Tracker, *fakeClient) { + c := &fakeClient{} + tracker := NewTracker(c) + tracker.SetSupportsWorkDoneProgress(true) + return context.Background(), tracker, c +} + +func TestProgressTracker_Reporting(t *testing.T) { + for _, test := range []struct { + name string + supported bool + token protocol.ProgressToken + wantReported, wantCreated, wantBegun, wantEnded int + wantMessages int + }{ + { + name: "unsupported", + wantMessages: 2, + }, + { + name: "random token", + supported: true, + wantCreated: 1, + wantBegun: 1, + wantReported: 1, + wantEnded: 1, + }, + { + name: "string token", + supported: true, + token: "token", + wantBegun: 1, + wantReported: 1, + wantEnded: 1, + }, + { + name: "numeric token", + supported: true, + token: 1, + wantReported: 1, + wantBegun: 1, + wantEnded: 1, + }, + } { + test := test + t.Run(test.name, func(t *testing.T) { + ctx, tracker, client := setup() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + tracker.supportsWorkDoneProgress = test.supported + work := tracker.Start(ctx, "work", "message", test.token, nil) + client.mu.Lock() + gotCreated, gotBegun := client.created, client.begun + client.mu.Unlock() + if gotCreated != test.wantCreated { + t.Errorf("got %d created tokens, want %d", gotCreated, test.wantCreated) + } + if gotBegun != test.wantBegun { + t.Errorf("got %d work begun, want %d", gotBegun, test.wantBegun) + } + // Ignore errors: this is just testing the reporting behavior. + work.Report(ctx, "report", 50) + client.mu.Lock() + gotReported := client.reported + client.mu.Unlock() + if gotReported != test.wantReported { + t.Errorf("got %d progress reports, want %d", gotReported, test.wantCreated) + } + work.End(ctx, "done") + client.mu.Lock() + gotEnded, gotMessages := client.ended, client.messages + client.mu.Unlock() + if gotEnded != test.wantEnded { + t.Errorf("got %d ended reports, want %d", gotEnded, test.wantEnded) + } + if gotMessages != test.wantMessages { + t.Errorf("got %d messages, want %d", gotMessages, test.wantMessages) + } + }) + } +} + +func TestProgressTracker_Cancellation(t *testing.T) { + for _, token := range []protocol.ProgressToken{nil, 1, "a"} { + ctx, tracker, _ := setup() + var canceled bool + cancel := func() { canceled = true } + work := tracker.Start(ctx, "work", "message", token, cancel) + if err := tracker.Cancel(work.Token()); err != nil { + t.Fatal(err) + } + if !canceled { + t.Errorf("tracker.cancel(...): cancel not called") + } + } +} diff --git a/contribs/gnopls/internal/protocol/command/command_gen.go b/contribs/gnopls/internal/protocol/command/command_gen.go new file mode 100644 index 00000000000..73e793c087e --- /dev/null +++ b/contribs/gnopls/internal/protocol/command/command_gen.go @@ -0,0 +1,677 @@ +// 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. + +// Don't include this file during code generation, or it will break the build +// if existing interface methods have been modified. +//go:build !generate +// +build !generate + +// Code generated by gen.go from gopls/internal/protocol/command. DO NOT EDIT. + +package command + +import ( + "context" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// Symbolic names for gopls commands, corresponding to methods of [Interface]. +// +// The string value is used in the Command field of protocol.Command. +// These commands may be obtained from a CodeLens or CodeAction request +// and executed by an ExecuteCommand request. +const ( + AddDependency Command = "gopls.add_dependency" + AddImport Command = "gopls.add_import" + AddTelemetryCounters Command = "gopls.add_telemetry_counters" + ApplyFix Command = "gopls.apply_fix" + Assembly Command = "gopls.assembly" + ChangeSignature Command = "gopls.change_signature" + CheckUpgrades Command = "gopls.check_upgrades" + ClientOpenURL Command = "gopls.client_open_url" + DiagnoseFiles Command = "gopls.diagnose_files" + Doc Command = "gopls.doc" + EditGoDirective Command = "gopls.edit_go_directive" + ExtractToNewFile Command = "gopls.extract_to_new_file" + FetchVulncheckResult Command = "gopls.fetch_vulncheck_result" + FreeSymbols Command = "gopls.free_symbols" + GCDetails Command = "gopls.gc_details" + Generate Command = "gopls.generate" + GoGetPackage Command = "gopls.go_get_package" + ListImports Command = "gopls.list_imports" + ListKnownPackages Command = "gopls.list_known_packages" + MaybePromptForTelemetry Command = "gopls.maybe_prompt_for_telemetry" + MemStats Command = "gopls.mem_stats" + Modules Command = "gopls.modules" + Packages Command = "gopls.packages" + RegenerateCgo Command = "gopls.regenerate_cgo" + RemoveDependency Command = "gopls.remove_dependency" + ResetGoModDiagnostics Command = "gopls.reset_go_mod_diagnostics" + RunGoWorkCommand Command = "gopls.run_go_work_command" + RunGovulncheck Command = "gopls.run_govulncheck" + RunTests Command = "gopls.run_tests" + ScanImports Command = "gopls.scan_imports" + StartDebugging Command = "gopls.start_debugging" + StartProfile Command = "gopls.start_profile" + StopProfile Command = "gopls.stop_profile" + Test Command = "gopls.test" + Tidy Command = "gopls.tidy" + ToggleGCDetails Command = "gopls.toggle_gc_details" + UpdateGoSum Command = "gopls.update_go_sum" + UpgradeDependency Command = "gopls.upgrade_dependency" + Vendor Command = "gopls.vendor" + Views Command = "gopls.views" + WorkspaceStats Command = "gopls.workspace_stats" +) + +var Commands = []Command{ + AddDependency, + AddImport, + AddTelemetryCounters, + ApplyFix, + Assembly, + ChangeSignature, + CheckUpgrades, + ClientOpenURL, + DiagnoseFiles, + Doc, + EditGoDirective, + ExtractToNewFile, + FetchVulncheckResult, + FreeSymbols, + GCDetails, + Generate, + GoGetPackage, + ListImports, + ListKnownPackages, + MaybePromptForTelemetry, + MemStats, + Modules, + Packages, + RegenerateCgo, + RemoveDependency, + ResetGoModDiagnostics, + RunGoWorkCommand, + RunGovulncheck, + RunTests, + ScanImports, + StartDebugging, + StartProfile, + StopProfile, + Test, + Tidy, + ToggleGCDetails, + UpdateGoSum, + UpgradeDependency, + Vendor, + Views, + WorkspaceStats, +} + +func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Interface) (interface{}, error) { + switch Command(params.Command) { + case AddDependency: + var a0 DependencyArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.AddDependency(ctx, a0) + case AddImport: + var a0 AddImportArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.AddImport(ctx, a0) + case AddTelemetryCounters: + var a0 AddTelemetryCountersArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.AddTelemetryCounters(ctx, a0) + case ApplyFix: + var a0 ApplyFixArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.ApplyFix(ctx, a0) + case Assembly: + var a0 string + var a1 string + var a2 string + if err := UnmarshalArgs(params.Arguments, &a0, &a1, &a2); err != nil { + return nil, err + } + return nil, s.Assembly(ctx, a0, a1, a2) + case ChangeSignature: + var a0 ChangeSignatureArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.ChangeSignature(ctx, a0) + case CheckUpgrades: + var a0 CheckUpgradesArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.CheckUpgrades(ctx, a0) + case ClientOpenURL: + var a0 string + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.ClientOpenURL(ctx, a0) + case DiagnoseFiles: + var a0 DiagnoseFilesArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.DiagnoseFiles(ctx, a0) + case Doc: + var a0 protocol.Location + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.Doc(ctx, a0) + case EditGoDirective: + var a0 EditGoDirectiveArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.EditGoDirective(ctx, a0) + case ExtractToNewFile: + var a0 protocol.Location + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.ExtractToNewFile(ctx, a0) + case FetchVulncheckResult: + var a0 URIArg + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.FetchVulncheckResult(ctx, a0) + case FreeSymbols: + var a0 string + var a1 protocol.Location + if err := UnmarshalArgs(params.Arguments, &a0, &a1); err != nil { + return nil, err + } + return nil, s.FreeSymbols(ctx, a0, a1) + case GCDetails: + var a0 protocol.DocumentURI + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.GCDetails(ctx, a0) + case Generate: + var a0 GenerateArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.Generate(ctx, a0) + case GoGetPackage: + var a0 GoGetPackageArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.GoGetPackage(ctx, a0) + case ListImports: + var a0 URIArg + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.ListImports(ctx, a0) + case ListKnownPackages: + var a0 URIArg + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.ListKnownPackages(ctx, a0) + case MaybePromptForTelemetry: + return nil, s.MaybePromptForTelemetry(ctx) + case MemStats: + return s.MemStats(ctx) + case Modules: + var a0 ModulesArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.Modules(ctx, a0) + case Packages: + var a0 PackagesArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.Packages(ctx, a0) + case RegenerateCgo: + var a0 URIArg + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.RegenerateCgo(ctx, a0) + case RemoveDependency: + var a0 RemoveDependencyArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.RemoveDependency(ctx, a0) + case ResetGoModDiagnostics: + var a0 ResetGoModDiagnosticsArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.ResetGoModDiagnostics(ctx, a0) + case RunGoWorkCommand: + var a0 RunGoWorkArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.RunGoWorkCommand(ctx, a0) + case RunGovulncheck: + var a0 VulncheckArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.RunGovulncheck(ctx, a0) + case RunTests: + var a0 RunTestsArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.RunTests(ctx, a0) + case ScanImports: + return nil, s.ScanImports(ctx) + case StartDebugging: + var a0 DebuggingArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.StartDebugging(ctx, a0) + case StartProfile: + var a0 StartProfileArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.StartProfile(ctx, a0) + case StopProfile: + var a0 StopProfileArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return s.StopProfile(ctx, a0) + case Test: + var a0 protocol.DocumentURI + var a1 []string + var a2 []string + if err := UnmarshalArgs(params.Arguments, &a0, &a1, &a2); err != nil { + return nil, err + } + return nil, s.Test(ctx, a0, a1, a2) + case Tidy: + var a0 URIArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.Tidy(ctx, a0) + case ToggleGCDetails: + var a0 URIArg + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.ToggleGCDetails(ctx, a0) + case UpdateGoSum: + var a0 URIArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.UpdateGoSum(ctx, a0) + case UpgradeDependency: + var a0 DependencyArgs + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.UpgradeDependency(ctx, a0) + case Vendor: + var a0 URIArg + if err := UnmarshalArgs(params.Arguments, &a0); err != nil { + return nil, err + } + return nil, s.Vendor(ctx, a0) + case Views: + return s.Views(ctx) + case WorkspaceStats: + return s.WorkspaceStats(ctx) + } + return nil, fmt.Errorf("unsupported command %q", params.Command) +} + +func NewAddDependencyCommand(title string, a0 DependencyArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: AddDependency.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewAddImportCommand(title string, a0 AddImportArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: AddImport.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewAddTelemetryCountersCommand(title string, a0 AddTelemetryCountersArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: AddTelemetryCounters.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewApplyFixCommand(title string, a0 ApplyFixArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ApplyFix.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewAssemblyCommand(title string, a0 string, a1 string, a2 string) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Assembly.String(), + Arguments: MustMarshalArgs(a0, a1, a2), + } +} + +func NewChangeSignatureCommand(title string, a0 ChangeSignatureArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ChangeSignature.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewCheckUpgradesCommand(title string, a0 CheckUpgradesArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: CheckUpgrades.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewClientOpenURLCommand(title string, a0 string) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ClientOpenURL.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewDiagnoseFilesCommand(title string, a0 DiagnoseFilesArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: DiagnoseFiles.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewDocCommand(title string, a0 protocol.Location) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Doc.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewEditGoDirectiveCommand(title string, a0 EditGoDirectiveArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: EditGoDirective.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewExtractToNewFileCommand(title string, a0 protocol.Location) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ExtractToNewFile.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewFetchVulncheckResultCommand(title string, a0 URIArg) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: FetchVulncheckResult.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewFreeSymbolsCommand(title string, a0 string, a1 protocol.Location) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: FreeSymbols.String(), + Arguments: MustMarshalArgs(a0, a1), + } +} + +func NewGCDetailsCommand(title string, a0 protocol.DocumentURI) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: GCDetails.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewGenerateCommand(title string, a0 GenerateArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Generate.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewGoGetPackageCommand(title string, a0 GoGetPackageArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: GoGetPackage.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewListImportsCommand(title string, a0 URIArg) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ListImports.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewListKnownPackagesCommand(title string, a0 URIArg) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ListKnownPackages.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewMaybePromptForTelemetryCommand(title string) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: MaybePromptForTelemetry.String(), + Arguments: MustMarshalArgs(), + } +} + +func NewMemStatsCommand(title string) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: MemStats.String(), + Arguments: MustMarshalArgs(), + } +} + +func NewModulesCommand(title string, a0 ModulesArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Modules.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewPackagesCommand(title string, a0 PackagesArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Packages.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewRegenerateCgoCommand(title string, a0 URIArg) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: RegenerateCgo.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewRemoveDependencyCommand(title string, a0 RemoveDependencyArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: RemoveDependency.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewResetGoModDiagnosticsCommand(title string, a0 ResetGoModDiagnosticsArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ResetGoModDiagnostics.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewRunGoWorkCommandCommand(title string, a0 RunGoWorkArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: RunGoWorkCommand.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewRunGovulncheckCommand(title string, a0 VulncheckArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: RunGovulncheck.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewRunTestsCommand(title string, a0 RunTestsArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: RunTests.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewScanImportsCommand(title string) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ScanImports.String(), + Arguments: MustMarshalArgs(), + } +} + +func NewStartDebuggingCommand(title string, a0 DebuggingArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: StartDebugging.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewStartProfileCommand(title string, a0 StartProfileArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: StartProfile.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewStopProfileCommand(title string, a0 StopProfileArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: StopProfile.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewTestCommand(title string, a0 protocol.DocumentURI, a1 []string, a2 []string) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Test.String(), + Arguments: MustMarshalArgs(a0, a1, a2), + } +} + +func NewTidyCommand(title string, a0 URIArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Tidy.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewToggleGCDetailsCommand(title string, a0 URIArg) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: ToggleGCDetails.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewUpdateGoSumCommand(title string, a0 URIArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: UpdateGoSum.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewUpgradeDependencyCommand(title string, a0 DependencyArgs) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: UpgradeDependency.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewVendorCommand(title string, a0 URIArg) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Vendor.String(), + Arguments: MustMarshalArgs(a0), + } +} + +func NewViewsCommand(title string) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: Views.String(), + Arguments: MustMarshalArgs(), + } +} + +func NewWorkspaceStatsCommand(title string) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: WorkspaceStats.String(), + Arguments: MustMarshalArgs(), + } +} diff --git a/contribs/gnopls/internal/protocol/command/commandmeta/meta.go b/contribs/gnopls/internal/protocol/command/commandmeta/meta.go new file mode 100644 index 00000000000..7cc4786f549 --- /dev/null +++ b/contribs/gnopls/internal/protocol/command/commandmeta/meta.go @@ -0,0 +1,264 @@ +// 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 commandmeta provides metadata about LSP commands, by +// statically analyzing the command.Interface type. +// +// It is used to generate JSONRPC dispatch and marshaling. +// TODO(adonovan): combine with gopls/internal/protocol/command/gen. +package commandmeta + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" + "strings" + "unicode" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/internal/aliases" + // (does not depend on gopls itself) +) + +// A Command describes a workspace/executeCommand extension command. +type Command struct { + MethodName string // e.g. "RunTests" + Name string // e.g. "gopls.run_tests" + Title string + Doc string + Args []*Field + Result *Field +} + +type Field struct { + Name string + Doc string + JSONTag string + Type types.Type + FieldMod string + // In some circumstances, we may want to recursively load additional field + // descriptors for fields of struct types, documenting their internals. + Fields []*Field +} + +// Load returns a description of the workspace/executeCommand commands +// supported by gopls based on static analysis of the command.Interface type. +func Load() ([]*Command, error) { + pkgs, err := packages.Load( + &packages.Config{ + Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps, + BuildFlags: []string{"-tags=generate"}, + }, + "golang.org/x/tools/gopls/internal/protocol/command", + ) + if err != nil { + return nil, fmt.Errorf("packages.Load: %v", err) + } + pkg := pkgs[0] + if len(pkg.Errors) > 0 { + return nil, pkg.Errors[0] + } + + // command.Interface + obj := pkg.Types.Scope().Lookup("Interface").Type().Underlying().(*types.Interface) + + // Load command metadata corresponding to each interface method. + var commands []*Command + loader := fieldLoader{make(map[types.Object]*Field)} + for i := 0; i < obj.NumMethods(); i++ { + m := obj.Method(i) + c, err := loader.loadMethod(pkg, m) + if err != nil { + return nil, fmt.Errorf("loading %s: %v", m.Name(), err) + } + commands = append(commands, c) + } + return commands, nil +} + +// fieldLoader loads field information, memoizing results to prevent infinite +// recursion. +type fieldLoader struct { + loaded map[types.Object]*Field +} + +var universeError = types.Universe.Lookup("error").Type() + +func (l *fieldLoader) loadMethod(pkg *packages.Package, m *types.Func) (*Command, error) { + node, err := findField(pkg, m.Pos()) + if err != nil { + return nil, err + } + title, doc := splitDoc(node.Doc.Text()) + c := &Command{ + MethodName: m.Name(), + Name: lspName(m.Name()), + Doc: doc, + Title: title, + } + sig := m.Type().Underlying().(*types.Signature) + rlen := sig.Results().Len() + if rlen > 2 || rlen == 0 { + return nil, fmt.Errorf("must have 1 or 2 returns, got %d", rlen) + } + finalResult := sig.Results().At(rlen - 1) + if !types.Identical(finalResult.Type(), universeError) { + return nil, fmt.Errorf("final return must be error") + } + if rlen == 2 { + obj := sig.Results().At(0) + c.Result, err = l.loadField(pkg, obj, "", "") + if err != nil { + return nil, err + } + } + for i := 0; i < sig.Params().Len(); i++ { + obj := sig.Params().At(i) + fld, err := l.loadField(pkg, obj, "", "") + if err != nil { + return nil, err + } + if i == 0 { + // Lazy check that the first argument is a context. We could relax this, + // but then the generated code gets more complicated. + if named, ok := aliases.Unalias(fld.Type).(*types.Named); !ok || named.Obj().Name() != "Context" || named.Obj().Pkg().Path() != "context" { + return nil, fmt.Errorf("first method parameter must be context.Context") + } + // Skip the context argument, as it is implied. + continue + } + c.Args = append(c.Args, fld) + } + return c, nil +} + +func (l *fieldLoader) loadField(pkg *packages.Package, obj *types.Var, doc, tag string) (*Field, error) { + if existing, ok := l.loaded[obj]; ok { + return existing, nil + } + fld := &Field{ + Name: obj.Name(), + Doc: strings.TrimSpace(doc), + Type: obj.Type(), + JSONTag: reflect.StructTag(tag).Get("json"), + } + + // This must be done here to handle nested types, such as: + // + // type Test struct { Subtests []Test } + l.loaded[obj] = fld + + under := fld.Type.Underlying() + // Quick-and-dirty handling for various underlying types. + switch p := under.(type) { + case *types.Pointer: + under = p.Elem().Underlying() + case *types.Array: + under = p.Elem().Underlying() + fld.FieldMod = fmt.Sprintf("[%d]", p.Len()) + case *types.Slice: + under = p.Elem().Underlying() + fld.FieldMod = "[]" + } + + if s, ok := under.(*types.Struct); ok { + for i := 0; i < s.NumFields(); i++ { + obj2 := s.Field(i) + pkg2 := pkg + if obj2.Pkg() != pkg2.Types { + pkg2, ok = pkg.Imports[obj2.Pkg().Path()] + if !ok { + return nil, fmt.Errorf("missing import for %q: %q", pkg.ID, obj2.Pkg().Path()) + } + } + node, err := findField(pkg2, obj2.Pos()) + if err != nil { + return nil, err + } + tag := s.Tag(i) + structField, err := l.loadField(pkg2, obj2, node.Doc.Text(), tag) + if err != nil { + return nil, err + } + fld.Fields = append(fld.Fields, structField) + } + } + return fld, nil +} + +// splitDoc parses a command doc string to separate the title from normal +// documentation. +// +// The doc comment should be of the form: "MethodName: Title\nDocumentation" +func splitDoc(text string) (title, doc string) { + docParts := strings.SplitN(text, "\n", 2) + titleParts := strings.SplitN(docParts[0], ":", 2) + if len(titleParts) > 1 { + title = strings.TrimSpace(titleParts[1]) + } + if len(docParts) > 1 { + doc = strings.TrimSpace(docParts[1]) + } + return title, doc +} + +// lspName returns the normalized command name to use in the LSP. +func lspName(methodName string) string { + words := splitCamel(methodName) + for i := range words { + words[i] = strings.ToLower(words[i]) + } + return "gopls." + strings.Join(words, "_") +} + +// splitCamel splits s into words, according to camel-case word boundaries. +// Initialisms are grouped as a single word. +// +// For example: +// +// "RunTests" -> []string{"Run", "Tests"} +// "GCDetails" -> []string{"GC", "Details"} +func splitCamel(s string) []string { + var words []string + for len(s) > 0 { + last := strings.LastIndexFunc(s, unicode.IsUpper) + if last < 0 { + last = 0 + } + if last == len(s)-1 { + // Group initialisms as a single word. + last = 1 + strings.LastIndexFunc(s[:last], func(r rune) bool { return !unicode.IsUpper(r) }) + } + words = append(words, s[last:]) + s = s[:last] + } + for i := 0; i < len(words)/2; i++ { + j := len(words) - i - 1 + words[i], words[j] = words[j], words[i] + } + return words +} + +// findField finds the struct field or interface method positioned at pos, +// within the AST. +func findField(pkg *packages.Package, pos token.Pos) (*ast.Field, error) { + fset := pkg.Fset + var file *ast.File + for _, f := range pkg.Syntax { + if fset.File(f.Pos()).Name() == fset.File(pos).Name() { + file = f + break + } + } + if file == nil { + return nil, fmt.Errorf("no file for pos %v", pos) + } + path, _ := astutil.PathEnclosingInterval(file, pos, pos) + // This is fragile, but in the cases we care about, the field will be in + // path[1]. + return path[1].(*ast.Field), nil +} diff --git a/contribs/gnopls/internal/protocol/command/gen/gen.go b/contribs/gnopls/internal/protocol/command/gen/gen.go new file mode 100644 index 00000000000..d9722902ca9 --- /dev/null +++ b/contribs/gnopls/internal/protocol/command/gen/gen.go @@ -0,0 +1,204 @@ +// 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 gen is used to generate command bindings from the gopls command +// interface. +package gen + +import ( + "bytes" + "fmt" + "go/types" + "log" + "text/template" + + "golang.org/x/tools/gopls/internal/protocol/command/commandmeta" + "golang.org/x/tools/internal/imports" +) + +const src = `// 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. + +// Don't include this file during code generation, or it will break the build +// if existing interface methods have been modified. +//go:build !generate +// +build !generate + +// Code generated by gen.go from gopls/internal/protocol/command. DO NOT EDIT. + +package command + +import ( + {{range $k, $v := .Imports -}} + "{{$k}}" + {{end}} +) + +// Symbolic names for gopls commands, corresponding to methods of [Interface]. +// +// The string value is used in the Command field of protocol.Command. +// These commands may be obtained from a CodeLens or CodeAction request +// and executed by an ExecuteCommand request. +const ( +{{- range .Commands}} + {{.MethodName}} Command = "{{.Name}}" +{{- end}} +) + +var Commands = []Command { +{{- range .Commands}} + {{.MethodName}}, +{{- end}} +} + +func Dispatch(ctx context.Context, params *protocol.ExecuteCommandParams, s Interface) (interface{}, error) { + switch Command(params.Command) { + {{- range .Commands}} + case {{.MethodName}}: + {{- if .Args -}} + {{- range $i, $v := .Args}} + var a{{$i}} {{typeString $v.Type}} + {{- end}} + if err := UnmarshalArgs(params.Arguments{{range $i, $v := .Args}}, &a{{$i}}{{end}}); err != nil { + return nil, err + } + {{end -}} + return {{if not .Result}}nil, {{end}}s.{{.MethodName}}(ctx{{range $i, $v := .Args}}, a{{$i}}{{end}}) + {{- end}} + } + return nil, fmt.Errorf("unsupported command %q", params.Command) +} +{{- range .Commands}} + +{{if fallible .Args}} +func New{{.MethodName}}Command(title string, {{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}} {{typeString $v.Type}}{{end}}) (*protocol.Command, error) { + args, err := MarshalArgs({{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}}{{end}}) + if err != nil { + return nil, err + } + return &protocol.Command{ + Title: title, + Command: {{.MethodName}}.String(), + Arguments: args, + }, nil +} +{{else}} +func New{{.MethodName}}Command(title string, {{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}} {{typeString $v.Type}}{{end}}) *protocol.Command { + return &protocol.Command{ + Title: title, + Command: {{.MethodName}}.String(), + Arguments: MustMarshalArgs({{range $i, $v := .Args}}{{if $i}}, {{end}}a{{$i}}{{end}}), + } +} +{{end}} + +{{end}} +` + +type data struct { + Imports map[string]bool + Commands []*commandmeta.Command +} + +// Generate computes the new contents of ../command_gen.go from a +// static analysis of the command.Interface type. +func Generate() ([]byte, error) { + cmds, err := commandmeta.Load() + if err != nil { + return nil, fmt.Errorf("loading command data: %v", err) + } + const thispkg = "golang.org/x/tools/gopls/internal/protocol/command" + qf := func(p *types.Package) string { + if p.Path() == thispkg { + return "" + } + return p.Name() + } + tmpl, err := template.New("").Funcs(template.FuncMap{ + "typeString": func(t types.Type) string { + return types.TypeString(t, qf) + }, + "fallible": func(args []*commandmeta.Field) bool { + var fallible func(types.Type) bool + fallible = func(t types.Type) bool { + switch t := t.Underlying().(type) { + case *types.Basic: + return false + case *types.Slice: + return fallible(t.Elem()) + case *types.Struct: + for i := 0; i < t.NumFields(); i++ { + if fallible(t.Field(i).Type()) { + return true + } + } + return false + } + // Assume all other types are fallible for now: + log.Println("Command.Args has fallible type", t) + return true + } + for _, arg := range args { + if fallible(arg.Type) { + return true + } + } + return false + }, + }).Parse(src) + if err != nil { + return nil, err + } + d := data{ + Commands: cmds, + Imports: map[string]bool{ + "context": true, + "fmt": true, + "golang.org/x/tools/gopls/internal/protocol": true, + }, + } + for _, c := range d.Commands { + for _, arg := range c.Args { + pth := pkgPath(arg.Type) + if pth != "" && pth != thispkg { + d.Imports[pth] = true + } + } + if c.Result != nil { + pth := pkgPath(c.Result.Type) + if pth != "" && pth != thispkg { + d.Imports[pth] = true + } + } + } + + var buf bytes.Buffer + if err := tmpl.Execute(&buf, d); err != nil { + return nil, fmt.Errorf("executing: %v", err) + } + + opts := &imports.Options{ + AllErrors: true, + FormatOnly: true, + Comments: true, + } + content, err := imports.Process("", buf.Bytes(), opts) + if err != nil { + return nil, fmt.Errorf("goimports: %v", err) + } + return content, nil +} + +func pkgPath(t types.Type) string { + type hasTypeName interface { // *Named or *Alias (or *TypeParam) + Obj() *types.TypeName + } + if t, ok := t.(hasTypeName); ok { + if pkg := t.Obj().Pkg(); pkg != nil { + return pkg.Path() + } + } + return "" +} diff --git a/contribs/gnopls/internal/protocol/command/generate.go b/contribs/gnopls/internal/protocol/command/generate.go new file mode 100644 index 00000000000..324bc51ccab --- /dev/null +++ b/contribs/gnopls/internal/protocol/command/generate.go @@ -0,0 +1,27 @@ +// 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. + +//go:build ignore +// +build ignore + +// The generate command generates command_gen.go from a combination of +// static and dynamic analysis of the command package. +package main + +import ( + "log" + "os" + + "golang.org/x/tools/gopls/internal/protocol/command/gen" +) + +func main() { + content, err := gen.Generate() + if err != nil { + log.Fatal(err) + } + if err := os.WriteFile("command_gen.go", content, 0644); err != nil { + log.Fatal(err) + } +} diff --git a/contribs/gnopls/internal/protocol/command/interface.go b/contribs/gnopls/internal/protocol/command/interface.go new file mode 100644 index 00000000000..4ea9157d7c1 --- /dev/null +++ b/contribs/gnopls/internal/protocol/command/interface.go @@ -0,0 +1,735 @@ +// 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. + +//go:generate go run -tags=generate generate.go + +// Package command defines the interface provided by gopls for the +// workspace/executeCommand LSP request. +// +// This interface is fully specified by the Interface type, provided it +// conforms to the restrictions outlined in its doc string. +// +// Bindings for server-side command dispatch and client-side serialization are +// also provided by this package, via code generation. +package command + +import ( + "context" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/vulncheck" +) + +// Interface defines the interface gopls exposes for the +// workspace/executeCommand request. +// +// This interface is used to generate logic for marshaling, +// unmarshaling, and dispatch, so it has some additional restrictions: +// +// 1. All method arguments must be JSON serializable. +// +// 2. Methods must return either error or (T, error), where T is a +// JSON serializable type. +// +// 3. The first line of the doc string is special. +// Everything after the colon is considered the command 'Title'. +// For example: +// +// Command: Capitalized verb phrase with no period +// +// Longer description here... +type Interface interface { + // ApplyFix: Apply a fix + // + // Applies a fix to a region of source code. + ApplyFix(context.Context, ApplyFixArgs) (*protocol.WorkspaceEdit, error) + + // 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. + Test(context.Context, protocol.DocumentURI, []string, []string) error + + // Test: 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. + RunTests(context.Context, RunTestsArgs) error + + // Generate: Run go generate + // + // Runs `go generate` for a given directory. + Generate(context.Context, GenerateArgs) error + + // Doc: Browse package documentation. + // + // Opens the Go package documentation page for the current + // package in a browser. + Doc(context.Context, protocol.Location) error + + // RegenerateCgo: Regenerate cgo + // + // Regenerates cgo definitions. + RegenerateCgo(context.Context, URIArg) error + + // Tidy: Run go mod tidy + // + // Runs `go mod tidy` for a module. + Tidy(context.Context, URIArgs) error + + // Vendor: Run go mod vendor + // + // Runs `go mod vendor` for a module. + Vendor(context.Context, URIArg) error + + // EditGoDirective: Run go mod edit -go=version + // + // Runs `go mod edit -go=version` for a module. + EditGoDirective(context.Context, EditGoDirectiveArgs) error + + // UpdateGoSum: Update go.sum + // + // Updates the go.sum file for a module. + UpdateGoSum(context.Context, URIArgs) error + + // CheckUpgrades: Check for upgrades + // + // Checks for module upgrades. + CheckUpgrades(context.Context, CheckUpgradesArgs) error + + // AddDependency: Add a dependency + // + // Adds a dependency to the go.mod file for a module. + AddDependency(context.Context, DependencyArgs) error + + // UpgradeDependency: Upgrade a dependency + // + // Upgrades a dependency in the go.mod file for a module. + UpgradeDependency(context.Context, DependencyArgs) error + + // RemoveDependency: Remove a dependency + // + // Removes a dependency from the go.mod file of a module. + RemoveDependency(context.Context, RemoveDependencyArgs) error + + // ResetGoModDiagnostics: Reset go.mod diagnostics + // + // Reset diagnostics in the go.mod file of a module. + ResetGoModDiagnostics(context.Context, ResetGoModDiagnosticsArgs) error + + // GoGetPackage: 'go get' a package + // + // Runs `go get` to fetch a package. + GoGetPackage(context.Context, GoGetPackageArgs) error + + // GCDetails: Toggle gc_details + // + // Toggle the calculation of gc annotations. + GCDetails(context.Context, protocol.DocumentURI) error + + // TODO: deprecate GCDetails in favor of ToggleGCDetails below. + + // ToggleGCDetails: Toggle gc_details + // + // Toggle the calculation of gc annotations. + ToggleGCDetails(context.Context, URIArg) error + + // ListKnownPackages: List known packages + // + // Retrieve a list of packages that are importable from the given URI. + ListKnownPackages(context.Context, URIArg) (ListKnownPackagesResult, error) + + // ListImports: List imports of a file and its package + // + // Retrieve a list of imports in the given Go file, and the package it + // belongs to. + ListImports(context.Context, URIArg) (ListImportsResult, error) + + // AddImport: 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. + AddImport(context.Context, AddImportArgs) error + + // ExtractToNewFile: Move selected declarations to a new file + // + // Used by the code action of the same name. + ExtractToNewFile(context.Context, protocol.Location) error + + // StartDebugging: Start the gopls debug server + // + // Start the gopls debug server if it isn't running, and return the debug + // address. + StartDebugging(context.Context, DebuggingArgs) (DebuggingResult, error) + + // StartProfile: 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. + StartProfile(context.Context, StartProfileArgs) (StartProfileResult, error) + + // StopProfile: Stop an ongoing profile + // + // This command is intended for internal use only, by the gopls benchmark + // runner. + StopProfile(context.Context, StopProfileArgs) (StopProfileResult, error) + + // RunGovulncheck: Run vulncheck + // + // Run vulnerability check (`govulncheck`). + // + // This command is asynchronous; clients must wait for the 'end' progress notification. + RunGovulncheck(context.Context, VulncheckArgs) (RunVulncheckResult, error) + + // FetchVulncheckResult: Get known vulncheck result + // + // Fetch the result of latest vulnerability check (`govulncheck`). + FetchVulncheckResult(context.Context, URIArg) (map[protocol.DocumentURI]*vulncheck.Result, error) + + // MemStats: 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. + MemStats(context.Context) (MemStatsResult, error) + + // WorkspaceStats: 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. + WorkspaceStats(context.Context) (WorkspaceStatsResult, error) + + // RunGoWorkCommand: Run `go work [args...]`, and apply the resulting go.work + // edits to the current go.work file + RunGoWorkCommand(context.Context, RunGoWorkArgs) error + + // AddTelemetryCounters: 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. + AddTelemetryCounters(context.Context, AddTelemetryCountersArgs) error + + // MaybePromptForTelemetry: 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". + MaybePromptForTelemetry(context.Context) error + + // ChangeSignature: Perform a "change signature" refactoring + // + // This command is experimental, currently only supporting parameter removal. + // Its signature will certainly change in the future (pun intended). + ChangeSignature(context.Context, ChangeSignatureArgs) (*protocol.WorkspaceEdit, error) + + // DiagnoseFiles: Cause server to publish diagnostics for the specified files. + // + // This command is needed by the 'gopls {check,fix}' CLI subcommands. + DiagnoseFiles(context.Context, DiagnoseFilesArgs) error + + // Views: List current Views on the server. + // + // This command is intended for use by gopls tests only. + Views(context.Context) ([]View, error) + + // FreeSymbols: 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. + FreeSymbols(ctx context.Context, viewID string, loc protocol.Location) error + + // 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. + Assembly(_ context.Context, viewID, packageID, symbol string) error + + // ClientOpenURL: Request that the client open a URL in a browser. + ClientOpenURL(_ context.Context, url string) error + + // ScanImports: force a sychronous scan of the imports cache. + // + // This command is intended for use by gopls tests only. + ScanImports(context.Context) error + + // Packages: Return information about packages + // + // This command returns an empty result if the specified files + // or directories are not associated with any Views on the + // server yet. + Packages(context.Context, PackagesArgs) (PackagesResult, error) + + // Modules: Return information about modules within a directory + // + // This command returns an empty result if there is no module, or if module + // mode is disabled. Modules will not cause any new views to be loaded and + // will only return modules associated with views that have already been + // loaded, regardless of how it is called. Given current usage (by the + // language server client), there should never be a case where Modules is + // called on a path that has not already been loaded. + Modules(context.Context, ModulesArgs) (ModulesResult, error) +} + +type RunTestsArgs struct { + // The test file containing the tests to run. + URI protocol.DocumentURI + + // Specific test names to run, e.g. TestFoo. + Tests []string + + // Specific benchmarks to run, e.g. BenchmarkFoo. + Benchmarks []string +} + +type GenerateArgs struct { + // URI for the directory to generate. + Dir protocol.DocumentURI + + // Whether to generate recursively (go generate ./...) + Recursive bool +} + +// TODO(rFindley): document the rest of these once the docgen is fleshed out. + +type ApplyFixArgs struct { + // 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 protocol.DocumentURI + // The document range to scan for fixes. + Range protocol.Range + // Whether to resolve and return the edits. + ResolveEdits bool +} + +type URIArg struct { + // The file URI. + URI protocol.DocumentURI +} + +type URIArgs struct { + // The file URIs. + URIs []protocol.DocumentURI +} + +type CheckUpgradesArgs struct { + // The go.mod file URI. + URI protocol.DocumentURI + // The modules to check. + Modules []string +} + +type DependencyArgs struct { + // The go.mod file URI. + URI protocol.DocumentURI + // Additional args to pass to the go command. + GoCmdArgs []string + // Whether to add a require directive. + AddRequire bool +} + +type RemoveDependencyArgs struct { + // The go.mod file URI. + URI protocol.DocumentURI + // 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 +} + +type EditGoDirectiveArgs struct { + // Any document URI within the relevant module. + URI protocol.DocumentURI + // The version to pass to `go mod edit -go`. + Version string +} + +type GoGetPackageArgs struct { + // Any document URI within the relevant module. + URI protocol.DocumentURI + // The package to go get. + Pkg string + AddRequire bool +} + +type AddImportArgs struct { + // 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 protocol.DocumentURI +} + +type ListKnownPackagesResult struct { + // 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 +} + +type ListImportsResult struct { + // Imports is a list of imports in the requested file. + Imports []FileImport + + // PackageImports is a list of all imports in the requested file's package. + PackageImports []PackageImport +} + +type FileImport struct { + // Path is the import path of the import. + Path string + // Name is the name of the import, e.g. `foo` in `import foo "strings"`. + Name string +} + +type PackageImport struct { + // Path is the import path of the import. + Path string +} + +type DebuggingArgs struct { + // 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 +} + +type DebuggingResult struct { + // 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 +} + +// StartProfileArgs holds the arguments to the StartProfile command. +// +// It is a placeholder for future compatibility. +type StartProfileArgs struct { +} + +// StartProfileResult holds the result of the StartProfile command. +// +// It is a placeholder for future compatibility. +type StartProfileResult struct { +} + +// StopProfileArgs holds the arguments to the StopProfile command. +// +// It is a placeholder for future compatibility. +type StopProfileArgs struct { +} + +// StopProfileResult holds the result to the StopProfile command. +type StopProfileResult struct { + // File is the profile file name. + File string +} + +type ResetGoModDiagnosticsArgs struct { + URIArg + + // Optional: source of the diagnostics to reset. + // If not set, all resettable go.mod diagnostics will be cleared. + DiagnosticSource string +} + +type VulncheckArgs struct { + // Any document in the directory from which govulncheck will run. + URI protocol.DocumentURI + + // Package pattern. E.g. "", ".", "./...". + Pattern string + + // TODO: -tests +} + +// RunVulncheckResult holds the result of asynchronously starting the vulncheck +// command. +type RunVulncheckResult struct { + // Token holds the progress token for LSP workDone reporting of the vulncheck + // invocation. + Token protocol.ProgressToken +} + +// CallStack models a trace of function calls starting +// with a client function or method and ending with a +// call to a vulnerable symbol. +type CallStack []StackEntry + +// StackEntry models an element of a call stack. +type StackEntry struct { + // See golang.org/x/exp/vulncheck.StackEntry. + + // User-friendly representation of function/method names. + // e.g. package.funcName, package.(recvType).methodName, ... + Name string + URI protocol.DocumentURI + Pos protocol.Position // Start position. (0-based. Column is always 0) +} + +// MemStatsResult holds selected fields from runtime.MemStats. +type MemStatsResult struct { + HeapAlloc uint64 + HeapInUse uint64 + TotalAlloc uint64 +} + +// WorkspaceStatsResult returns information about the size and shape of the +// workspace. +type WorkspaceStatsResult struct { + Files FileStats // file stats for the cache + Views []ViewStats // stats for each view in the session +} + +// FileStats holds information about a set of files. +type FileStats struct { + Total int // total number of files + Largest int // number of bytes in the largest file + Errs int // number of files that could not be read +} + +// ViewStats holds information about a single View in the session. +type ViewStats struct { + GoCommandVersion string // version of the Go command resolved for this view + AllPackages PackageStats // package info for all packages (incl. dependencies) + WorkspacePackages PackageStats // package info for workspace packages + Diagnostics int // total number of diagnostics in the workspace +} + +// PackageStats holds information about a collection of packages. +type PackageStats struct { + Packages int // total number of packages + LargestPackage int // number of files in the largest package + CompiledGoFiles int // total number of compiled Go files across all packages + Modules int // total number of unique modules +} + +type RunGoWorkArgs struct { + ViewID string // ID of the view to run the command from + InitFirst bool // Whether to run `go work init` first + Args []string // Args to pass to `go work` +} + +// AddTelemetryCountersArgs holds the arguments to the AddCounters command +// that updates the telemetry counters. +type AddTelemetryCountersArgs struct { + // Names and Values must have the same length. + Names []string // Name of counters. + Values []int64 // Values added to the corresponding counters. Must be non-negative. +} + +// ChangeSignatureArgs specifies a "change signature" refactoring to perform. +type ChangeSignatureArgs struct { + RemoveParameter protocol.Location + // Whether to resolve and return the edits. + ResolveEdits bool +} + +// DiagnoseFilesArgs specifies a set of files for which diagnostics are wanted. +type DiagnoseFilesArgs struct { + Files []protocol.DocumentURI +} + +// A View holds summary information about a cache.View. +type View struct { + ID string // view ID (the index of this view among all views created) + Type string // view type (via cache.ViewType.String) + Root protocol.DocumentURI // root dir of the view (e.g. containing go.mod or go.work) + Folder protocol.DocumentURI // workspace folder associated with the view + EnvOverlay []string // environment variable overrides +} + +// PackagesArgs holds arguments for the Packages command. +type PackagesArgs struct { + // Files is a list of files and directories whose associated + // packages should be described by the result. + // + // In some cases, a file may belong to more than one package; + // the result may describe any of them. + Files []protocol.DocumentURI + + // Enumerate all packages under the directory loadable with + // the ... pattern. + // The search does not cross the module boundaries and + // does not return packages that are not yet loaded. + // (e.g. those excluded by the gopls directory filter setting, + // or the go.work configuration) + Recursive bool `json:"Recursive,omitempty"` + + // Mode controls the types of information returned for each package. + Mode PackagesMode +} + +// PackagesMode controls the details to include in PackagesResult. +type PackagesMode uint64 + +const ( + // Populate the [TestFile.Tests] field in [Package] returned by the + // Packages command. + NeedTests PackagesMode = 1 << iota +) + +// PackagesResult is the result of the Packages command. +type PackagesResult struct { + // Packages is an unordered list of package metadata. + Packages []Package + + // Modules maps module path to module metadata for + // all the modules of the returned Packages. + Module map[string]Module +} + +// Package describes a Go package (not an empty parent). +type Package struct { + // Package path. + Path string + // Module path. Empty if the package doesn't + // belong to any module. + ModulePath string + // q in a "p [q.test]" package. + ForTest string + + // Note: the result does not include the directory name + // of the package because mapping between a package and + // a folder is not possible in certain build systems. + // If directory info is needed, one can guess it + // from the TestFile's file name. + + // TestFiles contains the subset of the files of the package + // whose name ends with "_test.go". + // They are ordered deterministically as determined + // by the underlying build system. + TestFiles []TestFile +} + +type Module struct { + Path string // module path + Version string // module version if any. + GoMod protocol.DocumentURI // path to the go.mod file. +} + +type TestFile struct { + URI protocol.DocumentURI // a *_test.go file + + // Tests is the list of tests in File, including subtests. + // + // The set of subtests is not exhaustive as in general they may be + // dynamically generated, so it is impossible for static heuristics + // to enumerate them. + // + // Tests are lexically ordered. + // Since subtest names are prefixed by their top-level test names + // each top-level test precedes its subtests. + Tests []TestCase +} + +// TestCase represents a test case. +// A test case can be a top-level Test/Fuzz/Benchmark/Example function, +// as recognized by 'go list' or 'go test -list', or +// a subtest within a top-level function. +type TestCase struct { + // Name is the complete name of the test (Test, Benchmark, Example, or Fuzz) + // or the subtest as it appears in the output of go test -json. + // The server may attempt to infer names of subtests by static + // analysis; if so, it should aim to simulate the actual computed + // name of the test, including any disambiguating suffix such as "#01". + // To run only this test, clients need to compute the -run, -bench, -fuzz + // flag values by first splitting the Name with "/" and + // quoting each element with "^" + regexp.QuoteMeta(Name) + "$". + // e.g. TestToplevel/Inner.Subtest → -run=^TestToplevel$/^Inner\.Subtest$ + Name string + + // Loc is the filename and range enclosing this test function + // or the subtest. This is used to place the gutter marker + // and group tests based on location. + // For subtests whose test names can be determined statically, + // this can be either t.Run or the test data table + // for table-driven setup. + // Some testing frameworks allow to declare the actual test + // logic in a different file. For example, one can define + // a testify test suite in suite_test.go and use it from + // main_test.go. + /* + -- main_test.go -- + ... + func TestFoo(t *testing.T) { + suite.Run(t, new(MyTestSuite)) + } + -- suite_test.go -- + type MyTestSuite struct { + suite.Suite + } + func (suite *MyTestSuite) TestBar() { ... } + */ + // In this case, the testing framework creates "TestFoo/TestBar" + // and the corresponding test case belongs to "main_test.go" + // TestFile. However, the test case has "suite_test.go" as its + // file location. + Loc protocol.Location +} + +type ModulesArgs struct { + // Dir is the directory in which to search for go.mod files. + Dir protocol.DocumentURI + + // MaxDepth is the directory walk limit. + // A value of 0 means inspect only Dir. + // 1 means inspect its child directories too, and so on. + // A negative value removes the limit. + MaxDepth int +} + +type ModulesResult struct { + Modules []Module +} diff --git a/contribs/gnopls/internal/protocol/command/interface_test.go b/contribs/gnopls/internal/protocol/command/interface_test.go new file mode 100644 index 00000000000..ca880619f0e --- /dev/null +++ b/contribs/gnopls/internal/protocol/command/interface_test.go @@ -0,0 +1,33 @@ +// 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 command_test + +import ( + "os" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol/command/gen" + "golang.org/x/tools/internal/testenv" +) + +// TestGenerated ensures that we haven't forgotten to update command_gen.go. +func TestGenerated(t *testing.T) { + testenv.NeedsGoPackages(t) + testenv.NeedsLocalXTools(t) + + onDisk, err := os.ReadFile("command_gen.go") + if err != nil { + t.Fatal(err) + } + + generated, err := gen.Generate() + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(string(generated), string(onDisk)); diff != "" { + t.Errorf("command_gen.go is stale -- regenerate (-generated +on disk)\n%s", diff) + } +} diff --git a/contribs/gnopls/internal/protocol/command/util.go b/contribs/gnopls/internal/protocol/command/util.go new file mode 100644 index 00000000000..7cd5662e3e1 --- /dev/null +++ b/contribs/gnopls/internal/protocol/command/util.go @@ -0,0 +1,79 @@ +// 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 command + +import ( + "encoding/json" + "fmt" +) + +// A Command identifies one of gopls' ad-hoc extension commands +// that may be invoked through LSP's executeCommand. +type Command string + +func (c Command) String() string { return string(c) } + +// IsAsync reports whether the command is asynchronous: +// clients must wait for the "end" progress notification. +func (c Command) IsAsync() bool { + switch string(c) { + // TODO(adonovan): derive this list from interface.go somewhow. + // Unfortunately we can't even reference the enum from here... + case "gopls.run_tests", "gopls.run_govulncheck", "gopls.test": + return true + } + return false +} + +// MarshalArgs encodes the given arguments to json.RawMessages. This function +// is used to construct arguments to a protocol.Command. +// +// Example usage: +// +// jsonArgs, err := MarshalArgs(1, "hello", true, StructuredArg{42, 12.6}) +func MarshalArgs(args ...interface{}) ([]json.RawMessage, error) { + var out []json.RawMessage + for _, arg := range args { + argJSON, err := json.Marshal(arg) + if err != nil { + return nil, err + } + out = append(out, argJSON) + } + return out, nil +} + +// MustMarshalArgs is like MarshalArgs, but panics on error. +func MustMarshalArgs(args ...interface{}) []json.RawMessage { + msg, err := MarshalArgs(args...) + if err != nil { + panic(err) + } + return msg +} + +// UnmarshalArgs decodes the given json.RawMessages to the variables provided +// by args. Each element of args should be a pointer. +// +// Example usage: +// +// var ( +// num int +// str string +// bul bool +// structured StructuredArg +// ) +// err := UnmarshalArgs(args, &num, &str, &bul, &structured) +func UnmarshalArgs(jsonArgs []json.RawMessage, args ...interface{}) error { + if len(args) != len(jsonArgs) { + return fmt.Errorf("DecodeArgs: expected %d input arguments, got %d JSON arguments", len(args), len(jsonArgs)) + } + for i, arg := range args { + if err := json.Unmarshal(jsonArgs[i], arg); err != nil { + return err + } + } + return nil +} diff --git a/contribs/gnopls/internal/protocol/context.go b/contribs/gnopls/internal/protocol/context.go new file mode 100644 index 00000000000..5f3151cda97 --- /dev/null +++ b/contribs/gnopls/internal/protocol/context.go @@ -0,0 +1,65 @@ +// 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. + +package protocol + +import ( + "bytes" + "context" + "sync" + + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/core" + "golang.org/x/tools/internal/event/export" + "golang.org/x/tools/internal/event/label" + "golang.org/x/tools/internal/xcontext" +) + +type contextKey int + +const ( + clientKey = contextKey(iota) +) + +func WithClient(ctx context.Context, client Client) context.Context { + return context.WithValue(ctx, clientKey, client) +} + +func LogEvent(ctx context.Context, ev core.Event, lm label.Map, mt MessageType) context.Context { + client, ok := ctx.Value(clientKey).(Client) + if !ok { + return ctx + } + buf := &bytes.Buffer{} + p := export.Printer{} + p.WriteEvent(buf, ev, lm) + msg := &LogMessageParams{Type: mt, Message: buf.String()} + // Handle messages generated via event.Error, which won't have a level Label. + if event.IsError(ev) { + msg.Type = Error + } + + // The background goroutine lives forever once started, + // and ensures log messages are sent in order (#61216). + startLogSenderOnce.Do(func() { + go func() { + for f := range logQueue { + f() + } + }() + }) + + // Add the log item to a queue, rather than sending a + // window/logMessage request to the client synchronously, + // which would slow down this thread. + ctx2 := xcontext.Detach(ctx) + logQueue <- func() { client.LogMessage(ctx2, msg) } + + return ctx +} + +var ( + startLogSenderOnce sync.Once + logQueue = make(chan func(), 100) // big enough for a large transient burst +) diff --git a/contribs/gnopls/internal/protocol/doc.go b/contribs/gnopls/internal/protocol/doc.go new file mode 100644 index 00000000000..4a7f90439d3 --- /dev/null +++ b/contribs/gnopls/internal/protocol/doc.go @@ -0,0 +1,18 @@ +// Copyright 2018 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:generate go run ./generate + +// Package protocol contains the structs that map directly to the +// request and response messages of the Language Server Protocol. +// +// It is a literal transcription, with unmodified comments, and only the changes +// required to make it go code. +// Names are uppercased to export them. +// All fields have JSON tags added to correct the names. +// Fields marked with a ? are also marked as "omitempty" +// Fields that are "|| null" are made pointers +// Fields that are string or number are left as string +// Fields that are type "number" are made float64 +package protocol diff --git a/contribs/gnopls/internal/protocol/edits.go b/contribs/gnopls/internal/protocol/edits.go new file mode 100644 index 00000000000..5f70c4efdb5 --- /dev/null +++ b/contribs/gnopls/internal/protocol/edits.go @@ -0,0 +1,175 @@ +// 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 protocol + +import ( + "fmt" + + "golang.org/x/tools/internal/diff" +) + +// EditsFromDiffEdits converts diff.Edits to a non-nil slice of LSP TextEdits. +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEditArray +func EditsFromDiffEdits(m *Mapper, edits []diff.Edit) ([]TextEdit, error) { + // LSP doesn't require TextEditArray to be sorted: + // this is the receiver's concern. But govim, and perhaps + // other clients have historically relied on the order. + edits = append([]diff.Edit(nil), edits...) + diff.SortEdits(edits) + + result := make([]TextEdit, len(edits)) + for i, edit := range edits { + rng, err := m.OffsetRange(edit.Start, edit.End) + if err != nil { + return nil, err + } + result[i] = TextEdit{ + Range: rng, + NewText: edit.New, + } + } + return result, nil +} + +// EditsToDiffEdits converts LSP TextEdits to diff.Edits. +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEditArray +func EditsToDiffEdits(m *Mapper, edits []TextEdit) ([]diff.Edit, error) { + if edits == nil { + return nil, nil + } + result := make([]diff.Edit, len(edits)) + for i, edit := range edits { + start, end, err := m.RangeOffsets(edit.Range) + if err != nil { + return nil, err + } + result[i] = diff.Edit{ + Start: start, + End: end, + New: edit.NewText, + } + } + return result, nil +} + +// ApplyEdits applies the patch (edits) to m.Content and returns the result. +// It also returns the edits converted to diff-package form. +func ApplyEdits(m *Mapper, edits []TextEdit) ([]byte, []diff.Edit, error) { + diffEdits, err := EditsToDiffEdits(m, edits) + if err != nil { + return nil, nil, err + } + out, err := diff.ApplyBytes(m.Content, diffEdits) + return out, diffEdits, err +} + +// AsTextEdits converts a slice possibly containing AnnotatedTextEdits +// to a slice of TextEdits. +func AsTextEdits(edits []Or_TextDocumentEdit_edits_Elem) []TextEdit { + var result []TextEdit + for _, e := range edits { + var te TextEdit + if x, ok := e.Value.(AnnotatedTextEdit); ok { + te = x.TextEdit + } else if x, ok := e.Value.(TextEdit); ok { + te = x + } else { + panic(fmt.Sprintf("unexpected type %T, expected AnnotatedTextEdit or TextEdit", e.Value)) + } + result = append(result, te) + } + return result +} + +// AsAnnotatedTextEdits converts a slice of TextEdits +// to a slice of Or_TextDocumentEdit_edits_Elem. +// (returning a typed nil is required in server: in code_action.go and command.go)) +func AsAnnotatedTextEdits(edits []TextEdit) []Or_TextDocumentEdit_edits_Elem { + if edits == nil { + return []Or_TextDocumentEdit_edits_Elem{} + } + var result []Or_TextDocumentEdit_edits_Elem + for _, e := range edits { + result = append(result, Or_TextDocumentEdit_edits_Elem{ + Value: TextEdit{ + Range: e.Range, + NewText: e.NewText, + }, + }) + } + return result +} + +// fileHandle abstracts file.Handle to avoid a cycle. +type fileHandle interface { + URI() DocumentURI + Version() int32 +} + +// NewWorkspaceEdit constructs a WorkspaceEdit from a list of document changes. +// +// Any ChangeAnnotations must be added after. +func NewWorkspaceEdit(changes ...DocumentChange) *WorkspaceEdit { + return &WorkspaceEdit{DocumentChanges: changes} +} + +// DocumentChangeEdit constructs a DocumentChange containing a +// TextDocumentEdit from a file.Handle and a list of TextEdits. +func DocumentChangeEdit(fh fileHandle, textedits []TextEdit) DocumentChange { + return DocumentChange{ + TextDocumentEdit: &TextDocumentEdit{ + TextDocument: OptionalVersionedTextDocumentIdentifier{ + Version: fh.Version(), + TextDocumentIdentifier: TextDocumentIdentifier{URI: fh.URI()}, + }, + Edits: AsAnnotatedTextEdits(textedits), + }, + } +} + +// DocumentChangeCreate constructs a DocumentChange that creates a file. +func DocumentChangeCreate(uri DocumentURI) DocumentChange { + return DocumentChange{ + CreateFile: &CreateFile{ + Kind: "create", + URI: uri, + }, + } +} + +// DocumentChangeRename constructs a DocumentChange that renames a file. +func DocumentChangeRename(src, dst DocumentURI) DocumentChange { + return DocumentChange{ + RenameFile: &RenameFile{ + Kind: "rename", + OldURI: src, + NewURI: dst, + }, + } +} + +// SelectCompletionTextEdit returns insert or replace mode TextEdit +// included in the completion item. +func SelectCompletionTextEdit(item CompletionItem, useReplaceMode bool) (TextEdit, error) { + var edit TextEdit + switch typ := item.TextEdit.Value.(type) { + case TextEdit: // old style completion item. + return typ, nil + case InsertReplaceEdit: + if useReplaceMode { + return TextEdit{ + NewText: typ.NewText, + Range: typ.Replace, + }, nil + } else { + return TextEdit{ + NewText: typ.NewText, + Range: typ.Insert, + }, nil + } + default: + return edit, fmt.Errorf("unsupported edit type %T", typ) + } +} diff --git a/contribs/gnopls/internal/protocol/enums.go b/contribs/gnopls/internal/protocol/enums.go new file mode 100644 index 00000000000..74a8a316777 --- /dev/null +++ b/contribs/gnopls/internal/protocol/enums.go @@ -0,0 +1,179 @@ +// Copyright 2018 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 protocol + +import ( + "fmt" +) + +// CodeActionUnknownTrigger indicates that the trigger for a +// CodeAction request is unknown. A missing +// CodeActionContext.TriggerKind should be treated as equivalent. +const CodeActionUnknownTrigger CodeActionTriggerKind = 0 + +var ( + namesTextDocumentSyncKind [int(Incremental) + 1]string + namesMessageType [int(Log) + 1]string + namesFileChangeType [int(Deleted) + 1]string + namesWatchKind [int(WatchDelete) + 1]string + namesCompletionTriggerKind [int(TriggerForIncompleteCompletions) + 1]string + namesDiagnosticSeverity [int(SeverityHint) + 1]string + namesDiagnosticTag [int(Unnecessary) + 1]string + namesCompletionItemKind [int(TypeParameterCompletion) + 1]string + namesInsertTextFormat [int(SnippetTextFormat) + 1]string + namesDocumentHighlightKind [int(Write) + 1]string + namesSymbolKind [int(TypeParameter) + 1]string + namesTextDocumentSaveReason [int(FocusOut) + 1]string +) + +func init() { + namesTextDocumentSyncKind[int(None)] = "None" + namesTextDocumentSyncKind[int(Full)] = "Full" + namesTextDocumentSyncKind[int(Incremental)] = "Incremental" + + namesMessageType[int(Error)] = "Error" + namesMessageType[int(Warning)] = "Warning" + namesMessageType[int(Info)] = "Info" + namesMessageType[int(Log)] = "Log" + + namesFileChangeType[int(Created)] = "Created" + namesFileChangeType[int(Changed)] = "Changed" + namesFileChangeType[int(Deleted)] = "Deleted" + + namesWatchKind[int(WatchCreate)] = "WatchCreate" + namesWatchKind[int(WatchChange)] = "WatchChange" + namesWatchKind[int(WatchDelete)] = "WatchDelete" + + namesCompletionTriggerKind[int(Invoked)] = "Invoked" + namesCompletionTriggerKind[int(TriggerCharacter)] = "TriggerCharacter" + namesCompletionTriggerKind[int(TriggerForIncompleteCompletions)] = "TriggerForIncompleteCompletions" + + namesDiagnosticSeverity[int(SeverityError)] = "Error" + namesDiagnosticSeverity[int(SeverityWarning)] = "Warning" + namesDiagnosticSeverity[int(SeverityInformation)] = "Information" + namesDiagnosticSeverity[int(SeverityHint)] = "Hint" + + namesDiagnosticTag[int(Unnecessary)] = "Unnecessary" + + namesCompletionItemKind[int(TextCompletion)] = "text" + namesCompletionItemKind[int(MethodCompletion)] = "method" + namesCompletionItemKind[int(FunctionCompletion)] = "func" + namesCompletionItemKind[int(ConstructorCompletion)] = "constructor" + namesCompletionItemKind[int(FieldCompletion)] = "field" + namesCompletionItemKind[int(VariableCompletion)] = "var" + namesCompletionItemKind[int(ClassCompletion)] = "type" + namesCompletionItemKind[int(InterfaceCompletion)] = "interface" + namesCompletionItemKind[int(ModuleCompletion)] = "package" + namesCompletionItemKind[int(PropertyCompletion)] = "property" + namesCompletionItemKind[int(UnitCompletion)] = "unit" + namesCompletionItemKind[int(ValueCompletion)] = "value" + namesCompletionItemKind[int(EnumCompletion)] = "enum" + namesCompletionItemKind[int(KeywordCompletion)] = "keyword" + namesCompletionItemKind[int(SnippetCompletion)] = "snippet" + namesCompletionItemKind[int(ColorCompletion)] = "color" + namesCompletionItemKind[int(FileCompletion)] = "file" + namesCompletionItemKind[int(ReferenceCompletion)] = "reference" + namesCompletionItemKind[int(FolderCompletion)] = "folder" + namesCompletionItemKind[int(EnumMemberCompletion)] = "enumMember" + namesCompletionItemKind[int(ConstantCompletion)] = "const" + namesCompletionItemKind[int(StructCompletion)] = "struct" + namesCompletionItemKind[int(EventCompletion)] = "event" + namesCompletionItemKind[int(OperatorCompletion)] = "operator" + namesCompletionItemKind[int(TypeParameterCompletion)] = "typeParam" + + namesInsertTextFormat[int(PlainTextTextFormat)] = "PlainText" + namesInsertTextFormat[int(SnippetTextFormat)] = "Snippet" + + namesDocumentHighlightKind[int(Text)] = "Text" + namesDocumentHighlightKind[int(Read)] = "Read" + namesDocumentHighlightKind[int(Write)] = "Write" + + namesSymbolKind[int(File)] = "File" + namesSymbolKind[int(Module)] = "Module" + namesSymbolKind[int(Namespace)] = "Namespace" + namesSymbolKind[int(Package)] = "Package" + namesSymbolKind[int(Class)] = "Class" + namesSymbolKind[int(Method)] = "Method" + namesSymbolKind[int(Property)] = "Property" + namesSymbolKind[int(Field)] = "Field" + namesSymbolKind[int(Constructor)] = "Constructor" + namesSymbolKind[int(Enum)] = "Enum" + namesSymbolKind[int(Interface)] = "Interface" + namesSymbolKind[int(Function)] = "Function" + namesSymbolKind[int(Variable)] = "Variable" + namesSymbolKind[int(Constant)] = "Constant" + namesSymbolKind[int(String)] = "String" + namesSymbolKind[int(Number)] = "Number" + namesSymbolKind[int(Boolean)] = "Boolean" + namesSymbolKind[int(Array)] = "Array" + namesSymbolKind[int(Object)] = "Object" + namesSymbolKind[int(Key)] = "Key" + namesSymbolKind[int(Null)] = "Null" + namesSymbolKind[int(EnumMember)] = "EnumMember" + namesSymbolKind[int(Struct)] = "Struct" + namesSymbolKind[int(Event)] = "Event" + namesSymbolKind[int(Operator)] = "Operator" + namesSymbolKind[int(TypeParameter)] = "TypeParameter" + + namesTextDocumentSaveReason[int(Manual)] = "Manual" + namesTextDocumentSaveReason[int(AfterDelay)] = "AfterDelay" + namesTextDocumentSaveReason[int(FocusOut)] = "FocusOut" +} + +func formatEnum(f fmt.State, i int, names []string, unknown string) { + s := "" + if i >= 0 && i < len(names) { + s = names[i] + } + if s != "" { + fmt.Fprint(f, s) + } else { + fmt.Fprintf(f, "%s(%d)", unknown, i) + } +} + +func (e TextDocumentSyncKind) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesTextDocumentSyncKind[:], "TextDocumentSyncKind") +} + +func (e MessageType) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesMessageType[:], "MessageType") +} + +func (e FileChangeType) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesFileChangeType[:], "FileChangeType") +} + +func (e CompletionTriggerKind) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesCompletionTriggerKind[:], "CompletionTriggerKind") +} + +func (e DiagnosticSeverity) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesDiagnosticSeverity[:], "DiagnosticSeverity") +} + +func (e DiagnosticTag) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesDiagnosticTag[:], "DiagnosticTag") +} + +func (e CompletionItemKind) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesCompletionItemKind[:], "CompletionItemKind") +} + +func (e InsertTextFormat) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesInsertTextFormat[:], "InsertTextFormat") +} + +func (e DocumentHighlightKind) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesDocumentHighlightKind[:], "DocumentHighlightKind") +} + +func (e SymbolKind) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesSymbolKind[:], "SymbolKind") +} + +func (e TextDocumentSaveReason) Format(f fmt.State, c rune) { + formatEnum(f, int(e), namesTextDocumentSaveReason[:], "TextDocumentSaveReason") +} diff --git a/contribs/gnopls/internal/protocol/generate/README.md b/contribs/gnopls/internal/protocol/generate/README.md new file mode 100644 index 00000000000..af5f101e77a --- /dev/null +++ b/contribs/gnopls/internal/protocol/generate/README.md @@ -0,0 +1,144 @@ +# LSP Support for gopls + +## The protocol + +The LSP protocol exchanges json-encoded messages between the client and the server. +(gopls is the server.) The messages are either Requests, which require Responses, or +Notifications, which generate no response. Each Request or Notification has a method name +such as "textDocument/hover" that indicates its meaning and determines which function in the server will handle it. +The protocol is described in a +[web page](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/), +in words, and in a json file (metaModel.json) available either linked towards the bottom of the +web page, or in the vscode-languageserver-node repository. This code uses the latter so the +exact version can be tied to a githash. By default, the command will download the `github.com/microsoft/vscode-languageserver-node` repository to a temporary directory. + +The specification has five sections + +1. Requests, which describe the Request and Response types for request methods (e.g., *textDocument/didChange*), +2. Notifications, which describe the Request types for notification methods, +3. Structures, which describe named struct-like types, +4. TypeAliases, which describe type aliases, +5. Enumerations, which describe named constants. + +Requests and Notifications are tagged with a Method (e.g., `"textDocument/hover"`). +The specification does not specify the names of the functions that handle the messages. These +names are specified by the `methodNames` map. Enumerations generate Go `const`s, but +in Typescript they are scoped to namespaces, while in Go they are scoped to a package, so the Go names +may need to be modified to avoid name collisions. (See the `disambiguate` map, and its use.) + +Finally, the specified types are Typescript types, which are quite different from Go types. + +### Optionality + +The specification can mark fields in structs as Optional. The client distinguishes between missing +fields and `null` fields in some cases. The Go translation for an optional type +should be making sure the field's value +can be `nil`, and adding the json tag `,omitempty`. The former condition would be satisfied by +adding `*` to the field's type if the type is not a reference type. + +### Types + +The specification uses a number of different types, only a few of which correspond directly to Go types. +The specification's types are "base", "reference", "map", "literal", "stringLiteral", "tuple", "and", "or". +The "base" types correspond directly to Go types, although some Go types needs to be chosen for `URI` and `DocumentUri`. (The "base" types`RegExp`, `BooleanLiteral`, `NumericLiteral` never occur.) + +"reference" types are the struct-like types in the Structures section of the specification. The given +names are suitable for Go to use, except the code needs to change names like `_Initialze` to `XInitialize` so +they are exported for json marshaling and unmarshaling. + +"map" types are just like Go. (The key type in all of them is `DocumentUri`.) + +"stringLiteral" types are types whose type name and value are a single string. The chosen Go equivalent +is to make the type `string` and the value a constant. (The alternative would be to generate a new +named type, which seemed redundant.) + +"literal" types are like Go anonymous structs, so they have to be given a name. (All instances +of the remaining types have to be given names. One approach is to construct the name from the components +of the type, but this leads to misleading punning, and is unstable if components are added. The other approach +is to construct the name from the context of the definition, that is, from the types it is defined within. +For instance `Lit__InitializeParams_clientInfo` is the "literal" type at the +`clientInfo` field in the `_InitializeParams` +struct. Although this choice is sensitive to the ordering of the components, the code uses this approach, +presuming that reordering components is an unlikely protocol change.) + +"tuple" types are generated as Go structs. (There is only one, with two `uint32` fields.) + +"and" types are Go structs with embedded type names. (There is only one, `And_Param_workspace_configuration`.) + +"or" types are the most complicated. There are a lot of them and there is no simple Go equivalent. +They are defined as structs with a single `Value interface{}` field and custom json marshaling +and unmarshaling code. Users can assign anything to `Value` but the type will be checked, and +correctly marshaled, by the custom marshaling code. The unmarshaling code checks types, so `Value` +will have one of the permitted types. (`nil` is always allowed.) There are about 40 "or" types that +have a single non-null component, and these are converted to the component type. + +## Processing + +The code parses the json specification file, and scans all the types. It assigns names, as described +above, to the types that are unnamed in the specification, and constructs Go equivalents as required. +(Most of this code is in typenames.go.) + +There are four output files. tsclient.go and tsserver.go contain the definition and implementation +of the `protocol.Client` and `protocol.Server` types and the code that dispatches on the Method +of the Request or Notification. tsjson.go contains the custom marshaling and unmarshaling code. +And tsprotocol.go contains the type and const definitions. + +### Accommodating gopls + +As the code generates output, mostly in generateoutput.go and main.go, +it makes adjustments so that no changes are required to the existing Go code. +(Organizing the computation this way makes the code's structure simpler, but results in +a lot of unused types.) +There are three major classes of these adjustments, and leftover special cases. + +The first major +adjustment is to change generated type names to the ones gopls expects. Some of these don't change the +semantics of the type, just the name. +But for historical reasons a lot of them replace "or" types by a single +component of the type. (Until fairly recently Go only saw or used only one of components.) +The `goplsType` map in tables.go controls this process. + +The second major adjustment is to the types of fields of structs, which is done using the +`renameProp` map in tables.go. + +The third major adjustment handles optionality, controlling `*` and `,omitempty` placement when +the default rules don't match what gopls is expecting. (The map is `goplsStar`, also in tables.go) +(If the intermediate components in expressions of the form `A.B.C.S` were optional, the code would need +a lot of useless checking for nils. Typescript has a language construct to avoid most checks.) + +Then there are some additional special cases. There are a few places with adjustments to avoid +recursive types. For instance `LSPArray` is `[]LSPAny`, but `LSPAny` is an "or" type including `LSPArray`. +The solution is to make `LSPAny` an `interface{}`. Another instance is `_InitializeParams.trace` +whose type is an "or" of 3 stringLiterals, which just becomes a `string`. + +### Checking + +`TestAll(t *testing.T)` checks that there are no unexpected fields in the json specification. + +While the code is executing, it checks that all the entries in the maps in tables.go are used. +It also checks that the entries in `renameProp` and `goplsStar` are not redundant. + +As a one-time check on the first release of this code, diff-ing the existing and generated tsclient.go +and tsserver.go code results in only whitespace and comment diffs. The existing and generated +tsprotocol.go differ in whitespace and comments, and in a substantial number of new type definitions +that the older, more heuristic, code did not generate. (And the unused type `_InitializeParams` differs +slightly between the new and the old, and is not worth fixing.) + +### Some history + +The original stub code was written by hand, but with the protocol under active development, that +couldn't last. The web page existed before the json specification, but it lagged the implementation +and was hard to process by machine. So the earlier version of the generating code was written in Typescript, and +used the Typescript compiler's API to parse the protocol code in the repository. +It then used a set of heuristics +to pick out the elements of the protocol, and another set of overlapping heuristics to create the Go code. +The output was functional, but idiosyncratic, and the code was fragile and barely maintainable. + +### The future + +Most of the adjustments using the maps in tables.go could be removed by making changes, mostly to names, +in the gopls code. Using more "or" types in gopls requires more elaborate, but stereotyped, changes. +But even without all the adjustments, making this its own module would face problems; a number of +dependencies would have to be factored out. And, it is fragile. The custom unmarshaling code knows what +types it expects. A design that return an 'any' on unexpected types would match the json +'ignore unexpected values' philosophy better, but the Go code would need extra checking. diff --git a/contribs/gnopls/internal/protocol/generate/generate.go b/contribs/gnopls/internal/protocol/generate/generate.go new file mode 100644 index 00000000000..7418918f51f --- /dev/null +++ b/contribs/gnopls/internal/protocol/generate/generate.go @@ -0,0 +1,118 @@ +// 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 main + +import ( + "bytes" + "fmt" + "log" + "strings" +) + +// a newType is a type that needs a name and a definition +// These are the various types that the json specification doesn't name +type newType struct { + name string + properties Properties // for struct/literal types + items []*Type // for other types ("and", "tuple") + line int + kind string // Or, And, Tuple, Lit, Map + typ *Type +} + +func generateDoc(out *bytes.Buffer, doc string) { + if doc == "" { + return + } + + if !strings.Contains(doc, "\n") { + fmt.Fprintf(out, "// %s\n", doc) + return + } + var list bool + for _, line := range strings.Split(doc, "\n") { + // Lists in metaModel.json start with a dash. + // To make a go doc list they have to be preceded + // by a blank line, and indented. + // (see type TextDccumentFilter in protocol.go) + if len(line) > 0 && line[0] == '-' { + if !list { + list = true + fmt.Fprintf(out, "//\n") + } + fmt.Fprintf(out, "// %s\n", line) + } else { + if len(line) == 0 { + list = false + } + fmt.Fprintf(out, "// %s\n", line) + } + } +} + +// decide if a property is optional, and if it needs a * +// return ",omitempty" if it is optional, and "*" if it needs a pointer +func propStar(name string, t NameType, gotype string) (string, string) { + var opt, star string + if t.Optional { + star = "*" + opt = ",omitempty" + } + if strings.HasPrefix(gotype, "[]") || strings.HasPrefix(gotype, "map[") { + star = "" // passed by reference, so no need for * + } else { + switch gotype { + case "bool", "uint32", "int32", "string", "interface{}": + star = "" // gopls compatibility if t.Optional + } + } + ostar, oopt := star, opt + if newStar, ok := goplsStar[prop{name, t.Name}]; ok { + switch newStar { + case nothing: + star, opt = "", "" + case wantStar: + star, opt = "*", "" + case wantOpt: + star, opt = "", ",omitempty" + case wantOptStar: + star, opt = "*", ",omitempty" + } + if star == ostar && opt == oopt { // no change + log.Printf("goplsStar[ {%q, %q} ](%d) useless %s/%s %s/%s", name, t.Name, t.Line, ostar, star, oopt, opt) + } + usedGoplsStar[prop{name, t.Name}] = true + } + + return opt, star +} + +func goName(s string) string { + // Go naming conventions + if strings.HasSuffix(s, "Id") { + s = s[:len(s)-len("Id")] + "ID" + } else if strings.HasSuffix(s, "Uri") { + s = s[:len(s)-3] + "URI" + } else if s == "uri" { + s = "URI" + } else if s == "id" { + s = "ID" + } + + // renames for temporary GOPLS compatibility + if news := goplsType[s]; news != "" { + usedGoplsType[s] = true + s = news + } + // Names beginning _ are not exported + if strings.HasPrefix(s, "_") { + s = strings.Replace(s, "_", "X", 1) + } + if s != "string" { // base types are unchanged (textDocuemnt/diagnostic) + // Title is deprecated, but a) s is only one word, b) replacement is too heavy-weight + s = strings.Title(s) + } + return s +} diff --git a/contribs/gnopls/internal/protocol/generate/main.go b/contribs/gnopls/internal/protocol/generate/main.go new file mode 100644 index 00000000000..de42540a054 --- /dev/null +++ b/contribs/gnopls/internal/protocol/generate/main.go @@ -0,0 +1,360 @@ +// 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. + +// The generate command generates Go declarations from VSCode's +// description of the Language Server Protocol. +// +// To run it, type 'go generate' in the parent (protocol) directory. +package main + +// see https://github.com/golang/go/issues/61217 for discussion of an issue + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "go/format" + "log" + "os" + "os/exec" + "path/filepath" + "strings" +) + +const vscodeRepo = "https://github.com/microsoft/vscode-languageserver-node" + +// lspGitRef names a branch or tag in vscodeRepo. +// It implicitly determines the protocol version of the LSP used by gopls. +// For example, tag release/protocol/3.17.3 of the repo defines +// protocol version 3.17.0 (as declared by the metaData.version field). +// (Point releases are reflected in the git tag version even when they are cosmetic +// and don't change the protocol.) +var lspGitRef = "release/protocol/3.17.6-next.2" + +var ( + repodir = flag.String("d", "", "directory containing clone of "+vscodeRepo) + outputdir = flag.String("o", ".", "output directory") + // PJW: not for real code + cmpdir = flag.String("c", "", "directory of earlier code") + doboth = flag.String("b", "", "generate and compare") + lineNumbers = flag.Bool("l", false, "add line numbers to generated output") +) + +func main() { + log.SetFlags(log.Lshortfile) // log file name and line number, not time + flag.Parse() + + processinline() +} + +func processinline() { + // A local repository may be specified during debugging. + // The default behavior is to download the canonical version. + if *repodir == "" { + tmpdir, err := os.MkdirTemp("", "") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(tmpdir) // ignore error + + // Clone the repository. + cmd := exec.Command("git", "clone", "--quiet", "--depth=1", "-c", "advice.detachedHead=false", vscodeRepo, "--branch="+lspGitRef, "--single-branch", tmpdir) + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + log.Fatal(err) + } + + *repodir = tmpdir + } else { + lspGitRef = fmt.Sprintf("(not git, local dir %s)", *repodir) + } + + model := parse(filepath.Join(*repodir, "protocol/metaModel.json")) + + findTypeNames(model) + generateOutput(model) + + fileHdr = fileHeader(model) + + // write the files + writeclient() + writeserver() + writeprotocol() + writejsons() + + checkTables() +} + +// common file header for output files +var fileHdr string + +func writeclient() { + out := new(bytes.Buffer) + fmt.Fprintln(out, fileHdr) + out.WriteString( + `import ( + "context" + + "golang.org/x/tools/internal/jsonrpc2" +) +`) + out.WriteString("type Client interface {\n") + for _, k := range cdecls.keys() { + out.WriteString(cdecls[k]) + } + out.WriteString("}\n\n") + out.WriteString(`func clientDispatch(ctx context.Context, client Client, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { + defer recoverHandlerPanic(r.Method()) + switch r.Method() { +`) + for _, k := range ccases.keys() { + out.WriteString(ccases[k]) + } + out.WriteString(("\tdefault:\n\t\treturn false, nil\n\t}\n}\n\n")) + for _, k := range cfuncs.keys() { + out.WriteString(cfuncs[k]) + } + formatTo("tsclient.go", out.Bytes()) +} + +func writeserver() { + out := new(bytes.Buffer) + fmt.Fprintln(out, fileHdr) + out.WriteString( + `import ( + "context" + + "golang.org/x/tools/internal/jsonrpc2" +) +`) + out.WriteString("type Server interface {\n") + for _, k := range sdecls.keys() { + out.WriteString(sdecls[k]) + } + out.WriteString(` +} + +func serverDispatch(ctx context.Context, server Server, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { + defer recoverHandlerPanic(r.Method()) + switch r.Method() { +`) + for _, k := range scases.keys() { + out.WriteString(scases[k]) + } + out.WriteString(("\tdefault:\n\t\treturn false, nil\n\t}\n}\n\n")) + for _, k := range sfuncs.keys() { + out.WriteString(sfuncs[k]) + } + formatTo("tsserver.go", out.Bytes()) +} + +func writeprotocol() { + out := new(bytes.Buffer) + fmt.Fprintln(out, fileHdr) + out.WriteString("import \"encoding/json\"\n\n") + + // The following are unneeded, but make the new code a superset of the old + hack := func(newer, existing string) { + if _, ok := types[existing]; !ok { + log.Fatalf("types[%q] not found", existing) + } + types[newer] = strings.Replace(types[existing], existing, newer, 1) + } + hack("ConfigurationParams", "ParamConfiguration") + hack("InitializeParams", "ParamInitialize") + hack("PreviousResultId", "PreviousResultID") + hack("WorkspaceFoldersServerCapabilities", "WorkspaceFolders5Gn") + hack("_InitializeParams", "XInitializeParams") + + for _, k := range types.keys() { + if k == "WatchKind" { + types[k] = "type WatchKind = uint32" // strict gopls compatibility needs the '=' + } + out.WriteString(types[k]) + } + + out.WriteString("\nconst (\n") + for _, k := range consts.keys() { + out.WriteString(consts[k]) + } + out.WriteString(")\n\n") + formatTo("tsprotocol.go", out.Bytes()) +} + +func writejsons() { + out := new(bytes.Buffer) + fmt.Fprintln(out, fileHdr) + out.WriteString("import \"encoding/json\"\n\n") + out.WriteString("import \"fmt\"\n") + + out.WriteString(` +// UnmarshalError indicates that a JSON value did not conform to +// one of the expected cases of an LSP union type. +type UnmarshalError struct { + msg string +} + +func (e UnmarshalError) Error() string { + return e.msg +} +`) + + for _, k := range jsons.keys() { + out.WriteString(jsons[k]) + } + formatTo("tsjson.go", out.Bytes()) +} + +// formatTo formats the Go source and writes it to *outputdir/basename. +func formatTo(basename string, src []byte) { + formatted, err := format.Source(src) + if err != nil { + failed := filepath.Join("/tmp", basename+".fail") + os.WriteFile(failed, src, 0644) + log.Fatalf("formatting %s: %v (see %s)", basename, err, failed) + } + if err := os.WriteFile(filepath.Join(*outputdir, basename), formatted, 0644); err != nil { + log.Fatal(err) + } +} + +// create the common file header for the output files +func fileHeader(model *Model) string { + fname := filepath.Join(*repodir, ".git", "HEAD") + buf, err := os.ReadFile(fname) + if err != nil { + log.Fatal(err) + } + buf = bytes.TrimSpace(buf) + var githash string + if len(buf) == 40 { + githash = string(buf[:40]) + } else if bytes.HasPrefix(buf, []byte("ref: ")) { + fname = filepath.Join(*repodir, ".git", string(buf[5:])) + buf, err = os.ReadFile(fname) + if err != nil { + log.Fatal(err) + } + githash = string(buf[:40]) + } else { + log.Fatalf("githash cannot be recovered from %s", fname) + } + + format := `// 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. + +// Code generated for LSP. DO NOT EDIT. + +package protocol + +// Code generated from %[1]s at ref %[2]s (hash %[3]s). +// %[4]s/blob/%[2]s/%[1]s +// LSP metaData.version = %[5]s. + +` + return fmt.Sprintf(format, + "protocol/metaModel.json", // 1 + lspGitRef, // 2 + githash, // 3 + vscodeRepo, // 4 + model.Version.Version) // 5 +} + +func parse(fname string) *Model { + buf, err := os.ReadFile(fname) + if err != nil { + log.Fatal(err) + } + buf = addLineNumbers(buf) + model := new(Model) + if err := json.Unmarshal(buf, model); err != nil { + log.Fatal(err) + } + return model +} + +// Type.Value has to be treated specially for literals and maps +func (t *Type) UnmarshalJSON(data []byte) error { + // First unmarshal only the unambiguous fields. + var x struct { + Kind string `json:"kind"` + Items []*Type `json:"items"` + Element *Type `json:"element"` + Name string `json:"name"` + Key *Type `json:"key"` + Value any `json:"value"` + Line int `json:"line"` + } + if err := json.Unmarshal(data, &x); err != nil { + return err + } + *t = Type{ + Kind: x.Kind, + Items: x.Items, + Element: x.Element, + Name: x.Name, + Value: x.Value, + Line: x.Line, + } + + // Then unmarshal the 'value' field based on the kind. + // This depends on Unmarshal ignoring fields it doesn't know about. + switch x.Kind { + case "map": + var x struct { + Key *Type `json:"key"` + Value *Type `json:"value"` + } + if err := json.Unmarshal(data, &x); err != nil { + return fmt.Errorf("Type.kind=map: %v", err) + } + t.Key = x.Key + t.Value = x.Value + + case "literal": + var z struct { + Value ParseLiteral `json:"value"` + } + + if err := json.Unmarshal(data, &z); err != nil { + return fmt.Errorf("Type.kind=literal: %v", err) + } + t.Value = z.Value + + case "base", "reference", "array", "and", "or", "tuple", + "stringLiteral": + // no-op. never seen integerLiteral or booleanLiteral. + + default: + return fmt.Errorf("cannot decode Type.kind %q: %s", x.Kind, data) + } + return nil +} + +// which table entries were not used +func checkTables() { + for k := range disambiguate { + if !usedDisambiguate[k] { + log.Printf("disambiguate[%v] unused", k) + } + } + for k := range renameProp { + if !usedRenameProp[k] { + log.Printf("renameProp {%q, %q} unused", k[0], k[1]) + } + } + for k := range goplsStar { + if !usedGoplsStar[k] { + log.Printf("goplsStar {%q, %q} unused", k[0], k[1]) + } + } + for k := range goplsType { + if !usedGoplsType[k] { + log.Printf("unused goplsType[%q]->%s", k, goplsType[k]) + } + } +} diff --git a/contribs/gnopls/internal/protocol/generate/main_test.go b/contribs/gnopls/internal/protocol/generate/main_test.go new file mode 100644 index 00000000000..73c22048a80 --- /dev/null +++ b/contribs/gnopls/internal/protocol/generate/main_test.go @@ -0,0 +1,116 @@ +// 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 main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "testing" +) + +// These tests require the result of +//"git clone https://github.com/microsoft/vscode-languageserver-node" in the HOME directory + +// this is not a test, but a way to get code coverage, +// (in vscode, just run the test with "go.coverOnSingleTest": true) +func TestAll(t *testing.T) { + t.Skip("needs vscode-languageserver-node repository") + *lineNumbers = true + log.SetFlags(log.Lshortfile) + main() +} + +// check that the parsed file includes all the information +// from the json file. This test will fail if the spec +// introduces new fields. (one can test this test by +// commenting out the version field in Model.) +func TestParseContents(t *testing.T) { + t.Skip("needs vscode-languageserver-node repository") + log.SetFlags(log.Lshortfile) + + // compute our parse of the specification + dir := os.Getenv("HOME") + "/vscode-languageserver-node" + fname := dir + "/protocol/metaModel.json" + v := parse(fname) + out, err := json.Marshal(v) + if err != nil { + t.Fatal(err) + } + var our interface{} + if err := json.Unmarshal(out, &our); err != nil { + t.Fatal(err) + } + + // process the json file + buf, err := os.ReadFile(fname) + if err != nil { + t.Fatalf("could not read metaModel.json: %v", err) + } + var raw interface{} + if err := json.Unmarshal(buf, &raw); err != nil { + t.Fatal(err) + } + + // convert to strings showing the fields + them := flatten(raw) + us := flatten(our) + + // everything in them should be in us + lesser := make(sortedMap[bool]) + for _, s := range them { + lesser[s] = true + } + greater := make(sortedMap[bool]) // set of fields we have + for _, s := range us { + greater[s] = true + } + for _, k := range lesser.keys() { // set if fields they have + if !greater[k] { + t.Errorf("missing %s", k) + } + } +} + +// flatten(nil) = "nil" +// flatten(v string) = fmt.Sprintf("%q", v) +// flatten(v float64)= fmt.Sprintf("%g", v) +// flatten(v bool) = fmt.Sprintf("%v", v) +// flatten(v []any) = []string{"[0]"flatten(v[0]), "[1]"flatten(v[1]), ...} +// flatten(v map[string]any) = {"key1": flatten(v["key1"]), "key2": flatten(v["key2"]), ...} +func flatten(x any) []string { + switch v := x.(type) { + case nil: + return []string{"nil"} + case string: + return []string{fmt.Sprintf("%q", v)} + case float64: + return []string{fmt.Sprintf("%g", v)} + case bool: + return []string{fmt.Sprintf("%v", v)} + case []any: + var ans []string + for i, x := range v { + idx := fmt.Sprintf("[%.3d]", i) + for _, s := range flatten(x) { + ans = append(ans, idx+s) + } + } + return ans + case map[string]any: + var ans []string + for k, x := range v { + idx := fmt.Sprintf("%q:", k) + for _, s := range flatten(x) { + ans = append(ans, idx+s) + } + } + return ans + default: + log.Fatalf("unexpected type %T", x) + return nil + } +} diff --git a/contribs/gnopls/internal/protocol/generate/output.go b/contribs/gnopls/internal/protocol/generate/output.go new file mode 100644 index 00000000000..87d6f66cccd --- /dev/null +++ b/contribs/gnopls/internal/protocol/generate/output.go @@ -0,0 +1,441 @@ +// 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 main + +import ( + "bytes" + "fmt" + "log" + "sort" + "strings" +) + +var ( + // tsclient.go has 3 sections + cdecls = make(sortedMap[string]) + ccases = make(sortedMap[string]) + cfuncs = make(sortedMap[string]) + // tsserver.go has 3 sections + sdecls = make(sortedMap[string]) + scases = make(sortedMap[string]) + sfuncs = make(sortedMap[string]) + // tsprotocol.go has 2 sections + types = make(sortedMap[string]) + consts = make(sortedMap[string]) + // tsjson has 1 section + jsons = make(sortedMap[string]) +) + +func generateOutput(model *Model) { + for _, r := range model.Requests { + genDecl(model, r.Method, r.Params, r.Result, r.Direction) + genCase(model, r.Method, r.Params, r.Result, r.Direction) + genFunc(model, r.Method, r.Params, r.Result, r.Direction, false) + } + for _, n := range model.Notifications { + if n.Method == "$/cancelRequest" { + continue // handled internally by jsonrpc2 + } + genDecl(model, n.Method, n.Params, nil, n.Direction) + genCase(model, n.Method, n.Params, nil, n.Direction) + genFunc(model, n.Method, n.Params, nil, n.Direction, true) + } + genStructs(model) + genAliases(model) + genGenTypes() // generate the unnamed types + genConsts(model) + genMarshal() +} + +func genDecl(model *Model, method string, param, result *Type, dir string) { + fname := methodName(method) + p := "" + if notNil(param) { + p = ", *" + goplsName(param) + } + ret := "error" + if notNil(result) { + tp := goplsName(result) + if !hasNilValue(tp) { + tp = "*" + tp + } + ret = fmt.Sprintf("(%s, error)", tp) + } + // special gopls compatibility case (PJW: still needed?) + switch method { + case "workspace/configuration": + // was And_Param_workspace_configuration, but the type substitution doesn't work, + // as ParamConfiguration is embedded in And_Param_workspace_configuration + p = ", *ParamConfiguration" + ret = "([]LSPAny, error)" + } + fragment := strings.ReplaceAll(strings.TrimPrefix(method, "$/"), "/", "_") + msg := fmt.Sprintf("\t%s\t%s(context.Context%s) %s\n", lspLink(model, fragment), fname, p, ret) + switch dir { + case "clientToServer": + sdecls[method] = msg + case "serverToClient": + cdecls[method] = msg + case "both": + sdecls[method] = msg + cdecls[method] = msg + default: + log.Fatalf("impossible direction %q", dir) + } +} + +func genCase(model *Model, method string, param, result *Type, dir string) { + out := new(bytes.Buffer) + fmt.Fprintf(out, "\tcase %q:\n", method) + var p string + fname := methodName(method) + if notNil(param) { + nm := goplsName(param) + if method == "workspace/configuration" { // gopls compatibility + // was And_Param_workspace_configuration, which contains ParamConfiguration + // so renaming the type leads to circular definitions + nm = "ParamConfiguration" // gopls compatibility + } + fmt.Fprintf(out, "\t\tvar params %s\n", nm) + fmt.Fprintf(out, "\t\tif err := UnmarshalJSON(r.Params(), ¶ms); err != nil {\n") + fmt.Fprintf(out, "\t\t\treturn true, sendParseError(ctx, reply, err)\n\t\t}\n") + p = ", ¶ms" + } + if notNil(result) { + fmt.Fprintf(out, "\t\tresp, err := %%s.%s(ctx%s)\n", fname, p) + out.WriteString("\t\tif err != nil {\n") + out.WriteString("\t\t\treturn true, reply(ctx, nil, err)\n") + out.WriteString("\t\t}\n") + out.WriteString("\t\treturn true, reply(ctx, resp, nil)\n") + } else { + fmt.Fprintf(out, "\t\terr := %%s.%s(ctx%s)\n", fname, p) + out.WriteString("\t\treturn true, reply(ctx, nil, err)\n") + } + out.WriteString("\n") + msg := out.String() + switch dir { + case "clientToServer": + scases[method] = fmt.Sprintf(msg, "server") + case "serverToClient": + ccases[method] = fmt.Sprintf(msg, "client") + case "both": + scases[method] = fmt.Sprintf(msg, "server") + ccases[method] = fmt.Sprintf(msg, "client") + default: + log.Fatalf("impossible direction %q", dir) + } +} + +func genFunc(model *Model, method string, param, result *Type, dir string, isnotify bool) { + out := new(bytes.Buffer) + var p, r string + var goResult string + if notNil(param) { + p = ", params *" + goplsName(param) + } + if notNil(result) { + goResult = goplsName(result) + if !hasNilValue(goResult) { + goResult = "*" + goResult + } + r = fmt.Sprintf("(%s, error)", goResult) + } else { + r = "error" + } + // special gopls compatibility case + switch method { + case "workspace/configuration": + // was And_Param_workspace_configuration, but the type substitution doesn't work, + // as ParamConfiguration is embedded in And_Param_workspace_configuration + p = ", params *ParamConfiguration" + r = "([]LSPAny, error)" + goResult = "[]LSPAny" + } + fname := methodName(method) + fmt.Fprintf(out, "func (s *%%sDispatcher) %s(ctx context.Context%s) %s {\n", + fname, p, r) + + if !notNil(result) { + if isnotify { + if notNil(param) { + fmt.Fprintf(out, "\treturn s.sender.Notify(ctx, %q, params)\n", method) + } else { + fmt.Fprintf(out, "\treturn s.sender.Notify(ctx, %q, nil)\n", method) + } + } else { + if notNil(param) { + fmt.Fprintf(out, "\treturn s.sender.Call(ctx, %q, params, nil)\n", method) + } else { + fmt.Fprintf(out, "\treturn s.sender.Call(ctx, %q, nil, nil)\n", method) + } + } + } else { + fmt.Fprintf(out, "\tvar result %s\n", goResult) + if isnotify { + if notNil(param) { + fmt.Fprintf(out, "\ts.sender.Notify(ctx, %q, params)\n", method) + } else { + fmt.Fprintf(out, "\t\tif err := s.sender.Notify(ctx, %q, nil); err != nil {\n", method) + } + } else { + if notNil(param) { + fmt.Fprintf(out, "\t\tif err := s.sender.Call(ctx, %q, params, &result); err != nil {\n", method) + } else { + fmt.Fprintf(out, "\t\tif err := s.sender.Call(ctx, %q, nil, &result); err != nil {\n", method) + } + } + fmt.Fprintf(out, "\t\treturn nil, err\n\t}\n\treturn result, nil\n") + } + out.WriteString("}\n") + msg := out.String() + switch dir { + case "clientToServer": + sfuncs[method] = fmt.Sprintf(msg, "server") + case "serverToClient": + cfuncs[method] = fmt.Sprintf(msg, "client") + case "both": + sfuncs[method] = fmt.Sprintf(msg, "server") + cfuncs[method] = fmt.Sprintf(msg, "client") + default: + log.Fatalf("impossible direction %q", dir) + } +} + +func genStructs(model *Model) { + structures := make(map[string]*Structure) // for expanding Extends + for _, s := range model.Structures { + structures[s.Name] = s + } + for _, s := range model.Structures { + out := new(bytes.Buffer) + generateDoc(out, s.Documentation) + nm := goName(s.Name) + if nm == "string" { // an unacceptable strut name + // a weird case, and needed only so the generated code contains the old gopls code + nm = "DocumentDiagnosticParams" + } + fmt.Fprintf(out, "//\n") + out.WriteString(lspLink(model, camelCase(s.Name))) + fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(s.Line)) + // for gpls compatibilitye, embed most extensions, but expand the rest some day + props := append([]NameType{}, s.Properties...) + if s.Name == "SymbolInformation" { // but expand this one + for _, ex := range s.Extends { + fmt.Fprintf(out, "\t// extends %s\n", ex.Name) + props = append(props, structures[ex.Name].Properties...) + } + genProps(out, props, nm) + } else { + genProps(out, props, nm) + for _, ex := range s.Extends { + fmt.Fprintf(out, "\t%s\n", goName(ex.Name)) + } + } + for _, ex := range s.Mixins { + fmt.Fprintf(out, "\t%s\n", goName(ex.Name)) + } + out.WriteString("}\n") + types[nm] = out.String() + } + + // base types + // (For URI and DocumentURI, see ../uri.go.) + types["LSPAny"] = "type LSPAny = interface{}\n" + // A special case, the only previously existing Or type + types["DocumentDiagnosticReport"] = "type DocumentDiagnosticReport = Or_DocumentDiagnosticReport // (alias) \n" + +} + +// "FooBar" -> "fooBar" +func camelCase(TitleCased string) string { + return strings.ToLower(TitleCased[:1]) + TitleCased[1:] +} + +func lspLink(model *Model, fragment string) string { + // Derive URL version from metaData.version in JSON file. + parts := strings.Split(model.Version.Version, ".") // e.g. "3.17.0" + return fmt.Sprintf("// See https://microsoft.github.io/language-server-protocol/specifications/lsp/%s.%s/specification#%s\n", + parts[0], parts[1], // major.minor + fragment) +} + +func genProps(out *bytes.Buffer, props []NameType, name string) { + for _, p := range props { + tp := goplsName(p.Type) + if newNm, ok := renameProp[prop{name, p.Name}]; ok { + usedRenameProp[prop{name, p.Name}] = true + if tp == newNm { + log.Printf("renameProp useless {%q, %q} for %s", name, p.Name, tp) + } + tp = newNm + } + // it's a pointer if it is optional, or for gopls compatibility + opt, star := propStar(name, p, tp) + json := fmt.Sprintf(" `json:\"%s%s\"`", p.Name, opt) + generateDoc(out, p.Documentation) + fmt.Fprintf(out, "\t%s %s%s %s\n", goName(p.Name), star, tp, json) + } +} + +func genAliases(model *Model) { + for _, ta := range model.TypeAliases { + out := new(bytes.Buffer) + generateDoc(out, ta.Documentation) + nm := goName(ta.Name) + if nm != ta.Name { + continue // renamed the type, e.g., "DocumentDiagnosticReport", an or-type to "string" + } + tp := goplsName(ta.Type) + fmt.Fprintf(out, "//\n") + out.WriteString(lspLink(model, camelCase(ta.Name))) + fmt.Fprintf(out, "type %s = %s // (alias)\n", nm, tp) + types[nm] = out.String() + } +} + +func genGenTypes() { + for _, nt := range genTypes { + out := new(bytes.Buffer) + nm := goplsName(nt.typ) + switch nt.kind { + case "literal": + fmt.Fprintf(out, "// created for Literal (%s)\n", nt.name) + fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(nt.line+1)) + genProps(out, nt.properties, nt.name) // systematic name, not gopls name; is this a good choice? + case "or": + if !strings.HasPrefix(nm, "Or") { + // It was replaced by a narrower type defined elsewhere + continue + } + names := []string{} + for _, t := range nt.items { + if notNil(t) { + names = append(names, goplsName(t)) + } + } + sort.Strings(names) + fmt.Fprintf(out, "// created for Or %v\n", names) + fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(nt.line+1)) + fmt.Fprintf(out, "\tValue interface{} `json:\"value\"`\n") + case "and": + fmt.Fprintf(out, "// created for And\n") + fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(nt.line+1)) + for _, x := range nt.items { + nm := goplsName(x) + fmt.Fprintf(out, "\t%s\n", nm) + } + case "tuple": // there's only this one + nt.name = "UIntCommaUInt" + fmt.Fprintf(out, "//created for Tuple\ntype %s struct {%s\n", nm, linex(nt.line+1)) + fmt.Fprintf(out, "\tFld0 uint32 `json:\"fld0\"`\n") + fmt.Fprintf(out, "\tFld1 uint32 `json:\"fld1\"`\n") + default: + log.Fatalf("%s not handled", nt.kind) + } + out.WriteString("}\n") + types[nm] = out.String() + } +} +func genConsts(model *Model) { + for _, e := range model.Enumerations { + out := new(bytes.Buffer) + generateDoc(out, e.Documentation) + tp := goplsName(e.Type) + nm := goName(e.Name) + fmt.Fprintf(out, "type %s %s%s\n", nm, tp, linex(e.Line)) + types[nm] = out.String() + vals := new(bytes.Buffer) + generateDoc(vals, e.Documentation) + for _, v := range e.Values { + generateDoc(vals, v.Documentation) + nm := goName(v.Name) + more, ok := disambiguate[e.Name] + if ok { + usedDisambiguate[e.Name] = true + nm = more.prefix + nm + more.suffix + nm = goName(nm) // stringType + } + var val string + switch v := v.Value.(type) { + case string: + val = fmt.Sprintf("%q", v) + case float64: + val = fmt.Sprintf("%d", int(v)) + default: + log.Fatalf("impossible type %T", v) + } + fmt.Fprintf(vals, "\t%s %s = %s%s\n", nm, e.Name, val, linex(v.Line)) + } + consts[nm] = vals.String() + } +} +func genMarshal() { + for _, nt := range genTypes { + nm := goplsName(nt.typ) + if !strings.HasPrefix(nm, "Or") { + continue + } + names := []string{} + for _, t := range nt.items { + if notNil(t) { + names = append(names, goplsName(t)) + } + } + sort.Strings(names) + var buf bytes.Buffer + fmt.Fprintf(&buf, "func (t %s) MarshalJSON() ([]byte, error) {\n", nm) + buf.WriteString("\tswitch x := t.Value.(type){\n") + for _, nmx := range names { + fmt.Fprintf(&buf, "\tcase %s:\n", nmx) + fmt.Fprintf(&buf, "\t\treturn json.Marshal(x)\n") + } + buf.WriteString("\tcase nil:\n\t\treturn []byte(\"null\"), nil\n\t}\n") + fmt.Fprintf(&buf, "\treturn nil, fmt.Errorf(\"type %%T not one of %v\", t)\n", names) + buf.WriteString("}\n\n") + + fmt.Fprintf(&buf, "func (t *%s) UnmarshalJSON(x []byte) error {\n", nm) + buf.WriteString("\tif string(x) == \"null\" {\n\t\tt.Value = nil\n\t\t\treturn nil\n\t}\n") + for i, nmx := range names { + fmt.Fprintf(&buf, "\tvar h%d %s\n", i, nmx) + fmt.Fprintf(&buf, "\tif err := json.Unmarshal(x, &h%d); err == nil {\n\t\tt.Value = h%d\n\t\t\treturn nil\n\t\t}\n", i, i) + } + fmt.Fprintf(&buf, "return &UnmarshalError{\"unmarshal failed to match one of %v\"}", names) + buf.WriteString("}\n\n") + jsons[nm] = buf.String() + } +} + +func linex(n int) string { + if *lineNumbers { + return fmt.Sprintf(" // line %d", n) + } + return "" +} + +func goplsName(t *Type) string { + nm := typeNames[t] + // translate systematic name to gopls name + if newNm, ok := goplsType[nm]; ok { + usedGoplsType[nm] = true + nm = newNm + } + return nm +} + +func notNil(t *Type) bool { // shutdwon is the special case that needs this + return t != nil && (t.Kind != "base" || t.Name != "null") +} + +func hasNilValue(t string) bool { + // this may be unreliable, and need a supplementary table + if strings.HasPrefix(t, "[]") || strings.HasPrefix(t, "*") { + return true + } + if t == "interface{}" || t == "any" { + return true + } + // that's all the cases that occur currently + return false +} diff --git a/contribs/gnopls/internal/protocol/generate/tables.go b/contribs/gnopls/internal/protocol/generate/tables.go new file mode 100644 index 00000000000..5ac5d473580 --- /dev/null +++ b/contribs/gnopls/internal/protocol/generate/tables.go @@ -0,0 +1,274 @@ +// 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 main + +import "log" + +// prop combines the name of a property with the name of the structure it is in. +type prop [2]string + +const ( + nothing = iota + wantStar + wantOpt + wantOptStar +) + +// goplsStar records the optionality of each field in the protocol. +// The comments are vague hints as to why removing the line is not trivial. +// A.B.C.D means that one of B or C would change to a pointer +// so a test or initialization would be needed +var goplsStar = map[prop]int{ + {"AnnotatedTextEdit", "annotationId"}: wantOptStar, + {"ClientCapabilities", "textDocument"}: wantOpt, // A.B.C.D at fake/editor.go:255 + {"ClientCapabilities", "window"}: wantOpt, // test failures + {"ClientCapabilities", "workspace"}: wantOpt, // test failures + {"CodeAction", "kind"}: wantOpt, // A.B.C.D + + {"CodeActionClientCapabilities", "codeActionLiteralSupport"}: wantOpt, // test failures + + {"CompletionClientCapabilities", "completionItem"}: wantOpt, // A.B.C.D + {"CompletionClientCapabilities", "insertTextMode"}: wantOpt, // A.B.C.D + {"CompletionItem", "kind"}: wantOpt, // need temporary variables + {"CompletionParams", "context"}: wantOpt, // needs nil checks + + {"Diagnostic", "severity"}: wantOpt, // nil checks or more careful thought + {"DidSaveTextDocumentParams", "text"}: wantOptStar, // capabilities_test.go:112 logic + {"DocumentHighlight", "kind"}: wantOpt, // need temporary variables + {"Hover", "range"}: wantOpt, // complex expressions + {"InlayHint", "kind"}: wantOpt, // temporary variables + + {"TextDocumentClientCapabilities", "codeAction"}: wantOpt, // A.B.C.D + {"TextDocumentClientCapabilities", "completion"}: wantOpt, // A.B.C.D + {"TextDocumentClientCapabilities", "documentSymbol"}: wantOpt, // A.B.C.D + {"TextDocumentClientCapabilities", "publishDiagnostics"}: wantOpt, //A.B.C.D + {"TextDocumentClientCapabilities", "semanticTokens"}: wantOpt, // A.B.C.D + {"TextDocumentContentChangePartial", "range"}: wantOptStar, // == nil test + {"TextDocumentSyncOptions", "change"}: wantOpt, // &constant + {"WorkDoneProgressParams", "workDoneToken"}: wantOpt, // test failures + {"WorkspaceClientCapabilities", "didChangeConfiguration"}: wantOpt, // A.B.C.D + {"WorkspaceClientCapabilities", "didChangeWatchedFiles"}: wantOpt, // A.B.C.D +} + +// keep track of which entries in goplsStar are used +var usedGoplsStar = make(map[prop]bool) + +// For gopls compatibility, use a different, typically more restrictive, type for some fields. +var renameProp = map[prop]string{ + {"CancelParams", "id"}: "interface{}", + {"Command", "arguments"}: "[]json.RawMessage", + {"CodeAction", "data"}: "json.RawMessage", // delay unmarshalling commands + {"Diagnostic", "code"}: "interface{}", + {"Diagnostic", "data"}: "json.RawMessage", // delay unmarshalling quickfixes + + {"DocumentDiagnosticReportPartialResult", "relatedDocuments"}: "map[DocumentURI]interface{}", + + {"ExecuteCommandParams", "arguments"}: "[]json.RawMessage", + {"FoldingRange", "kind"}: "string", + {"Hover", "contents"}: "MarkupContent", + {"InlayHint", "label"}: "[]InlayHintLabelPart", + + {"RelatedFullDocumentDiagnosticReport", "relatedDocuments"}: "map[DocumentURI]interface{}", + {"RelatedUnchangedDocumentDiagnosticReport", "relatedDocuments"}: "map[DocumentURI]interface{}", + + // PJW: this one is tricky. + {"ServerCapabilities", "codeActionProvider"}: "interface{}", + + {"ServerCapabilities", "inlayHintProvider"}: "interface{}", + // slightly tricky + {"ServerCapabilities", "renameProvider"}: "interface{}", + // slightly tricky + {"ServerCapabilities", "semanticTokensProvider"}: "interface{}", + // slightly tricky + {"ServerCapabilities", "textDocumentSync"}: "interface{}", + {"TextDocumentSyncOptions", "save"}: "SaveOptions", + {"WorkspaceEdit", "documentChanges"}: "[]DocumentChange", +} + +// which entries of renameProp were used +var usedRenameProp = make(map[prop]bool) + +type adjust struct { + prefix, suffix string +} + +// disambiguate specifies prefixes or suffixes to add to all values of +// some enum types to avoid name conflicts +var disambiguate = map[string]adjust{ + "CodeActionTriggerKind": {"CodeAction", ""}, + "CompletionItemKind": {"", "Completion"}, + "CompletionItemTag": {"Compl", ""}, + "DiagnosticSeverity": {"Severity", ""}, + "DocumentDiagnosticReportKind": {"Diagnostic", ""}, + "FileOperationPatternKind": {"", "Pattern"}, + "InlineCompletionTriggerKind": {"Inline", ""}, + "InsertTextFormat": {"", "TextFormat"}, + "LanguageKind": {"Lang", ""}, + "SemanticTokenModifiers": {"Mod", ""}, + "SemanticTokenTypes": {"", "Type"}, + "SignatureHelpTriggerKind": {"Sig", ""}, + "SymbolTag": {"", "Symbol"}, + "WatchKind": {"Watch", ""}, +} + +// which entries of disambiguate got used +var usedDisambiguate = make(map[string]bool) + +// for gopls compatibility, replace generated type names with existing ones +var goplsType = map[string]string{ + "And_RegOpt_textDocument_colorPresentation": "WorkDoneProgressOptionsAndTextDocumentRegistrationOptions", + "ConfigurationParams": "ParamConfiguration", + "DocumentDiagnosticParams": "string", + "DocumentDiagnosticReport": "string", + "DocumentUri": "DocumentURI", + "InitializeParams": "ParamInitialize", + "LSPAny": "interface{}", + + "Lit_SemanticTokensOptions_range_Item1": "PRangeESemanticTokensOptions", + + "Or_Declaration": "[]Location", + "Or_DidChangeConfigurationRegistrationOptions_section": "OrPSection_workspace_didChangeConfiguration", + "Or_InlayHintLabelPart_tooltip": "OrPTooltipPLabel", + "Or_InlayHint_tooltip": "OrPTooltip_textDocument_inlayHint", + "Or_LSPAny": "interface{}", + + "Or_ParameterInformation_documentation": "string", + "Or_ParameterInformation_label": "string", + "Or_PrepareRenameResult": "PrepareRenamePlaceholder", + "Or_ProgressToken": "interface{}", + "Or_Result_textDocument_completion": "CompletionList", + "Or_Result_textDocument_declaration": "Or_textDocument_declaration", + "Or_Result_textDocument_definition": "[]Location", + "Or_Result_textDocument_documentSymbol": "[]interface{}", + "Or_Result_textDocument_implementation": "[]Location", + "Or_Result_textDocument_semanticTokens_full_delta": "interface{}", + "Or_Result_textDocument_typeDefinition": "[]Location", + "Or_Result_workspace_symbol": "[]SymbolInformation", + "Or_TextDocumentContentChangeEvent": "TextDocumentContentChangePartial", + "Or_RelativePattern_baseUri": "DocumentURI", + + "Or_WorkspaceFoldersServerCapabilities_changeNotifications": "string", + "Or_WorkspaceSymbol_location": "OrPLocation_workspace_symbol", + + "Tuple_ParameterInformation_label_Item1": "UIntCommaUInt", + "WorkspaceFoldersServerCapabilities": "WorkspaceFolders5Gn", + "[]LSPAny": "[]interface{}", + + "[]Or_Result_textDocument_codeAction_Item0_Elem": "[]CodeAction", + "[]PreviousResultId": "[]PreviousResultID", + "[]uinteger": "[]uint32", + "boolean": "bool", + "decimal": "float64", + "integer": "int32", + "map[DocumentUri][]TextEdit": "map[DocumentURI][]TextEdit", + "uinteger": "uint32", +} + +var usedGoplsType = make(map[string]bool) + +// methodNames is a map from the method to the name of the function that handles it +var methodNames = map[string]string{ + "$/cancelRequest": "CancelRequest", + "$/logTrace": "LogTrace", + "$/progress": "Progress", + "$/setTrace": "SetTrace", + "callHierarchy/incomingCalls": "IncomingCalls", + "callHierarchy/outgoingCalls": "OutgoingCalls", + "client/registerCapability": "RegisterCapability", + "client/unregisterCapability": "UnregisterCapability", + "codeAction/resolve": "ResolveCodeAction", + "codeLens/resolve": "ResolveCodeLens", + "completionItem/resolve": "ResolveCompletionItem", + "documentLink/resolve": "ResolveDocumentLink", + "exit": "Exit", + "initialize": "Initialize", + "initialized": "Initialized", + "inlayHint/resolve": "Resolve", + "notebookDocument/didChange": "DidChangeNotebookDocument", + "notebookDocument/didClose": "DidCloseNotebookDocument", + "notebookDocument/didOpen": "DidOpenNotebookDocument", + "notebookDocument/didSave": "DidSaveNotebookDocument", + "shutdown": "Shutdown", + "telemetry/event": "Event", + "textDocument/codeAction": "CodeAction", + "textDocument/codeLens": "CodeLens", + "textDocument/colorPresentation": "ColorPresentation", + "textDocument/completion": "Completion", + "textDocument/declaration": "Declaration", + "textDocument/definition": "Definition", + "textDocument/diagnostic": "Diagnostic", + "textDocument/didChange": "DidChange", + "textDocument/didClose": "DidClose", + "textDocument/didOpen": "DidOpen", + "textDocument/didSave": "DidSave", + "textDocument/documentColor": "DocumentColor", + "textDocument/documentHighlight": "DocumentHighlight", + "textDocument/documentLink": "DocumentLink", + "textDocument/documentSymbol": "DocumentSymbol", + "textDocument/foldingRange": "FoldingRange", + "textDocument/formatting": "Formatting", + "textDocument/hover": "Hover", + "textDocument/implementation": "Implementation", + "textDocument/inlayHint": "InlayHint", + "textDocument/inlineCompletion": "InlineCompletion", + "textDocument/inlineValue": "InlineValue", + "textDocument/linkedEditingRange": "LinkedEditingRange", + "textDocument/moniker": "Moniker", + "textDocument/onTypeFormatting": "OnTypeFormatting", + "textDocument/prepareCallHierarchy": "PrepareCallHierarchy", + "textDocument/prepareRename": "PrepareRename", + "textDocument/prepareTypeHierarchy": "PrepareTypeHierarchy", + "textDocument/publishDiagnostics": "PublishDiagnostics", + "textDocument/rangeFormatting": "RangeFormatting", + "textDocument/rangesFormatting": "RangesFormatting", + "textDocument/references": "References", + "textDocument/rename": "Rename", + "textDocument/selectionRange": "SelectionRange", + "textDocument/semanticTokens/full": "SemanticTokensFull", + "textDocument/semanticTokens/full/delta": "SemanticTokensFullDelta", + "textDocument/semanticTokens/range": "SemanticTokensRange", + "textDocument/signatureHelp": "SignatureHelp", + "textDocument/typeDefinition": "TypeDefinition", + "textDocument/willSave": "WillSave", + "textDocument/willSaveWaitUntil": "WillSaveWaitUntil", + "typeHierarchy/subtypes": "Subtypes", + "typeHierarchy/supertypes": "Supertypes", + "window/logMessage": "LogMessage", + "window/showDocument": "ShowDocument", + "window/showMessage": "ShowMessage", + "window/showMessageRequest": "ShowMessageRequest", + "window/workDoneProgress/cancel": "WorkDoneProgressCancel", + "window/workDoneProgress/create": "WorkDoneProgressCreate", + "workspace/applyEdit": "ApplyEdit", + "workspace/codeLens/refresh": "CodeLensRefresh", + "workspace/configuration": "Configuration", + "workspace/diagnostic": "DiagnosticWorkspace", + "workspace/diagnostic/refresh": "DiagnosticRefresh", + "workspace/didChangeConfiguration": "DidChangeConfiguration", + "workspace/didChangeWatchedFiles": "DidChangeWatchedFiles", + "workspace/didChangeWorkspaceFolders": "DidChangeWorkspaceFolders", + "workspace/didCreateFiles": "DidCreateFiles", + "workspace/didDeleteFiles": "DidDeleteFiles", + "workspace/didRenameFiles": "DidRenameFiles", + "workspace/executeCommand": "ExecuteCommand", + "workspace/foldingRange/refresh": "FoldingRangeRefresh", + "workspace/inlayHint/refresh": "InlayHintRefresh", + "workspace/inlineValue/refresh": "InlineValueRefresh", + "workspace/semanticTokens/refresh": "SemanticTokensRefresh", + "workspace/symbol": "Symbol", + "workspace/willCreateFiles": "WillCreateFiles", + "workspace/willDeleteFiles": "WillDeleteFiles", + "workspace/willRenameFiles": "WillRenameFiles", + "workspace/workspaceFolders": "WorkspaceFolders", + "workspaceSymbol/resolve": "ResolveWorkspaceSymbol", +} + +func methodName(method string) string { + ans := methodNames[method] + if ans == "" { + log.Fatalf("unknown method %q", method) + } + return ans +} diff --git a/contribs/gnopls/internal/protocol/generate/typenames.go b/contribs/gnopls/internal/protocol/generate/typenames.go new file mode 100644 index 00000000000..69fa7cfdb15 --- /dev/null +++ b/contribs/gnopls/internal/protocol/generate/typenames.go @@ -0,0 +1,181 @@ +// 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 main + +import ( + "fmt" + "log" + "strings" +) + +var typeNames = make(map[*Type]string) +var genTypes []*newType + +func findTypeNames(model *Model) { + for _, s := range model.Structures { + for _, e := range s.Extends { + nameType(e, nil) // all references + } + for _, m := range s.Mixins { + nameType(m, nil) // all references + } + for _, p := range s.Properties { + nameType(p.Type, []string{s.Name, p.Name}) + } + } + for _, t := range model.Enumerations { + nameType(t.Type, []string{t.Name}) + } + for _, t := range model.TypeAliases { + nameType(t.Type, []string{t.Name}) + } + for _, r := range model.Requests { + nameType(r.Params, []string{"Param", r.Method}) + nameType(r.Result, []string{"Result", r.Method}) + nameType(r.RegistrationOptions, []string{"RegOpt", r.Method}) + } + for _, n := range model.Notifications { + nameType(n.Params, []string{"Param", n.Method}) + nameType(n.RegistrationOptions, []string{"RegOpt", n.Method}) + } +} + +// nameType populates typeNames[t] with the computed name of the type. +// path is the list of enclosing constructs in the JSON model. +func nameType(t *Type, path []string) string { + if t == nil || typeNames[t] != "" { + return "" + } + switch t.Kind { + case "base": + typeNames[t] = t.Name + return t.Name + case "reference": + typeNames[t] = t.Name + return t.Name + case "array": + nm := "[]" + nameType(t.Element, append(path, "Elem")) + typeNames[t] = nm + return nm + case "map": + key := nameType(t.Key, nil) // never a generated type + value := nameType(t.Value.(*Type), append(path, "Value")) + nm := "map[" + key + "]" + value + typeNames[t] = nm + return nm + // generated types + case "and": + nm := nameFromPath("And", path) + typeNames[t] = nm + for _, it := range t.Items { + nameType(it, append(path, "Item")) + } + genTypes = append(genTypes, &newType{ + name: nm, + typ: t, + kind: "and", + items: t.Items, + line: t.Line, + }) + return nm + case "literal": + nm := nameFromPath("Lit", path) + typeNames[t] = nm + for _, p := range t.Value.(ParseLiteral).Properties { + nameType(p.Type, append(path, p.Name)) + } + genTypes = append(genTypes, &newType{ + name: nm, + typ: t, + kind: "literal", + properties: t.Value.(ParseLiteral).Properties, + line: t.Line, + }) + return nm + case "tuple": + nm := nameFromPath("Tuple", path) + typeNames[t] = nm + for _, it := range t.Items { + nameType(it, append(path, "Item")) + } + genTypes = append(genTypes, &newType{ + name: nm, + typ: t, + kind: "tuple", + items: t.Items, + line: t.Line, + }) + return nm + case "or": + nm := nameFromPath("Or", path) + typeNames[t] = nm + for i, it := range t.Items { + // these names depend on the ordering within the "or" type + nameType(it, append(path, fmt.Sprintf("Item%d", i))) + } + // this code handles an "or" of stringLiterals (_InitializeParams.trace) + names := make(map[string]int) + msg := "" + for _, it := range t.Items { + if line, ok := names[typeNames[it]]; ok { + // duplicate component names are bad + msg += fmt.Sprintf("lines %d %d dup, %s for %s\n", line, it.Line, typeNames[it], nm) + } + names[typeNames[it]] = t.Line + } + // this code handles an "or" of stringLiterals (_InitializeParams.trace) + if len(names) == 1 { + var solekey string + for k := range names { + solekey = k // the sole name + } + if solekey == "string" { // _InitializeParams.trace + typeNames[t] = "string" + return "string" + } + // otherwise unexpected + log.Printf("unexpected: single-case 'or' type has non-string key %s: %s", nm, solekey) + log.Fatal(msg) + } else if len(names) == 2 { + // if one of the names is null, just use the other, rather than generating an "or". + // This removes about 40 types from the generated code. An entry in goplsStar + // could be added to handle the null case, if necessary. + newNm := "" + sawNull := false + for k := range names { + if k == "null" { + sawNull = true + } else { + newNm = k + } + } + if sawNull { + typeNames[t] = newNm + return newNm + } + } + genTypes = append(genTypes, &newType{ + name: nm, + typ: t, + kind: "or", + items: t.Items, + line: t.Line, + }) + return nm + case "stringLiteral": // a single type, like 'kind' or 'rename' + typeNames[t] = "string" + return "string" + default: + log.Fatalf("nameType: %T unexpected, line:%d path:%v", t, t.Line, path) + panic("unreachable in nameType") + } +} + +func nameFromPath(prefix string, path []string) string { + nm := prefix + "_" + strings.Join(path, "_") + // methods have slashes + nm = strings.ReplaceAll(nm, "/", "_") + return nm +} diff --git a/contribs/gnopls/internal/protocol/generate/types.go b/contribs/gnopls/internal/protocol/generate/types.go new file mode 100644 index 00000000000..0537748eb5b --- /dev/null +++ b/contribs/gnopls/internal/protocol/generate/types.go @@ -0,0 +1,167 @@ +// 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 main + +import ( + "fmt" + "sort" +) + +// Model contains the parsed version of the spec +type Model struct { + Version Metadata `json:"metaData"` + Requests []*Request `json:"requests"` + Notifications []*Notification `json:"notifications"` + Structures []*Structure `json:"structures"` + Enumerations []*Enumeration `json:"enumerations"` + TypeAliases []*TypeAlias `json:"typeAliases"` + Line int `json:"line"` +} + +// Metadata is information about the version of the spec +type Metadata struct { + Version string `json:"version"` + Line int `json:"line"` +} + +// A Request is the parsed version of an LSP request +type Request struct { + Documentation string `json:"documentation"` + ErrorData *Type `json:"errorData"` + Direction string `json:"messageDirection"` + Method string `json:"method"` + Params *Type `json:"params"` + PartialResult *Type `json:"partialResult"` + Proposed bool `json:"proposed"` + RegistrationMethod string `json:"registrationMethod"` + RegistrationOptions *Type `json:"registrationOptions"` + Result *Type `json:"result"` + Since string `json:"since"` + Line int `json:"line"` +} + +// A Notificatin is the parsed version of an LSP notification +type Notification struct { + Documentation string `json:"documentation"` + Direction string `json:"messageDirection"` + Method string `json:"method"` + Params *Type `json:"params"` + Proposed bool `json:"proposed"` + RegistrationMethod string `json:"registrationMethod"` + RegistrationOptions *Type `json:"registrationOptions"` + Since string `json:"since"` + Line int `json:"line"` +} + +// A Structure is the parsed version of an LSP structure from the spec +type Structure struct { + Documentation string `json:"documentation"` + Extends []*Type `json:"extends"` + Mixins []*Type `json:"mixins"` + Name string `json:"name"` + Properties []NameType `json:"properties"` + Proposed bool `json:"proposed"` + Since string `json:"since"` + Line int `json:"line"` +} + +// An enumeration is the parsed version of an LSP enumeration from the spec +type Enumeration struct { + Documentation string `json:"documentation"` + Name string `json:"name"` + Proposed bool `json:"proposed"` + Since string `json:"since"` + SupportsCustomValues bool `json:"supportsCustomValues"` + Type *Type `json:"type"` + Values []NameValue `json:"values"` + Line int `json:"line"` +} + +// A TypeAlias is the parsed version of an LSP type alias from the spec +type TypeAlias struct { + Documentation string `json:"documentation"` + Deprecated string `json:"deprecated"` + Name string `json:"name"` + Proposed bool `json:"proposed"` + Since string `json:"since"` + Type *Type `json:"type"` + Line int `json:"line"` +} + +// A NameValue describes an enumeration constant +type NameValue struct { + Documentation string `json:"documentation"` + Name string `json:"name"` + Proposed bool `json:"proposed"` + Since string `json:"since"` + Value any `json:"value"` // number or string + Line int `json:"line"` +} + +// A Type is the parsed version of an LSP type from the spec, +// or a Type the code constructs +type Type struct { + Kind string `json:"kind"` // -- which kind goes with which field -- + Items []*Type `json:"items"` // "and", "or", "tuple" + Element *Type `json:"element"` // "array" + Name string `json:"name"` // "base", "reference" + Key *Type `json:"key"` // "map" + Value any `json:"value"` // "map", "stringLiteral", "literal" + Line int `json:"line"` // JSON source line +} + +// ParsedLiteral is Type.Value when Type.Kind is "literal" +type ParseLiteral struct { + Properties `json:"properties"` +} + +// A NameType represents the name and type of a structure element +type NameType struct { + Name string `json:"name"` + Type *Type `json:"type"` + Optional bool `json:"optional"` + Documentation string `json:"documentation"` + Deprecated string `json:"deprecated"` + Since string `json:"since"` + Proposed bool `json:"proposed"` + Line int `json:"line"` +} + +// Properties are the collection of structure fields +type Properties []NameType + +// addLineNumbers adds a "line" field to each object in the JSON. +func addLineNumbers(buf []byte) []byte { + var ans []byte + // In the specification .json file, the delimiter '{' is + // always followed by a newline. There are other {s embedded in strings. + // json.Token does not return \n, or :, or , so using it would + // require parsing the json to reconstruct the missing information. + for linecnt, i := 1, 0; i < len(buf); i++ { + ans = append(ans, buf[i]) + switch buf[i] { + case '{': + if buf[i+1] == '\n' { + ans = append(ans, fmt.Sprintf(`"line": %d, `, linecnt)...) + // warning: this would fail if the spec file had + // `"value": {\n}`, but it does not, as comma is a separator. + } + case '\n': + linecnt++ + } + } + return ans +} + +type sortedMap[T any] map[string]T + +func (s sortedMap[T]) keys() []string { + var keys []string + for k := range s { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} diff --git a/contribs/gnopls/internal/protocol/json_test.go b/contribs/gnopls/internal/protocol/json_test.go new file mode 100644 index 00000000000..9aac110fa3b --- /dev/null +++ b/contribs/gnopls/internal/protocol/json_test.go @@ -0,0 +1,140 @@ +// 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 protocol_test + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" +) + +// verify that type errors in Initialize lsp messages don't cause +// any other unmarshalling errors. The code looks at single values and the +// first component of array values. Each occurrence is replaced by something +// of a different type, the resulting string unmarshalled, and compared to +// the unmarshalling of the unchanged strings. The test passes if there is no +// more than a single difference reported. That is, if changing a single value +// in the message changes no more than a single value in the unmarshalled struct, +// it is safe to ignore *json.UnmarshalTypeError. + +// strings are changed to numbers or bools (true) +// bools are changed to numbers or strings +// numbers are changed to strings or bools + +// a recent Initialize message taken from a log (at some point +// some field incompatibly changed from bool to int32) +const input = `{"processId":46408,"clientInfo":{"name":"Visual Studio Code - Insiders","version":"1.76.0-insider"},"locale":"en-us","rootPath":"/Users/pjw/hakim","rootUri":"file:///Users/pjw/hakim","capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional","normalizesLineEndings":true,"changeAnnotationSupport":{"groupsOnLabel":true}},"configuration":true,"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]},"resolveSupport":{"properties":["location.range"]}},"codeLens":{"refreshSupport":true},"executeCommand":{"dynamicRegistration":true},"didChangeConfiguration":{"dynamicRegistration":true},"workspaceFolders":true,"semanticTokens":{"refreshSupport":true},"fileOperations":{"dynamicRegistration":true,"didCreate":true,"didRename":true,"didDelete":true,"willCreate":true,"willRename":true,"willDelete":true},"inlineValue":{"refreshSupport":true},"inlayHint":{"refreshSupport":true},"diagnostics":{"refreshSupport":true}},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"codeDescriptionSupport":true,"dataSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true,"resolveSupport":{"properties":["documentation","detail","additionalTextEdits"]},"insertTextModeSupport":{"valueSet":[1,2]},"labelDetailsSupport":true},"insertTextMode":2,"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]},"completionList":{"itemDefaults":["commitCharacters","editRange","insertTextFormat","insertTextMode"]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true},"activeParameterSupport":true},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]},"labelSupport":true},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"disabledSupport":true,"dataSupport":true,"resolveSupport":{"properties":["edit"]},"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}},"honorsChangeAnnotations":false},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true,"prepareSupportDefaultBehavior":1,"honorsChangeAnnotations":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true,"foldingRangeKind":{"valueSet":["comment","imports","region"]},"foldingRange":{"collapsedText":false}},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"callHierarchy":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["namespace","type","class","enum","interface","struct","typeParameter","parameter","variable","property","enumMember","event","function","method","macro","keyword","modifier","comment","string","number","regexp","operator","decorator"],"tokenModifiers":["declaration","definition","readonly","static","deprecated","abstract","async","modification","documentation","defaultLibrary"],"formats":["relative"],"requests":{"range":true,"full":{"delta":true}},"multilineTokenSupport":false,"overlappingTokenSupport":false,"serverCancelSupport":true,"augmentsSyntaxTokens":true},"linkedEditingRange":{"dynamicRegistration":true},"typeHierarchy":{"dynamicRegistration":true},"inlineValue":{"dynamicRegistration":true},"inlayHint":{"dynamicRegistration":true,"resolveSupport":{"properties":["tooltip","textEdits","label.tooltip","label.location","label.command"]}},"diagnostic":{"dynamicRegistration":true,"relatedDocumentSupport":false}},"window":{"showMessage":{"messageActionItem":{"additionalPropertiesSupport":true}},"showDocument":{"support":true},"workDoneProgress":true},"general":{"staleRequestSupport":{"cancel":true,"retryOnContentModified":["textDocument/semanticTokens/full","textDocument/semanticTokens/range","textDocument/semanticTokens/full/delta"]},"regularExpressions":{"engine":"ECMAScript","version":"ES2020"},"markdown":{"parser":"marked","version":"1.1.0"},"positionEncodings":["utf-16"]},"notebookDocument":{"synchronization":{"dynamicRegistration":true,"executionSummarySupport":true}}},"initializationOptions":{"usePlaceholders":true,"completionDocumentation":true,"verboseOutput":false,"build.directoryFilters":["-foof","-internal/protocol/typescript"],"codelenses":{"reference":true,"gc_details":true},"analyses":{"fillstruct":true,"staticcheck":true,"unusedparams":false,"composites":false},"semanticTokens":true,"noSemanticString":true,"noSemanticNumber":true,"templateExtensions":["tmpl","gotmpl"],"ui.completion.matcher":"Fuzzy","ui.inlayhint.hints":{"assignVariableTypes":false,"compositeLiteralFields":false,"compositeLiteralTypes":false,"constantValues":false,"functionTypeParameters":false,"parameterNames":false,"rangeVariableTypes":false},"ui.vulncheck":"Off","allExperiments":true},"trace":"off","workspaceFolders":[{"uri":"file:///Users/pjw/hakim","name":"hakim"}]}` + +type DiffReporter struct { + path cmp.Path + diffs []string +} + +func (r *DiffReporter) PushStep(ps cmp.PathStep) { + r.path = append(r.path, ps) +} + +func (r *DiffReporter) Report(rs cmp.Result) { + if !rs.Equal() { + vx, vy := r.path.Last().Values() + r.diffs = append(r.diffs, fmt.Sprintf("%#v:\n\t-: %+v\n\t+: %+v\n", r.path, vx, vy)) + } +} + +func (r *DiffReporter) PopStep() { + r.path = r.path[:len(r.path)-1] +} + +func (r *DiffReporter) String() string { + return strings.Join(r.diffs, "\n") +} + +func TestStringChanges(t *testing.T) { + // string as value + stringLeaf := regexp.MustCompile(`:("[^"]*")`) + leafs := stringLeaf.FindAllStringSubmatchIndex(input, -1) + allDeltas(t, leafs, "23", "true") + // string as first element of array + stringArray := regexp.MustCompile(`[[]("[^"]*")`) + arrays := stringArray.FindAllStringSubmatchIndex(input, -1) + allDeltas(t, arrays, "23", "true") +} + +func TestBoolChanges(t *testing.T) { + boolLeaf := regexp.MustCompile(`:(true|false)(,|})`) + leafs := boolLeaf.FindAllStringSubmatchIndex(input, -1) + allDeltas(t, leafs, "23", `"xx"`) + boolArray := regexp.MustCompile(`:[[](true|false)(,|])`) + arrays := boolArray.FindAllStringSubmatchIndex(input, -1) + allDeltas(t, arrays, "23", `"xx"`) +} + +func TestNumberChanges(t *testing.T) { + numLeaf := regexp.MustCompile(`:(\d+)(,|})`) + leafs := numLeaf.FindAllStringSubmatchIndex(input, -1) + allDeltas(t, leafs, "true", `"xx"`) + numArray := regexp.MustCompile(`:[[](\d+)(,|])`) + arrays := numArray.FindAllStringSubmatchIndex(input, -1) + allDeltas(t, arrays, "true", `"xx"`) +} + +// v is a set of matches. check that substituting any repl never +// creates more than 1 unmarshaling error +func allDeltas(t *testing.T, v [][]int, repls ...string) { + t.Helper() + for _, repl := range repls { + for i, x := range v { + err := tryChange(x[2], x[3], repl) + if err != nil { + t.Errorf("%d:%q %v", i, input[x[2]:x[3]], err) + } + } + } +} + +func tryChange(start, end int, repl string) error { + var p, q protocol.ParamInitialize + mod := input[:start] + repl + input[end:] + excerpt := func() (string, string) { + a := start - 5 + if a < 0 { + a = 0 + } + b := end + 5 + if b > len(input) { + // trusting repl to be no longer than what it replaces + b = len(input) + } + ma := input[a:b] + mb := mod[a:b] + return ma, mb + } + + if err := json.Unmarshal([]byte(input), &p); err != nil { + return fmt.Errorf("%s %v", repl, err) + } + switch err := json.Unmarshal([]byte(mod), &q).(type) { + case nil: //ok + case *json.UnmarshalTypeError: + break + case *protocol.UnmarshalError: + return nil // cmp.Diff produces several diffs for custom unmrshalers + default: + return fmt.Errorf("%T unexpected unmarshal error", err) + } + + var r DiffReporter + cmp.Diff(p, q, cmp.Reporter(&r)) + if len(r.diffs) > 1 { // 0 is possible, e.g., for interface{} + ma, mb := excerpt() + return fmt.Errorf("got %d diffs for %q\n%s\n%s", len(r.diffs), repl, ma, mb) + } + return nil +} diff --git a/contribs/gnopls/internal/protocol/log.go b/contribs/gnopls/internal/protocol/log.go new file mode 100644 index 00000000000..fdcbb7a8d8b --- /dev/null +++ b/contribs/gnopls/internal/protocol/log.go @@ -0,0 +1,136 @@ +// 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. + +package protocol + +import ( + "context" + "fmt" + "io" + "strings" + "sync" + "time" + + "golang.org/x/tools/internal/jsonrpc2" +) + +type loggingStream struct { + stream jsonrpc2.Stream + logMu sync.Mutex + log io.Writer +} + +// LoggingStream returns a stream that does LSP protocol logging too +func LoggingStream(str jsonrpc2.Stream, w io.Writer) jsonrpc2.Stream { + return &loggingStream{stream: str, log: w} +} + +func (s *loggingStream) Read(ctx context.Context) (jsonrpc2.Message, int64, error) { + msg, count, err := s.stream.Read(ctx) + if err == nil { + s.logCommon(msg, true) + } + return msg, count, err +} + +func (s *loggingStream) Write(ctx context.Context, msg jsonrpc2.Message) (int64, error) { + s.logCommon(msg, false) + count, err := s.stream.Write(ctx, msg) + return count, err +} + +func (s *loggingStream) Close() error { + return s.stream.Close() +} + +type req struct { + method string + start time.Time +} + +type mapped struct { + mu sync.Mutex + clientCalls map[string]req + serverCalls map[string]req +} + +var maps = &mapped{ + sync.Mutex{}, + make(map[string]req), + make(map[string]req), +} + +// these 4 methods are each used exactly once, but it seemed +// better to have the encapsulation rather than ad hoc mutex +// code in 4 places +func (m *mapped) client(id string) req { + m.mu.Lock() + defer m.mu.Unlock() + v := m.clientCalls[id] + delete(m.clientCalls, id) + return v +} + +func (m *mapped) server(id string) req { + m.mu.Lock() + defer m.mu.Unlock() + v := m.serverCalls[id] + delete(m.serverCalls, id) + return v +} + +func (m *mapped) setClient(id string, r req) { + m.mu.Lock() + defer m.mu.Unlock() + m.clientCalls[id] = r +} + +func (m *mapped) setServer(id string, r req) { + m.mu.Lock() + defer m.mu.Unlock() + m.serverCalls[id] = r +} + +const eor = "\r\n\r\n\r\n" + +func (s *loggingStream) logCommon(msg jsonrpc2.Message, isRead bool) { + s.logMu.Lock() + defer s.logMu.Unlock() + direction, pastTense := "Received", "Received" + get, set := maps.client, maps.setServer + if isRead { + direction, pastTense = "Sending", "Sent" + get, set = maps.server, maps.setClient + } + if msg == nil || s.log == nil { + return + } + tm := time.Now() + tmfmt := tm.Format("15:04:05.000 PM") + + buf := strings.Builder{} + fmt.Fprintf(&buf, "[Trace - %s] ", tmfmt) // common beginning + switch msg := msg.(type) { + case *jsonrpc2.Call: + id := fmt.Sprint(msg.ID()) + fmt.Fprintf(&buf, "%s request '%s - (%s)'.\n", direction, msg.Method(), id) + fmt.Fprintf(&buf, "Params: %s%s", msg.Params(), eor) + set(id, req{method: msg.Method(), start: tm}) + case *jsonrpc2.Notification: + fmt.Fprintf(&buf, "%s notification '%s'.\n", direction, msg.Method()) + fmt.Fprintf(&buf, "Params: %s%s", msg.Params(), eor) + case *jsonrpc2.Response: + id := fmt.Sprint(msg.ID()) + if err := msg.Err(); err != nil { + fmt.Fprintf(s.log, "[Error - %s] %s #%s %s%s", pastTense, tmfmt, id, err, eor) + return + } + cc := get(id) + elapsed := tm.Sub(cc.start) + fmt.Fprintf(&buf, "%s response '%s - (%s)' in %dms.\n", + direction, cc.method, id, elapsed/time.Millisecond) + fmt.Fprintf(&buf, "Result: %s%s", msg.Result(), eor) + } + s.log.Write([]byte(buf.String())) +} diff --git a/contribs/gnopls/internal/protocol/mapper.go b/contribs/gnopls/internal/protocol/mapper.go new file mode 100644 index 00000000000..85997c24dc4 --- /dev/null +++ b/contribs/gnopls/internal/protocol/mapper.go @@ -0,0 +1,443 @@ +// 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 protocol + +// This file defines Mapper, which wraps a file content buffer +// ([]byte) and provides efficient conversion between every kind of +// position representation. +// +// gopls uses four main representations of position: +// +// 1. byte offsets, e.g. (start, end int), starting from zero. +// +// 2. go/token notation. Use these types when interacting directly +// with the go/* syntax packages: +// +// token.Pos +// token.FileSet +// token.File +// +// Because File.Offset and File.Pos panic on invalid inputs, +// we do not call them directly and instead use the safetoken package +// for these conversions. This is enforced by a static check. +// +// Beware also that the methods of token.File have two bugs for which +// safetoken contains workarounds: +// - #57490, whereby the parser may create ast.Nodes during error +// recovery whose computed positions are out of bounds (EOF+1). +// - #41029, whereby the wrong line number is returned for the EOF position. +// +// 3. the cmd package. +// +// cmd.point = (line, col8, offset). +// cmd.Span = (uri URI, start, end cmd.point) +// +// Line and column are 1-based. +// Columns are measured in bytes (UTF-8 codes). +// All fields are optional. +// +// These types are useful as intermediate conversions of validated +// ranges (though MappedRange is superior as it is self contained +// and universally convertible). Since their fields are optional +// they are also useful for parsing user-provided positions (e.g. in +// the CLI) before we have access to file contents. +// +// 4. protocol, the LSP RPC message format. +// +// protocol.Position = (Line, Character uint32) +// protocol.Range = (start, end Position) +// protocol.Location = (URI, protocol.Range) +// +// Line and Character are 0-based. +// Characters (columns) are measured in UTF-16 codes. +// +// protocol.Mapper holds the (URI, Content) of a file, enabling +// efficient mapping between byte offsets, cmd ranges, and +// protocol ranges. +// +// protocol.MappedRange holds a protocol.Mapper and valid (start, +// end int) byte offsets, enabling infallible, efficient conversion +// to any other format. + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "sort" + "strings" + "sync" + "unicode/utf8" + + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" +) + +// A Mapper wraps the content of a file and provides mapping +// between byte offsets and notations of position such as: +// +// - (line, col8) pairs, where col8 is a 1-based UTF-8 column number +// (bytes), as used by the go/token and cmd packages. +// +// - (line, col16) pairs, where col16 is a 1-based UTF-16 column +// number, as used by the LSP protocol. +// +// All conversion methods are named "FromTo", where From and To are the two types. +// For example, the PointPosition method converts from a Point to a Position. +// +// Mapper does not intrinsically depend on go/token-based +// representations. Use safetoken to map between token.Pos <=> byte +// offsets, or the convenience methods such as PosPosition, +// NodePosition, or NodeRange. +// +// See overview comments at top of this file. +type Mapper struct { + URI DocumentURI + Content []byte + + // Line-number information is requested only for a tiny + // fraction of Mappers, so we compute it lazily. + // Call initLines() before accessing fields below. + linesOnce sync.Once + lineStart []int // byte offset of start of ith line (0-based); last=EOF iff \n-terminated + nonASCII bool + + // TODO(adonovan): adding an extra lineStart entry for EOF + // might simplify every method that accesses it. Try it out. +} + +// NewMapper creates a new mapper for the given URI and content. +func NewMapper(uri DocumentURI, content []byte) *Mapper { + return &Mapper{URI: uri, Content: content} +} + +// initLines populates the lineStart table. +func (m *Mapper) initLines() { + m.linesOnce.Do(func() { + nlines := bytes.Count(m.Content, []byte("\n")) + m.lineStart = make([]int, 1, nlines+1) // initially []int{0} + for offset, b := range m.Content { + if b == '\n' { + m.lineStart = append(m.lineStart, offset+1) + } + if b >= utf8.RuneSelf { + m.nonASCII = true + } + } + }) +} + +// LineCol8Position converts a valid line and UTF-8 column number, +// both 1-based, to a protocol (UTF-16) position. +func (m *Mapper) LineCol8Position(line, col8 int) (Position, error) { + // Report a bug for inputs that are invalid for any file content. + if line < 1 { + return Position{}, bug.Errorf("invalid 1-based line number: %d", line) + } + if col8 < 1 { + return Position{}, bug.Errorf("invalid 1-based column number: %d", col8) + } + + m.initLines() + line0 := line - 1 // 0-based + if !(0 <= line0 && line0 < len(m.lineStart)) { + return Position{}, fmt.Errorf("line number %d out of range (max %d)", line, len(m.lineStart)) + } + + // content[start:end] is the preceding partial line. + start := m.lineStart[line0] + end := start + col8 - 1 + + // Validate column. + if end > len(m.Content) { + return Position{}, fmt.Errorf("column is beyond end of file") + } else if line0+1 < len(m.lineStart) && end >= m.lineStart[line0+1] { + return Position{}, fmt.Errorf("column is beyond end of line") + } + + char := UTF16Len(m.Content[start:end]) + return Position{Line: uint32(line0), Character: uint32(char)}, nil +} + +// -- conversions from byte offsets -- + +// OffsetLocation converts a byte-offset interval to a protocol (UTF-16) location. +func (m *Mapper) OffsetLocation(start, end int) (Location, error) { + rng, err := m.OffsetRange(start, end) + if err != nil { + return Location{}, err + } + return m.RangeLocation(rng), nil +} + +// OffsetRange converts a byte-offset interval to a protocol (UTF-16) range. +func (m *Mapper) OffsetRange(start, end int) (Range, error) { + if start > end { + return Range{}, fmt.Errorf("start offset (%d) > end (%d)", start, end) + } + startPosition, err := m.OffsetPosition(start) + if err != nil { + return Range{}, fmt.Errorf("start: %v", err) + } + endPosition, err := m.OffsetPosition(end) + if err != nil { + return Range{}, fmt.Errorf("end: %v", err) + } + return Range{Start: startPosition, End: endPosition}, nil +} + +// OffsetPosition converts a byte offset to a protocol (UTF-16) position. +func (m *Mapper) OffsetPosition(offset int) (Position, error) { + if !(0 <= offset && offset <= len(m.Content)) { + return Position{}, fmt.Errorf("invalid offset %d (want 0-%d)", offset, len(m.Content)) + } + // No error may be returned after this point, + // even if the offset does not fall at a rune boundary. + // (See panic in MappedRange.Range reachable.) + + line, col16 := m.lineCol16(offset) + return Position{Line: uint32(line), Character: uint32(col16)}, nil +} + +// lineCol16 converts a valid byte offset to line and UTF-16 column numbers, both 0-based. +func (m *Mapper) lineCol16(offset int) (int, int) { + line, start, cr := m.line(offset) + var col16 int + if m.nonASCII { + col16 = UTF16Len(m.Content[start:offset]) + } else { + col16 = offset - start + } + if cr { + col16-- // retreat from \r at line end + } + return line, col16 +} + +// OffsetLineCol8 converts a valid byte offset to line and UTF-8 column numbers, both 1-based. +func (m *Mapper) OffsetLineCol8(offset int) (int, int) { + line, start, cr := m.line(offset) + col8 := offset - start + if cr { + col8-- // retreat from \r at line end + } + return line + 1, col8 + 1 +} + +// line returns: +// - the 0-based index of the line that encloses the (valid) byte offset; +// - the start offset of that line; and +// - whether the offset denotes a carriage return (\r) at line end. +func (m *Mapper) line(offset int) (int, int, bool) { + m.initLines() + // In effect, binary search returns a 1-based result. + line := sort.Search(len(m.lineStart), func(i int) bool { + return offset < m.lineStart[i] + }) + + // Adjustment for line-endings: \r|\n is the same as |\r\n. + var eol int + if line == len(m.lineStart) { + eol = len(m.Content) // EOF + } else { + eol = m.lineStart[line] - 1 + } + cr := offset == eol && offset > 0 && m.Content[offset-1] == '\r' + + line-- // 0-based + + return line, m.lineStart[line], cr +} + +// OffsetMappedRange returns a MappedRange for the given byte offsets. +// A MappedRange can be converted to any other form. +func (m *Mapper) OffsetMappedRange(start, end int) (MappedRange, error) { + if !(0 <= start && start <= end && end <= len(m.Content)) { + return MappedRange{}, fmt.Errorf("invalid offsets (%d, %d) (file %s has size %d)", start, end, m.URI, len(m.Content)) + } + return MappedRange{m, start, end}, nil +} + +// -- conversions from protocol (UTF-16) domain -- + +// RangeOffsets converts a protocol (UTF-16) range to start/end byte offsets. +func (m *Mapper) RangeOffsets(r Range) (int, int, error) { + start, err := m.PositionOffset(r.Start) + if err != nil { + return 0, 0, err + } + end, err := m.PositionOffset(r.End) + if err != nil { + return 0, 0, err + } + return start, end, nil +} + +// PositionOffset converts a protocol (UTF-16) position to a byte offset. +func (m *Mapper) PositionOffset(p Position) (int, error) { + m.initLines() + + // Validate line number. + if p.Line > uint32(len(m.lineStart)) { + return 0, fmt.Errorf("line number %d out of range 0-%d", p.Line, len(m.lineStart)) + } else if p.Line == uint32(len(m.lineStart)) { + if p.Character == 0 { + return len(m.Content), nil // EOF + } + return 0, fmt.Errorf("column is beyond end of file") + } + + offset := m.lineStart[p.Line] + content := m.Content[offset:] // rest of file from start of enclosing line + + // Advance bytes up to the required number of UTF-16 codes. + col8 := 0 + for col16 := 0; col16 < int(p.Character); col16++ { + r, sz := utf8.DecodeRune(content) + if sz == 0 { + return 0, fmt.Errorf("column is beyond end of file") + } + if r == '\n' { + return 0, fmt.Errorf("column is beyond end of line") + } + if sz == 1 && r == utf8.RuneError { + return 0, fmt.Errorf("buffer contains invalid UTF-8 text") + } + content = content[sz:] + + if r >= 0x10000 { + col16++ // rune was encoded by a pair of surrogate UTF-16 codes + + if col16 == int(p.Character) { + break // requested position is in the middle of a rune + } + } + col8 += sz + } + return offset + col8, nil +} + +// -- go/token domain convenience methods -- + +// PosPosition converts a token pos to a protocol (UTF-16) position. +func (m *Mapper) PosPosition(tf *token.File, pos token.Pos) (Position, error) { + offset, err := safetoken.Offset(tf, pos) + if err != nil { + return Position{}, err + } + return m.OffsetPosition(offset) +} + +// PosLocation converts a token range to a protocol (UTF-16) location. +func (m *Mapper) PosLocation(tf *token.File, start, end token.Pos) (Location, error) { + startOffset, endOffset, err := safetoken.Offsets(tf, start, end) + if err != nil { + return Location{}, err + } + rng, err := m.OffsetRange(startOffset, endOffset) + if err != nil { + return Location{}, err + } + return m.RangeLocation(rng), nil +} + +// PosRange converts a token range to a protocol (UTF-16) range. +func (m *Mapper) PosRange(tf *token.File, start, end token.Pos) (Range, error) { + startOffset, endOffset, err := safetoken.Offsets(tf, start, end) + if err != nil { + return Range{}, err + } + return m.OffsetRange(startOffset, endOffset) +} + +// NodeRange converts a syntax node range to a protocol (UTF-16) range. +func (m *Mapper) NodeRange(tf *token.File, node ast.Node) (Range, error) { + return m.PosRange(tf, node.Pos(), node.End()) +} + +// RangeLocation pairs a protocol Range with its URI, in a Location. +func (m *Mapper) RangeLocation(rng Range) Location { + return Location{URI: m.URI, Range: rng} +} + +// PosMappedRange returns a MappedRange for the given token.Pos range. +func (m *Mapper) PosMappedRange(tf *token.File, start, end token.Pos) (MappedRange, error) { + startOffset, endOffset, err := safetoken.Offsets(tf, start, end) + if err != nil { + return MappedRange{}, nil + } + return m.OffsetMappedRange(startOffset, endOffset) +} + +// NodeMappedRange returns a MappedRange for the given node range. +func (m *Mapper) NodeMappedRange(tf *token.File, node ast.Node) (MappedRange, error) { + return m.PosMappedRange(tf, node.Pos(), node.End()) +} + +// -- MappedRange -- + +// A MappedRange represents a valid byte-offset range of a file. +// Through its Mapper it can be converted into other forms such +// as protocol.Range or UTF-8. +// +// Construct one by calling Mapper.OffsetMappedRange with start/end offsets. +// From the go/token domain, call safetoken.Offsets first, +// or use a helper such as parsego.File.MappedPosRange. +// +// Two MappedRanges produced the same Mapper are equal if and only if they +// denote the same range. Two MappedRanges produced by different Mappers +// are unequal even when they represent the same range of the same file. +type MappedRange struct { + Mapper *Mapper + start, end int // valid byte offsets: 0 <= start <= end <= len(Mapper.Content) +} + +// Offsets returns the (start, end) byte offsets of this range. +func (mr MappedRange) Offsets() (start, end int) { return mr.start, mr.end } + +// -- convenience functions -- + +// URI returns the URI of the range's file. +func (mr MappedRange) URI() DocumentURI { + return mr.Mapper.URI +} + +// Range returns the range in protocol (UTF-16) form. +func (mr MappedRange) Range() Range { + rng, err := mr.Mapper.OffsetRange(mr.start, mr.end) + if err != nil { + panic(err) // can't happen + } + return rng +} + +// Location returns the range in protocol location (UTF-16) form. +func (mr MappedRange) Location() Location { + return mr.Mapper.RangeLocation(mr.Range()) +} + +// String formats the range in UTF-8 notation. +func (mr MappedRange) String() string { + var s strings.Builder + startLine, startCol8 := mr.Mapper.OffsetLineCol8(mr.start) + fmt.Fprintf(&s, "%d:%d", startLine, startCol8) + if mr.end != mr.start { + endLine, endCol8 := mr.Mapper.OffsetLineCol8(mr.end) + if endLine == startLine { + fmt.Fprintf(&s, "-%d", endCol8) + } else { + fmt.Fprintf(&s, "-%d:%d", endLine, endCol8) + } + } + return s.String() +} + +// LocationTextDocumentPositionParams converts its argument to its result. +func LocationTextDocumentPositionParams(loc Location) TextDocumentPositionParams { + return TextDocumentPositionParams{ + TextDocument: TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + } +} diff --git a/contribs/gnopls/internal/protocol/mapper_test.go b/contribs/gnopls/internal/protocol/mapper_test.go new file mode 100644 index 00000000000..8ba611a99f9 --- /dev/null +++ b/contribs/gnopls/internal/protocol/mapper_test.go @@ -0,0 +1,449 @@ +// 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 protocol_test + +import ( + "fmt" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// This file tests Mapper's logic for converting between offsets, +// UTF-8 columns, and UTF-16 columns. (The strange form attests to +// earlier abstractions.) + +// 𐐀 is U+10400 = [F0 90 90 80] in UTF-8, [D801 DC00] in UTF-16. +var funnyString = []byte("𐐀23\n𐐀45") + +var toUTF16Tests = []struct { + scenario string + input []byte + line int // 1-indexed count + col int // 1-indexed byte position in line + offset int // 0-indexed byte offset into input + resUTF16col int // 1-indexed UTF-16 col number + pre string // everything before the cursor on the line + post string // everything from the cursor onwards + err string // expected error string in call to ToUTF16Column + issue *bool +}{ + { + scenario: "cursor missing content", + input: nil, + offset: -1, + err: "point has neither offset nor line/column", + }, + { + scenario: "cursor missing position", + input: funnyString, + line: -1, + col: -1, + offset: -1, + err: "point has neither offset nor line/column", + }, + { + scenario: "zero length input; cursor at first col, first line", + input: []byte(""), + line: 1, + col: 1, + offset: 0, + resUTF16col: 1, + }, + { + scenario: "cursor before funny character; first line", + input: funnyString, + line: 1, + col: 1, + offset: 0, + resUTF16col: 1, + pre: "", + post: "𐐀23", + }, + { + scenario: "cursor after funny character; first line", + input: funnyString, + line: 1, + col: 5, // 4 + 1 (1-indexed) + offset: 4, // (unused since we have line+col) + resUTF16col: 3, // 2 + 1 (1-indexed) + pre: "𐐀", + post: "23", + }, + { + scenario: "cursor after last character on first line", + input: funnyString, + line: 1, + col: 7, // 4 + 1 + 1 + 1 (1-indexed) + offset: 6, // 4 + 1 + 1 (unused since we have line+col) + resUTF16col: 5, // 2 + 1 + 1 + 1 (1-indexed) + pre: "𐐀23", + post: "", + }, + { + scenario: "cursor before funny character; second line", + input: funnyString, + line: 2, + col: 1, + offset: 7, // length of first line (unused since we have line+col) + resUTF16col: 1, + pre: "", + post: "𐐀45", + }, + { + scenario: "cursor after funny character; second line", + input: funnyString, + line: 1, + col: 5, // 4 + 1 (1-indexed) + offset: 11, // 7 (length of first line) + 4 (unused since we have line+col) + resUTF16col: 3, // 2 + 1 (1-indexed) + pre: "𐐀", + post: "45", + }, + { + scenario: "cursor after last character on second line", + input: funnyString, + line: 2, + col: 7, // 4 + 1 + 1 + 1 (1-indexed) + offset: 13, // 7 (length of first line) + 4 + 1 + 1 (unused since we have line+col) + resUTF16col: 5, // 2 + 1 + 1 + 1 (1-indexed) + pre: "𐐀45", + post: "", + }, + { + scenario: "cursor beyond end of file", + input: funnyString, + line: 2, + col: 8, // 4 + 1 + 1 + 1 + 1 (1-indexed) + offset: 14, // 4 + 1 + 1 + 1 (unused since we have line+col) + err: "column is beyond end of file", + }, +} + +var fromUTF16Tests = []struct { + scenario string + input []byte + line int // 1-indexed line number (isn't actually used) + utf16col int // 1-indexed UTF-16 col number + resCol int // 1-indexed byte position in line + resOffset int // 0-indexed byte offset into input + pre string // everything before the cursor on the line + post string // everything from the cursor onwards + err string // expected error string in call to ToUTF16Column +}{ + { + scenario: "zero length input; cursor at first col, first line", + input: []byte(""), + line: 1, + utf16col: 1, + resCol: 1, + resOffset: 0, + pre: "", + post: "", + }, + { + scenario: "cursor before funny character", + input: funnyString, + line: 1, + utf16col: 1, + resCol: 1, + resOffset: 0, + pre: "", + post: "𐐀23", + }, + { + scenario: "cursor after funny character", + input: funnyString, + line: 1, + utf16col: 3, + resCol: 5, + resOffset: 4, + pre: "𐐀", + post: "23", + }, + { + scenario: "cursor after last character on line", + input: funnyString, + line: 1, + utf16col: 5, + resCol: 7, + resOffset: 6, + pre: "𐐀23", + post: "", + }, + { + scenario: "cursor beyond last character on line", + input: funnyString, + line: 1, + utf16col: 6, + resCol: 7, + resOffset: 6, + pre: "𐐀23", + post: "", + err: "column is beyond end of line", + }, + { + scenario: "cursor before funny character; second line", + input: funnyString, + line: 2, + utf16col: 1, + resCol: 1, + resOffset: 7, + pre: "", + post: "𐐀45", + }, + { + scenario: "cursor after funny character; second line", + input: funnyString, + line: 2, + utf16col: 3, // 2 + 1 (1-indexed) + resCol: 5, // 4 + 1 (1-indexed) + resOffset: 11, // 7 (length of first line) + 4 + pre: "𐐀", + post: "45", + }, + { + scenario: "cursor after last character on second line", + input: funnyString, + line: 2, + utf16col: 5, // 2 + 1 + 1 + 1 (1-indexed) + resCol: 7, // 4 + 1 + 1 + 1 (1-indexed) + resOffset: 13, // 7 (length of first line) + 4 + 1 + 1 + pre: "𐐀45", + post: "", + }, + { + scenario: "cursor beyond end of file", + input: funnyString, + line: 2, + utf16col: 6, // 2 + 1 + 1 + 1 + 1(1-indexed) + resCol: 8, // 4 + 1 + 1 + 1 + 1 (1-indexed) + resOffset: 14, // 7 (length of first line) + 4 + 1 + 1 + 1 + err: "column is beyond end of file", + }, +} + +func TestToUTF16(t *testing.T) { + for _, e := range toUTF16Tests { + t.Run(e.scenario, func(t *testing.T) { + if e.issue != nil && !*e.issue { + t.Skip("expected to fail") + } + m := protocol.NewMapper("", e.input) + var pos protocol.Position + var err error + if e.line > 0 { + pos, err = m.LineCol8Position(e.line, e.col) + } else if e.offset >= 0 { + pos, err = m.OffsetPosition(e.offset) + } else { + err = fmt.Errorf("point has neither offset nor line/column") + } + if err != nil { + if err.Error() != e.err { + t.Fatalf("expected error %v; got %v", e.err, err) + } + return + } + if e.err != "" { + t.Fatalf("unexpected success; wanted %v", e.err) + } + got := int(pos.Character) + 1 + if got != e.resUTF16col { + t.Fatalf("expected result %v; got %v", e.resUTF16col, got) + } + pre, post := getPrePost(e.input, e.offset) + if pre != e.pre { + t.Fatalf("expected #%d pre %q; got %q", e.offset, e.pre, pre) + } + if post != e.post { + t.Fatalf("expected #%d, post %q; got %q", e.offset, e.post, post) + } + }) + } +} + +func TestFromUTF16(t *testing.T) { + for _, e := range fromUTF16Tests { + t.Run(e.scenario, func(t *testing.T) { + m := protocol.NewMapper("", e.input) + offset, err := m.PositionOffset(protocol.Position{ + Line: uint32(e.line - 1), + Character: uint32(e.utf16col - 1), + }) + if err != nil { + if err.Error() != e.err { + t.Fatalf("expected error %v; got %v", e.err, err) + } + return + } + if e.err != "" { + t.Fatalf("unexpected success; wanted %v", e.err) + } + if offset != e.resOffset { + t.Fatalf("expected offset %v; got %v", e.resOffset, offset) + } + line, col8 := m.OffsetLineCol8(offset) + if line != e.line { + t.Fatalf("expected resulting line %v; got %v", e.line, line) + } + if col8 != e.resCol { + t.Fatalf("expected resulting col %v; got %v", e.resCol, col8) + } + pre, post := getPrePost(e.input, offset) + if pre != e.pre { + t.Fatalf("expected #%d pre %q; got %q", offset, e.pre, pre) + } + if post != e.post { + t.Fatalf("expected #%d post %q; got %q", offset, e.post, post) + } + }) + } +} + +func getPrePost(content []byte, offset int) (string, string) { + pre, post := string(content)[:offset], string(content)[offset:] + if i := strings.LastIndex(pre, "\n"); i >= 0 { + pre = pre[i+1:] + } + if i := strings.IndexRune(post, '\n'); i >= 0 { + post = post[:i] + } + return pre, post +} + +// -- these are the historical lsppos tests -- + +type testCase struct { + content string // input text + substrOrOffset interface{} // explicit integer offset, or a substring + wantLine, wantChar int // expected LSP position information +} + +// offset returns the test case byte offset +func (c testCase) offset() int { + switch x := c.substrOrOffset.(type) { + case int: + return x + case string: + i := strings.Index(c.content, x) + if i < 0 { + panic(fmt.Sprintf("%q does not contain substring %q", c.content, x)) + } + return i + } + panic("substrOrIndex must be an integer or string") +} + +var tests = []testCase{ + {"a𐐀b", "a", 0, 0}, + {"a𐐀b", "𐐀", 0, 1}, + {"a𐐀b", "b", 0, 3}, + {"a𐐀b\n", "\n", 0, 4}, + {"a𐐀b\r\n", "\n", 0, 4}, // \r|\n is not a valid position, so we move back to the end of the first line. + {"a𐐀b\r\nx", "x", 1, 0}, + {"a𐐀b\r\nx\ny", "y", 2, 0}, + + // Testing EOL and EOF positions + {"", 0, 0, 0}, // 0th position of an empty buffer is (0, 0) + {"abc", "c", 0, 2}, + {"abc", 3, 0, 3}, + {"abc\n", "\n", 0, 3}, + {"abc\n", 4, 1, 0}, // position after a newline is on the next line +} + +func TestLineChar(t *testing.T) { + for _, test := range tests { + m := protocol.NewMapper("", []byte(test.content)) + offset := test.offset() + posn, _ := m.OffsetPosition(offset) + gotLine, gotChar := int(posn.Line), int(posn.Character) + if gotLine != test.wantLine || gotChar != test.wantChar { + t.Errorf("LineChar(%d) = (%d,%d), want (%d,%d)", offset, gotLine, gotChar, test.wantLine, test.wantChar) + } + } +} + +func TestInvalidOffset(t *testing.T) { + content := []byte("a𐐀b\r\nx\ny") + m := protocol.NewMapper("", content) + for _, offset := range []int{-1, 100} { + posn, err := m.OffsetPosition(offset) + if err == nil { + t.Errorf("OffsetPosition(%d) = %s, want error", offset, posn) + } + } +} + +func TestPosition(t *testing.T) { + for _, test := range tests { + m := protocol.NewMapper("", []byte(test.content)) + offset := test.offset() + got, err := m.OffsetPosition(offset) + if err != nil { + t.Errorf("OffsetPosition(%d) failed: %v", offset, err) + continue + } + want := protocol.Position{Line: uint32(test.wantLine), Character: uint32(test.wantChar)} + if got != want { + t.Errorf("Position(%d) = %v, want %v", offset, got, want) + } + } +} + +func TestRange(t *testing.T) { + for _, test := range tests { + m := protocol.NewMapper("", []byte(test.content)) + offset := test.offset() + got, err := m.OffsetRange(0, offset) + if err != nil { + t.Fatal(err) + } + want := protocol.Range{ + End: protocol.Position{Line: uint32(test.wantLine), Character: uint32(test.wantChar)}, + } + if got != want { + t.Errorf("Range(%d) = %v, want %v", offset, got, want) + } + } +} + +func TestBytesOffset(t *testing.T) { + tests := []struct { + text string + pos protocol.Position + want int + }{ + // U+10400 encodes as [F0 90 90 80] in UTF-8 and [D801 DC00] in UTF-16. + {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 0}, want: 0}, + {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 1}, want: 1}, + {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 2}, want: 1}, + {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 3}, want: 5}, + {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 4}, want: 6}, + {text: `a𐐀b`, pos: protocol.Position{Line: 0, Character: 5}, want: -1}, + {text: "aaa\nbbb\n", pos: protocol.Position{Line: 0, Character: 3}, want: 3}, + {text: "aaa\nbbb\n", pos: protocol.Position{Line: 0, Character: 4}, want: -1}, + {text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 0}, want: 4}, + {text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 3}, want: 7}, + {text: "aaa\nbbb\n", pos: protocol.Position{Line: 1, Character: 4}, want: -1}, + {text: "aaa\nbbb\n", pos: protocol.Position{Line: 2, Character: 0}, want: 8}, + {text: "aaa\nbbb\n", pos: protocol.Position{Line: 2, Character: 1}, want: -1}, + {text: "aaa\nbbb\n\n", pos: protocol.Position{Line: 2, Character: 0}, want: 8}, + } + + for i, test := range tests { + fname := fmt.Sprintf("test %d", i) + uri := protocol.URIFromPath(fname) + mapper := protocol.NewMapper(uri, []byte(test.text)) + got, err := mapper.PositionOffset(test.pos) + if err != nil && test.want != -1 { + t.Errorf("%d: unexpected error: %v", i, err) + } + if err == nil && got != test.want { + t.Errorf("want %d for %q(Line:%d,Character:%d), but got %d", test.want, test.text, int(test.pos.Line), int(test.pos.Character), got) + } + } +} diff --git a/contribs/gnopls/internal/protocol/protocol.go b/contribs/gnopls/internal/protocol/protocol.go new file mode 100644 index 00000000000..7cc5589aa0b --- /dev/null +++ b/contribs/gnopls/internal/protocol/protocol.go @@ -0,0 +1,313 @@ +// Copyright 2018 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 protocol + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + + "golang.org/x/telemetry/crashmonitor" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/jsonrpc2" + jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" + "golang.org/x/tools/internal/xcontext" +) + +var ( + // RequestCancelledError should be used when a request is cancelled early. + RequestCancelledError = jsonrpc2.NewError(-32800, "JSON RPC cancelled") + RequestCancelledErrorV2 = jsonrpc2_v2.NewError(-32800, "JSON RPC cancelled") +) + +type ClientCloser interface { + Client + io.Closer +} + +type connSender interface { + io.Closer + + Notify(ctx context.Context, method string, params interface{}) error + Call(ctx context.Context, method string, params, result interface{}) error +} + +type clientDispatcher struct { + sender connSender +} + +func (c *clientDispatcher) Close() error { + return c.sender.Close() +} + +// ClientDispatcher returns a Client that dispatches LSP requests across the +// given jsonrpc2 connection. +func ClientDispatcher(conn jsonrpc2.Conn) ClientCloser { + return &clientDispatcher{sender: clientConn{conn}} +} + +type clientConn struct { + conn jsonrpc2.Conn +} + +func (c clientConn) Close() error { + return c.conn.Close() +} + +func (c clientConn) Notify(ctx context.Context, method string, params interface{}) error { + return c.conn.Notify(ctx, method, params) +} + +func (c clientConn) Call(ctx context.Context, method string, params interface{}, result interface{}) error { + id, err := c.conn.Call(ctx, method, params, result) + if ctx.Err() != nil { + cancelCall(ctx, c, id) + } + return err +} + +func ClientDispatcherV2(conn *jsonrpc2_v2.Connection) ClientCloser { + return &clientDispatcher{clientConnV2{conn}} +} + +type clientConnV2 struct { + conn *jsonrpc2_v2.Connection +} + +func (c clientConnV2) Close() error { + return c.conn.Close() +} + +func (c clientConnV2) Notify(ctx context.Context, method string, params interface{}) error { + return c.conn.Notify(ctx, method, params) +} + +func (c clientConnV2) Call(ctx context.Context, method string, params interface{}, result interface{}) error { + call := c.conn.Call(ctx, method, params) + err := call.Await(ctx, result) + if ctx.Err() != nil { + detached := xcontext.Detach(ctx) + c.conn.Notify(detached, "$/cancelRequest", &CancelParams{ID: call.ID().Raw()}) + } + return err +} + +// ServerDispatcher returns a Server that dispatches LSP requests across the +// given jsonrpc2 connection. +func ServerDispatcher(conn jsonrpc2.Conn) Server { + return &serverDispatcher{sender: clientConn{conn}} +} + +func ServerDispatcherV2(conn *jsonrpc2_v2.Connection) Server { + return &serverDispatcher{sender: clientConnV2{conn}} +} + +type serverDispatcher struct { + sender connSender +} + +func ClientHandler(client Client, handler jsonrpc2.Handler) jsonrpc2.Handler { + return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + if ctx.Err() != nil { + ctx := xcontext.Detach(ctx) + return reply(ctx, nil, RequestCancelledError) + } + handled, err := clientDispatch(ctx, client, reply, req) + if handled || err != nil { + return err + } + return handler(ctx, reply, req) + } +} + +func ClientHandlerV2(client Client) jsonrpc2_v2.Handler { + return jsonrpc2_v2.HandlerFunc(func(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) { + if ctx.Err() != nil { + return nil, RequestCancelledErrorV2 + } + req1 := req2to1(req) + var ( + result interface{} + resErr error + ) + replier := func(_ context.Context, res interface{}, err error) error { + if err != nil { + resErr = err + return nil + } + result = res + return nil + } + _, err := clientDispatch(ctx, client, replier, req1) + if err != nil { + return nil, err + } + return result, resErr + }) +} + +func ServerHandler(server Server, handler jsonrpc2.Handler) jsonrpc2.Handler { + return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + if ctx.Err() != nil { + ctx := xcontext.Detach(ctx) + return reply(ctx, nil, RequestCancelledError) + } + handled, err := serverDispatch(ctx, server, reply, req) + if handled || err != nil { + return err + } + return handler(ctx, reply, req) + } +} + +func ServerHandlerV2(server Server) jsonrpc2_v2.Handler { + return jsonrpc2_v2.HandlerFunc(func(ctx context.Context, req *jsonrpc2_v2.Request) (interface{}, error) { + if ctx.Err() != nil { + return nil, RequestCancelledErrorV2 + } + req1 := req2to1(req) + var ( + result interface{} + resErr error + ) + replier := func(_ context.Context, res interface{}, err error) error { + if err != nil { + resErr = err + return nil + } + result = res + return nil + } + _, err := serverDispatch(ctx, server, replier, req1) + if err != nil { + return nil, err + } + return result, resErr + }) +} + +func req2to1(req2 *jsonrpc2_v2.Request) jsonrpc2.Request { + if req2.ID.IsValid() { + raw := req2.ID.Raw() + var idv1 jsonrpc2.ID + switch v := raw.(type) { + case int64: + idv1 = jsonrpc2.NewIntID(v) + case string: + idv1 = jsonrpc2.NewStringID(v) + default: + panic(fmt.Sprintf("unsupported ID type %T", raw)) + } + req1, err := jsonrpc2.NewCall(idv1, req2.Method, req2.Params) + if err != nil { + panic(err) + } + return req1 + } + req1, err := jsonrpc2.NewNotification(req2.Method, req2.Params) + if err != nil { + panic(err) + } + return req1 +} + +func Handlers(handler jsonrpc2.Handler) jsonrpc2.Handler { + return CancelHandler( + jsonrpc2.AsyncHandler( + jsonrpc2.MustReplyHandler(handler))) +} + +func CancelHandler(handler jsonrpc2.Handler) jsonrpc2.Handler { + handler, canceller := jsonrpc2.CancelHandler(handler) + return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + if req.Method() != "$/cancelRequest" { + // TODO(iancottrell): See if we can generate a reply for the request to be cancelled + // at the point of cancellation rather than waiting for gopls to naturally reply. + // To do that, we need to keep track of whether a reply has been sent already and + // be careful about racing between the two paths. + // TODO(iancottrell): Add a test that watches the stream and verifies the response + // for the cancelled request flows. + replyWithDetachedContext := func(ctx context.Context, resp interface{}, err error) error { + // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest + if ctx.Err() != nil && err == nil { + err = RequestCancelledError + } + ctx = xcontext.Detach(ctx) + return reply(ctx, resp, err) + } + return handler(ctx, replyWithDetachedContext, req) + } + var params CancelParams + if err := UnmarshalJSON(req.Params(), ¶ms); err != nil { + return sendParseError(ctx, reply, err) + } + if n, ok := params.ID.(float64); ok { + canceller(jsonrpc2.NewIntID(int64(n))) + } else if s, ok := params.ID.(string); ok { + canceller(jsonrpc2.NewStringID(s)) + } else { + return sendParseError(ctx, reply, fmt.Errorf("request ID %v malformed", params.ID)) + } + return reply(ctx, nil, nil) + } +} + +func Call(ctx context.Context, conn jsonrpc2.Conn, method string, params interface{}, result interface{}) error { + id, err := conn.Call(ctx, method, params, result) + if ctx.Err() != nil { + cancelCall(ctx, clientConn{conn}, id) + } + return err +} + +func cancelCall(ctx context.Context, sender connSender, id jsonrpc2.ID) { + ctx = xcontext.Detach(ctx) + ctx, done := event.Start(ctx, "protocol.canceller") + defer done() + // Note that only *jsonrpc2.ID implements json.Marshaler. + sender.Notify(ctx, "$/cancelRequest", &CancelParams{ID: &id}) +} + +// UnmarshalJSON unmarshals msg into the variable pointed to by +// params. In JSONRPC, optional messages may be +// "null", in which case it is a no-op. +func UnmarshalJSON(msg json.RawMessage, v any) error { + if len(msg) == 0 || bytes.Equal(msg, []byte("null")) { + return nil + } + return json.Unmarshal(msg, v) +} + +func sendParseError(ctx context.Context, reply jsonrpc2.Replier, err error) error { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) +} + +// NonNilSlice returns x, or an empty slice if x was nil. +// +// (Many slice fields of protocol structs must be non-nil +// to avoid being encoded as JSON "null".) +func NonNilSlice[T comparable](x []T) []T { + if x == nil { + return []T{} + } + return x +} + +func recoverHandlerPanic(method string) { + // Report panics in the handler goroutine, + // unless we have enabled the monitor, + // which reports all crashes. + if !crashmonitor.Supported() { + defer func() { + if x := recover(); x != nil { + bug.Reportf("panic in %s request", method) + panic(x) + } + }() + } +} diff --git a/contribs/gnopls/internal/protocol/semantic.go b/contribs/gnopls/internal/protocol/semantic.go new file mode 100644 index 00000000000..23356dd8ef2 --- /dev/null +++ b/contribs/gnopls/internal/protocol/semantic.go @@ -0,0 +1,58 @@ +// 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 protocol + +// The file defines helpers for semantics tokens. + +import "fmt" + +// SemanticTypes to use in case there is no client, as in the command line, or tests. +func SemanticTypes() []string { + return semanticTypes[:] +} + +// SemanticModifiers to use in case there is no client. +func SemanticModifiers() []string { + return semanticModifiers[:] +} + +// SemType returns a string equivalent of the type, for gopls semtok +func SemType(n int) string { + tokTypes := SemanticTypes() + tokMods := SemanticModifiers() + if n >= 0 && n < len(tokTypes) { + return tokTypes[n] + } + // not found for some reason + return fmt.Sprintf("?%d[%d,%d]?", n, len(tokTypes), len(tokMods)) +} + +// SemMods returns the []string equivalent of the mods, for gopls semtok. +func SemMods(n int) []string { + tokMods := SemanticModifiers() + mods := []string{} + for i := 0; i < len(tokMods); i++ { + if (n & (1 << uint(i))) != 0 { + mods = append(mods, tokMods[i]) + } + } + return mods +} + +// From https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens +var ( + semanticTypes = [...]string{ + "namespace", "type", "class", "enum", "interface", + "struct", "typeParameter", "parameter", "variable", "property", "enumMember", + "event", "function", "method", "macro", "keyword", "modifier", "comment", + "string", "number", "regexp", "operator", + } + semanticModifiers = [...]string{ + "declaration", "definition", "readonly", "static", + "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", + // Additional modifiers + "interface", "struct", "signature", "pointer", "array", "map", "slice", "chan", "string", "number", "bool", "invalid", + } +) diff --git a/contribs/gnopls/internal/protocol/semtok/semtok.go b/contribs/gnopls/internal/protocol/semtok/semtok.go new file mode 100644 index 00000000000..850e234a1b0 --- /dev/null +++ b/contribs/gnopls/internal/protocol/semtok/semtok.go @@ -0,0 +1,108 @@ +// 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 semtok package provides an encoder for LSP's semantic tokens. +package semtok + +import "sort" + +// A Token provides the extent and semantics of a token. +type Token struct { + Line, Start uint32 + Len uint32 + Type TokenType + Modifiers []string +} + +type TokenType string + +const ( + // These are the tokens defined by LSP 3.17, but a client is + // free to send its own set; any tokens that the server emits + // that are not in this set are simply not encoded in the bitfield. + TokNamespace TokenType = "namespace" + TokType TokenType = "type" + TokInterface TokenType = "interface" + TokTypeParam TokenType = "typeParameter" + TokParameter TokenType = "parameter" + TokVariable TokenType = "variable" + TokMethod TokenType = "method" + TokFunction TokenType = "function" + TokKeyword TokenType = "keyword" + TokComment TokenType = "comment" + TokString TokenType = "string" + TokNumber TokenType = "number" + TokOperator TokenType = "operator" + TokMacro TokenType = "macro" // for templates + + // not part of LSP 3.17 (even though JS has labels) + // https://github.com/microsoft/vscode-languageserver-node/issues/1422 + TokLabel TokenType = "label" +) + +// Encode returns the LSP encoding of a sequence of tokens. +// The noStrings, noNumbers options cause strings, numbers to be skipped. +// The lists of types and modifiers determines the bitfield encoding. +func Encode( + tokens []Token, + noStrings, noNumbers bool, + types, modifiers []string) []uint32 { + + // binary operators, at least, will be out of order + sort.Slice(tokens, func(i, j int) bool { + if tokens[i].Line != tokens[j].Line { + return tokens[i].Line < tokens[j].Line + } + return tokens[i].Start < tokens[j].Start + }) + + typeMap := make(map[TokenType]int) + for i, t := range types { + typeMap[TokenType(t)] = i + } + + modMap := make(map[string]int) + for i, m := range modifiers { + modMap[m] = 1 << uint(i) // go 1.12 compatibility + } + + // each semantic token needs five values + // (see Integer Encoding for Tokens in the LSP spec) + x := make([]uint32, 5*len(tokens)) + var j int + var last Token + for i := 0; i < len(tokens); i++ { + item := tokens[i] + typ, ok := typeMap[item.Type] + if !ok { + continue // client doesn't want typeStr + } + if item.Type == TokString && noStrings { + continue + } + if item.Type == TokNumber && noNumbers { + continue + } + if j == 0 { + x[0] = tokens[0].Line + } else { + x[j] = item.Line - last.Line + } + x[j+1] = item.Start + if j > 0 && x[j] == 0 { + x[j+1] = item.Start - last.Start + } + x[j+2] = item.Len + x[j+3] = uint32(typ) + mask := 0 + for _, s := range item.Modifiers { + // modMap[s] is 0 if the client doesn't want this modifier + mask |= modMap[s] + } + x[j+4] = uint32(mask) + j += 5 + last = item + } + return x[:j] +} diff --git a/contribs/gnopls/internal/protocol/span.go b/contribs/gnopls/internal/protocol/span.go new file mode 100644 index 00000000000..47d04df9d0e --- /dev/null +++ b/contribs/gnopls/internal/protocol/span.go @@ -0,0 +1,100 @@ +// Copyright 2018 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 protocol + +import ( + "fmt" + "unicode/utf8" +) + +// CompareLocation defines a three-valued comparison over locations, +// lexicographically ordered by (URI, Range). +func CompareLocation(x, y Location) int { + if x.URI != y.URI { + if x.URI < y.URI { + return -1 + } else { + return +1 + } + } + return CompareRange(x.Range, y.Range) +} + +// CompareRange returns -1 if a is before b, 0 if a == b, and 1 if a is after b. +// +// A range a is defined to be 'before' b if a.Start is before b.Start, or +// a.Start == b.Start and a.End is before b.End. +func CompareRange(a, b Range) int { + if r := ComparePosition(a.Start, b.Start); r != 0 { + return r + } + return ComparePosition(a.End, b.End) +} + +// ComparePosition returns -1 if a is before b, 0 if a == b, and 1 if a is after b. +func ComparePosition(a, b Position) int { + if a.Line != b.Line { + if a.Line < b.Line { + return -1 + } else { + return +1 + } + } + if a.Character != b.Character { + if a.Character < b.Character { + return -1 + } else { + return +1 + } + } + return 0 +} + +func Intersect(a, b Range) bool { + if a.Start.Line > b.End.Line || a.End.Line < b.Start.Line { + return false + } + return !((a.Start.Line == b.End.Line) && a.Start.Character > b.End.Character || + (a.End.Line == b.Start.Line) && a.End.Character < b.Start.Character) +} + +// Format implements fmt.Formatter. +// +// Note: Formatter is implemented instead of Stringer (presumably) for +// performance reasons, though it is not clear that it matters in practice. +func (r Range) Format(f fmt.State, _ rune) { + fmt.Fprintf(f, "%v-%v", r.Start, r.End) +} + +// Format implements fmt.Formatter. +// +// See Range.Format for discussion of why the Formatter interface is +// implemented rather than Stringer. +func (p Position) Format(f fmt.State, _ rune) { + fmt.Fprintf(f, "%v:%v", p.Line, p.Character) +} + +// -- implementation helpers -- + +// UTF16Len returns the number of codes in the UTF-16 transcoding of s. +func UTF16Len(s []byte) int { + var n int + for len(s) > 0 { + n++ + + // Fast path for ASCII. + if s[0] < 0x80 { + s = s[1:] + continue + } + + r, size := utf8.DecodeRune(s) + if r >= 0x10000 { + n++ // surrogate pair + } + s = s[size:] + } + return n +} diff --git a/contribs/gnopls/internal/protocol/tsclient.go b/contribs/gnopls/internal/protocol/tsclient.go new file mode 100644 index 00000000000..3f860d5351a --- /dev/null +++ b/contribs/gnopls/internal/protocol/tsclient.go @@ -0,0 +1,296 @@ +// 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. + +// Code generated for LSP. DO NOT EDIT. + +package protocol + +// Code generated from protocol/metaModel.json at ref release/protocol/3.17.6-next.2 (hash 654dc9be6673c61476c28fda604406279c3258d7). +// https://github.com/microsoft/vscode-languageserver-node/blob/release/protocol/3.17.6-next.2/protocol/metaModel.json +// LSP metaData.version = 3.17.0. + +import ( + "context" + + "golang.org/x/tools/internal/jsonrpc2" +) + +type Client interface { + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#logTrace + LogTrace(context.Context, *LogTraceParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#progress + Progress(context.Context, *ProgressParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#client_registerCapability + RegisterCapability(context.Context, *RegistrationParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#client_unregisterCapability + UnregisterCapability(context.Context, *UnregistrationParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#telemetry_event + Event(context.Context, *interface{}) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_publishDiagnostics + PublishDiagnostics(context.Context, *PublishDiagnosticsParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#window_logMessage + LogMessage(context.Context, *LogMessageParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#window_showDocument + ShowDocument(context.Context, *ShowDocumentParams) (*ShowDocumentResult, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#window_showMessage + ShowMessage(context.Context, *ShowMessageParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#window_showMessageRequest + ShowMessageRequest(context.Context, *ShowMessageRequestParams) (*MessageActionItem, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#window_workDoneProgress_create + WorkDoneProgressCreate(context.Context, *WorkDoneProgressCreateParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_applyEdit + ApplyEdit(context.Context, *ApplyWorkspaceEditParams) (*ApplyWorkspaceEditResult, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_codeLens_refresh + CodeLensRefresh(context.Context) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_configuration + Configuration(context.Context, *ParamConfiguration) ([]LSPAny, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_diagnostic_refresh + DiagnosticRefresh(context.Context) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_foldingRange_refresh + FoldingRangeRefresh(context.Context) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_inlayHint_refresh + InlayHintRefresh(context.Context) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_inlineValue_refresh + InlineValueRefresh(context.Context) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_semanticTokens_refresh + SemanticTokensRefresh(context.Context) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_workspaceFolders + WorkspaceFolders(context.Context) ([]WorkspaceFolder, error) +} + +func clientDispatch(ctx context.Context, client Client, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { + defer recoverHandlerPanic(r.Method()) + switch r.Method() { + case "$/logTrace": + var params LogTraceParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.LogTrace(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "$/progress": + var params ProgressParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.Progress(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "client/registerCapability": + var params RegistrationParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.RegisterCapability(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "client/unregisterCapability": + var params UnregistrationParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.UnregisterCapability(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "telemetry/event": + var params interface{} + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.Event(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "textDocument/publishDiagnostics": + var params PublishDiagnosticsParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.PublishDiagnostics(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "window/logMessage": + var params LogMessageParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.LogMessage(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "window/showDocument": + var params ShowDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := client.ShowDocument(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "window/showMessage": + var params ShowMessageParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.ShowMessage(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "window/showMessageRequest": + var params ShowMessageRequestParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := client.ShowMessageRequest(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "window/workDoneProgress/create": + var params WorkDoneProgressCreateParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := client.WorkDoneProgressCreate(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "workspace/applyEdit": + var params ApplyWorkspaceEditParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := client.ApplyEdit(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "workspace/codeLens/refresh": + err := client.CodeLensRefresh(ctx) + return true, reply(ctx, nil, err) + + case "workspace/configuration": + var params ParamConfiguration + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := client.Configuration(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "workspace/diagnostic/refresh": + err := client.DiagnosticRefresh(ctx) + return true, reply(ctx, nil, err) + + case "workspace/foldingRange/refresh": + err := client.FoldingRangeRefresh(ctx) + return true, reply(ctx, nil, err) + + case "workspace/inlayHint/refresh": + err := client.InlayHintRefresh(ctx) + return true, reply(ctx, nil, err) + + case "workspace/inlineValue/refresh": + err := client.InlineValueRefresh(ctx) + return true, reply(ctx, nil, err) + + case "workspace/semanticTokens/refresh": + err := client.SemanticTokensRefresh(ctx) + return true, reply(ctx, nil, err) + + case "workspace/workspaceFolders": + resp, err := client.WorkspaceFolders(ctx) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + default: + return false, nil + } +} + +func (s *clientDispatcher) LogTrace(ctx context.Context, params *LogTraceParams) error { + return s.sender.Notify(ctx, "$/logTrace", params) +} +func (s *clientDispatcher) Progress(ctx context.Context, params *ProgressParams) error { + return s.sender.Notify(ctx, "$/progress", params) +} +func (s *clientDispatcher) RegisterCapability(ctx context.Context, params *RegistrationParams) error { + return s.sender.Call(ctx, "client/registerCapability", params, nil) +} +func (s *clientDispatcher) UnregisterCapability(ctx context.Context, params *UnregistrationParams) error { + return s.sender.Call(ctx, "client/unregisterCapability", params, nil) +} +func (s *clientDispatcher) Event(ctx context.Context, params *interface{}) error { + return s.sender.Notify(ctx, "telemetry/event", params) +} +func (s *clientDispatcher) PublishDiagnostics(ctx context.Context, params *PublishDiagnosticsParams) error { + return s.sender.Notify(ctx, "textDocument/publishDiagnostics", params) +} +func (s *clientDispatcher) LogMessage(ctx context.Context, params *LogMessageParams) error { + return s.sender.Notify(ctx, "window/logMessage", params) +} +func (s *clientDispatcher) ShowDocument(ctx context.Context, params *ShowDocumentParams) (*ShowDocumentResult, error) { + var result *ShowDocumentResult + if err := s.sender.Call(ctx, "window/showDocument", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *clientDispatcher) ShowMessage(ctx context.Context, params *ShowMessageParams) error { + return s.sender.Notify(ctx, "window/showMessage", params) +} +func (s *clientDispatcher) ShowMessageRequest(ctx context.Context, params *ShowMessageRequestParams) (*MessageActionItem, error) { + var result *MessageActionItem + if err := s.sender.Call(ctx, "window/showMessageRequest", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *clientDispatcher) WorkDoneProgressCreate(ctx context.Context, params *WorkDoneProgressCreateParams) error { + return s.sender.Call(ctx, "window/workDoneProgress/create", params, nil) +} +func (s *clientDispatcher) ApplyEdit(ctx context.Context, params *ApplyWorkspaceEditParams) (*ApplyWorkspaceEditResult, error) { + var result *ApplyWorkspaceEditResult + if err := s.sender.Call(ctx, "workspace/applyEdit", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *clientDispatcher) CodeLensRefresh(ctx context.Context) error { + return s.sender.Call(ctx, "workspace/codeLens/refresh", nil, nil) +} +func (s *clientDispatcher) Configuration(ctx context.Context, params *ParamConfiguration) ([]LSPAny, error) { + var result []LSPAny + if err := s.sender.Call(ctx, "workspace/configuration", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *clientDispatcher) DiagnosticRefresh(ctx context.Context) error { + return s.sender.Call(ctx, "workspace/diagnostic/refresh", nil, nil) +} +func (s *clientDispatcher) FoldingRangeRefresh(ctx context.Context) error { + return s.sender.Call(ctx, "workspace/foldingRange/refresh", nil, nil) +} +func (s *clientDispatcher) InlayHintRefresh(ctx context.Context) error { + return s.sender.Call(ctx, "workspace/inlayHint/refresh", nil, nil) +} +func (s *clientDispatcher) InlineValueRefresh(ctx context.Context) error { + return s.sender.Call(ctx, "workspace/inlineValue/refresh", nil, nil) +} +func (s *clientDispatcher) SemanticTokensRefresh(ctx context.Context) error { + return s.sender.Call(ctx, "workspace/semanticTokens/refresh", nil, nil) +} +func (s *clientDispatcher) WorkspaceFolders(ctx context.Context) ([]WorkspaceFolder, error) { + var result []WorkspaceFolder + if err := s.sender.Call(ctx, "workspace/workspaceFolders", nil, &result); err != nil { + return nil, err + } + return result, nil +} diff --git a/contribs/gnopls/internal/protocol/tsdocument_changes.go b/contribs/gnopls/internal/protocol/tsdocument_changes.go new file mode 100644 index 00000000000..63b9914eb73 --- /dev/null +++ b/contribs/gnopls/internal/protocol/tsdocument_changes.go @@ -0,0 +1,81 @@ +// 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 protocol + +import ( + "encoding/json" + "fmt" +) + +// DocumentChange is a union of various file edit operations. +// +// Exactly one field of this struct is non-nil; see [DocumentChange.Valid]. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#resourceChanges +type DocumentChange struct { + TextDocumentEdit *TextDocumentEdit + CreateFile *CreateFile + RenameFile *RenameFile + DeleteFile *DeleteFile +} + +// Valid reports whether the DocumentChange sum-type value is valid, +// that is, exactly one of create, delete, edit, or rename. +func (ch DocumentChange) Valid() bool { + n := 0 + if ch.TextDocumentEdit != nil { + n++ + } + if ch.CreateFile != nil { + n++ + } + if ch.RenameFile != nil { + n++ + } + if ch.DeleteFile != nil { + n++ + } + return n == 1 +} + +func (d *DocumentChange) UnmarshalJSON(data []byte) error { + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + return err + } + + if _, ok := m["textDocument"]; ok { + d.TextDocumentEdit = new(TextDocumentEdit) + return json.Unmarshal(data, d.TextDocumentEdit) + } + + // The {Create,Rename,Delete}File types all share a 'kind' field. + kind := m["kind"] + switch kind { + case "create": + d.CreateFile = new(CreateFile) + return json.Unmarshal(data, d.CreateFile) + case "rename": + d.RenameFile = new(RenameFile) + return json.Unmarshal(data, d.RenameFile) + case "delete": + d.DeleteFile = new(DeleteFile) + return json.Unmarshal(data, d.DeleteFile) + } + return fmt.Errorf("DocumentChanges: unexpected kind: %q", kind) +} + +func (d *DocumentChange) MarshalJSON() ([]byte, error) { + if d.TextDocumentEdit != nil { + return json.Marshal(d.TextDocumentEdit) + } else if d.CreateFile != nil { + return json.Marshal(d.CreateFile) + } else if d.RenameFile != nil { + return json.Marshal(d.RenameFile) + } else if d.DeleteFile != nil { + return json.Marshal(d.DeleteFile) + } + return nil, fmt.Errorf("empty DocumentChanges union value") +} diff --git a/contribs/gnopls/internal/protocol/tsinsertreplaceedit.go b/contribs/gnopls/internal/protocol/tsinsertreplaceedit.go new file mode 100644 index 00000000000..6daa489b675 --- /dev/null +++ b/contribs/gnopls/internal/protocol/tsinsertreplaceedit.go @@ -0,0 +1,40 @@ +// 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 protocol + +import ( + "encoding/json" + "fmt" +) + +// InsertReplaceEdit is used instead of TextEdit in CompletionItem +// in editors that support it. These two types are alike in appearance +// but can be differentiated by the presence or absence of +// certain properties. UnmarshalJSON of the sum type tries to +// unmarshal as TextEdit only if unmarshal as InsertReplaceEdit fails. +// However, due to this similarity, unmarshal with the other type +// never fails. This file has a custom JSON unmarshaller for +// InsertReplaceEdit, that fails if the required fields are missing. + +// UnmarshalJSON unmarshals InsertReplaceEdit with extra +// checks on the presence of "insert" and "replace" properties. +func (e *InsertReplaceEdit) UnmarshalJSON(data []byte) error { + var required struct { + NewText string + Insert *Range `json:"insert,omitempty"` + Replace *Range `json:"replace,omitempty"` + } + + if err := json.Unmarshal(data, &required); err != nil { + return err + } + if required.Insert == nil && required.Replace == nil { + return fmt.Errorf("not InsertReplaceEdit") + } + e.NewText = required.NewText + e.Insert = *required.Insert + e.Replace = *required.Replace + return nil +} diff --git a/contribs/gnopls/internal/protocol/tsinsertreplaceedit_test.go b/contribs/gnopls/internal/protocol/tsinsertreplaceedit_test.go new file mode 100644 index 00000000000..2b2e429e39d --- /dev/null +++ b/contribs/gnopls/internal/protocol/tsinsertreplaceedit_test.go @@ -0,0 +1,44 @@ +// 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 protocol + +import ( + "encoding/json" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestInsertReplaceEdit_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + in any + wantErr bool + }{ + { + name: "TextEdit", + in: TextEdit{NewText: "new text", Range: Range{Start: Position{Line: 1}}}, + }, + { + name: "InsertReplaceEdit", + in: InsertReplaceEdit{NewText: "new text", Insert: Range{Start: Position{Line: 100}}, Replace: Range{End: Position{Line: 200}}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := json.MarshalIndent(Or_CompletionItem_textEdit{Value: tt.in}, "", " ") + if err != nil { + t.Fatalf("failed to marshal: %v", err) + } + var decoded Or_CompletionItem_textEdit + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("failed to unmarshal: %v", err) + } + if diff := cmp.Diff(tt.in, decoded.Value); diff != "" { + t.Errorf("unmarshal returns unexpected result: (-want +got):\n%s", diff) + } + }) + } +} diff --git a/contribs/gnopls/internal/protocol/tsjson.go b/contribs/gnopls/internal/protocol/tsjson.go new file mode 100644 index 00000000000..7f77ffa999f --- /dev/null +++ b/contribs/gnopls/internal/protocol/tsjson.go @@ -0,0 +1,2130 @@ +// 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. + +// Code generated for LSP. DO NOT EDIT. + +package protocol + +// Code generated from protocol/metaModel.json at ref release/protocol/3.17.6-next.2 (hash 654dc9be6673c61476c28fda604406279c3258d7). +// https://github.com/microsoft/vscode-languageserver-node/blob/release/protocol/3.17.6-next.2/protocol/metaModel.json +// LSP metaData.version = 3.17.0. + +import "encoding/json" + +import "fmt" + +// UnmarshalError indicates that a JSON value did not conform to +// one of the expected cases of an LSP union type. +type UnmarshalError struct { + msg string +} + +func (e UnmarshalError) Error() string { + return e.msg +} +func (t OrPLocation_workspace_symbol) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case Location: + return json.Marshal(x) + case LocationUriOnly: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [Location LocationUriOnly]", t) +} + +func (t *OrPLocation_workspace_symbol) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 Location + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 LocationUriOnly + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [Location LocationUriOnly]"} +} + +func (t OrPSection_workspace_didChangeConfiguration) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case []string: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [[]string string]", t) +} + +func (t *OrPSection_workspace_didChangeConfiguration) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 []string + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [[]string string]"} +} + +func (t OrPTooltipPLabel) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case MarkupContent: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [MarkupContent string]", t) +} + +func (t *OrPTooltipPLabel) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 MarkupContent + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [MarkupContent string]"} +} + +func (t OrPTooltip_textDocument_inlayHint) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case MarkupContent: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [MarkupContent string]", t) +} + +func (t *OrPTooltip_textDocument_inlayHint) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 MarkupContent + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [MarkupContent string]"} +} + +func (t Or_CancelParams_id) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case int32: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [int32 string]", t) +} + +func (t *Or_CancelParams_id) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 int32 + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [int32 string]"} +} + +func (t Or_ClientSemanticTokensRequestOptions_full) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case ClientSemanticTokensRequestFullDelta: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [ClientSemanticTokensRequestFullDelta bool]", t) +} + +func (t *Or_ClientSemanticTokensRequestOptions_full) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 ClientSemanticTokensRequestFullDelta + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [ClientSemanticTokensRequestFullDelta bool]"} +} + +func (t Or_ClientSemanticTokensRequestOptions_range) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case Lit_ClientSemanticTokensRequestOptions_range_Item1: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [Lit_ClientSemanticTokensRequestOptions_range_Item1 bool]", t) +} + +func (t *Or_ClientSemanticTokensRequestOptions_range) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 Lit_ClientSemanticTokensRequestOptions_range_Item1 + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [Lit_ClientSemanticTokensRequestOptions_range_Item1 bool]"} +} + +func (t Or_CompletionItemDefaults_editRange) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case EditRangeWithInsertReplace: + return json.Marshal(x) + case Range: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [EditRangeWithInsertReplace Range]", t) +} + +func (t *Or_CompletionItemDefaults_editRange) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 EditRangeWithInsertReplace + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 Range + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [EditRangeWithInsertReplace Range]"} +} + +func (t Or_CompletionItem_documentation) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case MarkupContent: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [MarkupContent string]", t) +} + +func (t *Or_CompletionItem_documentation) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 MarkupContent + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [MarkupContent string]"} +} + +func (t Or_CompletionItem_textEdit) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case InsertReplaceEdit: + return json.Marshal(x) + case TextEdit: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [InsertReplaceEdit TextEdit]", t) +} + +func (t *Or_CompletionItem_textEdit) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 InsertReplaceEdit + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 TextEdit + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [InsertReplaceEdit TextEdit]"} +} + +func (t Or_Definition) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case Location: + return json.Marshal(x) + case []Location: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [Location []Location]", t) +} + +func (t *Or_Definition) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 Location + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 []Location + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [Location []Location]"} +} + +func (t Or_Diagnostic_code) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case int32: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [int32 string]", t) +} + +func (t *Or_Diagnostic_code) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 int32 + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [int32 string]"} +} + +func (t Or_DocumentDiagnosticReport) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case RelatedFullDocumentDiagnosticReport: + return json.Marshal(x) + case RelatedUnchangedDocumentDiagnosticReport: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [RelatedFullDocumentDiagnosticReport RelatedUnchangedDocumentDiagnosticReport]", t) +} + +func (t *Or_DocumentDiagnosticReport) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 RelatedFullDocumentDiagnosticReport + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 RelatedUnchangedDocumentDiagnosticReport + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [RelatedFullDocumentDiagnosticReport RelatedUnchangedDocumentDiagnosticReport]"} +} + +func (t Or_DocumentDiagnosticReportPartialResult_relatedDocuments_Value) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case FullDocumentDiagnosticReport: + return json.Marshal(x) + case UnchangedDocumentDiagnosticReport: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport]", t) +} + +func (t *Or_DocumentDiagnosticReportPartialResult_relatedDocuments_Value) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 FullDocumentDiagnosticReport + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 UnchangedDocumentDiagnosticReport + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport]"} +} + +func (t Or_DocumentFilter) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case NotebookCellTextDocumentFilter: + return json.Marshal(x) + case TextDocumentFilter: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [NotebookCellTextDocumentFilter TextDocumentFilter]", t) +} + +func (t *Or_DocumentFilter) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 NotebookCellTextDocumentFilter + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 TextDocumentFilter + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [NotebookCellTextDocumentFilter TextDocumentFilter]"} +} + +func (t Or_GlobPattern) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case Pattern: + return json.Marshal(x) + case RelativePattern: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [Pattern RelativePattern]", t) +} + +func (t *Or_GlobPattern) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 Pattern + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 RelativePattern + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [Pattern RelativePattern]"} +} + +func (t Or_Hover_contents) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case MarkedString: + return json.Marshal(x) + case MarkupContent: + return json.Marshal(x) + case []MarkedString: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [MarkedString MarkupContent []MarkedString]", t) +} + +func (t *Or_Hover_contents) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 MarkedString + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 MarkupContent + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 []MarkedString + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [MarkedString MarkupContent []MarkedString]"} +} + +func (t Or_InlayHint_label) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case []InlayHintLabelPart: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [[]InlayHintLabelPart string]", t) +} + +func (t *Or_InlayHint_label) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 []InlayHintLabelPart + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [[]InlayHintLabelPart string]"} +} + +func (t Or_InlineCompletionItem_insertText) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case StringValue: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [StringValue string]", t) +} + +func (t *Or_InlineCompletionItem_insertText) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 StringValue + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [StringValue string]"} +} + +func (t Or_InlineValue) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case InlineValueEvaluatableExpression: + return json.Marshal(x) + case InlineValueText: + return json.Marshal(x) + case InlineValueVariableLookup: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [InlineValueEvaluatableExpression InlineValueText InlineValueVariableLookup]", t) +} + +func (t *Or_InlineValue) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 InlineValueEvaluatableExpression + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 InlineValueText + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 InlineValueVariableLookup + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [InlineValueEvaluatableExpression InlineValueText InlineValueVariableLookup]"} +} + +func (t Or_MarkedString) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case MarkedStringWithLanguage: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [MarkedStringWithLanguage string]", t) +} + +func (t *Or_MarkedString) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 MarkedStringWithLanguage + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [MarkedStringWithLanguage string]"} +} + +func (t Or_NotebookCellTextDocumentFilter_notebook) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case NotebookDocumentFilter: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [NotebookDocumentFilter string]", t) +} + +func (t *Or_NotebookCellTextDocumentFilter_notebook) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 NotebookDocumentFilter + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [NotebookDocumentFilter string]"} +} + +func (t Or_NotebookDocumentFilter) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case NotebookDocumentFilterNotebookType: + return json.Marshal(x) + case NotebookDocumentFilterPattern: + return json.Marshal(x) + case NotebookDocumentFilterScheme: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [NotebookDocumentFilterNotebookType NotebookDocumentFilterPattern NotebookDocumentFilterScheme]", t) +} + +func (t *Or_NotebookDocumentFilter) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 NotebookDocumentFilterNotebookType + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 NotebookDocumentFilterPattern + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 NotebookDocumentFilterScheme + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [NotebookDocumentFilterNotebookType NotebookDocumentFilterPattern NotebookDocumentFilterScheme]"} +} + +func (t Or_NotebookDocumentFilterWithCells_notebook) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case NotebookDocumentFilter: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [NotebookDocumentFilter string]", t) +} + +func (t *Or_NotebookDocumentFilterWithCells_notebook) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 NotebookDocumentFilter + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [NotebookDocumentFilter string]"} +} + +func (t Or_NotebookDocumentFilterWithNotebook_notebook) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case NotebookDocumentFilter: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [NotebookDocumentFilter string]", t) +} + +func (t *Or_NotebookDocumentFilterWithNotebook_notebook) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 NotebookDocumentFilter + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [NotebookDocumentFilter string]"} +} + +func (t Or_NotebookDocumentSyncOptions_notebookSelector_Elem) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case NotebookDocumentFilterWithCells: + return json.Marshal(x) + case NotebookDocumentFilterWithNotebook: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [NotebookDocumentFilterWithCells NotebookDocumentFilterWithNotebook]", t) +} + +func (t *Or_NotebookDocumentSyncOptions_notebookSelector_Elem) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 NotebookDocumentFilterWithCells + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 NotebookDocumentFilterWithNotebook + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [NotebookDocumentFilterWithCells NotebookDocumentFilterWithNotebook]"} +} + +func (t Or_RelatedFullDocumentDiagnosticReport_relatedDocuments_Value) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case FullDocumentDiagnosticReport: + return json.Marshal(x) + case UnchangedDocumentDiagnosticReport: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport]", t) +} + +func (t *Or_RelatedFullDocumentDiagnosticReport_relatedDocuments_Value) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 FullDocumentDiagnosticReport + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 UnchangedDocumentDiagnosticReport + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport]"} +} + +func (t Or_RelatedUnchangedDocumentDiagnosticReport_relatedDocuments_Value) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case FullDocumentDiagnosticReport: + return json.Marshal(x) + case UnchangedDocumentDiagnosticReport: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport]", t) +} + +func (t *Or_RelatedUnchangedDocumentDiagnosticReport_relatedDocuments_Value) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 FullDocumentDiagnosticReport + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 UnchangedDocumentDiagnosticReport + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport]"} +} + +func (t Or_Result_textDocument_codeAction_Item0_Elem) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case CodeAction: + return json.Marshal(x) + case Command: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [CodeAction Command]", t) +} + +func (t *Or_Result_textDocument_codeAction_Item0_Elem) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 CodeAction + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 Command + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [CodeAction Command]"} +} + +func (t Or_Result_textDocument_inlineCompletion) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case InlineCompletionList: + return json.Marshal(x) + case []InlineCompletionItem: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [InlineCompletionList []InlineCompletionItem]", t) +} + +func (t *Or_Result_textDocument_inlineCompletion) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 InlineCompletionList + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 []InlineCompletionItem + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [InlineCompletionList []InlineCompletionItem]"} +} + +func (t Or_SemanticTokensOptions_full) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case SemanticTokensFullDelta: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [SemanticTokensFullDelta bool]", t) +} + +func (t *Or_SemanticTokensOptions_full) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 SemanticTokensFullDelta + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [SemanticTokensFullDelta bool]"} +} + +func (t Or_SemanticTokensOptions_range) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case PRangeESemanticTokensOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [PRangeESemanticTokensOptions bool]", t) +} + +func (t *Or_SemanticTokensOptions_range) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 PRangeESemanticTokensOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [PRangeESemanticTokensOptions bool]"} +} + +func (t Or_ServerCapabilities_callHierarchyProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case CallHierarchyOptions: + return json.Marshal(x) + case CallHierarchyRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [CallHierarchyOptions CallHierarchyRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_callHierarchyProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 CallHierarchyOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 CallHierarchyRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [CallHierarchyOptions CallHierarchyRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_codeActionProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case CodeActionOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [CodeActionOptions bool]", t) +} + +func (t *Or_ServerCapabilities_codeActionProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 CodeActionOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [CodeActionOptions bool]"} +} + +func (t Or_ServerCapabilities_colorProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case DocumentColorOptions: + return json.Marshal(x) + case DocumentColorRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [DocumentColorOptions DocumentColorRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_colorProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 DocumentColorOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 DocumentColorRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [DocumentColorOptions DocumentColorRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_declarationProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case DeclarationOptions: + return json.Marshal(x) + case DeclarationRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [DeclarationOptions DeclarationRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_declarationProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 DeclarationOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 DeclarationRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [DeclarationOptions DeclarationRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_definitionProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case DefinitionOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [DefinitionOptions bool]", t) +} + +func (t *Or_ServerCapabilities_definitionProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 DefinitionOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [DefinitionOptions bool]"} +} + +func (t Or_ServerCapabilities_diagnosticProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case DiagnosticOptions: + return json.Marshal(x) + case DiagnosticRegistrationOptions: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [DiagnosticOptions DiagnosticRegistrationOptions]", t) +} + +func (t *Or_ServerCapabilities_diagnosticProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 DiagnosticOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 DiagnosticRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [DiagnosticOptions DiagnosticRegistrationOptions]"} +} + +func (t Or_ServerCapabilities_documentFormattingProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case DocumentFormattingOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [DocumentFormattingOptions bool]", t) +} + +func (t *Or_ServerCapabilities_documentFormattingProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 DocumentFormattingOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [DocumentFormattingOptions bool]"} +} + +func (t Or_ServerCapabilities_documentHighlightProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case DocumentHighlightOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [DocumentHighlightOptions bool]", t) +} + +func (t *Or_ServerCapabilities_documentHighlightProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 DocumentHighlightOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [DocumentHighlightOptions bool]"} +} + +func (t Or_ServerCapabilities_documentRangeFormattingProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case DocumentRangeFormattingOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [DocumentRangeFormattingOptions bool]", t) +} + +func (t *Or_ServerCapabilities_documentRangeFormattingProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 DocumentRangeFormattingOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [DocumentRangeFormattingOptions bool]"} +} + +func (t Or_ServerCapabilities_documentSymbolProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case DocumentSymbolOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [DocumentSymbolOptions bool]", t) +} + +func (t *Or_ServerCapabilities_documentSymbolProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 DocumentSymbolOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [DocumentSymbolOptions bool]"} +} + +func (t Or_ServerCapabilities_foldingRangeProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case FoldingRangeOptions: + return json.Marshal(x) + case FoldingRangeRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [FoldingRangeOptions FoldingRangeRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_foldingRangeProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 FoldingRangeOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 FoldingRangeRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [FoldingRangeOptions FoldingRangeRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_hoverProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case HoverOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [HoverOptions bool]", t) +} + +func (t *Or_ServerCapabilities_hoverProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 HoverOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [HoverOptions bool]"} +} + +func (t Or_ServerCapabilities_implementationProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case ImplementationOptions: + return json.Marshal(x) + case ImplementationRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [ImplementationOptions ImplementationRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_implementationProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 ImplementationOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 ImplementationRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [ImplementationOptions ImplementationRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_inlayHintProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case InlayHintOptions: + return json.Marshal(x) + case InlayHintRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [InlayHintOptions InlayHintRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_inlayHintProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 InlayHintOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 InlayHintRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [InlayHintOptions InlayHintRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_inlineCompletionProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case InlineCompletionOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [InlineCompletionOptions bool]", t) +} + +func (t *Or_ServerCapabilities_inlineCompletionProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 InlineCompletionOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [InlineCompletionOptions bool]"} +} + +func (t Or_ServerCapabilities_inlineValueProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case InlineValueOptions: + return json.Marshal(x) + case InlineValueRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [InlineValueOptions InlineValueRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_inlineValueProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 InlineValueOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 InlineValueRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [InlineValueOptions InlineValueRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_linkedEditingRangeProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case LinkedEditingRangeOptions: + return json.Marshal(x) + case LinkedEditingRangeRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [LinkedEditingRangeOptions LinkedEditingRangeRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_linkedEditingRangeProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 LinkedEditingRangeOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 LinkedEditingRangeRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [LinkedEditingRangeOptions LinkedEditingRangeRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_monikerProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case MonikerOptions: + return json.Marshal(x) + case MonikerRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [MonikerOptions MonikerRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_monikerProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 MonikerOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 MonikerRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [MonikerOptions MonikerRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_notebookDocumentSync) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case NotebookDocumentSyncOptions: + return json.Marshal(x) + case NotebookDocumentSyncRegistrationOptions: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [NotebookDocumentSyncOptions NotebookDocumentSyncRegistrationOptions]", t) +} + +func (t *Or_ServerCapabilities_notebookDocumentSync) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 NotebookDocumentSyncOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 NotebookDocumentSyncRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [NotebookDocumentSyncOptions NotebookDocumentSyncRegistrationOptions]"} +} + +func (t Or_ServerCapabilities_referencesProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case ReferenceOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [ReferenceOptions bool]", t) +} + +func (t *Or_ServerCapabilities_referencesProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 ReferenceOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [ReferenceOptions bool]"} +} + +func (t Or_ServerCapabilities_renameProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case RenameOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [RenameOptions bool]", t) +} + +func (t *Or_ServerCapabilities_renameProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 RenameOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [RenameOptions bool]"} +} + +func (t Or_ServerCapabilities_selectionRangeProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case SelectionRangeOptions: + return json.Marshal(x) + case SelectionRangeRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [SelectionRangeOptions SelectionRangeRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_selectionRangeProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 SelectionRangeOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 SelectionRangeRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [SelectionRangeOptions SelectionRangeRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_semanticTokensProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case SemanticTokensOptions: + return json.Marshal(x) + case SemanticTokensRegistrationOptions: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [SemanticTokensOptions SemanticTokensRegistrationOptions]", t) +} + +func (t *Or_ServerCapabilities_semanticTokensProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 SemanticTokensOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 SemanticTokensRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [SemanticTokensOptions SemanticTokensRegistrationOptions]"} +} + +func (t Or_ServerCapabilities_textDocumentSync) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case TextDocumentSyncKind: + return json.Marshal(x) + case TextDocumentSyncOptions: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [TextDocumentSyncKind TextDocumentSyncOptions]", t) +} + +func (t *Or_ServerCapabilities_textDocumentSync) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 TextDocumentSyncKind + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 TextDocumentSyncOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [TextDocumentSyncKind TextDocumentSyncOptions]"} +} + +func (t Or_ServerCapabilities_typeDefinitionProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case TypeDefinitionOptions: + return json.Marshal(x) + case TypeDefinitionRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [TypeDefinitionOptions TypeDefinitionRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_typeDefinitionProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 TypeDefinitionOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 TypeDefinitionRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [TypeDefinitionOptions TypeDefinitionRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_typeHierarchyProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case TypeHierarchyOptions: + return json.Marshal(x) + case TypeHierarchyRegistrationOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [TypeHierarchyOptions TypeHierarchyRegistrationOptions bool]", t) +} + +func (t *Or_ServerCapabilities_typeHierarchyProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 TypeHierarchyOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 TypeHierarchyRegistrationOptions + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 bool + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [TypeHierarchyOptions TypeHierarchyRegistrationOptions bool]"} +} + +func (t Or_ServerCapabilities_workspaceSymbolProvider) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case WorkspaceSymbolOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [WorkspaceSymbolOptions bool]", t) +} + +func (t *Or_ServerCapabilities_workspaceSymbolProvider) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 WorkspaceSymbolOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [WorkspaceSymbolOptions bool]"} +} + +func (t Or_SignatureInformation_documentation) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case MarkupContent: + return json.Marshal(x) + case string: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [MarkupContent string]", t) +} + +func (t *Or_SignatureInformation_documentation) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 MarkupContent + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 string + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [MarkupContent string]"} +} + +func (t Or_TextDocumentEdit_edits_Elem) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case AnnotatedTextEdit: + return json.Marshal(x) + case TextEdit: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [AnnotatedTextEdit TextEdit]", t) +} + +func (t *Or_TextDocumentEdit_edits_Elem) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 AnnotatedTextEdit + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 TextEdit + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [AnnotatedTextEdit TextEdit]"} +} + +func (t Or_TextDocumentFilter) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case TextDocumentFilterLanguage: + return json.Marshal(x) + case TextDocumentFilterPattern: + return json.Marshal(x) + case TextDocumentFilterScheme: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [TextDocumentFilterLanguage TextDocumentFilterPattern TextDocumentFilterScheme]", t) +} + +func (t *Or_TextDocumentFilter) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 TextDocumentFilterLanguage + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 TextDocumentFilterPattern + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 TextDocumentFilterScheme + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [TextDocumentFilterLanguage TextDocumentFilterPattern TextDocumentFilterScheme]"} +} + +func (t Or_TextDocumentSyncOptions_save) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case SaveOptions: + return json.Marshal(x) + case bool: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [SaveOptions bool]", t) +} + +func (t *Or_TextDocumentSyncOptions_save) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 SaveOptions + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 bool + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [SaveOptions bool]"} +} + +func (t Or_WorkspaceDocumentDiagnosticReport) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case WorkspaceFullDocumentDiagnosticReport: + return json.Marshal(x) + case WorkspaceUnchangedDocumentDiagnosticReport: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [WorkspaceFullDocumentDiagnosticReport WorkspaceUnchangedDocumentDiagnosticReport]", t) +} + +func (t *Or_WorkspaceDocumentDiagnosticReport) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 WorkspaceFullDocumentDiagnosticReport + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 WorkspaceUnchangedDocumentDiagnosticReport + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [WorkspaceFullDocumentDiagnosticReport WorkspaceUnchangedDocumentDiagnosticReport]"} +} + +func (t Or_WorkspaceEdit_documentChanges_Elem) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case CreateFile: + return json.Marshal(x) + case DeleteFile: + return json.Marshal(x) + case RenameFile: + return json.Marshal(x) + case TextDocumentEdit: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [CreateFile DeleteFile RenameFile TextDocumentEdit]", t) +} + +func (t *Or_WorkspaceEdit_documentChanges_Elem) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 CreateFile + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 DeleteFile + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + var h2 RenameFile + if err := json.Unmarshal(x, &h2); err == nil { + t.Value = h2 + return nil + } + var h3 TextDocumentEdit + if err := json.Unmarshal(x, &h3); err == nil { + t.Value = h3 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [CreateFile DeleteFile RenameFile TextDocumentEdit]"} +} + +func (t Or_textDocument_declaration) MarshalJSON() ([]byte, error) { + switch x := t.Value.(type) { + case Declaration: + return json.Marshal(x) + case []DeclarationLink: + return json.Marshal(x) + case nil: + return []byte("null"), nil + } + return nil, fmt.Errorf("type %T not one of [Declaration []DeclarationLink]", t) +} + +func (t *Or_textDocument_declaration) UnmarshalJSON(x []byte) error { + if string(x) == "null" { + t.Value = nil + return nil + } + var h0 Declaration + if err := json.Unmarshal(x, &h0); err == nil { + t.Value = h0 + return nil + } + var h1 []DeclarationLink + if err := json.Unmarshal(x, &h1); err == nil { + t.Value = h1 + return nil + } + return &UnmarshalError{"unmarshal failed to match one of [Declaration []DeclarationLink]"} +} diff --git a/contribs/gnopls/internal/protocol/tsprotocol.go b/contribs/gnopls/internal/protocol/tsprotocol.go new file mode 100644 index 00000000000..65b97f5b164 --- /dev/null +++ b/contribs/gnopls/internal/protocol/tsprotocol.go @@ -0,0 +1,6727 @@ +// 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. + +// Code generated for LSP. DO NOT EDIT. + +package protocol + +// Code generated from protocol/metaModel.json at ref release/protocol/3.17.6-next.2 (hash 654dc9be6673c61476c28fda604406279c3258d7). +// https://github.com/microsoft/vscode-languageserver-node/blob/release/protocol/3.17.6-next.2/protocol/metaModel.json +// LSP metaData.version = 3.17.0. + +import "encoding/json" + +// A special text edit with an additional change annotation. +// +// @since 3.16.0. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#annotatedTextEdit +type AnnotatedTextEdit struct { + // The actual identifier of the change annotation + AnnotationID *ChangeAnnotationIdentifier `json:"annotationId,omitempty"` + TextEdit +} + +// The parameters passed via an apply workspace edit request. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#applyWorkspaceEditParams +type ApplyWorkspaceEditParams struct { + // An optional label of the workspace edit. This label is + // presented in the user interface for example on an undo + // stack to undo the workspace edit. + Label string `json:"label,omitempty"` + // The edits to apply. + Edit WorkspaceEdit `json:"edit"` +} + +// The result returned from the apply workspace edit request. +// +// @since 3.17 renamed from ApplyWorkspaceEditResponse +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#applyWorkspaceEditResult +type ApplyWorkspaceEditResult struct { + // Indicates whether the edit was applied or not. + Applied bool `json:"applied"` + // An optional textual description for why the edit was not applied. + // This may be used by the server for diagnostic logging or to provide + // a suitable error for a request that triggered the edit. + FailureReason string `json:"failureReason,omitempty"` + // Depending on the client's failure handling strategy `failedChange` might + // contain the index of the change that failed. This property is only available + // if the client signals a `failureHandlingStrategy` in its client capabilities. + FailedChange uint32 `json:"failedChange,omitempty"` +} + +// A base for all symbol information. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#baseSymbolInformation +type BaseSymbolInformation struct { + // The name of this symbol. + Name string `json:"name"` + // The kind of this symbol. + Kind SymbolKind `json:"kind"` + // Tags for this symbol. + // + // @since 3.16.0 + Tags []SymbolTag `json:"tags,omitempty"` + // The name of the symbol containing this symbol. This information is for + // user interface purposes (e.g. to render a qualifier in the user interface + // if necessary). It can't be used to re-infer a hierarchy for the document + // symbols. + ContainerName string `json:"containerName,omitempty"` +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyClientCapabilities +type CallHierarchyClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is set to `true` + // the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + // return value for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// Represents an incoming call, e.g. a caller of a method or constructor. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyIncomingCall +type CallHierarchyIncomingCall struct { + // The item that makes the call. + From CallHierarchyItem `json:"from"` + // The ranges at which the calls appear. This is relative to the caller + // denoted by {@link CallHierarchyIncomingCall.from `this.from`}. + FromRanges []Range `json:"fromRanges"` +} + +// The parameter of a `callHierarchy/incomingCalls` request. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyIncomingCallsParams +type CallHierarchyIncomingCallsParams struct { + Item CallHierarchyItem `json:"item"` + WorkDoneProgressParams + PartialResultParams +} + +// Represents programming constructs like functions or constructors in the context +// of call hierarchy. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyItem +type CallHierarchyItem struct { + // The name of this item. + Name string `json:"name"` + // The kind of this item. + Kind SymbolKind `json:"kind"` + // Tags for this item. + Tags []SymbolTag `json:"tags,omitempty"` + // More detail for this item, e.g. the signature of a function. + Detail string `json:"detail,omitempty"` + // The resource identifier of this item. + URI DocumentURI `json:"uri"` + // The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. + Range Range `json:"range"` + // The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function. + // Must be contained by the {@link CallHierarchyItem.range `range`}. + SelectionRange Range `json:"selectionRange"` + // A data entry field that is preserved between a call hierarchy prepare and + // incoming calls or outgoing calls requests. + Data interface{} `json:"data,omitempty"` +} + +// Call hierarchy options used during static registration. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyOptions +type CallHierarchyOptions struct { + WorkDoneProgressOptions +} + +// Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyOutgoingCall +type CallHierarchyOutgoingCall struct { + // The item that is called. + To CallHierarchyItem `json:"to"` + // The range at which this item is called. This is the range relative to the caller, e.g the item + // passed to {@link CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls `provideCallHierarchyOutgoingCalls`} + // and not {@link CallHierarchyOutgoingCall.to `this.to`}. + FromRanges []Range `json:"fromRanges"` +} + +// The parameter of a `callHierarchy/outgoingCalls` request. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyOutgoingCallsParams +type CallHierarchyOutgoingCallsParams struct { + Item CallHierarchyItem `json:"item"` + WorkDoneProgressParams + PartialResultParams +} + +// The parameter of a `textDocument/prepareCallHierarchy` request. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyPrepareParams +type CallHierarchyPrepareParams struct { + TextDocumentPositionParams + WorkDoneProgressParams +} + +// Call hierarchy options used during static or dynamic registration. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyRegistrationOptions +type CallHierarchyRegistrationOptions struct { + TextDocumentRegistrationOptions + CallHierarchyOptions + StaticRegistrationOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#cancelParams +type CancelParams struct { + // The request id to cancel. + ID interface{} `json:"id"` +} + +// Additional information that describes document changes. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#changeAnnotation +type ChangeAnnotation struct { + // A human-readable string describing the actual change. The string + // is rendered prominent in the user interface. + Label string `json:"label"` + // A flag which indicates that user confirmation is needed + // before applying the change. + NeedsConfirmation bool `json:"needsConfirmation,omitempty"` + // A human-readable string which is rendered less prominent in + // the user interface. + Description string `json:"description,omitempty"` +} + +// An identifier to refer to a change annotation stored with a workspace edit. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#changeAnnotationIdentifier +type ChangeAnnotationIdentifier = string // (alias) +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#changeAnnotationsSupportOptions +type ChangeAnnotationsSupportOptions struct { + // Whether the client groups edits with equal labels into tree nodes, + // for instance all edits labelled with "Changes in Strings" would + // be a tree node. + GroupsOnLabel bool `json:"groupsOnLabel,omitempty"` +} + +// Defines the capabilities provided by the client. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientCapabilities +type ClientCapabilities struct { + // Workspace specific client capabilities. + Workspace WorkspaceClientCapabilities `json:"workspace,omitempty"` + // Text document specific client capabilities. + TextDocument TextDocumentClientCapabilities `json:"textDocument,omitempty"` + // Capabilities specific to the notebook document support. + // + // @since 3.17.0 + NotebookDocument *NotebookDocumentClientCapabilities `json:"notebookDocument,omitempty"` + // Window specific client capabilities. + Window WindowClientCapabilities `json:"window,omitempty"` + // General client capabilities. + // + // @since 3.16.0 + General *GeneralClientCapabilities `json:"general,omitempty"` + // Experimental client capabilities. + Experimental interface{} `json:"experimental,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientCodeActionKindOptions +type ClientCodeActionKindOptions struct { + // The code action kind values the client supports. When this + // property exists the client also guarantees that it will + // handle values outside its set gracefully and falls back + // to a default value when unknown. + ValueSet []CodeActionKind `json:"valueSet"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientCodeActionLiteralOptions +type ClientCodeActionLiteralOptions struct { + // The code action kind is support with the following value + // set. + CodeActionKind ClientCodeActionKindOptions `json:"codeActionKind"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientCodeActionResolveOptions +type ClientCodeActionResolveOptions struct { + // The properties that a client can resolve lazily. + Properties []string `json:"properties"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientCompletionItemInsertTextModeOptions +type ClientCompletionItemInsertTextModeOptions struct { + ValueSet []InsertTextMode `json:"valueSet"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientCompletionItemOptions +type ClientCompletionItemOptions struct { + // Client supports snippets as insert text. + // + // A snippet can define tab stops and placeholders with `$1`, `$2` + // and `${3:foo}`. `$0` defines the final tab stop, it defaults to + // the end of the snippet. Placeholders with equal identifiers are linked, + // that is typing in one will update others too. + SnippetSupport bool `json:"snippetSupport,omitempty"` + // Client supports commit characters on a completion item. + CommitCharactersSupport bool `json:"commitCharactersSupport,omitempty"` + // Client supports the following content formats for the documentation + // property. The order describes the preferred format of the client. + DocumentationFormat []MarkupKind `json:"documentationFormat,omitempty"` + // Client supports the deprecated property on a completion item. + DeprecatedSupport bool `json:"deprecatedSupport,omitempty"` + // Client supports the preselect property on a completion item. + PreselectSupport bool `json:"preselectSupport,omitempty"` + // Client supports the tag property on a completion item. Clients supporting + // tags have to handle unknown tags gracefully. Clients especially need to + // preserve unknown tags when sending a completion item back to the server in + // a resolve call. + // + // @since 3.15.0 + TagSupport *CompletionItemTagOptions `json:"tagSupport,omitempty"` + // Client support insert replace edit to control different behavior if a + // completion item is inserted in the text or should replace text. + // + // @since 3.16.0 + InsertReplaceSupport bool `json:"insertReplaceSupport,omitempty"` + // Indicates which properties a client can resolve lazily on a completion + // item. Before version 3.16.0 only the predefined properties `documentation` + // and `details` could be resolved lazily. + // + // @since 3.16.0 + ResolveSupport *ClientCompletionItemResolveOptions `json:"resolveSupport,omitempty"` + // The client supports the `insertTextMode` property on + // a completion item to override the whitespace handling mode + // as defined by the client (see `insertTextMode`). + // + // @since 3.16.0 + InsertTextModeSupport *ClientCompletionItemInsertTextModeOptions `json:"insertTextModeSupport,omitempty"` + // The client has support for completion item label + // details (see also `CompletionItemLabelDetails`). + // + // @since 3.17.0 + LabelDetailsSupport bool `json:"labelDetailsSupport,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientCompletionItemOptionsKind +type ClientCompletionItemOptionsKind struct { + // The completion item kind values the client supports. When this + // property exists the client also guarantees that it will + // handle values outside its set gracefully and falls back + // to a default value when unknown. + // + // If this property is not present the client only supports + // the completion items kinds from `Text` to `Reference` as defined in + // the initial version of the protocol. + ValueSet []CompletionItemKind `json:"valueSet,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientCompletionItemResolveOptions +type ClientCompletionItemResolveOptions struct { + // The properties that a client can resolve lazily. + Properties []string `json:"properties"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientDiagnosticsTagOptions +type ClientDiagnosticsTagOptions struct { + // The tags supported by the client. + ValueSet []DiagnosticTag `json:"valueSet"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientFoldingRangeKindOptions +type ClientFoldingRangeKindOptions struct { + // The folding range kind values the client supports. When this + // property exists the client also guarantees that it will + // handle values outside its set gracefully and falls back + // to a default value when unknown. + ValueSet []FoldingRangeKind `json:"valueSet,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientFoldingRangeOptions +type ClientFoldingRangeOptions struct { + // If set, the client signals that it supports setting collapsedText on + // folding ranges to display custom labels instead of the default text. + // + // @since 3.17.0 + CollapsedText bool `json:"collapsedText,omitempty"` +} + +// Information about the client +// +// @since 3.15.0 +// @since 3.18.0 ClientInfo type name added. +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientInfo +type ClientInfo struct { + // The name of the client as defined by the client. + Name string `json:"name"` + // The client's version as defined by the client. + Version string `json:"version,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientInlayHintResolveOptions +type ClientInlayHintResolveOptions struct { + // The properties that a client can resolve lazily. + Properties []string `json:"properties"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientSemanticTokensRequestFullDelta +type ClientSemanticTokensRequestFullDelta struct { + // The client will send the `textDocument/semanticTokens/full/delta` request if + // the server provides a corresponding handler. + Delta bool `json:"delta,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientSemanticTokensRequestOptions +type ClientSemanticTokensRequestOptions struct { + // The client will send the `textDocument/semanticTokens/range` request if + // the server provides a corresponding handler. + Range *Or_ClientSemanticTokensRequestOptions_range `json:"range,omitempty"` + // The client will send the `textDocument/semanticTokens/full` request if + // the server provides a corresponding handler. + Full *Or_ClientSemanticTokensRequestOptions_full `json:"full,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientShowMessageActionItemOptions +type ClientShowMessageActionItemOptions struct { + // Whether the client supports additional attributes which + // are preserved and send back to the server in the + // request's response. + AdditionalPropertiesSupport bool `json:"additionalPropertiesSupport,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientSignatureInformationOptions +type ClientSignatureInformationOptions struct { + // Client supports the following content formats for the documentation + // property. The order describes the preferred format of the client. + DocumentationFormat []MarkupKind `json:"documentationFormat,omitempty"` + // Client capabilities specific to parameter information. + ParameterInformation *ClientSignatureParameterInformationOptions `json:"parameterInformation,omitempty"` + // The client supports the `activeParameter` property on `SignatureInformation` + // literal. + // + // @since 3.16.0 + ActiveParameterSupport bool `json:"activeParameterSupport,omitempty"` + // The client supports the `activeParameter` property on + // `SignatureHelp`/`SignatureInformation` being set to `null` to + // indicate that no parameter should be active. + // + // @since 3.18.0 + // @proposed + NoActiveParameterSupport bool `json:"noActiveParameterSupport,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientSignatureParameterInformationOptions +type ClientSignatureParameterInformationOptions struct { + // The client supports processing label offsets instead of a + // simple label string. + // + // @since 3.14.0 + LabelOffsetSupport bool `json:"labelOffsetSupport,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientSymbolKindOptions +type ClientSymbolKindOptions struct { + // The symbol kind values the client supports. When this + // property exists the client also guarantees that it will + // handle values outside its set gracefully and falls back + // to a default value when unknown. + // + // If this property is not present the client only supports + // the symbol kinds from `File` to `Array` as defined in + // the initial version of the protocol. + ValueSet []SymbolKind `json:"valueSet,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientSymbolResolveOptions +type ClientSymbolResolveOptions struct { + // The properties that a client can resolve lazily. Usually + // `location.range` + Properties []string `json:"properties"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#clientSymbolTagOptions +type ClientSymbolTagOptions struct { + // The tags supported by the client. + ValueSet []SymbolTag `json:"valueSet"` +} + +// A code action represents a change that can be performed in code, e.g. to fix a problem or +// to refactor code. +// +// A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeAction +type CodeAction struct { + // A short, human-readable, title for this code action. + Title string `json:"title"` + // The kind of the code action. + // + // Used to filter code actions. + Kind CodeActionKind `json:"kind,omitempty"` + // The diagnostics that this code action resolves. + Diagnostics []Diagnostic `json:"diagnostics,omitempty"` + // Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted + // by keybindings. + // + // A quick fix should be marked preferred if it properly addresses the underlying error. + // A refactoring should be marked preferred if it is the most reasonable choice of actions to take. + // + // @since 3.15.0 + IsPreferred bool `json:"isPreferred,omitempty"` + // Marks that the code action cannot currently be applied. + // + // Clients should follow the following guidelines regarding disabled code actions: + // + // - Disabled code actions are not shown in automatic [lightbulbs](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) + // code action menus. + // + // - Disabled actions are shown as faded out in the code action menu when the user requests a more specific type + // of code action, such as refactorings. + // + // - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) + // that auto applies a code action and only disabled code actions are returned, the client should show the user an + // error message with `reason` in the editor. + // + // @since 3.16.0 + Disabled *CodeActionDisabled `json:"disabled,omitempty"` + // The workspace edit this code action performs. + Edit *WorkspaceEdit `json:"edit,omitempty"` + // A command this code action executes. If a code action + // provides an edit and a command, first the edit is + // executed and then the command. + Command *Command `json:"command,omitempty"` + // A data entry field that is preserved on a code action between + // a `textDocument/codeAction` and a `codeAction/resolve` request. + // + // @since 3.16.0 + Data *json.RawMessage `json:"data,omitempty"` +} + +// The Client Capabilities of a {@link CodeActionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeActionClientCapabilities +type CodeActionClientCapabilities struct { + // Whether code action supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client support code action literals of type `CodeAction` as a valid + // response of the `textDocument/codeAction` request. If the property is not + // set the request can only return `Command` literals. + // + // @since 3.8.0 + CodeActionLiteralSupport ClientCodeActionLiteralOptions `json:"codeActionLiteralSupport,omitempty"` + // Whether code action supports the `isPreferred` property. + // + // @since 3.15.0 + IsPreferredSupport bool `json:"isPreferredSupport,omitempty"` + // Whether code action supports the `disabled` property. + // + // @since 3.16.0 + DisabledSupport bool `json:"disabledSupport,omitempty"` + // Whether code action supports the `data` property which is + // preserved between a `textDocument/codeAction` and a + // `codeAction/resolve` request. + // + // @since 3.16.0 + DataSupport bool `json:"dataSupport,omitempty"` + // Whether the client supports resolving additional code action + // properties via a separate `codeAction/resolve` request. + // + // @since 3.16.0 + ResolveSupport *ClientCodeActionResolveOptions `json:"resolveSupport,omitempty"` + // Whether the client honors the change annotations in + // text edits and resource operations returned via the + // `CodeAction#edit` property by for example presenting + // the workspace edit in the user interface and asking + // for confirmation. + // + // @since 3.16.0 + HonorsChangeAnnotations bool `json:"honorsChangeAnnotations,omitempty"` + // Whether the client supports documentation for a class of + // code actions. + // + // @since 3.18.0 + // @proposed + DocumentationSupport bool `json:"documentationSupport,omitempty"` +} + +// Contains additional diagnostic information about the context in which +// a {@link CodeActionProvider.provideCodeActions code action} is run. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeActionContext +type CodeActionContext struct { + // An array of diagnostics known on the client side overlapping the range provided to the + // `textDocument/codeAction` request. They are provided so that the server knows which + // errors are currently presented to the user for the given range. There is no guarantee + // that these accurately reflect the error state of the resource. The primary parameter + // to compute code actions is the provided range. + Diagnostics []Diagnostic `json:"diagnostics"` + // Requested kind of actions to return. + // + // Actions not of this kind are filtered out by the client before being shown. So servers + // can omit computing them. + Only []CodeActionKind `json:"only,omitempty"` + // The reason why code actions were requested. + // + // @since 3.17.0 + TriggerKind *CodeActionTriggerKind `json:"triggerKind,omitempty"` +} + +// Captures why the code action is currently disabled. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeActionDisabled +type CodeActionDisabled struct { + // Human readable description of why the code action is currently disabled. + // + // This is displayed in the code actions UI. + Reason string `json:"reason"` +} + +// A set of predefined code action kinds +type CodeActionKind string + +// Documentation for a class of code actions. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeActionKindDocumentation +type CodeActionKindDocumentation struct { + // The kind of the code action being documented. + // + // If the kind is generic, such as `CodeActionKind.Refactor`, the documentation will be shown whenever any + // refactorings are returned. If the kind if more specific, such as `CodeActionKind.RefactorExtract`, the + // documentation will only be shown when extract refactoring code actions are returned. + Kind CodeActionKind `json:"kind"` + // Command that is ued to display the documentation to the user. + // + // The title of this documentation code action is taken from {@linkcode Command.title} + Command Command `json:"command"` +} + +// Provider options for a {@link CodeActionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeActionOptions +type CodeActionOptions struct { + // CodeActionKinds that this server may return. + // + // The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server + // may list out every specific kind they provide. + CodeActionKinds []CodeActionKind `json:"codeActionKinds,omitempty"` + // Static documentation for a class of code actions. + // + // Documentation from the provider should be shown in the code actions menu if either: + // + // + // - Code actions of `kind` are requested by the editor. In this case, the editor will show the documentation that + // most closely matches the requested code action kind. For example, if a provider has documentation for + // both `Refactor` and `RefactorExtract`, when the user requests code actions for `RefactorExtract`, + // the editor will use the documentation for `RefactorExtract` instead of the documentation for `Refactor`. + // + // + // - Any code actions of `kind` are returned by the provider. + // + // At most one documentation entry should be shown per provider. + // + // @since 3.18.0 + // @proposed + Documentation []CodeActionKindDocumentation `json:"documentation,omitempty"` + // The server provides support to resolve additional + // information for a code action. + // + // @since 3.16.0 + ResolveProvider bool `json:"resolveProvider,omitempty"` + WorkDoneProgressOptions +} + +// The parameters of a {@link CodeActionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeActionParams +type CodeActionParams struct { + // The document in which the command was invoked. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The range for which the command was invoked. + Range Range `json:"range"` + // Context carrying additional information. + Context CodeActionContext `json:"context"` + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link CodeActionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeActionRegistrationOptions +type CodeActionRegistrationOptions struct { + TextDocumentRegistrationOptions + CodeActionOptions +} + +// The reason why code actions were requested. +// +// @since 3.17.0 +type CodeActionTriggerKind uint32 + +// Structure to capture a description for an error code. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeDescription +type CodeDescription struct { + // An URI to open with more information about the diagnostic error. + Href URI `json:"href"` +} + +// A code lens represents a {@link Command command} that should be shown along with +// source text, like the number of references, a way to run tests, etc. +// +// A code lens is _unresolved_ when no command is associated to it. For performance +// reasons the creation of a code lens and resolving should be done in two stages. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeLens +type CodeLens struct { + // The range in which this code lens is valid. Should only span a single line. + Range Range `json:"range"` + // The command this code lens represents. + Command *Command `json:"command,omitempty"` + // A data entry field that is preserved on a code lens item between + // a {@link CodeLensRequest} and a {@link CodeLensResolveRequest} + Data interface{} `json:"data,omitempty"` +} + +// The client capabilities of a {@link CodeLensRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeLensClientCapabilities +type CodeLensClientCapabilities struct { + // Whether code lens supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// Code Lens provider options of a {@link CodeLensRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeLensOptions +type CodeLensOptions struct { + // Code lens has a resolve provider as well. + ResolveProvider bool `json:"resolveProvider,omitempty"` + WorkDoneProgressOptions +} + +// The parameters of a {@link CodeLensRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeLensParams +type CodeLensParams struct { + // The document to request code lens for. + TextDocument TextDocumentIdentifier `json:"textDocument"` + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link CodeLensRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeLensRegistrationOptions +type CodeLensRegistrationOptions struct { + TextDocumentRegistrationOptions + CodeLensOptions +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeLensWorkspaceClientCapabilities +type CodeLensWorkspaceClientCapabilities struct { + // Whether the client implementation supports a refresh request sent from the + // server to the client. + // + // Note that this event is global and will force the client to refresh all + // code lenses currently shown. It should be used with absolute care and is + // useful for situation where a server for example detect a project wide + // change that requires such a calculation. + RefreshSupport bool `json:"refreshSupport,omitempty"` +} + +// Represents a color in RGBA space. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#color +type Color struct { + // The red component of this color in the range [0-1]. + Red float64 `json:"red"` + // The green component of this color in the range [0-1]. + Green float64 `json:"green"` + // The blue component of this color in the range [0-1]. + Blue float64 `json:"blue"` + // The alpha component of this color in the range [0-1]. + Alpha float64 `json:"alpha"` +} + +// Represents a color range from a document. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#colorInformation +type ColorInformation struct { + // The range in the document where this color appears. + Range Range `json:"range"` + // The actual color value for this color range. + Color Color `json:"color"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#colorPresentation +type ColorPresentation struct { + // The label of this color presentation. It will be shown on the color + // picker header. By default this is also the text that is inserted when selecting + // this color presentation. + Label string `json:"label"` + // An {@link TextEdit edit} which is applied to a document when selecting + // this presentation for the color. When `falsy` the {@link ColorPresentation.label label} + // is used. + TextEdit *TextEdit `json:"textEdit,omitempty"` + // An optional array of additional {@link TextEdit text edits} that are applied when + // selecting this color presentation. Edits must not overlap with the main {@link ColorPresentation.textEdit edit} nor with themselves. + AdditionalTextEdits []TextEdit `json:"additionalTextEdits,omitempty"` +} + +// Parameters for a {@link ColorPresentationRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#colorPresentationParams +type ColorPresentationParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The color to request presentations for. + Color Color `json:"color"` + // The range where the color would be inserted. Serves as a context. + Range Range `json:"range"` + WorkDoneProgressParams + PartialResultParams +} + +// Represents a reference to a command. Provides a title which +// will be used to represent a command in the UI and, optionally, +// an array of arguments which will be passed to the command handler +// function when invoked. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#command +type Command struct { + // Title of the command, like `save`. + Title string `json:"title"` + // An optional tooltip. + // + // @since 3.18.0 + // @proposed + Tooltip string `json:"tooltip,omitempty"` + // The identifier of the actual command handler. + Command string `json:"command"` + // Arguments that the command handler should be + // invoked with. + Arguments []json.RawMessage `json:"arguments,omitempty"` +} + +// Completion client capabilities +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionClientCapabilities +type CompletionClientCapabilities struct { + // Whether completion supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client supports the following `CompletionItem` specific + // capabilities. + CompletionItem ClientCompletionItemOptions `json:"completionItem,omitempty"` + CompletionItemKind *ClientCompletionItemOptionsKind `json:"completionItemKind,omitempty"` + // Defines how the client handles whitespace and indentation + // when accepting a completion item that uses multi line + // text in either `insertText` or `textEdit`. + // + // @since 3.17.0 + InsertTextMode InsertTextMode `json:"insertTextMode,omitempty"` + // The client supports to send additional context information for a + // `textDocument/completion` request. + ContextSupport bool `json:"contextSupport,omitempty"` + // The client supports the following `CompletionList` specific + // capabilities. + // + // @since 3.17.0 + CompletionList *CompletionListCapabilities `json:"completionList,omitempty"` +} + +// Contains additional information about the context in which a completion request is triggered. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionContext +type CompletionContext struct { + // How the completion was triggered. + TriggerKind CompletionTriggerKind `json:"triggerKind"` + // The trigger character (a single character) that has trigger code complete. + // Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` + TriggerCharacter string `json:"triggerCharacter,omitempty"` +} + +// A completion item represents a text snippet that is +// proposed to complete text that is being typed. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionItem +type CompletionItem struct { + // The label of this completion item. + // + // The label property is also by default the text that + // is inserted when selecting this completion. + // + // If label details are provided the label itself should + // be an unqualified name of the completion item. + Label string `json:"label"` + // Additional details for the label + // + // @since 3.17.0 + LabelDetails *CompletionItemLabelDetails `json:"labelDetails,omitempty"` + // The kind of this completion item. Based of the kind + // an icon is chosen by the editor. + Kind CompletionItemKind `json:"kind,omitempty"` + // Tags for this completion item. + // + // @since 3.15.0 + Tags []CompletionItemTag `json:"tags,omitempty"` + // A human-readable string with additional information + // about this item, like type or symbol information. + Detail string `json:"detail,omitempty"` + // A human-readable string that represents a doc-comment. + Documentation *Or_CompletionItem_documentation `json:"documentation,omitempty"` + // Indicates if this item is deprecated. + // @deprecated Use `tags` instead. + Deprecated bool `json:"deprecated,omitempty"` + // Select this item when showing. + // + // *Note* that only one completion item can be selected and that the + // tool / client decides which item that is. The rule is that the *first* + // item of those that match best is selected. + Preselect bool `json:"preselect,omitempty"` + // A string that should be used when comparing this item + // with other items. When `falsy` the {@link CompletionItem.label label} + // is used. + SortText string `json:"sortText,omitempty"` + // A string that should be used when filtering a set of + // completion items. When `falsy` the {@link CompletionItem.label label} + // is used. + FilterText string `json:"filterText,omitempty"` + // A string that should be inserted into a document when selecting + // this completion. When `falsy` the {@link CompletionItem.label label} + // is used. + // + // The `insertText` is subject to interpretation by the client side. + // Some tools might not take the string literally. For example + // VS Code when code complete is requested in this example + // `con` and a completion item with an `insertText` of + // `console` is provided it will only insert `sole`. Therefore it is + // recommended to use `textEdit` instead since it avoids additional client + // side interpretation. + InsertText string `json:"insertText,omitempty"` + // The format of the insert text. The format applies to both the + // `insertText` property and the `newText` property of a provided + // `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`. + // + // Please note that the insertTextFormat doesn't apply to + // `additionalTextEdits`. + InsertTextFormat *InsertTextFormat `json:"insertTextFormat,omitempty"` + // How whitespace and indentation is handled during completion + // item insertion. If not provided the clients default value depends on + // the `textDocument.completion.insertTextMode` client capability. + // + // @since 3.16.0 + InsertTextMode *InsertTextMode `json:"insertTextMode,omitempty"` + // An {@link TextEdit edit} which is applied to a document when selecting + // this completion. When an edit is provided the value of + // {@link CompletionItem.insertText insertText} is ignored. + // + // Most editors support two different operations when accepting a completion + // item. One is to insert a completion text and the other is to replace an + // existing text with a completion text. Since this can usually not be + // predetermined by a server it can report both ranges. Clients need to + // signal support for `InsertReplaceEdits` via the + // `textDocument.completion.insertReplaceSupport` client capability + // property. + // + // *Note 1:* The text edit's range as well as both ranges from an insert + // replace edit must be a [single line] and they must contain the position + // at which completion has been requested. + // *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range + // must be a prefix of the edit's replace range, that means it must be + // contained and starting at the same position. + // + // @since 3.16.0 additional type `InsertReplaceEdit` + TextEdit *Or_CompletionItem_textEdit `json:"textEdit,omitempty"` + // The edit text used if the completion item is part of a CompletionList and + // CompletionList defines an item default for the text edit range. + // + // Clients will only honor this property if they opt into completion list + // item defaults using the capability `completionList.itemDefaults`. + // + // If not provided and a list's default range is provided the label + // property is used as a text. + // + // @since 3.17.0 + TextEditText string `json:"textEditText,omitempty"` + // An optional array of additional {@link TextEdit text edits} that are applied when + // selecting this completion. Edits must not overlap (including the same insert position) + // with the main {@link CompletionItem.textEdit edit} nor with themselves. + // + // Additional text edits should be used to change text unrelated to the current cursor position + // (for example adding an import statement at the top of the file if the completion item will + // insert an unqualified type). + AdditionalTextEdits []TextEdit `json:"additionalTextEdits,omitempty"` + // An optional set of characters that when pressed while this completion is active will accept it first and + // then type that character. *Note* that all commit characters should have `length=1` and that superfluous + // characters will be ignored. + CommitCharacters []string `json:"commitCharacters,omitempty"` + // An optional {@link Command command} that is executed *after* inserting this completion. *Note* that + // additional modifications to the current document should be described with the + // {@link CompletionItem.additionalTextEdits additionalTextEdits}-property. + Command *Command `json:"command,omitempty"` + // A data entry field that is preserved on a completion item between a + // {@link CompletionRequest} and a {@link CompletionResolveRequest}. + Data interface{} `json:"data,omitempty"` +} + +// In many cases the items of an actual completion result share the same +// value for properties like `commitCharacters` or the range of a text +// edit. A completion list can therefore define item defaults which will +// be used if a completion item itself doesn't specify the value. +// +// If a completion list specifies a default value and a completion item +// also specifies a corresponding value the one from the item is used. +// +// Servers are only allowed to return default values if the client +// signals support for this via the `completionList.itemDefaults` +// capability. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionItemDefaults +type CompletionItemDefaults struct { + // A default commit character set. + // + // @since 3.17.0 + CommitCharacters []string `json:"commitCharacters,omitempty"` + // A default edit range. + // + // @since 3.17.0 + EditRange *Or_CompletionItemDefaults_editRange `json:"editRange,omitempty"` + // A default insert text format. + // + // @since 3.17.0 + InsertTextFormat *InsertTextFormat `json:"insertTextFormat,omitempty"` + // A default insert text mode. + // + // @since 3.17.0 + InsertTextMode *InsertTextMode `json:"insertTextMode,omitempty"` + // A default data value. + // + // @since 3.17.0 + Data interface{} `json:"data,omitempty"` +} + +// The kind of a completion entry. +type CompletionItemKind uint32 + +// Additional details for a completion item label. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionItemLabelDetails +type CompletionItemLabelDetails struct { + // An optional string which is rendered less prominently directly after {@link CompletionItem.label label}, + // without any spacing. Should be used for function signatures and type annotations. + Detail string `json:"detail,omitempty"` + // An optional string which is rendered less prominently after {@link CompletionItem.detail}. Should be used + // for fully qualified names and file paths. + Description string `json:"description,omitempty"` +} + +// Completion item tags are extra annotations that tweak the rendering of a completion +// item. +// +// @since 3.15.0 +type CompletionItemTag uint32 + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionItemTagOptions +type CompletionItemTagOptions struct { + // The tags supported by the client. + ValueSet []CompletionItemTag `json:"valueSet"` +} + +// Represents a collection of {@link CompletionItem completion items} to be presented +// in the editor. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionList +type CompletionList struct { + // This list it not complete. Further typing results in recomputing this list. + // + // Recomputed lists have all their items replaced (not appended) in the + // incomplete completion sessions. + IsIncomplete bool `json:"isIncomplete"` + // In many cases the items of an actual completion result share the same + // value for properties like `commitCharacters` or the range of a text + // edit. A completion list can therefore define item defaults which will + // be used if a completion item itself doesn't specify the value. + // + // If a completion list specifies a default value and a completion item + // also specifies a corresponding value the one from the item is used. + // + // Servers are only allowed to return default values if the client + // signals support for this via the `completionList.itemDefaults` + // capability. + // + // @since 3.17.0 + ItemDefaults *CompletionItemDefaults `json:"itemDefaults,omitempty"` + // The completion items. + Items []CompletionItem `json:"items"` +} + +// The client supports the following `CompletionList` specific +// capabilities. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionListCapabilities +type CompletionListCapabilities struct { + // The client supports the following itemDefaults on + // a completion list. + // + // The value lists the supported property names of the + // `CompletionList.itemDefaults` object. If omitted + // no properties are supported. + // + // @since 3.17.0 + ItemDefaults []string `json:"itemDefaults,omitempty"` +} + +// Completion options. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionOptions +type CompletionOptions struct { + // Most tools trigger completion request automatically without explicitly requesting + // it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user + // starts to type an identifier. For example if the user types `c` in a JavaScript file + // code complete will automatically pop up present `console` besides others as a + // completion item. Characters that make up identifiers don't need to be listed here. + // + // If code complete should automatically be trigger on characters not being valid inside + // an identifier (for example `.` in JavaScript) list them in `triggerCharacters`. + TriggerCharacters []string `json:"triggerCharacters,omitempty"` + // The list of all possible characters that commit a completion. This field can be used + // if clients don't support individual commit characters per completion item. See + // `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport` + // + // If a server provides both `allCommitCharacters` and commit characters on an individual + // completion item the ones on the completion item win. + // + // @since 3.2.0 + AllCommitCharacters []string `json:"allCommitCharacters,omitempty"` + // The server provides support to resolve additional + // information for a completion item. + ResolveProvider bool `json:"resolveProvider,omitempty"` + // The server supports the following `CompletionItem` specific + // capabilities. + // + // @since 3.17.0 + CompletionItem *ServerCompletionItemOptions `json:"completionItem,omitempty"` + WorkDoneProgressOptions +} + +// Completion parameters +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionParams +type CompletionParams struct { + // The completion context. This is only available it the client specifies + // to send this using the client capability `textDocument.completion.contextSupport === true` + Context CompletionContext `json:"context,omitempty"` + TextDocumentPositionParams + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link CompletionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionRegistrationOptions +type CompletionRegistrationOptions struct { + TextDocumentRegistrationOptions + CompletionOptions +} + +// How a completion was triggered +type CompletionTriggerKind uint32 + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#configurationItem +type ConfigurationItem struct { + // The scope to get the configuration section for. + ScopeURI *URI `json:"scopeUri,omitempty"` + // The configuration section asked for. + Section string `json:"section,omitempty"` +} + +// The parameters of a configuration request. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#configurationParams +type ConfigurationParams struct { + Items []ConfigurationItem `json:"items"` +} + +// Create file operation. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#createFile +type CreateFile struct { + // A create + Kind string `json:"kind"` + // The resource to create. + URI DocumentURI `json:"uri"` + // Additional options + Options *CreateFileOptions `json:"options,omitempty"` + ResourceOperation +} + +// Options to create a file. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#createFileOptions +type CreateFileOptions struct { + // Overwrite existing file. Overwrite wins over `ignoreIfExists` + Overwrite bool `json:"overwrite,omitempty"` + // Ignore if exists. + IgnoreIfExists bool `json:"ignoreIfExists,omitempty"` +} + +// The parameters sent in notifications/requests for user-initiated creation of +// files. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#createFilesParams +type CreateFilesParams struct { + // An array of all files/folders created in this operation. + Files []FileCreate `json:"files"` +} + +// The declaration of a symbol representation as one or many {@link Location locations}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#declaration +type Declaration = []Location // (alias) +// @since 3.14.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#declarationClientCapabilities +type DeclarationClientCapabilities struct { + // Whether declaration supports dynamic registration. If this is set to `true` + // the client supports the new `DeclarationRegistrationOptions` return value + // for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client supports additional metadata in the form of declaration links. + LinkSupport bool `json:"linkSupport,omitempty"` +} + +// Information about where a symbol is declared. +// +// Provides additional metadata over normal {@link Location location} declarations, including the range of +// the declaring symbol. +// +// Servers should prefer returning `DeclarationLink` over `Declaration` if supported +// by the client. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#declarationLink +type DeclarationLink = LocationLink // (alias) +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#declarationOptions +type DeclarationOptions struct { + WorkDoneProgressOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#declarationParams +type DeclarationParams struct { + TextDocumentPositionParams + WorkDoneProgressParams + PartialResultParams +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#declarationRegistrationOptions +type DeclarationRegistrationOptions struct { + DeclarationOptions + TextDocumentRegistrationOptions + StaticRegistrationOptions +} + +// The definition of a symbol represented as one or many {@link Location locations}. +// For most programming languages there is only one location at which a symbol is +// defined. +// +// Servers should prefer returning `DefinitionLink` over `Definition` if supported +// by the client. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#definition +type Definition = Or_Definition // (alias) +// Client Capabilities for a {@link DefinitionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#definitionClientCapabilities +type DefinitionClientCapabilities struct { + // Whether definition supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client supports additional metadata in the form of definition links. + // + // @since 3.14.0 + LinkSupport bool `json:"linkSupport,omitempty"` +} + +// Information about where a symbol is defined. +// +// Provides additional metadata over normal {@link Location location} definitions, including the range of +// the defining symbol +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#definitionLink +type DefinitionLink = LocationLink // (alias) +// Server Capabilities for a {@link DefinitionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#definitionOptions +type DefinitionOptions struct { + WorkDoneProgressOptions +} + +// Parameters for a {@link DefinitionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#definitionParams +type DefinitionParams struct { + TextDocumentPositionParams + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link DefinitionRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#definitionRegistrationOptions +type DefinitionRegistrationOptions struct { + TextDocumentRegistrationOptions + DefinitionOptions +} + +// Delete file operation +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#deleteFile +type DeleteFile struct { + // A delete + Kind string `json:"kind"` + // The file to delete. + URI DocumentURI `json:"uri"` + // Delete options. + Options *DeleteFileOptions `json:"options,omitempty"` + ResourceOperation +} + +// Delete file options +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#deleteFileOptions +type DeleteFileOptions struct { + // Delete the content recursively if a folder is denoted. + Recursive bool `json:"recursive,omitempty"` + // Ignore the operation if the file doesn't exist. + IgnoreIfNotExists bool `json:"ignoreIfNotExists,omitempty"` +} + +// The parameters sent in notifications/requests for user-initiated deletes of +// files. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#deleteFilesParams +type DeleteFilesParams struct { + // An array of all files/folders deleted in this operation. + Files []FileDelete `json:"files"` +} + +// Represents a diagnostic, such as a compiler error or warning. Diagnostic objects +// are only valid in the scope of a resource. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#diagnostic +type Diagnostic struct { + // The range at which the message applies + Range Range `json:"range"` + // The diagnostic's severity. Can be omitted. If omitted it is up to the + // client to interpret diagnostics as error, warning, info or hint. + Severity DiagnosticSeverity `json:"severity,omitempty"` + // The diagnostic's code, which usually appear in the user interface. + Code interface{} `json:"code,omitempty"` + // An optional property to describe the error code. + // Requires the code field (above) to be present/not null. + // + // @since 3.16.0 + CodeDescription *CodeDescription `json:"codeDescription,omitempty"` + // A human-readable string describing the source of this + // diagnostic, e.g. 'typescript' or 'super lint'. It usually + // appears in the user interface. + Source string `json:"source,omitempty"` + // The diagnostic's message. It usually appears in the user interface + Message string `json:"message"` + // Additional metadata about the diagnostic. + // + // @since 3.15.0 + Tags []DiagnosticTag `json:"tags,omitempty"` + // An array of related diagnostic information, e.g. when symbol-names within + // a scope collide all definitions can be marked via this property. + RelatedInformation []DiagnosticRelatedInformation `json:"relatedInformation,omitempty"` + // A data entry field that is preserved between a `textDocument/publishDiagnostics` + // notification and `textDocument/codeAction` request. + // + // @since 3.16.0 + Data *json.RawMessage `json:"data,omitempty"` +} + +// Client capabilities specific to diagnostic pull requests. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#diagnosticClientCapabilities +type DiagnosticClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is set to `true` + // the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + // return value for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Whether the clients supports related documents for document diagnostic pulls. + RelatedDocumentSupport bool `json:"relatedDocumentSupport,omitempty"` +} + +// Diagnostic options. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#diagnosticOptions +type DiagnosticOptions struct { + // An optional identifier under which the diagnostics are + // managed by the client. + Identifier string `json:"identifier,omitempty"` + // Whether the language has inter file dependencies meaning that + // editing code in one file can result in a different diagnostic + // set in another file. Inter file dependencies are common for + // most programming languages and typically uncommon for linters. + InterFileDependencies bool `json:"interFileDependencies"` + // The server provides support for workspace diagnostics as well. + WorkspaceDiagnostics bool `json:"workspaceDiagnostics"` + WorkDoneProgressOptions +} + +// Diagnostic registration options. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#diagnosticRegistrationOptions +type DiagnosticRegistrationOptions struct { + TextDocumentRegistrationOptions + DiagnosticOptions + StaticRegistrationOptions +} + +// Represents a related message and source code location for a diagnostic. This should be +// used to point to code locations that cause or related to a diagnostics, e.g when duplicating +// a symbol in a scope. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#diagnosticRelatedInformation +type DiagnosticRelatedInformation struct { + // The location of this related diagnostic information. + Location Location `json:"location"` + // The message of this related diagnostic information. + Message string `json:"message"` +} + +// Cancellation data returned from a diagnostic request. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#diagnosticServerCancellationData +type DiagnosticServerCancellationData struct { + RetriggerRequest bool `json:"retriggerRequest"` +} + +// The diagnostic's severity. +type DiagnosticSeverity uint32 + +// The diagnostic tags. +// +// @since 3.15.0 +type DiagnosticTag uint32 + +// Workspace client capabilities specific to diagnostic pull requests. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#diagnosticWorkspaceClientCapabilities +type DiagnosticWorkspaceClientCapabilities struct { + // Whether the client implementation supports a refresh request sent from + // the server to the client. + // + // Note that this event is global and will force the client to refresh all + // pulled diagnostics currently shown. It should be used with absolute care and + // is useful for situation where a server for example detects a project wide + // change that requires such a calculation. + RefreshSupport bool `json:"refreshSupport,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeConfigurationClientCapabilities +type DidChangeConfigurationClientCapabilities struct { + // Did change configuration notification supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// The parameters of a change configuration notification. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeConfigurationParams +type DidChangeConfigurationParams struct { + // The actual changed settings + Settings interface{} `json:"settings"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeConfigurationRegistrationOptions +type DidChangeConfigurationRegistrationOptions struct { + Section *OrPSection_workspace_didChangeConfiguration `json:"section,omitempty"` +} + +// The params sent in a change notebook document notification. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeNotebookDocumentParams +type DidChangeNotebookDocumentParams struct { + // The notebook document that did change. The version number points + // to the version after all provided changes have been applied. If + // only the text document content of a cell changes the notebook version + // doesn't necessarily have to change. + NotebookDocument VersionedNotebookDocumentIdentifier `json:"notebookDocument"` + // The actual changes to the notebook document. + // + // The changes describe single state changes to the notebook document. + // So if there are two changes c1 (at array index 0) and c2 (at array + // index 1) for a notebook in state S then c1 moves the notebook from + // S to S' and c2 from S' to S''. So c1 is computed on the state S and + // c2 is computed on the state S'. + // + // To mirror the content of a notebook using change events use the following approach: + // + // - start with the same initial content + // - apply the 'notebookDocument/didChange' notifications in the order you receive them. + // - apply the `NotebookChangeEvent`s in a single notification in the order + // you receive them. + Change NotebookDocumentChangeEvent `json:"change"` +} + +// The change text document notification's parameters. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeTextDocumentParams +type DidChangeTextDocumentParams struct { + // The document that did change. The version number points + // to the version after all provided content changes have + // been applied. + TextDocument VersionedTextDocumentIdentifier `json:"textDocument"` + // The actual content changes. The content changes describe single state changes + // to the document. So if there are two content changes c1 (at array index 0) and + // c2 (at array index 1) for a document in state S then c1 moves the document from + // S to S' and c2 from S' to S''. So c1 is computed on the state S and c2 is computed + // on the state S'. + // + // To mirror the content of a document using change events use the following approach: + // + // - start with the same initial content + // - apply the 'textDocument/didChange' notifications in the order you receive them. + // - apply the `TextDocumentContentChangeEvent`s in a single notification in the order + // you receive them. + ContentChanges []TextDocumentContentChangeEvent `json:"contentChanges"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeWatchedFilesClientCapabilities +type DidChangeWatchedFilesClientCapabilities struct { + // Did change watched files notification supports dynamic registration. Please note + // that the current protocol doesn't support static configuration for file changes + // from the server side. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Whether the client has support for {@link RelativePattern relative pattern} + // or not. + // + // @since 3.17.0 + RelativePatternSupport bool `json:"relativePatternSupport,omitempty"` +} + +// The watched files change notification's parameters. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeWatchedFilesParams +type DidChangeWatchedFilesParams struct { + // The actual file events. + Changes []FileEvent `json:"changes"` +} + +// Describe options to be used when registered for text document change events. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeWatchedFilesRegistrationOptions +type DidChangeWatchedFilesRegistrationOptions struct { + // The watchers to register. + Watchers []FileSystemWatcher `json:"watchers"` +} + +// The parameters of a `workspace/didChangeWorkspaceFolders` notification. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didChangeWorkspaceFoldersParams +type DidChangeWorkspaceFoldersParams struct { + // The actual workspace folder change event. + Event WorkspaceFoldersChangeEvent `json:"event"` +} + +// The params sent in a close notebook document notification. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didCloseNotebookDocumentParams +type DidCloseNotebookDocumentParams struct { + // The notebook document that got closed. + NotebookDocument NotebookDocumentIdentifier `json:"notebookDocument"` + // The text documents that represent the content + // of a notebook cell that got closed. + CellTextDocuments []TextDocumentIdentifier `json:"cellTextDocuments"` +} + +// The parameters sent in a close text document notification +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didCloseTextDocumentParams +type DidCloseTextDocumentParams struct { + // The document that was closed. + TextDocument TextDocumentIdentifier `json:"textDocument"` +} + +// The params sent in an open notebook document notification. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didOpenNotebookDocumentParams +type DidOpenNotebookDocumentParams struct { + // The notebook document that got opened. + NotebookDocument NotebookDocument `json:"notebookDocument"` + // The text documents that represent the content + // of a notebook cell. + CellTextDocuments []TextDocumentItem `json:"cellTextDocuments"` +} + +// The parameters sent in an open text document notification +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didOpenTextDocumentParams +type DidOpenTextDocumentParams struct { + // The document that was opened. + TextDocument TextDocumentItem `json:"textDocument"` +} + +// The params sent in a save notebook document notification. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didSaveNotebookDocumentParams +type DidSaveNotebookDocumentParams struct { + // The notebook document that got saved. + NotebookDocument NotebookDocumentIdentifier `json:"notebookDocument"` +} + +// The parameters sent in a save text document notification +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#didSaveTextDocumentParams +type DidSaveTextDocumentParams struct { + // The document that was saved. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // Optional the content when saved. Depends on the includeText value + // when the save notification was requested. + Text *string `json:"text,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentColorClientCapabilities +type DocumentColorClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is set to `true` + // the client supports the new `DocumentColorRegistrationOptions` return value + // for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentColorOptions +type DocumentColorOptions struct { + WorkDoneProgressOptions +} + +// Parameters for a {@link DocumentColorRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentColorParams +type DocumentColorParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + WorkDoneProgressParams + PartialResultParams +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentColorRegistrationOptions +type DocumentColorRegistrationOptions struct { + TextDocumentRegistrationOptions + DocumentColorOptions + StaticRegistrationOptions +} + +// Parameters of the document diagnostic request. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentDiagnosticParams +type DocumentDiagnosticParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The additional identifier provided during registration. + Identifier string `json:"identifier,omitempty"` + // The result id of a previous response if provided. + PreviousResultID string `json:"previousResultId,omitempty"` + WorkDoneProgressParams + PartialResultParams +} +type DocumentDiagnosticReport = Or_DocumentDiagnosticReport // (alias) +// The document diagnostic report kinds. +// +// @since 3.17.0 +type DocumentDiagnosticReportKind string + +// A partial result for a document diagnostic report. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentDiagnosticReportPartialResult +type DocumentDiagnosticReportPartialResult struct { + RelatedDocuments map[DocumentURI]interface{} `json:"relatedDocuments"` +} + +// A document filter describes a top level text document or +// a notebook cell document. +// +// @since 3.17.0 - proposed support for NotebookCellTextDocumentFilter. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentFilter +type DocumentFilter = Or_DocumentFilter // (alias) +// Client capabilities of a {@link DocumentFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentFormattingClientCapabilities +type DocumentFormattingClientCapabilities struct { + // Whether formatting supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// Provider options for a {@link DocumentFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentFormattingOptions +type DocumentFormattingOptions struct { + WorkDoneProgressOptions +} + +// The parameters of a {@link DocumentFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentFormattingParams +type DocumentFormattingParams struct { + // The document to format. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The format options. + Options FormattingOptions `json:"options"` + WorkDoneProgressParams +} + +// Registration options for a {@link DocumentFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentFormattingRegistrationOptions +type DocumentFormattingRegistrationOptions struct { + TextDocumentRegistrationOptions + DocumentFormattingOptions +} + +// A document highlight is a range inside a text document which deserves +// special attention. Usually a document highlight is visualized by changing +// the background color of its range. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentHighlight +type DocumentHighlight struct { + // The range this highlight applies to. + Range Range `json:"range"` + // The highlight kind, default is {@link DocumentHighlightKind.Text text}. + Kind DocumentHighlightKind `json:"kind,omitempty"` +} + +// Client Capabilities for a {@link DocumentHighlightRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentHighlightClientCapabilities +type DocumentHighlightClientCapabilities struct { + // Whether document highlight supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// A document highlight kind. +type DocumentHighlightKind uint32 + +// Provider options for a {@link DocumentHighlightRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentHighlightOptions +type DocumentHighlightOptions struct { + WorkDoneProgressOptions +} + +// Parameters for a {@link DocumentHighlightRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentHighlightParams +type DocumentHighlightParams struct { + TextDocumentPositionParams + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link DocumentHighlightRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentHighlightRegistrationOptions +type DocumentHighlightRegistrationOptions struct { + TextDocumentRegistrationOptions + DocumentHighlightOptions +} + +// A document link is a range in a text document that links to an internal or external resource, like another +// text document or a web site. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentLink +type DocumentLink struct { + // The range this link applies to. + Range Range `json:"range"` + // The uri this link points to. If missing a resolve request is sent later. + Target *URI `json:"target,omitempty"` + // The tooltip text when you hover over this link. + // + // If a tooltip is provided, is will be displayed in a string that includes instructions on how to + // trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS, + // user settings, and localization. + // + // @since 3.15.0 + Tooltip string `json:"tooltip,omitempty"` + // A data entry field that is preserved on a document link between a + // DocumentLinkRequest and a DocumentLinkResolveRequest. + Data interface{} `json:"data,omitempty"` +} + +// The client capabilities of a {@link DocumentLinkRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentLinkClientCapabilities +type DocumentLinkClientCapabilities struct { + // Whether document link supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Whether the client supports the `tooltip` property on `DocumentLink`. + // + // @since 3.15.0 + TooltipSupport bool `json:"tooltipSupport,omitempty"` +} + +// Provider options for a {@link DocumentLinkRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentLinkOptions +type DocumentLinkOptions struct { + // Document links have a resolve provider as well. + ResolveProvider bool `json:"resolveProvider,omitempty"` + WorkDoneProgressOptions +} + +// The parameters of a {@link DocumentLinkRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentLinkParams +type DocumentLinkParams struct { + // The document to provide document links for. + TextDocument TextDocumentIdentifier `json:"textDocument"` + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link DocumentLinkRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentLinkRegistrationOptions +type DocumentLinkRegistrationOptions struct { + TextDocumentRegistrationOptions + DocumentLinkOptions +} + +// Client capabilities of a {@link DocumentOnTypeFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentOnTypeFormattingClientCapabilities +type DocumentOnTypeFormattingClientCapabilities struct { + // Whether on type formatting supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// Provider options for a {@link DocumentOnTypeFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentOnTypeFormattingOptions +type DocumentOnTypeFormattingOptions struct { + // A character on which formatting should be triggered, like `{`. + FirstTriggerCharacter string `json:"firstTriggerCharacter"` + // More trigger characters. + MoreTriggerCharacter []string `json:"moreTriggerCharacter,omitempty"` +} + +// The parameters of a {@link DocumentOnTypeFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentOnTypeFormattingParams +type DocumentOnTypeFormattingParams struct { + // The document to format. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The position around which the on type formatting should happen. + // This is not necessarily the exact position where the character denoted + // by the property `ch` got typed. + Position Position `json:"position"` + // The character that has been typed that triggered the formatting + // on type request. That is not necessarily the last character that + // got inserted into the document since the client could auto insert + // characters as well (e.g. like automatic brace completion). + Ch string `json:"ch"` + // The formatting options. + Options FormattingOptions `json:"options"` +} + +// Registration options for a {@link DocumentOnTypeFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentOnTypeFormattingRegistrationOptions +type DocumentOnTypeFormattingRegistrationOptions struct { + TextDocumentRegistrationOptions + DocumentOnTypeFormattingOptions +} + +// Client capabilities of a {@link DocumentRangeFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentRangeFormattingClientCapabilities +type DocumentRangeFormattingClientCapabilities struct { + // Whether range formatting supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Whether the client supports formatting multiple ranges at once. + // + // @since 3.18.0 + // @proposed + RangesSupport bool `json:"rangesSupport,omitempty"` +} + +// Provider options for a {@link DocumentRangeFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentRangeFormattingOptions +type DocumentRangeFormattingOptions struct { + // Whether the server supports formatting multiple ranges at once. + // + // @since 3.18.0 + // @proposed + RangesSupport bool `json:"rangesSupport,omitempty"` + WorkDoneProgressOptions +} + +// The parameters of a {@link DocumentRangeFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentRangeFormattingParams +type DocumentRangeFormattingParams struct { + // The document to format. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The range to format + Range Range `json:"range"` + // The format options + Options FormattingOptions `json:"options"` + WorkDoneProgressParams +} + +// Registration options for a {@link DocumentRangeFormattingRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentRangeFormattingRegistrationOptions +type DocumentRangeFormattingRegistrationOptions struct { + TextDocumentRegistrationOptions + DocumentRangeFormattingOptions +} + +// The parameters of a {@link DocumentRangesFormattingRequest}. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentRangesFormattingParams +type DocumentRangesFormattingParams struct { + // The document to format. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The ranges to format + Ranges []Range `json:"ranges"` + // The format options + Options FormattingOptions `json:"options"` + WorkDoneProgressParams +} + +// A document selector is the combination of one or many document filters. +// +// @sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**∕tsconfig.json' }]`; +// +// The use of a string as a document filter is deprecated @since 3.16.0. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSelector +type DocumentSelector = []DocumentFilter // (alias) +// Represents programming constructs like variables, classes, interfaces etc. +// that appear in a document. Document symbols can be hierarchical and they +// have two ranges: one that encloses its definition and one that points to +// its most interesting range, e.g. the range of an identifier. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbol +type DocumentSymbol struct { + // The name of this symbol. Will be displayed in the user interface and therefore must not be + // an empty string or a string only consisting of white spaces. + Name string `json:"name"` + // More detail for this symbol, e.g the signature of a function. + Detail string `json:"detail,omitempty"` + // The kind of this symbol. + Kind SymbolKind `json:"kind"` + // Tags for this document symbol. + // + // @since 3.16.0 + Tags []SymbolTag `json:"tags,omitempty"` + // Indicates if this symbol is deprecated. + // + // @deprecated Use tags instead + Deprecated bool `json:"deprecated,omitempty"` + // The range enclosing this symbol not including leading/trailing whitespace but everything else + // like comments. This information is typically used to determine if the clients cursor is + // inside the symbol to reveal in the symbol in the UI. + Range Range `json:"range"` + // The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. + // Must be contained by the `range`. + SelectionRange Range `json:"selectionRange"` + // Children of this symbol, e.g. properties of a class. + Children []DocumentSymbol `json:"children,omitempty"` +} + +// Client Capabilities for a {@link DocumentSymbolRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbolClientCapabilities +type DocumentSymbolClientCapabilities struct { + // Whether document symbol supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Specific capabilities for the `SymbolKind` in the + // `textDocument/documentSymbol` request. + SymbolKind *ClientSymbolKindOptions `json:"symbolKind,omitempty"` + // The client supports hierarchical document symbols. + HierarchicalDocumentSymbolSupport bool `json:"hierarchicalDocumentSymbolSupport,omitempty"` + // The client supports tags on `SymbolInformation`. Tags are supported on + // `DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true. + // Clients supporting tags have to handle unknown tags gracefully. + // + // @since 3.16.0 + TagSupport *ClientSymbolTagOptions `json:"tagSupport,omitempty"` + // The client supports an additional label presented in the UI when + // registering a document symbol provider. + // + // @since 3.16.0 + LabelSupport bool `json:"labelSupport,omitempty"` +} + +// Provider options for a {@link DocumentSymbolRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbolOptions +type DocumentSymbolOptions struct { + // A human-readable string that is shown when multiple outlines trees + // are shown for the same document. + // + // @since 3.16.0 + Label string `json:"label,omitempty"` + WorkDoneProgressOptions +} + +// Parameters for a {@link DocumentSymbolRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbolParams +type DocumentSymbolParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link DocumentSymbolRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbolRegistrationOptions +type DocumentSymbolRegistrationOptions struct { + TextDocumentRegistrationOptions + DocumentSymbolOptions +} + +// Edit range variant that includes ranges for insert and replace operations. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#editRangeWithInsertReplace +type EditRangeWithInsertReplace struct { + Insert Range `json:"insert"` + Replace Range `json:"replace"` +} + +// Predefined error codes. +type ErrorCodes int32 + +// The client capabilities of a {@link ExecuteCommandRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#executeCommandClientCapabilities +type ExecuteCommandClientCapabilities struct { + // Execute command supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// The server capabilities of a {@link ExecuteCommandRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#executeCommandOptions +type ExecuteCommandOptions struct { + // The commands to be executed on the server + Commands []string `json:"commands"` + WorkDoneProgressOptions +} + +// The parameters of a {@link ExecuteCommandRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#executeCommandParams +type ExecuteCommandParams struct { + // The identifier of the actual command handler. + Command string `json:"command"` + // Arguments that the command should be invoked with. + Arguments []json.RawMessage `json:"arguments,omitempty"` + WorkDoneProgressParams +} + +// Registration options for a {@link ExecuteCommandRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#executeCommandRegistrationOptions +type ExecuteCommandRegistrationOptions struct { + ExecuteCommandOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#executionSummary +type ExecutionSummary struct { + // A strict monotonically increasing value + // indicating the execution order of a cell + // inside a notebook. + ExecutionOrder uint32 `json:"executionOrder"` + // Whether the execution was successful or + // not if known by the client. + Success bool `json:"success,omitempty"` +} +type FailureHandlingKind string + +// The file event type +type FileChangeType uint32 + +// Represents information on a file/folder create. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileCreate +type FileCreate struct { + // A file:// URI for the location of the file/folder being created. + URI string `json:"uri"` +} + +// Represents information on a file/folder delete. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileDelete +type FileDelete struct { + // A file:// URI for the location of the file/folder being deleted. + URI string `json:"uri"` +} + +// An event describing a file change. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileEvent +type FileEvent struct { + // The file's uri. + URI DocumentURI `json:"uri"` + // The change type. + Type FileChangeType `json:"type"` +} + +// Capabilities relating to events from file operations by the user in the client. +// +// These events do not come from the file system, they come from user operations +// like renaming a file in the UI. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileOperationClientCapabilities +type FileOperationClientCapabilities struct { + // Whether the client supports dynamic registration for file requests/notifications. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client has support for sending didCreateFiles notifications. + DidCreate bool `json:"didCreate,omitempty"` + // The client has support for sending willCreateFiles requests. + WillCreate bool `json:"willCreate,omitempty"` + // The client has support for sending didRenameFiles notifications. + DidRename bool `json:"didRename,omitempty"` + // The client has support for sending willRenameFiles requests. + WillRename bool `json:"willRename,omitempty"` + // The client has support for sending didDeleteFiles notifications. + DidDelete bool `json:"didDelete,omitempty"` + // The client has support for sending willDeleteFiles requests. + WillDelete bool `json:"willDelete,omitempty"` +} + +// A filter to describe in which file operation requests or notifications +// the server is interested in receiving. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileOperationFilter +type FileOperationFilter struct { + // A Uri scheme like `file` or `untitled`. + Scheme string `json:"scheme,omitempty"` + // The actual file operation pattern. + Pattern FileOperationPattern `json:"pattern"` +} + +// Options for notifications/requests for user operations on files. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileOperationOptions +type FileOperationOptions struct { + // The server is interested in receiving didCreateFiles notifications. + DidCreate *FileOperationRegistrationOptions `json:"didCreate,omitempty"` + // The server is interested in receiving willCreateFiles requests. + WillCreate *FileOperationRegistrationOptions `json:"willCreate,omitempty"` + // The server is interested in receiving didRenameFiles notifications. + DidRename *FileOperationRegistrationOptions `json:"didRename,omitempty"` + // The server is interested in receiving willRenameFiles requests. + WillRename *FileOperationRegistrationOptions `json:"willRename,omitempty"` + // The server is interested in receiving didDeleteFiles file notifications. + DidDelete *FileOperationRegistrationOptions `json:"didDelete,omitempty"` + // The server is interested in receiving willDeleteFiles file requests. + WillDelete *FileOperationRegistrationOptions `json:"willDelete,omitempty"` +} + +// A pattern to describe in which file operation requests or notifications +// the server is interested in receiving. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileOperationPattern +type FileOperationPattern struct { + // The glob pattern to match. Glob patterns can have the following syntax: + // + // - `*` to match one or more characters in a path segment + // - `?` to match on one character in a path segment + // - `**` to match any number of path segments, including none + // - `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) + // - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) + // - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) + Glob string `json:"glob"` + // Whether to match files or folders with this pattern. + // + // Matches both if undefined. + Matches *FileOperationPatternKind `json:"matches,omitempty"` + // Additional options used during matching. + Options *FileOperationPatternOptions `json:"options,omitempty"` +} + +// A pattern kind describing if a glob pattern matches a file a folder or +// both. +// +// @since 3.16.0 +type FileOperationPatternKind string + +// Matching options for the file operation pattern. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileOperationPatternOptions +type FileOperationPatternOptions struct { + // The pattern should be matched ignoring casing. + IgnoreCase bool `json:"ignoreCase,omitempty"` +} + +// The options to register for file operations. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileOperationRegistrationOptions +type FileOperationRegistrationOptions struct { + // The actual filters. + Filters []FileOperationFilter `json:"filters"` +} + +// Represents information on a file/folder rename. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileRename +type FileRename struct { + // A file:// URI for the original location of the file/folder being renamed. + OldURI string `json:"oldUri"` + // A file:// URI for the new location of the file/folder being renamed. + NewURI string `json:"newUri"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fileSystemWatcher +type FileSystemWatcher struct { + // The glob pattern to watch. See {@link GlobPattern glob pattern} for more detail. + // + // @since 3.17.0 support for relative patterns. + GlobPattern GlobPattern `json:"globPattern"` + // The kind of events of interest. If omitted it defaults + // to WatchKind.Create | WatchKind.Change | WatchKind.Delete + // which is 7. + Kind *WatchKind `json:"kind,omitempty"` +} + +// Represents a folding range. To be valid, start and end line must be bigger than zero and smaller +// than the number of lines in the document. Clients are free to ignore invalid ranges. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#foldingRange +type FoldingRange struct { + // The zero-based start line of the range to fold. The folded area starts after the line's last character. + // To be valid, the end must be zero or larger and smaller than the number of lines in the document. + StartLine uint32 `json:"startLine"` + // The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. + StartCharacter uint32 `json:"startCharacter,omitempty"` + // The zero-based end line of the range to fold. The folded area ends with the line's last character. + // To be valid, the end must be zero or larger and smaller than the number of lines in the document. + EndLine uint32 `json:"endLine"` + // The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. + EndCharacter uint32 `json:"endCharacter,omitempty"` + // Describes the kind of the folding range such as 'comment' or 'region'. The kind + // is used to categorize folding ranges and used by commands like 'Fold all comments'. + // See {@link FoldingRangeKind} for an enumeration of standardized kinds. + Kind string `json:"kind,omitempty"` + // The text that the client should show when the specified range is + // collapsed. If not defined or not supported by the client, a default + // will be chosen by the client. + // + // @since 3.17.0 + CollapsedText string `json:"collapsedText,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#foldingRangeClientCapabilities +type FoldingRangeClientCapabilities struct { + // Whether implementation supports dynamic registration for folding range + // providers. If this is set to `true` the client supports the new + // `FoldingRangeRegistrationOptions` return value for the corresponding + // server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The maximum number of folding ranges that the client prefers to receive + // per document. The value serves as a hint, servers are free to follow the + // limit. + RangeLimit uint32 `json:"rangeLimit,omitempty"` + // If set, the client signals that it only supports folding complete lines. + // If set, client will ignore specified `startCharacter` and `endCharacter` + // properties in a FoldingRange. + LineFoldingOnly bool `json:"lineFoldingOnly,omitempty"` + // Specific options for the folding range kind. + // + // @since 3.17.0 + FoldingRangeKind *ClientFoldingRangeKindOptions `json:"foldingRangeKind,omitempty"` + // Specific options for the folding range. + // + // @since 3.17.0 + FoldingRange *ClientFoldingRangeOptions `json:"foldingRange,omitempty"` +} + +// A set of predefined range kinds. +type FoldingRangeKind string + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#foldingRangeOptions +type FoldingRangeOptions struct { + WorkDoneProgressOptions +} + +// Parameters for a {@link FoldingRangeRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#foldingRangeParams +type FoldingRangeParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + WorkDoneProgressParams + PartialResultParams +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#foldingRangeRegistrationOptions +type FoldingRangeRegistrationOptions struct { + TextDocumentRegistrationOptions + FoldingRangeOptions + StaticRegistrationOptions +} + +// Client workspace capabilities specific to folding ranges +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#foldingRangeWorkspaceClientCapabilities +type FoldingRangeWorkspaceClientCapabilities struct { + // Whether the client implementation supports a refresh request sent from the + // server to the client. + // + // Note that this event is global and will force the client to refresh all + // folding ranges currently shown. It should be used with absolute care and is + // useful for situation where a server for example detects a project wide + // change that requires such a calculation. + // + // @since 3.18.0 + // @proposed + RefreshSupport bool `json:"refreshSupport,omitempty"` +} + +// Value-object describing what options formatting should use. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#formattingOptions +type FormattingOptions struct { + // Size of a tab in spaces. + TabSize uint32 `json:"tabSize"` + // Prefer spaces over tabs. + InsertSpaces bool `json:"insertSpaces"` + // Trim trailing whitespace on a line. + // + // @since 3.15.0 + TrimTrailingWhitespace bool `json:"trimTrailingWhitespace,omitempty"` + // Insert a newline character at the end of the file if one does not exist. + // + // @since 3.15.0 + InsertFinalNewline bool `json:"insertFinalNewline,omitempty"` + // Trim all newlines after the final newline at the end of the file. + // + // @since 3.15.0 + TrimFinalNewlines bool `json:"trimFinalNewlines,omitempty"` +} + +// A diagnostic report with a full set of problems. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#fullDocumentDiagnosticReport +type FullDocumentDiagnosticReport struct { + // A full document diagnostic report. + Kind string `json:"kind"` + // An optional result id. If provided it will + // be sent on the next diagnostic request for the + // same document. + ResultID string `json:"resultId,omitempty"` + // The actual items. + Items []Diagnostic `json:"items"` +} + +// General client capabilities. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#generalClientCapabilities +type GeneralClientCapabilities struct { + // Client capability that signals how the client + // handles stale requests (e.g. a request + // for which the client will not process the response + // anymore since the information is outdated). + // + // @since 3.17.0 + StaleRequestSupport *StaleRequestSupportOptions `json:"staleRequestSupport,omitempty"` + // Client capabilities specific to regular expressions. + // + // @since 3.16.0 + RegularExpressions *RegularExpressionsClientCapabilities `json:"regularExpressions,omitempty"` + // Client capabilities specific to the client's markdown parser. + // + // @since 3.16.0 + Markdown *MarkdownClientCapabilities `json:"markdown,omitempty"` + // The position encodings supported by the client. Client and server + // have to agree on the same position encoding to ensure that offsets + // (e.g. character position in a line) are interpreted the same on both + // sides. + // + // To keep the protocol backwards compatible the following applies: if + // the value 'utf-16' is missing from the array of position encodings + // servers can assume that the client supports UTF-16. UTF-16 is + // therefore a mandatory encoding. + // + // If omitted it defaults to ['utf-16']. + // + // Implementation considerations: since the conversion from one encoding + // into another requires the content of the file / line the conversion + // is best done where the file is read which is usually on the server + // side. + // + // @since 3.17.0 + PositionEncodings []PositionEncodingKind `json:"positionEncodings,omitempty"` +} + +// The glob pattern. Either a string pattern or a relative pattern. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#globPattern +type GlobPattern = Or_GlobPattern // (alias) +// The result of a hover request. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#hover +type Hover struct { + // The hover's content + Contents MarkupContent `json:"contents"` + // An optional range inside the text document that is used to + // visualize the hover, e.g. by changing the background color. + Range Range `json:"range,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#hoverClientCapabilities +type HoverClientCapabilities struct { + // Whether hover supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Client supports the following content formats for the content + // property. The order describes the preferred format of the client. + ContentFormat []MarkupKind `json:"contentFormat,omitempty"` +} + +// Hover options. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#hoverOptions +type HoverOptions struct { + WorkDoneProgressOptions +} + +// Parameters for a {@link HoverRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#hoverParams +type HoverParams struct { + TextDocumentPositionParams + WorkDoneProgressParams +} + +// Registration options for a {@link HoverRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#hoverRegistrationOptions +type HoverRegistrationOptions struct { + TextDocumentRegistrationOptions + HoverOptions +} + +// @since 3.6.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#implementationClientCapabilities +type ImplementationClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is set to `true` + // the client supports the new `ImplementationRegistrationOptions` return value + // for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client supports additional metadata in the form of definition links. + // + // @since 3.14.0 + LinkSupport bool `json:"linkSupport,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#implementationOptions +type ImplementationOptions struct { + WorkDoneProgressOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#implementationParams +type ImplementationParams struct { + TextDocumentPositionParams + WorkDoneProgressParams + PartialResultParams +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#implementationRegistrationOptions +type ImplementationRegistrationOptions struct { + TextDocumentRegistrationOptions + ImplementationOptions + StaticRegistrationOptions +} + +// The data type of the ResponseError if the +// initialize request fails. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#initializeError +type InitializeError struct { + // Indicates whether the client execute the following retry logic: + // (1) show the message provided by the ResponseError to the user + // (2) user selects retry or cancel + // (3) if user selected retry the initialize method is sent again. + Retry bool `json:"retry"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#initializeParams +type InitializeParams struct { + XInitializeParams + WorkspaceFoldersInitializeParams +} + +// The result returned from an initialize request. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#initializeResult +type InitializeResult struct { + // The capabilities the language server provides. + Capabilities ServerCapabilities `json:"capabilities"` + // Information about the server. + // + // @since 3.15.0 + ServerInfo *ServerInfo `json:"serverInfo,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#initializedParams +type InitializedParams struct { +} + +// Inlay hint information. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlayHint +type InlayHint struct { + // The position of this hint. + // + // If multiple hints have the same position, they will be shown in the order + // they appear in the response. + Position Position `json:"position"` + // The label of this hint. A human readable string or an array of + // InlayHintLabelPart label parts. + // + // *Note* that neither the string nor the label part can be empty. + Label []InlayHintLabelPart `json:"label"` + // The kind of this hint. Can be omitted in which case the client + // should fall back to a reasonable default. + Kind InlayHintKind `json:"kind,omitempty"` + // Optional text edits that are performed when accepting this inlay hint. + // + // *Note* that edits are expected to change the document so that the inlay + // hint (or its nearest variant) is now part of the document and the inlay + // hint itself is now obsolete. + TextEdits []TextEdit `json:"textEdits,omitempty"` + // The tooltip text when you hover over this item. + Tooltip *OrPTooltip_textDocument_inlayHint `json:"tooltip,omitempty"` + // Render padding before the hint. + // + // Note: Padding should use the editor's background color, not the + // background color of the hint itself. That means padding can be used + // to visually align/separate an inlay hint. + PaddingLeft bool `json:"paddingLeft,omitempty"` + // Render padding after the hint. + // + // Note: Padding should use the editor's background color, not the + // background color of the hint itself. That means padding can be used + // to visually align/separate an inlay hint. + PaddingRight bool `json:"paddingRight,omitempty"` + // A data entry field that is preserved on an inlay hint between + // a `textDocument/inlayHint` and a `inlayHint/resolve` request. + Data interface{} `json:"data,omitempty"` +} + +// Inlay hint client capabilities. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlayHintClientCapabilities +type InlayHintClientCapabilities struct { + // Whether inlay hints support dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Indicates which properties a client can resolve lazily on an inlay + // hint. + ResolveSupport *ClientInlayHintResolveOptions `json:"resolveSupport,omitempty"` +} + +// Inlay hint kinds. +// +// @since 3.17.0 +type InlayHintKind uint32 + +// An inlay hint label part allows for interactive and composite labels +// of inlay hints. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlayHintLabelPart +type InlayHintLabelPart struct { + // The value of this label part. + Value string `json:"value"` + // The tooltip text when you hover over this label part. Depending on + // the client capability `inlayHint.resolveSupport` clients might resolve + // this property late using the resolve request. + Tooltip *OrPTooltipPLabel `json:"tooltip,omitempty"` + // An optional source code location that represents this + // label part. + // + // The editor will use this location for the hover and for code navigation + // features: This part will become a clickable link that resolves to the + // definition of the symbol at the given location (not necessarily the + // location itself), it shows the hover that shows at the given location, + // and it shows a context menu with further code navigation commands. + // + // Depending on the client capability `inlayHint.resolveSupport` clients + // might resolve this property late using the resolve request. + Location *Location `json:"location,omitempty"` + // An optional command for this label part. + // + // Depending on the client capability `inlayHint.resolveSupport` clients + // might resolve this property late using the resolve request. + Command *Command `json:"command,omitempty"` +} + +// Inlay hint options used during static registration. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlayHintOptions +type InlayHintOptions struct { + // The server provides support to resolve additional + // information for an inlay hint item. + ResolveProvider bool `json:"resolveProvider,omitempty"` + WorkDoneProgressOptions +} + +// A parameter literal used in inlay hint requests. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlayHintParams +type InlayHintParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The document range for which inlay hints should be computed. + Range Range `json:"range"` + WorkDoneProgressParams +} + +// Inlay hint options used during static or dynamic registration. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlayHintRegistrationOptions +type InlayHintRegistrationOptions struct { + InlayHintOptions + TextDocumentRegistrationOptions + StaticRegistrationOptions +} + +// Client workspace capabilities specific to inlay hints. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlayHintWorkspaceClientCapabilities +type InlayHintWorkspaceClientCapabilities struct { + // Whether the client implementation supports a refresh request sent from + // the server to the client. + // + // Note that this event is global and will force the client to refresh all + // inlay hints currently shown. It should be used with absolute care and + // is useful for situation where a server for example detects a project wide + // change that requires such a calculation. + RefreshSupport bool `json:"refreshSupport,omitempty"` +} + +// Client capabilities specific to inline completions. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineCompletionClientCapabilities +type InlineCompletionClientCapabilities struct { + // Whether implementation supports dynamic registration for inline completion providers. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// Provides information about the context in which an inline completion was requested. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineCompletionContext +type InlineCompletionContext struct { + // Describes how the inline completion was triggered. + TriggerKind InlineCompletionTriggerKind `json:"triggerKind"` + // Provides information about the currently selected item in the autocomplete widget if it is visible. + SelectedCompletionInfo *SelectedCompletionInfo `json:"selectedCompletionInfo,omitempty"` +} + +// An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineCompletionItem +type InlineCompletionItem struct { + // The text to replace the range with. Must be set. + InsertText Or_InlineCompletionItem_insertText `json:"insertText"` + // A text that is used to decide if this inline completion should be shown. When `falsy` the {@link InlineCompletionItem.insertText} is used. + FilterText string `json:"filterText,omitempty"` + // The range to replace. Must begin and end on the same line. + Range *Range `json:"range,omitempty"` + // An optional {@link Command} that is executed *after* inserting this completion. + Command *Command `json:"command,omitempty"` +} + +// Represents a collection of {@link InlineCompletionItem inline completion items} to be presented in the editor. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineCompletionList +type InlineCompletionList struct { + // The inline completion items + Items []InlineCompletionItem `json:"items"` +} + +// Inline completion options used during static registration. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineCompletionOptions +type InlineCompletionOptions struct { + WorkDoneProgressOptions +} + +// A parameter literal used in inline completion requests. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineCompletionParams +type InlineCompletionParams struct { + // Additional information about the context in which inline completions were + // requested. + Context InlineCompletionContext `json:"context"` + TextDocumentPositionParams + WorkDoneProgressParams +} + +// Inline completion options used during static or dynamic registration. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineCompletionRegistrationOptions +type InlineCompletionRegistrationOptions struct { + InlineCompletionOptions + TextDocumentRegistrationOptions + StaticRegistrationOptions +} + +// Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. +// +// @since 3.18.0 +// @proposed +type InlineCompletionTriggerKind uint32 + +// Inline value information can be provided by different means: +// +// - directly as a text value (class InlineValueText). +// - as a name to use for a variable lookup (class InlineValueVariableLookup) +// - as an evaluatable expression (class InlineValueEvaluatableExpression) +// +// The InlineValue types combines all inline value types into one type. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValue +type InlineValue = Or_InlineValue // (alias) +// Client capabilities specific to inline values. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueClientCapabilities +type InlineValueClientCapabilities struct { + // Whether implementation supports dynamic registration for inline value providers. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueContext +type InlineValueContext struct { + // The stack frame (as a DAP Id) where the execution has stopped. + FrameID int32 `json:"frameId"` + // The document range where execution has stopped. + // Typically the end position of the range denotes the line where the inline values are shown. + StoppedLocation Range `json:"stoppedLocation"` +} + +// Provide an inline value through an expression evaluation. +// If only a range is specified, the expression will be extracted from the underlying document. +// An optional expression can be used to override the extracted expression. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueEvaluatableExpression +type InlineValueEvaluatableExpression struct { + // The document range for which the inline value applies. + // The range is used to extract the evaluatable expression from the underlying document. + Range Range `json:"range"` + // If specified the expression overrides the extracted expression. + Expression string `json:"expression,omitempty"` +} + +// Inline value options used during static registration. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueOptions +type InlineValueOptions struct { + WorkDoneProgressOptions +} + +// A parameter literal used in inline value requests. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueParams +type InlineValueParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The document range for which inline values should be computed. + Range Range `json:"range"` + // Additional information about the context in which inline values were + // requested. + Context InlineValueContext `json:"context"` + WorkDoneProgressParams +} + +// Inline value options used during static or dynamic registration. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueRegistrationOptions +type InlineValueRegistrationOptions struct { + InlineValueOptions + TextDocumentRegistrationOptions + StaticRegistrationOptions +} + +// Provide inline value as text. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueText +type InlineValueText struct { + // The document range for which the inline value applies. + Range Range `json:"range"` + // The text of the inline value. + Text string `json:"text"` +} + +// Provide inline value through a variable lookup. +// If only a range is specified, the variable name will be extracted from the underlying document. +// An optional variable name can be used to override the extracted name. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueVariableLookup +type InlineValueVariableLookup struct { + // The document range for which the inline value applies. + // The range is used to extract the variable name from the underlying document. + Range Range `json:"range"` + // If specified the name of the variable to look up. + VariableName string `json:"variableName,omitempty"` + // How to perform the lookup. + CaseSensitiveLookup bool `json:"caseSensitiveLookup"` +} + +// Client workspace capabilities specific to inline values. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlineValueWorkspaceClientCapabilities +type InlineValueWorkspaceClientCapabilities struct { + // Whether the client implementation supports a refresh request sent from the + // server to the client. + // + // Note that this event is global and will force the client to refresh all + // inline values currently shown. It should be used with absolute care and is + // useful for situation where a server for example detects a project wide + // change that requires such a calculation. + RefreshSupport bool `json:"refreshSupport,omitempty"` +} + +// A special text edit to provide an insert and a replace operation. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#insertReplaceEdit +type InsertReplaceEdit struct { + // The string to be inserted. + NewText string `json:"newText"` + // The range if the insert is requested + Insert Range `json:"insert"` + // The range if the replace is requested. + Replace Range `json:"replace"` +} + +// Defines whether the insert text in a completion item should be interpreted as +// plain text or a snippet. +type InsertTextFormat uint32 + +// How whitespace and indentation is handled during completion +// item insertion. +// +// @since 3.16.0 +type InsertTextMode uint32 +type LSPAny = interface{} + +// LSP arrays. +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#lSPArray +type LSPArray = []interface{} // (alias) +type LSPErrorCodes int32 + +// LSP object definition. +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#lSPObject +type LSPObject = map[string]LSPAny // (alias) +// Predefined Language kinds +// @since 3.18.0 +// @proposed +type LanguageKind string + +// Client capabilities for the linked editing range request. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#linkedEditingRangeClientCapabilities +type LinkedEditingRangeClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is set to `true` + // the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + // return value for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#linkedEditingRangeOptions +type LinkedEditingRangeOptions struct { + WorkDoneProgressOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#linkedEditingRangeParams +type LinkedEditingRangeParams struct { + TextDocumentPositionParams + WorkDoneProgressParams +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#linkedEditingRangeRegistrationOptions +type LinkedEditingRangeRegistrationOptions struct { + TextDocumentRegistrationOptions + LinkedEditingRangeOptions + StaticRegistrationOptions +} + +// The result of a linked editing range request. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#linkedEditingRanges +type LinkedEditingRanges struct { + // A list of ranges that can be edited together. The ranges must have + // identical length and contain identical text content. The ranges cannot overlap. + Ranges []Range `json:"ranges"` + // An optional word pattern (regular expression) that describes valid contents for + // the given ranges. If no pattern is provided, the client configuration's word + // pattern will be used. + WordPattern string `json:"wordPattern,omitempty"` +} + +// created for Literal (Lit_ClientSemanticTokensRequestOptions_range_Item1) +type Lit_ClientSemanticTokensRequestOptions_range_Item1 struct { +} + +// Represents a location inside a resource, such as a line +// inside a text file. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#location +type Location struct { + URI DocumentURI `json:"uri"` + Range Range `json:"range"` +} + +// Represents the connection of two locations. Provides additional metadata over normal {@link Location locations}, +// including an origin range. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#locationLink +type LocationLink struct { + // Span of the origin of this link. + // + // Used as the underlined span for mouse interaction. Defaults to the word range at + // the definition position. + OriginSelectionRange *Range `json:"originSelectionRange,omitempty"` + // The target resource identifier of this link. + TargetURI DocumentURI `json:"targetUri"` + // The full target range of this link. If the target for example is a symbol then target range is the + // range enclosing this symbol not including leading/trailing whitespace but everything else + // like comments. This information is typically used to highlight the range in the editor. + TargetRange Range `json:"targetRange"` + // The range that should be selected and revealed when this link is being followed, e.g the name of a function. + // Must be contained by the `targetRange`. See also `DocumentSymbol#range` + TargetSelectionRange Range `json:"targetSelectionRange"` +} + +// Location with only uri and does not include range. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#locationUriOnly +type LocationUriOnly struct { + URI DocumentURI `json:"uri"` +} + +// The log message parameters. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#logMessageParams +type LogMessageParams struct { + // The message type. See {@link MessageType} + Type MessageType `json:"type"` + // The actual message. + Message string `json:"message"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#logTraceParams +type LogTraceParams struct { + Message string `json:"message"` + Verbose string `json:"verbose,omitempty"` +} + +// Client capabilities specific to the used markdown parser. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#markdownClientCapabilities +type MarkdownClientCapabilities struct { + // The name of the parser. + Parser string `json:"parser"` + // The version of the parser. + Version string `json:"version,omitempty"` + // A list of HTML tags that the client allows / supports in + // Markdown. + // + // @since 3.17.0 + AllowedTags []string `json:"allowedTags,omitempty"` +} + +// MarkedString can be used to render human readable text. It is either a markdown string +// or a code-block that provides a language and a code snippet. The language identifier +// is semantically equal to the optional language identifier in fenced code blocks in GitHub +// issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting +// +// The pair of a language and a value is an equivalent to markdown: +// ```${language} +// ${value} +// ``` +// +// Note that markdown strings will be sanitized - that means html will be escaped. +// @deprecated use MarkupContent instead. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#markedString +type MarkedString = Or_MarkedString // (alias) +// @since 3.18.0 +// @proposed +// @deprecated use MarkupContent instead. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#markedStringWithLanguage +type MarkedStringWithLanguage struct { + Language string `json:"language"` + Value string `json:"value"` +} + +// A `MarkupContent` literal represents a string value which content is interpreted base on its +// kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. +// +// If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. +// See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting +// +// Here is an example how such a string can be constructed using JavaScript / TypeScript: +// ```ts +// +// let markdown: MarkdownContent = { +// kind: MarkupKind.Markdown, +// value: [ +// '# Header', +// 'Some text', +// '```typescript', +// 'someCode();', +// '```' +// ].join('\n') +// }; +// +// ``` +// +// *Please Note* that clients might sanitize the return markdown. A client could decide to +// remove HTML from the markdown to avoid script execution. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#markupContent +type MarkupContent struct { + // The type of the Markup + Kind MarkupKind `json:"kind"` + // The content itself + Value string `json:"value"` +} + +// Describes the content type that a client supports in various +// result literals like `Hover`, `ParameterInfo` or `CompletionItem`. +// +// Please note that `MarkupKinds` must not start with a `$`. This kinds +// are reserved for internal usage. +type MarkupKind string + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#messageActionItem +type MessageActionItem struct { + // A short title like 'Retry', 'Open Log' etc. + Title string `json:"title"` +} + +// The message type +type MessageType uint32 + +// Moniker definition to match LSIF 0.5 moniker definition. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#moniker +type Moniker struct { + // The scheme of the moniker. For example tsc or .Net + Scheme string `json:"scheme"` + // The identifier of the moniker. The value is opaque in LSIF however + // schema owners are allowed to define the structure if they want. + Identifier string `json:"identifier"` + // The scope in which the moniker is unique + Unique UniquenessLevel `json:"unique"` + // The moniker kind if known. + Kind *MonikerKind `json:"kind,omitempty"` +} + +// Client capabilities specific to the moniker request. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#monikerClientCapabilities +type MonikerClientCapabilities struct { + // Whether moniker supports dynamic registration. If this is set to `true` + // the client supports the new `MonikerRegistrationOptions` return value + // for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// The moniker kind. +// +// @since 3.16.0 +type MonikerKind string + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#monikerOptions +type MonikerOptions struct { + WorkDoneProgressOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#monikerParams +type MonikerParams struct { + TextDocumentPositionParams + WorkDoneProgressParams + PartialResultParams +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#monikerRegistrationOptions +type MonikerRegistrationOptions struct { + TextDocumentRegistrationOptions + MonikerOptions +} + +// A notebook cell. +// +// A cell's document URI must be unique across ALL notebook +// cells and can therefore be used to uniquely identify a +// notebook cell or the cell's text document. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookCell +type NotebookCell struct { + // The cell's kind + Kind NotebookCellKind `json:"kind"` + // The URI of the cell's text document + // content. + Document DocumentURI `json:"document"` + // Additional metadata stored with the cell. + // + // Note: should always be an object literal (e.g. LSPObject) + Metadata *LSPObject `json:"metadata,omitempty"` + // Additional execution summary information + // if supported by the client. + ExecutionSummary *ExecutionSummary `json:"executionSummary,omitempty"` +} + +// A change describing how to move a `NotebookCell` +// array from state S to S'. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookCellArrayChange +type NotebookCellArrayChange struct { + // The start oftest of the cell that changed. + Start uint32 `json:"start"` + // The deleted cells + DeleteCount uint32 `json:"deleteCount"` + // The new cells, if any + Cells []NotebookCell `json:"cells,omitempty"` +} + +// A notebook cell kind. +// +// @since 3.17.0 +type NotebookCellKind uint32 + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookCellLanguage +type NotebookCellLanguage struct { + Language string `json:"language"` +} + +// A notebook cell text document filter denotes a cell text +// document by different properties. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookCellTextDocumentFilter +type NotebookCellTextDocumentFilter struct { + // A filter that matches against the notebook + // containing the notebook cell. If a string + // value is provided it matches against the + // notebook type. '*' matches every notebook. + Notebook Or_NotebookCellTextDocumentFilter_notebook `json:"notebook"` + // A language id like `python`. + // + // Will be matched against the language id of the + // notebook cell document. '*' matches every language. + Language string `json:"language,omitempty"` +} + +// A notebook document. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocument +type NotebookDocument struct { + // The notebook document's uri. + URI URI `json:"uri"` + // The type of the notebook. + NotebookType string `json:"notebookType"` + // The version number of this document (it will increase after each + // change, including undo/redo). + Version int32 `json:"version"` + // Additional metadata stored with the notebook + // document. + // + // Note: should always be an object literal (e.g. LSPObject) + Metadata *LSPObject `json:"metadata,omitempty"` + // The cells of a notebook. + Cells []NotebookCell `json:"cells"` +} + +// Structural changes to cells in a notebook document. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentCellChangeStructure +type NotebookDocumentCellChangeStructure struct { + // The change to the cell array. + Array NotebookCellArrayChange `json:"array"` + // Additional opened cell text documents. + DidOpen []TextDocumentItem `json:"didOpen,omitempty"` + // Additional closed cell text documents. + DidClose []TextDocumentIdentifier `json:"didClose,omitempty"` +} + +// Cell changes to a notebook document. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentCellChanges +type NotebookDocumentCellChanges struct { + // Changes to the cell structure to add or + // remove cells. + Structure *NotebookDocumentCellChangeStructure `json:"structure,omitempty"` + // Changes to notebook cells properties like its + // kind, execution summary or metadata. + Data []NotebookCell `json:"data,omitempty"` + // Changes to the text content of notebook cells. + TextContent []NotebookDocumentCellContentChanges `json:"textContent,omitempty"` +} + +// Content changes to a cell in a notebook document. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentCellContentChanges +type NotebookDocumentCellContentChanges struct { + Document VersionedTextDocumentIdentifier `json:"document"` + Changes []TextDocumentContentChangeEvent `json:"changes"` +} + +// A change event for a notebook document. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentChangeEvent +type NotebookDocumentChangeEvent struct { + // The changed meta data if any. + // + // Note: should always be an object literal (e.g. LSPObject) + Metadata *LSPObject `json:"metadata,omitempty"` + // Changes to cells + Cells *NotebookDocumentCellChanges `json:"cells,omitempty"` +} + +// Capabilities specific to the notebook document support. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentClientCapabilities +type NotebookDocumentClientCapabilities struct { + // Capabilities specific to notebook document synchronization + // + // @since 3.17.0 + Synchronization NotebookDocumentSyncClientCapabilities `json:"synchronization"` +} + +// A notebook document filter denotes a notebook document by +// different properties. The properties will be match +// against the notebook's URI (same as with documents) +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentFilter +type NotebookDocumentFilter = Or_NotebookDocumentFilter // (alias) +// A notebook document filter where `notebookType` is required field. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentFilterNotebookType +type NotebookDocumentFilterNotebookType struct { + // The type of the enclosing notebook. + NotebookType string `json:"notebookType"` + // A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + Scheme string `json:"scheme,omitempty"` + // A glob pattern. + Pattern string `json:"pattern,omitempty"` +} + +// A notebook document filter where `pattern` is required field. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentFilterPattern +type NotebookDocumentFilterPattern struct { + // The type of the enclosing notebook. + NotebookType string `json:"notebookType,omitempty"` + // A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + Scheme string `json:"scheme,omitempty"` + // A glob pattern. + Pattern string `json:"pattern"` +} + +// A notebook document filter where `scheme` is required field. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentFilterScheme +type NotebookDocumentFilterScheme struct { + // The type of the enclosing notebook. + NotebookType string `json:"notebookType,omitempty"` + // A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + Scheme string `json:"scheme"` + // A glob pattern. + Pattern string `json:"pattern,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentFilterWithCells +type NotebookDocumentFilterWithCells struct { + // The notebook to be synced If a string + // value is provided it matches against the + // notebook type. '*' matches every notebook. + Notebook *Or_NotebookDocumentFilterWithCells_notebook `json:"notebook,omitempty"` + // The cells of the matching notebook to be synced. + Cells []NotebookCellLanguage `json:"cells"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentFilterWithNotebook +type NotebookDocumentFilterWithNotebook struct { + // The notebook to be synced If a string + // value is provided it matches against the + // notebook type. '*' matches every notebook. + Notebook Or_NotebookDocumentFilterWithNotebook_notebook `json:"notebook"` + // The cells of the matching notebook to be synced. + Cells []NotebookCellLanguage `json:"cells,omitempty"` +} + +// A literal to identify a notebook document in the client. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentIdentifier +type NotebookDocumentIdentifier struct { + // The notebook document's uri. + URI URI `json:"uri"` +} + +// Notebook specific client capabilities. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentSyncClientCapabilities +type NotebookDocumentSyncClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is + // set to `true` the client supports the new + // `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + // return value for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client supports sending execution summary data per cell. + ExecutionSummarySupport bool `json:"executionSummarySupport,omitempty"` +} + +// Options specific to a notebook plus its cells +// to be synced to the server. +// +// If a selector provides a notebook document +// filter but no cell selector all cells of a +// matching notebook document will be synced. +// +// If a selector provides no notebook document +// filter but only a cell selector all notebook +// document that contain at least one matching +// cell will be synced. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentSyncOptions +type NotebookDocumentSyncOptions struct { + // The notebooks to be synced + NotebookSelector []Or_NotebookDocumentSyncOptions_notebookSelector_Elem `json:"notebookSelector"` + // Whether save notification should be forwarded to + // the server. Will only be honored if mode === `notebook`. + Save bool `json:"save,omitempty"` +} + +// Registration options specific to a notebook. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocumentSyncRegistrationOptions +type NotebookDocumentSyncRegistrationOptions struct { + NotebookDocumentSyncOptions + StaticRegistrationOptions +} + +// A text document identifier to optionally denote a specific version of a text document. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#optionalVersionedTextDocumentIdentifier +type OptionalVersionedTextDocumentIdentifier struct { + // The version number of this document. If a versioned text document identifier + // is sent from the server to the client and the file is not open in the editor + // (the server has not received an open notification before) the server can send + // `null` to indicate that the version is unknown and the content on disk is the + // truth (as specified with document content ownership). + Version int32 `json:"version"` + TextDocumentIdentifier +} + +// created for Or [Location LocationUriOnly] +type OrPLocation_workspace_symbol struct { + Value interface{} `json:"value"` +} + +// created for Or [[]string string] +type OrPSection_workspace_didChangeConfiguration struct { + Value interface{} `json:"value"` +} + +// created for Or [MarkupContent string] +type OrPTooltipPLabel struct { + Value interface{} `json:"value"` +} + +// created for Or [MarkupContent string] +type OrPTooltip_textDocument_inlayHint struct { + Value interface{} `json:"value"` +} + +// created for Or [int32 string] +type Or_CancelParams_id struct { + Value interface{} `json:"value"` +} + +// created for Or [ClientSemanticTokensRequestFullDelta bool] +type Or_ClientSemanticTokensRequestOptions_full struct { + Value interface{} `json:"value"` +} + +// created for Or [Lit_ClientSemanticTokensRequestOptions_range_Item1 bool] +type Or_ClientSemanticTokensRequestOptions_range struct { + Value interface{} `json:"value"` +} + +// created for Or [EditRangeWithInsertReplace Range] +type Or_CompletionItemDefaults_editRange struct { + Value interface{} `json:"value"` +} + +// created for Or [MarkupContent string] +type Or_CompletionItem_documentation struct { + Value interface{} `json:"value"` +} + +// created for Or [InsertReplaceEdit TextEdit] +type Or_CompletionItem_textEdit struct { + Value interface{} `json:"value"` +} + +// created for Or [Location []Location] +type Or_Definition struct { + Value interface{} `json:"value"` +} + +// created for Or [int32 string] +type Or_Diagnostic_code struct { + Value interface{} `json:"value"` +} + +// created for Or [RelatedFullDocumentDiagnosticReport RelatedUnchangedDocumentDiagnosticReport] +type Or_DocumentDiagnosticReport struct { + Value interface{} `json:"value"` +} + +// created for Or [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport] +type Or_DocumentDiagnosticReportPartialResult_relatedDocuments_Value struct { + Value interface{} `json:"value"` +} + +// created for Or [NotebookCellTextDocumentFilter TextDocumentFilter] +type Or_DocumentFilter struct { + Value interface{} `json:"value"` +} + +// created for Or [Pattern RelativePattern] +type Or_GlobPattern struct { + Value interface{} `json:"value"` +} + +// created for Or [MarkedString MarkupContent []MarkedString] +type Or_Hover_contents struct { + Value interface{} `json:"value"` +} + +// created for Or [[]InlayHintLabelPart string] +type Or_InlayHint_label struct { + Value interface{} `json:"value"` +} + +// created for Or [StringValue string] +type Or_InlineCompletionItem_insertText struct { + Value interface{} `json:"value"` +} + +// created for Or [InlineValueEvaluatableExpression InlineValueText InlineValueVariableLookup] +type Or_InlineValue struct { + Value interface{} `json:"value"` +} + +// created for Or [MarkedStringWithLanguage string] +type Or_MarkedString struct { + Value interface{} `json:"value"` +} + +// created for Or [NotebookDocumentFilter string] +type Or_NotebookCellTextDocumentFilter_notebook struct { + Value interface{} `json:"value"` +} + +// created for Or [NotebookDocumentFilterNotebookType NotebookDocumentFilterPattern NotebookDocumentFilterScheme] +type Or_NotebookDocumentFilter struct { + Value interface{} `json:"value"` +} + +// created for Or [NotebookDocumentFilter string] +type Or_NotebookDocumentFilterWithCells_notebook struct { + Value interface{} `json:"value"` +} + +// created for Or [NotebookDocumentFilter string] +type Or_NotebookDocumentFilterWithNotebook_notebook struct { + Value interface{} `json:"value"` +} + +// created for Or [NotebookDocumentFilterWithCells NotebookDocumentFilterWithNotebook] +type Or_NotebookDocumentSyncOptions_notebookSelector_Elem struct { + Value interface{} `json:"value"` +} + +// created for Or [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport] +type Or_RelatedFullDocumentDiagnosticReport_relatedDocuments_Value struct { + Value interface{} `json:"value"` +} + +// created for Or [FullDocumentDiagnosticReport UnchangedDocumentDiagnosticReport] +type Or_RelatedUnchangedDocumentDiagnosticReport_relatedDocuments_Value struct { + Value interface{} `json:"value"` +} + +// created for Or [CodeAction Command] +type Or_Result_textDocument_codeAction_Item0_Elem struct { + Value interface{} `json:"value"` +} + +// created for Or [InlineCompletionList []InlineCompletionItem] +type Or_Result_textDocument_inlineCompletion struct { + Value interface{} `json:"value"` +} + +// created for Or [SemanticTokensFullDelta bool] +type Or_SemanticTokensOptions_full struct { + Value interface{} `json:"value"` +} + +// created for Or [PRangeESemanticTokensOptions bool] +type Or_SemanticTokensOptions_range struct { + Value interface{} `json:"value"` +} + +// created for Or [CallHierarchyOptions CallHierarchyRegistrationOptions bool] +type Or_ServerCapabilities_callHierarchyProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [CodeActionOptions bool] +type Or_ServerCapabilities_codeActionProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [DocumentColorOptions DocumentColorRegistrationOptions bool] +type Or_ServerCapabilities_colorProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [DeclarationOptions DeclarationRegistrationOptions bool] +type Or_ServerCapabilities_declarationProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [DefinitionOptions bool] +type Or_ServerCapabilities_definitionProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [DiagnosticOptions DiagnosticRegistrationOptions] +type Or_ServerCapabilities_diagnosticProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [DocumentFormattingOptions bool] +type Or_ServerCapabilities_documentFormattingProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [DocumentHighlightOptions bool] +type Or_ServerCapabilities_documentHighlightProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [DocumentRangeFormattingOptions bool] +type Or_ServerCapabilities_documentRangeFormattingProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [DocumentSymbolOptions bool] +type Or_ServerCapabilities_documentSymbolProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [FoldingRangeOptions FoldingRangeRegistrationOptions bool] +type Or_ServerCapabilities_foldingRangeProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [HoverOptions bool] +type Or_ServerCapabilities_hoverProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [ImplementationOptions ImplementationRegistrationOptions bool] +type Or_ServerCapabilities_implementationProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [InlayHintOptions InlayHintRegistrationOptions bool] +type Or_ServerCapabilities_inlayHintProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [InlineCompletionOptions bool] +type Or_ServerCapabilities_inlineCompletionProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [InlineValueOptions InlineValueRegistrationOptions bool] +type Or_ServerCapabilities_inlineValueProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [LinkedEditingRangeOptions LinkedEditingRangeRegistrationOptions bool] +type Or_ServerCapabilities_linkedEditingRangeProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [MonikerOptions MonikerRegistrationOptions bool] +type Or_ServerCapabilities_monikerProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [NotebookDocumentSyncOptions NotebookDocumentSyncRegistrationOptions] +type Or_ServerCapabilities_notebookDocumentSync struct { + Value interface{} `json:"value"` +} + +// created for Or [ReferenceOptions bool] +type Or_ServerCapabilities_referencesProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [RenameOptions bool] +type Or_ServerCapabilities_renameProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [SelectionRangeOptions SelectionRangeRegistrationOptions bool] +type Or_ServerCapabilities_selectionRangeProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [SemanticTokensOptions SemanticTokensRegistrationOptions] +type Or_ServerCapabilities_semanticTokensProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [TextDocumentSyncKind TextDocumentSyncOptions] +type Or_ServerCapabilities_textDocumentSync struct { + Value interface{} `json:"value"` +} + +// created for Or [TypeDefinitionOptions TypeDefinitionRegistrationOptions bool] +type Or_ServerCapabilities_typeDefinitionProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [TypeHierarchyOptions TypeHierarchyRegistrationOptions bool] +type Or_ServerCapabilities_typeHierarchyProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [WorkspaceSymbolOptions bool] +type Or_ServerCapabilities_workspaceSymbolProvider struct { + Value interface{} `json:"value"` +} + +// created for Or [MarkupContent string] +type Or_SignatureInformation_documentation struct { + Value interface{} `json:"value"` +} + +// created for Or [AnnotatedTextEdit TextEdit] +type Or_TextDocumentEdit_edits_Elem struct { + Value interface{} `json:"value"` +} + +// created for Or [TextDocumentFilterLanguage TextDocumentFilterPattern TextDocumentFilterScheme] +type Or_TextDocumentFilter struct { + Value interface{} `json:"value"` +} + +// created for Or [SaveOptions bool] +type Or_TextDocumentSyncOptions_save struct { + Value interface{} `json:"value"` +} + +// created for Or [WorkspaceFullDocumentDiagnosticReport WorkspaceUnchangedDocumentDiagnosticReport] +type Or_WorkspaceDocumentDiagnosticReport struct { + Value interface{} `json:"value"` +} + +// created for Or [CreateFile DeleteFile RenameFile TextDocumentEdit] +type Or_WorkspaceEdit_documentChanges_Elem struct { + Value interface{} `json:"value"` +} + +// created for Or [Declaration []DeclarationLink] +type Or_textDocument_declaration struct { + Value interface{} `json:"value"` +} + +// created for Literal (Lit_SemanticTokensOptions_range_Item1) +type PRangeESemanticTokensOptions struct { +} + +// The parameters of a configuration request. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#configurationParams +type ParamConfiguration struct { + Items []ConfigurationItem `json:"items"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#initializeParams +type ParamInitialize struct { + XInitializeParams + WorkspaceFoldersInitializeParams +} + +// Represents a parameter of a callable-signature. A parameter can +// have a label and a doc-comment. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#parameterInformation +type ParameterInformation struct { + // The label of this parameter information. + // + // Either a string or an inclusive start and exclusive end offsets within its containing + // signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 + // string representation as `Position` and `Range` does. + // + // To avoid ambiguities a server should use the [start, end] offset value instead of using + // a substring. Whether a client support this is controlled via `labelOffsetSupport` client + // capability. + // + // *Note*: a label of type string should be a substring of its containing signature label. + // Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. + Label string `json:"label"` + // The human-readable doc-comment of this parameter. Will be shown + // in the UI but can be omitted. + Documentation string `json:"documentation,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#partialResultParams +type PartialResultParams struct { + // An optional token that a server can use to report partial results (e.g. streaming) to + // the client. + PartialResultToken *ProgressToken `json:"partialResultToken,omitempty"` +} + +// The glob pattern to watch relative to the base path. Glob patterns can have the following syntax: +// +// - `*` to match one or more characters in a path segment +// - `?` to match on one character in a path segment +// - `**` to match any number of path segments, including none +// - `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +// - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +// - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#pattern +type Pattern = string // (alias) +// Position in a text document expressed as zero-based line and character +// offset. Prior to 3.17 the offsets were always 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. +// Since 3.17 clients and servers can agree on a different string encoding +// representation (e.g. UTF-8). The client announces it's supported encoding +// via the client capability [`general.positionEncodings`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#clientCapabilities). +// The value is an array of position encodings the client supports, with +// decreasing preference (e.g. the encoding at index `0` is the most preferred +// one). To stay backwards compatible the only mandatory encoding is UTF-16 +// represented via the string `utf-16`. The server can pick one of the +// encodings offered by the client and signals that encoding back to the +// client via the initialize result's property +// [`capabilities.positionEncoding`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#serverCapabilities). If the string value +// `utf-16` is missing from the client's capability `general.positionEncodings` +// servers can safely assume that the client supports UTF-16. If the server +// omits the position encoding in its initialize result the encoding defaults +// to the string value `utf-16`. Implementation considerations: since the +// conversion from one encoding into another requires the content of the +// file / line the conversion is best done where the file is read which is +// usually on the server side. +// +// Positions are line end character agnostic. So you can not specify a position +// that denotes `\r|\n` or `\n|` where `|` represents the character offset. +// +// @since 3.17.0 - support for negotiated position encoding. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#position +type Position struct { + // Line position in a document (zero-based). + // + // If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document. + // If a line number is negative, it defaults to 0. + Line uint32 `json:"line"` + // Character offset on a line in a document (zero-based). + // + // The meaning of this offset is determined by the negotiated + // `PositionEncodingKind`. + // + // If the character value is greater than the line length it defaults back to the + // line length. + Character uint32 `json:"character"` +} + +// A set of predefined position encoding kinds. +// +// @since 3.17.0 +type PositionEncodingKind string + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#prepareRenameDefaultBehavior +type PrepareRenameDefaultBehavior struct { + DefaultBehavior bool `json:"defaultBehavior"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#prepareRenameParams +type PrepareRenameParams struct { + TextDocumentPositionParams + WorkDoneProgressParams +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#prepareRenamePlaceholder +type PrepareRenamePlaceholder struct { + Range Range `json:"range"` + Placeholder string `json:"placeholder"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#prepareRenameResult +type PrepareRenameResult = PrepareRenamePlaceholder // (alias) +type PrepareSupportDefaultBehavior uint32 + +// A previous result id in a workspace pull request. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#previousResultId +type PreviousResultID struct { + // The URI for which the client knowns a + // result id. + URI DocumentURI `json:"uri"` + // The value of the previous result id. + Value string `json:"value"` +} + +// A previous result id in a workspace pull request. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#previousResultId +type PreviousResultId struct { + // The URI for which the client knowns a + // result id. + URI DocumentURI `json:"uri"` + // The value of the previous result id. + Value string `json:"value"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#progressParams +type ProgressParams struct { + // The progress token provided by the client or server. + Token ProgressToken `json:"token"` + // The progress data. + Value interface{} `json:"value"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#progressToken +type ProgressToken = interface{} // (alias) +// The publish diagnostic client capabilities. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#publishDiagnosticsClientCapabilities +type PublishDiagnosticsClientCapabilities struct { + // Whether the clients accepts diagnostics with related information. + RelatedInformation bool `json:"relatedInformation,omitempty"` + // Client supports the tag property to provide meta data about a diagnostic. + // Clients supporting tags have to handle unknown tags gracefully. + // + // @since 3.15.0 + TagSupport *ClientDiagnosticsTagOptions `json:"tagSupport,omitempty"` + // Whether the client interprets the version property of the + // `textDocument/publishDiagnostics` notification's parameter. + // + // @since 3.15.0 + VersionSupport bool `json:"versionSupport,omitempty"` + // Client supports a codeDescription property + // + // @since 3.16.0 + CodeDescriptionSupport bool `json:"codeDescriptionSupport,omitempty"` + // Whether code action supports the `data` property which is + // preserved between a `textDocument/publishDiagnostics` and + // `textDocument/codeAction` request. + // + // @since 3.16.0 + DataSupport bool `json:"dataSupport,omitempty"` +} + +// The publish diagnostic notification's parameters. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#publishDiagnosticsParams +type PublishDiagnosticsParams struct { + // The URI for which diagnostic information is reported. + URI DocumentURI `json:"uri"` + // Optional the version number of the document the diagnostics are published for. + // + // @since 3.15.0 + Version int32 `json:"version,omitempty"` + // An array of diagnostic information items. + Diagnostics []Diagnostic `json:"diagnostics"` +} + +// A range in a text document expressed as (zero-based) start and end positions. +// +// If you want to specify a range that contains a line including the line ending +// character(s) then use an end position denoting the start of the next line. +// For example: +// ```ts +// +// { +// start: { line: 5, character: 23 } +// end : { line 6, character : 0 } +// } +// +// ``` +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#range +type Range struct { + // The range's start position. + Start Position `json:"start"` + // The range's end position. + End Position `json:"end"` +} + +// Client Capabilities for a {@link ReferencesRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#referenceClientCapabilities +type ReferenceClientCapabilities struct { + // Whether references supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// Value-object that contains additional information when +// requesting references. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#referenceContext +type ReferenceContext struct { + // Include the declaration of the current symbol. + IncludeDeclaration bool `json:"includeDeclaration"` +} + +// Reference options. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#referenceOptions +type ReferenceOptions struct { + WorkDoneProgressOptions +} + +// Parameters for a {@link ReferencesRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#referenceParams +type ReferenceParams struct { + Context ReferenceContext `json:"context"` + TextDocumentPositionParams + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link ReferencesRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#referenceRegistrationOptions +type ReferenceRegistrationOptions struct { + TextDocumentRegistrationOptions + ReferenceOptions +} + +// General parameters to register for a notification or to register a provider. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#registration +type Registration struct { + // The id used to register the request. The id can be used to deregister + // the request again. + ID string `json:"id"` + // The method / capability to register for. + Method string `json:"method"` + // Options necessary for the registration. + RegisterOptions interface{} `json:"registerOptions,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#registrationParams +type RegistrationParams struct { + Registrations []Registration `json:"registrations"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#regularExpressionEngineKind +type RegularExpressionEngineKind = string // (alias) +// Client capabilities specific to regular expressions. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#regularExpressionsClientCapabilities +type RegularExpressionsClientCapabilities struct { + // The engine's name. + Engine RegularExpressionEngineKind `json:"engine"` + // The engine's version. + Version string `json:"version,omitempty"` +} + +// A full diagnostic report with a set of related documents. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#relatedFullDocumentDiagnosticReport +type RelatedFullDocumentDiagnosticReport struct { + // Diagnostics of related documents. This information is useful + // in programming languages where code in a file A can generate + // diagnostics in a file B which A depends on. An example of + // such a language is C/C++ where marco definitions in a file + // a.cpp and result in errors in a header file b.hpp. + // + // @since 3.17.0 + RelatedDocuments map[DocumentURI]interface{} `json:"relatedDocuments,omitempty"` + FullDocumentDiagnosticReport +} + +// An unchanged diagnostic report with a set of related documents. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#relatedUnchangedDocumentDiagnosticReport +type RelatedUnchangedDocumentDiagnosticReport struct { + // Diagnostics of related documents. This information is useful + // in programming languages where code in a file A can generate + // diagnostics in a file B which A depends on. An example of + // such a language is C/C++ where marco definitions in a file + // a.cpp and result in errors in a header file b.hpp. + // + // @since 3.17.0 + RelatedDocuments map[DocumentURI]interface{} `json:"relatedDocuments,omitempty"` + UnchangedDocumentDiagnosticReport +} + +// A relative pattern is a helper to construct glob patterns that are matched +// relatively to a base URI. The common value for a `baseUri` is a workspace +// folder root, but it can be another absolute URI as well. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#relativePattern +type RelativePattern struct { + // A workspace folder or a base URI to which this pattern will be matched + // against relatively. + BaseURI DocumentURI `json:"baseUri"` + // The actual glob pattern; + Pattern Pattern `json:"pattern"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#renameClientCapabilities +type RenameClientCapabilities struct { + // Whether rename supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Client supports testing for validity of rename operations + // before execution. + // + // @since 3.12.0 + PrepareSupport bool `json:"prepareSupport,omitempty"` + // Client supports the default behavior result. + // + // The value indicates the default behavior used by the + // client. + // + // @since 3.16.0 + PrepareSupportDefaultBehavior *PrepareSupportDefaultBehavior `json:"prepareSupportDefaultBehavior,omitempty"` + // Whether the client honors the change annotations in + // text edits and resource operations returned via the + // rename request's workspace edit by for example presenting + // the workspace edit in the user interface and asking + // for confirmation. + // + // @since 3.16.0 + HonorsChangeAnnotations bool `json:"honorsChangeAnnotations,omitempty"` +} + +// Rename file operation +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#renameFile +type RenameFile struct { + // A rename + Kind string `json:"kind"` + // The old (existing) location. + OldURI DocumentURI `json:"oldUri"` + // The new location. + NewURI DocumentURI `json:"newUri"` + // Rename options. + Options *RenameFileOptions `json:"options,omitempty"` + ResourceOperation +} + +// Rename file options +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#renameFileOptions +type RenameFileOptions struct { + // Overwrite target if existing. Overwrite wins over `ignoreIfExists` + Overwrite bool `json:"overwrite,omitempty"` + // Ignores if target exists. + IgnoreIfExists bool `json:"ignoreIfExists,omitempty"` +} + +// The parameters sent in notifications/requests for user-initiated renames of +// files. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#renameFilesParams +type RenameFilesParams struct { + // An array of all files/folders renamed in this operation. When a folder is renamed, only + // the folder will be included, and not its children. + Files []FileRename `json:"files"` +} + +// Provider options for a {@link RenameRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#renameOptions +type RenameOptions struct { + // Renames should be checked and tested before being executed. + // + // @since version 3.12.0 + PrepareProvider bool `json:"prepareProvider,omitempty"` + WorkDoneProgressOptions +} + +// The parameters of a {@link RenameRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#renameParams +type RenameParams struct { + // The document to rename. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The position at which this request was sent. + Position Position `json:"position"` + // The new name of the symbol. If the given name is not valid the + // request must return a {@link ResponseError} with an + // appropriate message set. + NewName string `json:"newName"` + WorkDoneProgressParams +} + +// Registration options for a {@link RenameRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#renameRegistrationOptions +type RenameRegistrationOptions struct { + TextDocumentRegistrationOptions + RenameOptions +} + +// A generic resource operation. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#resourceOperation +type ResourceOperation struct { + // The resource operation kind. + Kind string `json:"kind"` + // An optional annotation identifier describing the operation. + // + // @since 3.16.0 + AnnotationID *ChangeAnnotationIdentifier `json:"annotationId,omitempty"` +} +type ResourceOperationKind string + +// Save options. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#saveOptions +type SaveOptions struct { + // The client is supposed to include the content on save. + IncludeText bool `json:"includeText,omitempty"` +} + +// Describes the currently selected completion item. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#selectedCompletionInfo +type SelectedCompletionInfo struct { + // The range that will be replaced if this completion item is accepted. + Range Range `json:"range"` + // The text the range will be replaced with if this completion is accepted. + Text string `json:"text"` +} + +// A selection range represents a part of a selection hierarchy. A selection range +// may have a parent selection range that contains it. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#selectionRange +type SelectionRange struct { + // The {@link Range range} of this selection range. + Range Range `json:"range"` + // The parent selection range containing this range. Therefore `parent.range` must contain `this.range`. + Parent *SelectionRange `json:"parent,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#selectionRangeClientCapabilities +type SelectionRangeClientCapabilities struct { + // Whether implementation supports dynamic registration for selection range providers. If this is set to `true` + // the client supports the new `SelectionRangeRegistrationOptions` return value for the corresponding server + // capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#selectionRangeOptions +type SelectionRangeOptions struct { + WorkDoneProgressOptions +} + +// A parameter literal used in selection range requests. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#selectionRangeParams +type SelectionRangeParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The positions inside the text document. + Positions []Position `json:"positions"` + WorkDoneProgressParams + PartialResultParams +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#selectionRangeRegistrationOptions +type SelectionRangeRegistrationOptions struct { + SelectionRangeOptions + TextDocumentRegistrationOptions + StaticRegistrationOptions +} + +// A set of predefined token modifiers. This set is not fixed +// an clients can specify additional token types via the +// corresponding client capabilities. +// +// @since 3.16.0 +type SemanticTokenModifiers string + +// A set of predefined token types. This set is not fixed +// an clients can specify additional token types via the +// corresponding client capabilities. +// +// @since 3.16.0 +type SemanticTokenTypes string + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokens +type SemanticTokens struct { + // An optional result id. If provided and clients support delta updating + // the client will include the result id in the next semantic token request. + // A server can then instead of computing all semantic tokens again simply + // send a delta. + ResultID string `json:"resultId,omitempty"` + // The actual tokens. + Data []uint32 `json:"data"` +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensClientCapabilities +type SemanticTokensClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is set to `true` + // the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + // return value for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Which requests the client supports and might send to the server + // depending on the server's capability. Please note that clients might not + // show semantic tokens or degrade some of the user experience if a range + // or full request is advertised by the client but not provided by the + // server. If for example the client capability `requests.full` and + // `request.range` are both set to true but the server only provides a + // range provider the client might not render a minimap correctly or might + // even decide to not show any semantic tokens at all. + Requests ClientSemanticTokensRequestOptions `json:"requests"` + // The token types that the client supports. + TokenTypes []string `json:"tokenTypes"` + // The token modifiers that the client supports. + TokenModifiers []string `json:"tokenModifiers"` + // The token formats the clients supports. + Formats []TokenFormat `json:"formats"` + // Whether the client supports tokens that can overlap each other. + OverlappingTokenSupport bool `json:"overlappingTokenSupport,omitempty"` + // Whether the client supports tokens that can span multiple lines. + MultilineTokenSupport bool `json:"multilineTokenSupport,omitempty"` + // Whether the client allows the server to actively cancel a + // semantic token request, e.g. supports returning + // LSPErrorCodes.ServerCancelled. If a server does the client + // needs to retrigger the request. + // + // @since 3.17.0 + ServerCancelSupport bool `json:"serverCancelSupport,omitempty"` + // Whether the client uses semantic tokens to augment existing + // syntax tokens. If set to `true` client side created syntax + // tokens and semantic tokens are both used for colorization. If + // set to `false` the client only uses the returned semantic tokens + // for colorization. + // + // If the value is `undefined` then the client behavior is not + // specified. + // + // @since 3.17.0 + AugmentsSyntaxTokens bool `json:"augmentsSyntaxTokens,omitempty"` +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensDelta +type SemanticTokensDelta struct { + ResultID string `json:"resultId,omitempty"` + // The semantic token edits to transform a previous result into a new result. + Edits []SemanticTokensEdit `json:"edits"` +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensDeltaParams +type SemanticTokensDeltaParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The result id of a previous response. The result Id can either point to a full response + // or a delta response depending on what was received last. + PreviousResultID string `json:"previousResultId"` + WorkDoneProgressParams + PartialResultParams +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensDeltaPartialResult +type SemanticTokensDeltaPartialResult struct { + Edits []SemanticTokensEdit `json:"edits"` +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensEdit +type SemanticTokensEdit struct { + // The start offset of the edit. + Start uint32 `json:"start"` + // The count of elements to remove. + DeleteCount uint32 `json:"deleteCount"` + // The elements to insert. + Data []uint32 `json:"data,omitempty"` +} + +// Semantic tokens options to support deltas for full documents +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensFullDelta +type SemanticTokensFullDelta struct { + // The server supports deltas for full documents. + Delta bool `json:"delta,omitempty"` +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensLegend +type SemanticTokensLegend struct { + // The token types a server uses. + TokenTypes []string `json:"tokenTypes"` + // The token modifiers a server uses. + TokenModifiers []string `json:"tokenModifiers"` +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensOptions +type SemanticTokensOptions struct { + // The legend used by the server + Legend SemanticTokensLegend `json:"legend"` + // Server supports providing semantic tokens for a specific range + // of a document. + Range *Or_SemanticTokensOptions_range `json:"range,omitempty"` + // Server supports providing semantic tokens for a full document. + Full *Or_SemanticTokensOptions_full `json:"full,omitempty"` + WorkDoneProgressOptions +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensParams +type SemanticTokensParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + WorkDoneProgressParams + PartialResultParams +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensPartialResult +type SemanticTokensPartialResult struct { + Data []uint32 `json:"data"` +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensRangeParams +type SemanticTokensRangeParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The range the semantic tokens are requested for. + Range Range `json:"range"` + WorkDoneProgressParams + PartialResultParams +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensRegistrationOptions +type SemanticTokensRegistrationOptions struct { + TextDocumentRegistrationOptions + SemanticTokensOptions + StaticRegistrationOptions +} + +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#semanticTokensWorkspaceClientCapabilities +type SemanticTokensWorkspaceClientCapabilities struct { + // Whether the client implementation supports a refresh request sent from + // the server to the client. + // + // Note that this event is global and will force the client to refresh all + // semantic tokens currently shown. It should be used with absolute care + // and is useful for situation where a server for example detects a project + // wide change that requires such a calculation. + RefreshSupport bool `json:"refreshSupport,omitempty"` +} + +// Defines the capabilities provided by a language +// server. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#serverCapabilities +type ServerCapabilities struct { + // The position encoding the server picked from the encodings offered + // by the client via the client capability `general.positionEncodings`. + // + // If the client didn't provide any position encodings the only valid + // value that a server can return is 'utf-16'. + // + // If omitted it defaults to 'utf-16'. + // + // @since 3.17.0 + PositionEncoding *PositionEncodingKind `json:"positionEncoding,omitempty"` + // Defines how text documents are synced. Is either a detailed structure + // defining each notification or for backwards compatibility the + // TextDocumentSyncKind number. + TextDocumentSync interface{} `json:"textDocumentSync,omitempty"` + // Defines how notebook documents are synced. + // + // @since 3.17.0 + NotebookDocumentSync *Or_ServerCapabilities_notebookDocumentSync `json:"notebookDocumentSync,omitempty"` + // The server provides completion support. + CompletionProvider *CompletionOptions `json:"completionProvider,omitempty"` + // The server provides hover support. + HoverProvider *Or_ServerCapabilities_hoverProvider `json:"hoverProvider,omitempty"` + // The server provides signature help support. + SignatureHelpProvider *SignatureHelpOptions `json:"signatureHelpProvider,omitempty"` + // The server provides Goto Declaration support. + DeclarationProvider *Or_ServerCapabilities_declarationProvider `json:"declarationProvider,omitempty"` + // The server provides goto definition support. + DefinitionProvider *Or_ServerCapabilities_definitionProvider `json:"definitionProvider,omitempty"` + // The server provides Goto Type Definition support. + TypeDefinitionProvider *Or_ServerCapabilities_typeDefinitionProvider `json:"typeDefinitionProvider,omitempty"` + // The server provides Goto Implementation support. + ImplementationProvider *Or_ServerCapabilities_implementationProvider `json:"implementationProvider,omitempty"` + // The server provides find references support. + ReferencesProvider *Or_ServerCapabilities_referencesProvider `json:"referencesProvider,omitempty"` + // The server provides document highlight support. + DocumentHighlightProvider *Or_ServerCapabilities_documentHighlightProvider `json:"documentHighlightProvider,omitempty"` + // The server provides document symbol support. + DocumentSymbolProvider *Or_ServerCapabilities_documentSymbolProvider `json:"documentSymbolProvider,omitempty"` + // The server provides code actions. CodeActionOptions may only be + // specified if the client states that it supports + // `codeActionLiteralSupport` in its initial `initialize` request. + CodeActionProvider interface{} `json:"codeActionProvider,omitempty"` + // The server provides code lens. + CodeLensProvider *CodeLensOptions `json:"codeLensProvider,omitempty"` + // The server provides document link support. + DocumentLinkProvider *DocumentLinkOptions `json:"documentLinkProvider,omitempty"` + // The server provides color provider support. + ColorProvider *Or_ServerCapabilities_colorProvider `json:"colorProvider,omitempty"` + // The server provides workspace symbol support. + WorkspaceSymbolProvider *Or_ServerCapabilities_workspaceSymbolProvider `json:"workspaceSymbolProvider,omitempty"` + // The server provides document formatting. + DocumentFormattingProvider *Or_ServerCapabilities_documentFormattingProvider `json:"documentFormattingProvider,omitempty"` + // The server provides document range formatting. + DocumentRangeFormattingProvider *Or_ServerCapabilities_documentRangeFormattingProvider `json:"documentRangeFormattingProvider,omitempty"` + // The server provides document formatting on typing. + DocumentOnTypeFormattingProvider *DocumentOnTypeFormattingOptions `json:"documentOnTypeFormattingProvider,omitempty"` + // The server provides rename support. RenameOptions may only be + // specified if the client states that it supports + // `prepareSupport` in its initial `initialize` request. + RenameProvider interface{} `json:"renameProvider,omitempty"` + // The server provides folding provider support. + FoldingRangeProvider *Or_ServerCapabilities_foldingRangeProvider `json:"foldingRangeProvider,omitempty"` + // The server provides selection range support. + SelectionRangeProvider *Or_ServerCapabilities_selectionRangeProvider `json:"selectionRangeProvider,omitempty"` + // The server provides execute command support. + ExecuteCommandProvider *ExecuteCommandOptions `json:"executeCommandProvider,omitempty"` + // The server provides call hierarchy support. + // + // @since 3.16.0 + CallHierarchyProvider *Or_ServerCapabilities_callHierarchyProvider `json:"callHierarchyProvider,omitempty"` + // The server provides linked editing range support. + // + // @since 3.16.0 + LinkedEditingRangeProvider *Or_ServerCapabilities_linkedEditingRangeProvider `json:"linkedEditingRangeProvider,omitempty"` + // The server provides semantic tokens support. + // + // @since 3.16.0 + SemanticTokensProvider interface{} `json:"semanticTokensProvider,omitempty"` + // The server provides moniker support. + // + // @since 3.16.0 + MonikerProvider *Or_ServerCapabilities_monikerProvider `json:"monikerProvider,omitempty"` + // The server provides type hierarchy support. + // + // @since 3.17.0 + TypeHierarchyProvider *Or_ServerCapabilities_typeHierarchyProvider `json:"typeHierarchyProvider,omitempty"` + // The server provides inline values. + // + // @since 3.17.0 + InlineValueProvider *Or_ServerCapabilities_inlineValueProvider `json:"inlineValueProvider,omitempty"` + // The server provides inlay hints. + // + // @since 3.17.0 + InlayHintProvider interface{} `json:"inlayHintProvider,omitempty"` + // The server has support for pull model diagnostics. + // + // @since 3.17.0 + DiagnosticProvider *Or_ServerCapabilities_diagnosticProvider `json:"diagnosticProvider,omitempty"` + // Inline completion options used during static registration. + // + // @since 3.18.0 + // @proposed + InlineCompletionProvider *Or_ServerCapabilities_inlineCompletionProvider `json:"inlineCompletionProvider,omitempty"` + // Workspace specific server capabilities. + Workspace *WorkspaceOptions `json:"workspace,omitempty"` + // Experimental server capabilities. + Experimental interface{} `json:"experimental,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#serverCompletionItemOptions +type ServerCompletionItemOptions struct { + // The server has support for completion item label + // details (see also `CompletionItemLabelDetails`) when + // receiving a completion item in a resolve call. + // + // @since 3.17.0 + LabelDetailsSupport bool `json:"labelDetailsSupport,omitempty"` +} + +// Information about the server +// +// @since 3.15.0 +// @since 3.18.0 ServerInfo type name added. +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#serverInfo +type ServerInfo struct { + // The name of the server as defined by the server. + Name string `json:"name"` + // The server's version as defined by the server. + Version string `json:"version,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#setTraceParams +type SetTraceParams struct { + Value TraceValue `json:"value"` +} + +// Client capabilities for the showDocument request. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#showDocumentClientCapabilities +type ShowDocumentClientCapabilities struct { + // The client has support for the showDocument + // request. + Support bool `json:"support"` +} + +// Params to show a resource in the UI. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#showDocumentParams +type ShowDocumentParams struct { + // The uri to show. + URI URI `json:"uri"` + // Indicates to show the resource in an external program. + // To show, for example, `https://code.visualstudio.com/` + // in the default WEB browser set `external` to `true`. + External bool `json:"external,omitempty"` + // An optional property to indicate whether the editor + // showing the document should take focus or not. + // Clients might ignore this property if an external + // program is started. + TakeFocus bool `json:"takeFocus,omitempty"` + // An optional selection range if the document is a text + // document. Clients might ignore the property if an + // external program is started or the file is not a text + // file. + Selection *Range `json:"selection,omitempty"` +} + +// The result of a showDocument request. +// +// @since 3.16.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#showDocumentResult +type ShowDocumentResult struct { + // A boolean indicating if the show was successful. + Success bool `json:"success"` +} + +// The parameters of a notification message. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#showMessageParams +type ShowMessageParams struct { + // The message type. See {@link MessageType} + Type MessageType `json:"type"` + // The actual message. + Message string `json:"message"` +} + +// Show message request client capabilities +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#showMessageRequestClientCapabilities +type ShowMessageRequestClientCapabilities struct { + // Capabilities specific to the `MessageActionItem` type. + MessageActionItem *ClientShowMessageActionItemOptions `json:"messageActionItem,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#showMessageRequestParams +type ShowMessageRequestParams struct { + // The message type. See {@link MessageType} + Type MessageType `json:"type"` + // The actual message. + Message string `json:"message"` + // The message action items to present. + Actions []MessageActionItem `json:"actions,omitempty"` +} + +// Signature help represents the signature of something +// callable. There can be multiple signature but only one +// active and only one active parameter. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#signatureHelp +type SignatureHelp struct { + // One or more signatures. + Signatures []SignatureInformation `json:"signatures"` + // The active signature. If omitted or the value lies outside the + // range of `signatures` the value defaults to zero or is ignored if + // the `SignatureHelp` has no signatures. + // + // Whenever possible implementors should make an active decision about + // the active signature and shouldn't rely on a default value. + // + // In future version of the protocol this property might become + // mandatory to better express this. + ActiveSignature uint32 `json:"activeSignature,omitempty"` + // The active parameter of the active signature. + // + // If `null`, no parameter of the signature is active (for example a named + // argument that does not match any declared parameters). This is only valid + // if the client specifies the client capability + // `textDocument.signatureHelp.noActiveParameterSupport === true` + // + // If omitted or the value lies outside the range of + // `signatures[activeSignature].parameters` defaults to 0 if the active + // signature has parameters. + // + // If the active signature has no parameters it is ignored. + // + // In future version of the protocol this property might become + // mandatory (but still nullable) to better express the active parameter if + // the active signature does have any. + ActiveParameter uint32 `json:"activeParameter,omitempty"` +} + +// Client Capabilities for a {@link SignatureHelpRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#signatureHelpClientCapabilities +type SignatureHelpClientCapabilities struct { + // Whether signature help supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client supports the following `SignatureInformation` + // specific properties. + SignatureInformation *ClientSignatureInformationOptions `json:"signatureInformation,omitempty"` + // The client supports to send additional context information for a + // `textDocument/signatureHelp` request. A client that opts into + // contextSupport will also support the `retriggerCharacters` on + // `SignatureHelpOptions`. + // + // @since 3.15.0 + ContextSupport bool `json:"contextSupport,omitempty"` +} + +// Additional information about the context in which a signature help request was triggered. +// +// @since 3.15.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#signatureHelpContext +type SignatureHelpContext struct { + // Action that caused signature help to be triggered. + TriggerKind SignatureHelpTriggerKind `json:"triggerKind"` + // Character that caused signature help to be triggered. + // + // This is undefined when `triggerKind !== SignatureHelpTriggerKind.TriggerCharacter` + TriggerCharacter string `json:"triggerCharacter,omitempty"` + // `true` if signature help was already showing when it was triggered. + // + // Retriggers occurs when the signature help is already active and can be caused by actions such as + // typing a trigger character, a cursor move, or document content changes. + IsRetrigger bool `json:"isRetrigger"` + // The currently active `SignatureHelp`. + // + // The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field updated based on + // the user navigating through available signatures. + ActiveSignatureHelp *SignatureHelp `json:"activeSignatureHelp,omitempty"` +} + +// Server Capabilities for a {@link SignatureHelpRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#signatureHelpOptions +type SignatureHelpOptions struct { + // List of characters that trigger signature help automatically. + TriggerCharacters []string `json:"triggerCharacters,omitempty"` + // List of characters that re-trigger signature help. + // + // These trigger characters are only active when signature help is already showing. All trigger characters + // are also counted as re-trigger characters. + // + // @since 3.15.0 + RetriggerCharacters []string `json:"retriggerCharacters,omitempty"` + WorkDoneProgressOptions +} + +// Parameters for a {@link SignatureHelpRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#signatureHelpParams +type SignatureHelpParams struct { + // The signature help context. This is only available if the client specifies + // to send this using the client capability `textDocument.signatureHelp.contextSupport === true` + // + // @since 3.15.0 + Context *SignatureHelpContext `json:"context,omitempty"` + TextDocumentPositionParams + WorkDoneProgressParams +} + +// Registration options for a {@link SignatureHelpRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#signatureHelpRegistrationOptions +type SignatureHelpRegistrationOptions struct { + TextDocumentRegistrationOptions + SignatureHelpOptions +} + +// How a signature help was triggered. +// +// @since 3.15.0 +type SignatureHelpTriggerKind uint32 + +// Represents the signature of something callable. A signature +// can have a label, like a function-name, a doc-comment, and +// a set of parameters. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#signatureInformation +type SignatureInformation struct { + // The label of this signature. Will be shown in + // the UI. + Label string `json:"label"` + // The human-readable doc-comment of this signature. Will be shown + // in the UI but can be omitted. + Documentation *Or_SignatureInformation_documentation `json:"documentation,omitempty"` + // The parameters of this signature. + Parameters []ParameterInformation `json:"parameters,omitempty"` + // The index of the active parameter. + // + // If `null`, no parameter of the signature is active (for example a named + // argument that does not match any declared parameters). This is only valid + // if the client specifies the client capability + // `textDocument.signatureHelp.noActiveParameterSupport === true` + // + // If provided (or `null`), this is used in place of + // `SignatureHelp.activeParameter`. + // + // @since 3.16.0 + ActiveParameter uint32 `json:"activeParameter,omitempty"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#staleRequestSupportOptions +type StaleRequestSupportOptions struct { + // The client will actively cancel the request. + Cancel bool `json:"cancel"` + // The list of requests for which the client + // will retry the request if it receives a + // response with error code `ContentModified` + RetryOnContentModified []string `json:"retryOnContentModified"` +} + +// Static registration options to be returned in the initialize +// request. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#staticRegistrationOptions +type StaticRegistrationOptions struct { + // The id used to register the request. The id can be used to deregister + // the request again. See also Registration#id. + ID string `json:"id,omitempty"` +} + +// A string value used as a snippet is a template which allows to insert text +// and to control the editor cursor when insertion happens. +// +// A snippet can define tab stops and placeholders with `$1`, `$2` +// and `${3:foo}`. `$0` defines the final tab stop, it defaults to +// the end of the snippet. Variables are defined with `$name` and +// `${name:default value}`. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#stringValue +type StringValue struct { + // The kind of string value. + Kind string `json:"kind"` + // The snippet string. + Value string `json:"value"` +} + +// Represents information about programming constructs like variables, classes, +// interfaces etc. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#symbolInformation +type SymbolInformation struct { + // extends BaseSymbolInformation + // Indicates if this symbol is deprecated. + // + // @deprecated Use tags instead + Deprecated bool `json:"deprecated,omitempty"` + // The location of this symbol. The location's range is used by a tool + // to reveal the location in the editor. If the symbol is selected in the + // tool the range's start information is used to position the cursor. So + // the range usually spans more than the actual symbol's name and does + // normally include things like visibility modifiers. + // + // The range doesn't have to denote a node range in the sense of an abstract + // syntax tree. It can therefore not be used to re-construct a hierarchy of + // the symbols. + Location Location `json:"location"` + // The name of this symbol. + Name string `json:"name"` + // The kind of this symbol. + Kind SymbolKind `json:"kind"` + // Tags for this symbol. + // + // @since 3.16.0 + Tags []SymbolTag `json:"tags,omitempty"` + // The name of the symbol containing this symbol. This information is for + // user interface purposes (e.g. to render a qualifier in the user interface + // if necessary). It can't be used to re-infer a hierarchy for the document + // symbols. + ContainerName string `json:"containerName,omitempty"` +} + +// A symbol kind. +type SymbolKind uint32 + +// Symbol tags are extra annotations that tweak the rendering of a symbol. +// +// @since 3.16 +type SymbolTag uint32 + +// Describe options to be used when registered for text document change events. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentChangeRegistrationOptions +type TextDocumentChangeRegistrationOptions struct { + // How documents are synced to the server. + SyncKind TextDocumentSyncKind `json:"syncKind"` + TextDocumentRegistrationOptions +} + +// Text document specific client capabilities. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentClientCapabilities +type TextDocumentClientCapabilities struct { + // Defines which synchronization capabilities the client supports. + Synchronization *TextDocumentSyncClientCapabilities `json:"synchronization,omitempty"` + // Capabilities specific to the `textDocument/completion` request. + Completion CompletionClientCapabilities `json:"completion,omitempty"` + // Capabilities specific to the `textDocument/hover` request. + Hover *HoverClientCapabilities `json:"hover,omitempty"` + // Capabilities specific to the `textDocument/signatureHelp` request. + SignatureHelp *SignatureHelpClientCapabilities `json:"signatureHelp,omitempty"` + // Capabilities specific to the `textDocument/declaration` request. + // + // @since 3.14.0 + Declaration *DeclarationClientCapabilities `json:"declaration,omitempty"` + // Capabilities specific to the `textDocument/definition` request. + Definition *DefinitionClientCapabilities `json:"definition,omitempty"` + // Capabilities specific to the `textDocument/typeDefinition` request. + // + // @since 3.6.0 + TypeDefinition *TypeDefinitionClientCapabilities `json:"typeDefinition,omitempty"` + // Capabilities specific to the `textDocument/implementation` request. + // + // @since 3.6.0 + Implementation *ImplementationClientCapabilities `json:"implementation,omitempty"` + // Capabilities specific to the `textDocument/references` request. + References *ReferenceClientCapabilities `json:"references,omitempty"` + // Capabilities specific to the `textDocument/documentHighlight` request. + DocumentHighlight *DocumentHighlightClientCapabilities `json:"documentHighlight,omitempty"` + // Capabilities specific to the `textDocument/documentSymbol` request. + DocumentSymbol DocumentSymbolClientCapabilities `json:"documentSymbol,omitempty"` + // Capabilities specific to the `textDocument/codeAction` request. + CodeAction CodeActionClientCapabilities `json:"codeAction,omitempty"` + // Capabilities specific to the `textDocument/codeLens` request. + CodeLens *CodeLensClientCapabilities `json:"codeLens,omitempty"` + // Capabilities specific to the `textDocument/documentLink` request. + DocumentLink *DocumentLinkClientCapabilities `json:"documentLink,omitempty"` + // Capabilities specific to the `textDocument/documentColor` and the + // `textDocument/colorPresentation` request. + // + // @since 3.6.0 + ColorProvider *DocumentColorClientCapabilities `json:"colorProvider,omitempty"` + // Capabilities specific to the `textDocument/formatting` request. + Formatting *DocumentFormattingClientCapabilities `json:"formatting,omitempty"` + // Capabilities specific to the `textDocument/rangeFormatting` request. + RangeFormatting *DocumentRangeFormattingClientCapabilities `json:"rangeFormatting,omitempty"` + // Capabilities specific to the `textDocument/onTypeFormatting` request. + OnTypeFormatting *DocumentOnTypeFormattingClientCapabilities `json:"onTypeFormatting,omitempty"` + // Capabilities specific to the `textDocument/rename` request. + Rename *RenameClientCapabilities `json:"rename,omitempty"` + // Capabilities specific to the `textDocument/foldingRange` request. + // + // @since 3.10.0 + FoldingRange *FoldingRangeClientCapabilities `json:"foldingRange,omitempty"` + // Capabilities specific to the `textDocument/selectionRange` request. + // + // @since 3.15.0 + SelectionRange *SelectionRangeClientCapabilities `json:"selectionRange,omitempty"` + // Capabilities specific to the `textDocument/publishDiagnostics` notification. + PublishDiagnostics PublishDiagnosticsClientCapabilities `json:"publishDiagnostics,omitempty"` + // Capabilities specific to the various call hierarchy requests. + // + // @since 3.16.0 + CallHierarchy *CallHierarchyClientCapabilities `json:"callHierarchy,omitempty"` + // Capabilities specific to the various semantic token request. + // + // @since 3.16.0 + SemanticTokens SemanticTokensClientCapabilities `json:"semanticTokens,omitempty"` + // Capabilities specific to the `textDocument/linkedEditingRange` request. + // + // @since 3.16.0 + LinkedEditingRange *LinkedEditingRangeClientCapabilities `json:"linkedEditingRange,omitempty"` + // Client capabilities specific to the `textDocument/moniker` request. + // + // @since 3.16.0 + Moniker *MonikerClientCapabilities `json:"moniker,omitempty"` + // Capabilities specific to the various type hierarchy requests. + // + // @since 3.17.0 + TypeHierarchy *TypeHierarchyClientCapabilities `json:"typeHierarchy,omitempty"` + // Capabilities specific to the `textDocument/inlineValue` request. + // + // @since 3.17.0 + InlineValue *InlineValueClientCapabilities `json:"inlineValue,omitempty"` + // Capabilities specific to the `textDocument/inlayHint` request. + // + // @since 3.17.0 + InlayHint *InlayHintClientCapabilities `json:"inlayHint,omitempty"` + // Capabilities specific to the diagnostic pull model. + // + // @since 3.17.0 + Diagnostic *DiagnosticClientCapabilities `json:"diagnostic,omitempty"` + // Client capabilities specific to inline completions. + // + // @since 3.18.0 + // @proposed + InlineCompletion *InlineCompletionClientCapabilities `json:"inlineCompletion,omitempty"` +} + +// An event describing a change to a text document. If only a text is provided +// it is considered to be the full content of the document. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentContentChangeEvent +type TextDocumentContentChangeEvent = TextDocumentContentChangePartial // (alias) +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentContentChangePartial +type TextDocumentContentChangePartial struct { + // The range of the document that changed. + Range *Range `json:"range,omitempty"` + // The optional length of the range that got replaced. + // + // @deprecated use range instead. + RangeLength uint32 `json:"rangeLength,omitempty"` + // The new text for the provided range. + Text string `json:"text"` +} + +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentContentChangeWholeDocument +type TextDocumentContentChangeWholeDocument struct { + // The new text of the whole document. + Text string `json:"text"` +} + +// Describes textual changes on a text document. A TextDocumentEdit describes all changes +// on a document version Si and after they are applied move the document to version Si+1. +// So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any +// kind of ordering. However the edits must be non overlapping. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentEdit +type TextDocumentEdit struct { + // The text document to change. + TextDocument OptionalVersionedTextDocumentIdentifier `json:"textDocument"` + // The edits to be applied. + // + // @since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a + // client capability. + Edits []Or_TextDocumentEdit_edits_Elem `json:"edits"` +} + +// A document filter denotes a document by different properties like +// the {@link TextDocument.languageId language}, the {@link Uri.scheme scheme} of +// its resource, or a glob-pattern that is applied to the {@link TextDocument.fileName path}. +// +// Glob patterns can have the following syntax: +// +// - `*` to match one or more characters in a path segment +// - `?` to match on one character in a path segment +// - `**` to match any number of path segments, including none +// - `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +// - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +// - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) +// +// @sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }` +// @sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }` +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentFilter +type TextDocumentFilter = Or_TextDocumentFilter // (alias) +// A document filter where `language` is required field. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentFilterLanguage +type TextDocumentFilterLanguage struct { + // A language id, like `typescript`. + Language string `json:"language"` + // A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + Scheme string `json:"scheme,omitempty"` + // A glob pattern, like **​/*.{ts,js}. See TextDocumentFilter for examples. + Pattern string `json:"pattern,omitempty"` +} + +// A document filter where `pattern` is required field. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentFilterPattern +type TextDocumentFilterPattern struct { + // A language id, like `typescript`. + Language string `json:"language,omitempty"` + // A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + Scheme string `json:"scheme,omitempty"` + // A glob pattern, like **​/*.{ts,js}. See TextDocumentFilter for examples. + Pattern string `json:"pattern"` +} + +// A document filter where `scheme` is required field. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentFilterScheme +type TextDocumentFilterScheme struct { + // A language id, like `typescript`. + Language string `json:"language,omitempty"` + // A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. + Scheme string `json:"scheme"` + // A glob pattern, like **​/*.{ts,js}. See TextDocumentFilter for examples. + Pattern string `json:"pattern,omitempty"` +} + +// A literal to identify a text document in the client. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentIdentifier +type TextDocumentIdentifier struct { + // The text document's uri. + URI DocumentURI `json:"uri"` +} + +// An item to transfer a text document from the client to the +// server. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentItem +type TextDocumentItem struct { + // The text document's uri. + URI DocumentURI `json:"uri"` + // The text document's language identifier. + LanguageID LanguageKind `json:"languageId"` + // The version number of this document (it will increase after each + // change, including undo/redo). + Version int32 `json:"version"` + // The content of the opened text document. + Text string `json:"text"` +} + +// A parameter literal used in requests to pass a text document and a position inside that +// document. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentPositionParams +type TextDocumentPositionParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The position inside the text document. + Position Position `json:"position"` +} + +// General text document registration options. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentRegistrationOptions +type TextDocumentRegistrationOptions struct { + // A document selector to identify the scope of the registration. If set to null + // the document selector provided on the client side will be used. + DocumentSelector DocumentSelector `json:"documentSelector"` +} + +// Represents reasons why a text document is saved. +type TextDocumentSaveReason uint32 + +// Save registration options. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentSaveRegistrationOptions +type TextDocumentSaveRegistrationOptions struct { + TextDocumentRegistrationOptions + SaveOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentSyncClientCapabilities +type TextDocumentSyncClientCapabilities struct { + // Whether text document synchronization supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client supports sending will save notifications. + WillSave bool `json:"willSave,omitempty"` + // The client supports sending a will save request and + // waits for a response providing text edits which will + // be applied to the document before it is saved. + WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"` + // The client supports did save notifications. + DidSave bool `json:"didSave,omitempty"` +} + +// Defines how the host (editor) should sync +// document changes to the language server. +type TextDocumentSyncKind uint32 + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocumentSyncOptions +type TextDocumentSyncOptions struct { + // Open and close notifications are sent to the server. If omitted open close notification should not + // be sent. + OpenClose bool `json:"openClose,omitempty"` + // Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full + // and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None. + Change TextDocumentSyncKind `json:"change,omitempty"` + // If present will save notifications are sent to the server. If omitted the notification should not be + // sent. + WillSave bool `json:"willSave,omitempty"` + // If present will save wait until requests are sent to the server. If omitted the request should not be + // sent. + WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"` + // If present save notifications are sent to the server. If omitted the notification should not be + // sent. + Save *SaveOptions `json:"save,omitempty"` +} + +// A text edit applicable to a text document. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textEdit +type TextEdit struct { + // The range of the text document to be manipulated. To insert + // text into a document create a range where start === end. + Range Range `json:"range"` + // The string to be inserted. For delete operations use an + // empty string. + NewText string `json:"newText"` +} +type TokenFormat string +type TraceValue string + +// Since 3.6.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeDefinitionClientCapabilities +type TypeDefinitionClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is set to `true` + // the client supports the new `TypeDefinitionRegistrationOptions` return value + // for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // The client supports additional metadata in the form of definition links. + // + // Since 3.14.0 + LinkSupport bool `json:"linkSupport,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeDefinitionOptions +type TypeDefinitionOptions struct { + WorkDoneProgressOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeDefinitionParams +type TypeDefinitionParams struct { + TextDocumentPositionParams + WorkDoneProgressParams + PartialResultParams +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeDefinitionRegistrationOptions +type TypeDefinitionRegistrationOptions struct { + TextDocumentRegistrationOptions + TypeDefinitionOptions + StaticRegistrationOptions +} + +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchyClientCapabilities +type TypeHierarchyClientCapabilities struct { + // Whether implementation supports dynamic registration. If this is set to `true` + // the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + // return value for the corresponding server capability as well. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` +} + +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchyItem +type TypeHierarchyItem struct { + // The name of this item. + Name string `json:"name"` + // The kind of this item. + Kind SymbolKind `json:"kind"` + // Tags for this item. + Tags []SymbolTag `json:"tags,omitempty"` + // More detail for this item, e.g. the signature of a function. + Detail string `json:"detail,omitempty"` + // The resource identifier of this item. + URI DocumentURI `json:"uri"` + // The range enclosing this symbol not including leading/trailing whitespace + // but everything else, e.g. comments and code. + Range Range `json:"range"` + // The range that should be selected and revealed when this symbol is being + // picked, e.g. the name of a function. Must be contained by the + // {@link TypeHierarchyItem.range `range`}. + SelectionRange Range `json:"selectionRange"` + // A data entry field that is preserved between a type hierarchy prepare and + // supertypes or subtypes requests. It could also be used to identify the + // type hierarchy in the server, helping improve the performance on + // resolving supertypes and subtypes. + Data interface{} `json:"data,omitempty"` +} + +// Type hierarchy options used during static registration. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchyOptions +type TypeHierarchyOptions struct { + WorkDoneProgressOptions +} + +// The parameter of a `textDocument/prepareTypeHierarchy` request. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchyPrepareParams +type TypeHierarchyPrepareParams struct { + TextDocumentPositionParams + WorkDoneProgressParams +} + +// Type hierarchy options used during static or dynamic registration. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchyRegistrationOptions +type TypeHierarchyRegistrationOptions struct { + TextDocumentRegistrationOptions + TypeHierarchyOptions + StaticRegistrationOptions +} + +// The parameter of a `typeHierarchy/subtypes` request. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchySubtypesParams +type TypeHierarchySubtypesParams struct { + Item TypeHierarchyItem `json:"item"` + WorkDoneProgressParams + PartialResultParams +} + +// The parameter of a `typeHierarchy/supertypes` request. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchySupertypesParams +type TypeHierarchySupertypesParams struct { + Item TypeHierarchyItem `json:"item"` + WorkDoneProgressParams + PartialResultParams +} + +// created for Tuple +type UIntCommaUInt struct { + Fld0 uint32 `json:"fld0"` + Fld1 uint32 `json:"fld1"` +} + +// A diagnostic report indicating that the last returned +// report is still accurate. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#unchangedDocumentDiagnosticReport +type UnchangedDocumentDiagnosticReport struct { + // A document diagnostic report indicating + // no changes to the last result. A server can + // only return `unchanged` if result ids are + // provided. + Kind string `json:"kind"` + // A result id which will be sent on the next + // diagnostic request for the same document. + ResultID string `json:"resultId"` +} + +// Moniker uniqueness level to define scope of the moniker. +// +// @since 3.16.0 +type UniquenessLevel string + +// General parameters to unregister a request or notification. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#unregistration +type Unregistration struct { + // The id used to unregister the request or notification. Usually an id + // provided during the register request. + ID string `json:"id"` + // The method to unregister for. + Method string `json:"method"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#unregistrationParams +type UnregistrationParams struct { + Unregisterations []Unregistration `json:"unregisterations"` +} + +// A versioned notebook document identifier. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#versionedNotebookDocumentIdentifier +type VersionedNotebookDocumentIdentifier struct { + // The version number of this notebook document. + Version int32 `json:"version"` + // The notebook document's uri. + URI URI `json:"uri"` +} + +// A text document identifier to denote a specific version of a text document. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#versionedTextDocumentIdentifier +type VersionedTextDocumentIdentifier struct { + // The version number of this document. + Version int32 `json:"version"` + TextDocumentIdentifier +} +type WatchKind = uint32 // The parameters sent in a will save text document notification. +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#willSaveTextDocumentParams +type WillSaveTextDocumentParams struct { + // The document that will be saved. + TextDocument TextDocumentIdentifier `json:"textDocument"` + // The 'TextDocumentSaveReason'. + Reason TextDocumentSaveReason `json:"reason"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#windowClientCapabilities +type WindowClientCapabilities struct { + // It indicates whether the client supports server initiated + // progress using the `window/workDoneProgress/create` request. + // + // The capability also controls Whether client supports handling + // of progress notifications. If set servers are allowed to report a + // `workDoneProgress` property in the request specific server + // capabilities. + // + // @since 3.15.0 + WorkDoneProgress bool `json:"workDoneProgress,omitempty"` + // Capabilities specific to the showMessage request. + // + // @since 3.16.0 + ShowMessage *ShowMessageRequestClientCapabilities `json:"showMessage,omitempty"` + // Capabilities specific to the showDocument request. + // + // @since 3.16.0 + ShowDocument *ShowDocumentClientCapabilities `json:"showDocument,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workDoneProgressBegin +type WorkDoneProgressBegin struct { + Kind string `json:"kind"` + // Mandatory title of the progress operation. Used to briefly inform about + // the kind of operation being performed. + // + // Examples: "Indexing" or "Linking dependencies". + Title string `json:"title"` + // Controls if a cancel button should show to allow the user to cancel the + // long running operation. Clients that don't support cancellation are allowed + // to ignore the setting. + Cancellable bool `json:"cancellable,omitempty"` + // Optional, more detailed associated progress message. Contains + // complementary information to the `title`. + // + // Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + // If unset, the previous progress message (if any) is still valid. + Message string `json:"message,omitempty"` + // Optional progress percentage to display (value 100 is considered 100%). + // If not provided infinite progress is assumed and clients are allowed + // to ignore the `percentage` value in subsequent in report notifications. + // + // The value should be steadily rising. Clients are free to ignore values + // that are not following this rule. The value range is [0, 100]. + Percentage uint32 `json:"percentage,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workDoneProgressCancelParams +type WorkDoneProgressCancelParams struct { + // The token to be used to report progress. + Token ProgressToken `json:"token"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workDoneProgressCreateParams +type WorkDoneProgressCreateParams struct { + // The token to be used to report progress. + Token ProgressToken `json:"token"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workDoneProgressEnd +type WorkDoneProgressEnd struct { + Kind string `json:"kind"` + // Optional, a final message indicating to for example indicate the outcome + // of the operation. + Message string `json:"message,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workDoneProgressOptions +type WorkDoneProgressOptions struct { + WorkDoneProgress bool `json:"workDoneProgress,omitempty"` +} + +// created for And +type WorkDoneProgressOptionsAndTextDocumentRegistrationOptions struct { + WorkDoneProgressOptions + TextDocumentRegistrationOptions +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workDoneProgressParams +type WorkDoneProgressParams struct { + // An optional token that a server can use to report work done progress. + WorkDoneToken ProgressToken `json:"workDoneToken,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workDoneProgressReport +type WorkDoneProgressReport struct { + Kind string `json:"kind"` + // Controls enablement state of a cancel button. + // + // Clients that don't support cancellation or don't support controlling the button's + // enablement state are allowed to ignore the property. + Cancellable bool `json:"cancellable,omitempty"` + // Optional, more detailed associated progress message. Contains + // complementary information to the `title`. + // + // Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + // If unset, the previous progress message (if any) is still valid. + Message string `json:"message,omitempty"` + // Optional progress percentage to display (value 100 is considered 100%). + // If not provided infinite progress is assumed and clients are allowed + // to ignore the `percentage` value in subsequent in report notifications. + // + // The value should be steadily rising. Clients are free to ignore values + // that are not following this rule. The value range is [0, 100] + Percentage uint32 `json:"percentage,omitempty"` +} + +// Workspace specific client capabilities. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceClientCapabilities +type WorkspaceClientCapabilities struct { + // The client supports applying batch edits + // to the workspace by supporting the request + // 'workspace/applyEdit' + ApplyEdit bool `json:"applyEdit,omitempty"` + // Capabilities specific to `WorkspaceEdit`s. + WorkspaceEdit *WorkspaceEditClientCapabilities `json:"workspaceEdit,omitempty"` + // Capabilities specific to the `workspace/didChangeConfiguration` notification. + DidChangeConfiguration DidChangeConfigurationClientCapabilities `json:"didChangeConfiguration,omitempty"` + // Capabilities specific to the `workspace/didChangeWatchedFiles` notification. + DidChangeWatchedFiles DidChangeWatchedFilesClientCapabilities `json:"didChangeWatchedFiles,omitempty"` + // Capabilities specific to the `workspace/symbol` request. + Symbol *WorkspaceSymbolClientCapabilities `json:"symbol,omitempty"` + // Capabilities specific to the `workspace/executeCommand` request. + ExecuteCommand *ExecuteCommandClientCapabilities `json:"executeCommand,omitempty"` + // The client has support for workspace folders. + // + // @since 3.6.0 + WorkspaceFolders bool `json:"workspaceFolders,omitempty"` + // The client supports `workspace/configuration` requests. + // + // @since 3.6.0 + Configuration bool `json:"configuration,omitempty"` + // Capabilities specific to the semantic token requests scoped to the + // workspace. + // + // @since 3.16.0. + SemanticTokens *SemanticTokensWorkspaceClientCapabilities `json:"semanticTokens,omitempty"` + // Capabilities specific to the code lens requests scoped to the + // workspace. + // + // @since 3.16.0. + CodeLens *CodeLensWorkspaceClientCapabilities `json:"codeLens,omitempty"` + // The client has support for file notifications/requests for user operations on files. + // + // Since 3.16.0 + FileOperations *FileOperationClientCapabilities `json:"fileOperations,omitempty"` + // Capabilities specific to the inline values requests scoped to the + // workspace. + // + // @since 3.17.0. + InlineValue *InlineValueWorkspaceClientCapabilities `json:"inlineValue,omitempty"` + // Capabilities specific to the inlay hint requests scoped to the + // workspace. + // + // @since 3.17.0. + InlayHint *InlayHintWorkspaceClientCapabilities `json:"inlayHint,omitempty"` + // Capabilities specific to the diagnostic requests scoped to the + // workspace. + // + // @since 3.17.0. + Diagnostics *DiagnosticWorkspaceClientCapabilities `json:"diagnostics,omitempty"` + // Capabilities specific to the folding range requests scoped to the workspace. + // + // @since 3.18.0 + // @proposed + FoldingRange *FoldingRangeWorkspaceClientCapabilities `json:"foldingRange,omitempty"` +} + +// Parameters of the workspace diagnostic request. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceDiagnosticParams +type WorkspaceDiagnosticParams struct { + // The additional identifier provided during registration. + Identifier string `json:"identifier,omitempty"` + // The currently known diagnostic reports with their + // previous result ids. + PreviousResultIds []PreviousResultID `json:"previousResultIds"` + WorkDoneProgressParams + PartialResultParams +} + +// A workspace diagnostic report. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceDiagnosticReport +type WorkspaceDiagnosticReport struct { + Items []WorkspaceDocumentDiagnosticReport `json:"items"` +} + +// A partial result for a workspace diagnostic report. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceDiagnosticReportPartialResult +type WorkspaceDiagnosticReportPartialResult struct { + Items []WorkspaceDocumentDiagnosticReport `json:"items"` +} + +// A workspace diagnostic document report. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceDocumentDiagnosticReport +type WorkspaceDocumentDiagnosticReport = Or_WorkspaceDocumentDiagnosticReport // (alias) +// A workspace edit represents changes to many resources managed in the workspace. The edit +// should either provide `changes` or `documentChanges`. If documentChanges are present +// they are preferred over `changes` if the client can handle versioned document edits. +// +// Since version 3.13.0 a workspace edit can contain resource operations as well. If resource +// operations are present clients need to execute the operations in the order in which they +// are provided. So a workspace edit for example can consist of the following two changes: +// (1) a create file a.txt and (2) a text document edit which insert text into file a.txt. +// +// An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will +// cause failure of the operation. How the client recovers from the failure is described by +// the client capability: `workspace.workspaceEdit.failureHandling` +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceEdit +type WorkspaceEdit struct { + // Holds changes to existing resources. + Changes map[DocumentURI][]TextEdit `json:"changes,omitempty"` + // 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 []DocumentChange `json:"documentChanges,omitempty"` + // 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[ChangeAnnotationIdentifier]ChangeAnnotation `json:"changeAnnotations,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceEditClientCapabilities +type WorkspaceEditClientCapabilities struct { + // The client supports versioned document changes in `WorkspaceEdit`s + DocumentChanges bool `json:"documentChanges,omitempty"` + // The resource operations the client supports. Clients should at least + // support 'create', 'rename' and 'delete' files and folders. + // + // @since 3.13.0 + ResourceOperations []ResourceOperationKind `json:"resourceOperations,omitempty"` + // The failure handling strategy of a client if applying the workspace edit + // fails. + // + // @since 3.13.0 + FailureHandling *FailureHandlingKind `json:"failureHandling,omitempty"` + // Whether the client normalizes line endings to the client specific + // setting. + // If set to `true` the client will normalize line ending characters + // in a workspace edit to the client-specified new line + // character. + // + // @since 3.16.0 + NormalizesLineEndings bool `json:"normalizesLineEndings,omitempty"` + // Whether the client in general supports change annotations on text edits, + // create file, rename file and delete file changes. + // + // @since 3.16.0 + ChangeAnnotationSupport *ChangeAnnotationsSupportOptions `json:"changeAnnotationSupport,omitempty"` +} + +// A workspace folder inside a client. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceFolder +type WorkspaceFolder struct { + // The associated URI for this workspace folder. + URI URI `json:"uri"` + // The name of the workspace folder. Used to refer to this + // workspace folder in the user interface. + Name string `json:"name"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceFoldersServerCapabilities +type WorkspaceFolders5Gn struct { + // The server has support for workspace folders + Supported bool `json:"supported,omitempty"` + // Whether the server wants to receive workspace folder + // change notifications. + // + // If a string is provided the string is treated as an ID + // under which the notification is registered on the client + // side. The ID can be used to unregister for these events + // using the `client/unregisterCapability` request. + ChangeNotifications string `json:"changeNotifications,omitempty"` +} + +// The workspace folder change event. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceFoldersChangeEvent +type WorkspaceFoldersChangeEvent struct { + // The array of added workspace folders + Added []WorkspaceFolder `json:"added"` + // The array of the removed workspace folders + Removed []WorkspaceFolder `json:"removed"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceFoldersInitializeParams +type WorkspaceFoldersInitializeParams struct { + // The workspace folders configured in the client when the server starts. + // + // This property is only available if the client supports workspace folders. + // It can be `null` if the client supports workspace folders but none are + // configured. + // + // @since 3.6.0 + WorkspaceFolders []WorkspaceFolder `json:"workspaceFolders,omitempty"` +} + +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceFoldersServerCapabilities +type WorkspaceFoldersServerCapabilities struct { + // The server has support for workspace folders + Supported bool `json:"supported,omitempty"` + // Whether the server wants to receive workspace folder + // change notifications. + // + // If a string is provided the string is treated as an ID + // under which the notification is registered on the client + // side. The ID can be used to unregister for these events + // using the `client/unregisterCapability` request. + ChangeNotifications string `json:"changeNotifications,omitempty"` +} + +// A full document diagnostic report for a workspace diagnostic result. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceFullDocumentDiagnosticReport +type WorkspaceFullDocumentDiagnosticReport struct { + // The URI for which diagnostic information is reported. + URI DocumentURI `json:"uri"` + // The version number for which the diagnostics are reported. + // If the document is not marked as open `null` can be provided. + Version int32 `json:"version"` + FullDocumentDiagnosticReport +} + +// Defines workspace specific capabilities of the server. +// +// @since 3.18.0 +// @proposed +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceOptions +type WorkspaceOptions struct { + // The server supports workspace folder. + // + // @since 3.6.0 + WorkspaceFolders *WorkspaceFolders5Gn `json:"workspaceFolders,omitempty"` + // The server is interested in notifications/requests for operations on files. + // + // @since 3.16.0 + FileOperations *FileOperationOptions `json:"fileOperations,omitempty"` +} + +// A special workspace symbol that supports locations without a range. +// +// See also SymbolInformation. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceSymbol +type WorkspaceSymbol struct { + // The location of the symbol. Whether a server is allowed to + // return a location without a range depends on the client + // capability `workspace.symbol.resolveSupport`. + // + // See SymbolInformation#location for more details. + Location OrPLocation_workspace_symbol `json:"location"` + // A data entry field that is preserved on a workspace symbol between a + // workspace symbol request and a workspace symbol resolve request. + Data interface{} `json:"data,omitempty"` + BaseSymbolInformation +} + +// Client capabilities for a {@link WorkspaceSymbolRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceSymbolClientCapabilities +type WorkspaceSymbolClientCapabilities struct { + // Symbol request supports dynamic registration. + DynamicRegistration bool `json:"dynamicRegistration,omitempty"` + // Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. + SymbolKind *ClientSymbolKindOptions `json:"symbolKind,omitempty"` + // The client supports tags on `SymbolInformation`. + // Clients supporting tags have to handle unknown tags gracefully. + // + // @since 3.16.0 + TagSupport *ClientSymbolTagOptions `json:"tagSupport,omitempty"` + // The client support partial workspace symbols. The client will send the + // request `workspaceSymbol/resolve` to the server to resolve additional + // properties. + // + // @since 3.17.0 + ResolveSupport *ClientSymbolResolveOptions `json:"resolveSupport,omitempty"` +} + +// Server capabilities for a {@link WorkspaceSymbolRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceSymbolOptions +type WorkspaceSymbolOptions struct { + // The server provides support to resolve additional + // information for a workspace symbol. + // + // @since 3.17.0 + ResolveProvider bool `json:"resolveProvider,omitempty"` + WorkDoneProgressOptions +} + +// The parameters of a {@link WorkspaceSymbolRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceSymbolParams +type WorkspaceSymbolParams struct { + // A query string to filter symbols by. Clients may send an empty + // string here to request all symbols. + Query string `json:"query"` + WorkDoneProgressParams + PartialResultParams +} + +// Registration options for a {@link WorkspaceSymbolRequest}. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceSymbolRegistrationOptions +type WorkspaceSymbolRegistrationOptions struct { + WorkspaceSymbolOptions +} + +// An unchanged document diagnostic report for a workspace diagnostic result. +// +// @since 3.17.0 +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceUnchangedDocumentDiagnosticReport +type WorkspaceUnchangedDocumentDiagnosticReport struct { + // The URI for which diagnostic information is reported. + URI DocumentURI `json:"uri"` + // The version number for which the diagnostics are reported. + // If the document is not marked as open `null` can be provided. + Version int32 `json:"version"` + UnchangedDocumentDiagnosticReport +} + +// The initialize parameters +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#_InitializeParams +type XInitializeParams struct { + // The process Id of the parent process that started + // the server. + // + // Is `null` if the process has not been started by another process. + // If the parent process is not alive then the server should exit. + ProcessID int32 `json:"processId"` + // Information about the client + // + // @since 3.15.0 + ClientInfo *ClientInfo `json:"clientInfo,omitempty"` + // The locale the client is currently showing the user interface + // in. This must not necessarily be the locale of the operating + // system. + // + // Uses IETF language tags as the value's syntax + // (See https://en.wikipedia.org/wiki/IETF_language_tag) + // + // @since 3.16.0 + Locale string `json:"locale,omitempty"` + // The rootPath of the workspace. Is null + // if no folder is open. + // + // @deprecated in favour of rootUri. + RootPath string `json:"rootPath,omitempty"` + // The rootUri of the workspace. Is null if no + // folder is open. If both `rootPath` and `rootUri` are set + // `rootUri` wins. + // + // @deprecated in favour of workspaceFolders. + RootURI DocumentURI `json:"rootUri"` + // The capabilities provided by the client (editor or tool) + Capabilities ClientCapabilities `json:"capabilities"` + // User provided initialization options. + InitializationOptions interface{} `json:"initializationOptions,omitempty"` + // The initial trace setting. If omitted trace is disabled ('off'). + Trace *TraceValue `json:"trace,omitempty"` + WorkDoneProgressParams +} + +// The initialize parameters +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#_InitializeParams +type _InitializeParams struct { + // The process Id of the parent process that started + // the server. + // + // Is `null` if the process has not been started by another process. + // If the parent process is not alive then the server should exit. + ProcessID int32 `json:"processId"` + // Information about the client + // + // @since 3.15.0 + ClientInfo *ClientInfo `json:"clientInfo,omitempty"` + // The locale the client is currently showing the user interface + // in. This must not necessarily be the locale of the operating + // system. + // + // Uses IETF language tags as the value's syntax + // (See https://en.wikipedia.org/wiki/IETF_language_tag) + // + // @since 3.16.0 + Locale string `json:"locale,omitempty"` + // The rootPath of the workspace. Is null + // if no folder is open. + // + // @deprecated in favour of rootUri. + RootPath string `json:"rootPath,omitempty"` + // The rootUri of the workspace. Is null if no + // folder is open. If both `rootPath` and `rootUri` are set + // `rootUri` wins. + // + // @deprecated in favour of workspaceFolders. + RootURI DocumentURI `json:"rootUri"` + // The capabilities provided by the client (editor or tool) + Capabilities ClientCapabilities `json:"capabilities"` + // User provided initialization options. + InitializationOptions interface{} `json:"initializationOptions,omitempty"` + // The initial trace setting. If omitted trace is disabled ('off'). + Trace *TraceValue `json:"trace,omitempty"` + WorkDoneProgressParams +} + +const ( + // A set of predefined code action kinds + // Empty kind. + Empty CodeActionKind = "" + // Base kind for quickfix actions: 'quickfix' + QuickFix CodeActionKind = "quickfix" + // Base kind for refactoring actions: 'refactor' + Refactor CodeActionKind = "refactor" + // Base kind for refactoring extraction actions: 'refactor.extract' + // + // Example extract actions: + // + // + // - Extract method + // - Extract function + // - Extract variable + // - Extract interface from class + // - ... + RefactorExtract CodeActionKind = "refactor.extract" + // Base kind for refactoring inline actions: 'refactor.inline' + // + // Example inline actions: + // + // + // - Inline function + // - Inline variable + // - Inline constant + // - ... + RefactorInline CodeActionKind = "refactor.inline" + // Base kind for refactoring move actions: `refactor.move` + // + // Example move actions: + // + // + // - Move a function to a new file + // - Move a property between classes + // - Move method to base class + // - ... + // + // @since 3.18.0 + // @proposed + RefactorMove CodeActionKind = "refactor.move" + // Base kind for refactoring rewrite actions: 'refactor.rewrite' + // + // Example rewrite actions: + // + // + // - Convert JavaScript function to class + // - Add or remove parameter + // - Encapsulate field + // - Make method static + // - Move method to base class + // - ... + RefactorRewrite CodeActionKind = "refactor.rewrite" + // Base kind for source actions: `source` + // + // Source code actions apply to the entire file. + Source CodeActionKind = "source" + // Base kind for an organize imports source action: `source.organizeImports` + SourceOrganizeImports CodeActionKind = "source.organizeImports" + // Base kind for auto-fix source actions: `source.fixAll`. + // + // Fix all actions automatically fix errors that have a clear fix that do not require user input. + // They should not suppress errors or perform unsafe fixes such as generating new types or classes. + // + // @since 3.15.0 + SourceFixAll CodeActionKind = "source.fixAll" + // Base kind for all code actions applying to the entire notebook's scope. CodeActionKinds using + // this should always begin with `notebook.` + // + // @since 3.18.0 + Notebook CodeActionKind = "notebook" + // The reason why code actions were requested. + // + // @since 3.17.0 + // Code actions were explicitly requested by the user or by an extension. + CodeActionInvoked CodeActionTriggerKind = 1 + // Code actions were requested automatically. + // + // This typically happens when current selection in a file changes, but can + // also be triggered when file content changes. + CodeActionAutomatic CodeActionTriggerKind = 2 + // The kind of a completion entry. + TextCompletion CompletionItemKind = 1 + MethodCompletion CompletionItemKind = 2 + FunctionCompletion CompletionItemKind = 3 + ConstructorCompletion CompletionItemKind = 4 + FieldCompletion CompletionItemKind = 5 + VariableCompletion CompletionItemKind = 6 + ClassCompletion CompletionItemKind = 7 + InterfaceCompletion CompletionItemKind = 8 + ModuleCompletion CompletionItemKind = 9 + PropertyCompletion CompletionItemKind = 10 + UnitCompletion CompletionItemKind = 11 + ValueCompletion CompletionItemKind = 12 + EnumCompletion CompletionItemKind = 13 + KeywordCompletion CompletionItemKind = 14 + SnippetCompletion CompletionItemKind = 15 + ColorCompletion CompletionItemKind = 16 + FileCompletion CompletionItemKind = 17 + ReferenceCompletion CompletionItemKind = 18 + FolderCompletion CompletionItemKind = 19 + EnumMemberCompletion CompletionItemKind = 20 + ConstantCompletion CompletionItemKind = 21 + StructCompletion CompletionItemKind = 22 + EventCompletion CompletionItemKind = 23 + OperatorCompletion CompletionItemKind = 24 + TypeParameterCompletion CompletionItemKind = 25 + // Completion item tags are extra annotations that tweak the rendering of a completion + // item. + // + // @since 3.15.0 + // Render a completion as obsolete, usually using a strike-out. + ComplDeprecated CompletionItemTag = 1 + // How a completion was triggered + // Completion was triggered by typing an identifier (24x7 code + // complete), manual invocation (e.g Ctrl+Space) or via API. + Invoked CompletionTriggerKind = 1 + // Completion was triggered by a trigger character specified by + // the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + TriggerCharacter CompletionTriggerKind = 2 + // Completion was re-triggered as current completion list is incomplete + TriggerForIncompleteCompletions CompletionTriggerKind = 3 + // The diagnostic's severity. + // Reports an error. + SeverityError DiagnosticSeverity = 1 + // Reports a warning. + SeverityWarning DiagnosticSeverity = 2 + // Reports an information. + SeverityInformation DiagnosticSeverity = 3 + // Reports a hint. + SeverityHint DiagnosticSeverity = 4 + // The diagnostic tags. + // + // @since 3.15.0 + // Unused or unnecessary code. + // + // Clients are allowed to render diagnostics with this tag faded out instead of having + // an error squiggle. + Unnecessary DiagnosticTag = 1 + // Deprecated or obsolete code. + // + // Clients are allowed to rendered diagnostics with this tag strike through. + Deprecated DiagnosticTag = 2 + // The document diagnostic report kinds. + // + // @since 3.17.0 + // A diagnostic report with a full + // set of problems. + DiagnosticFull DocumentDiagnosticReportKind = "full" + // A report indicating that the last + // returned report is still accurate. + DiagnosticUnchanged DocumentDiagnosticReportKind = "unchanged" + // A document highlight kind. + // A textual occurrence. + Text DocumentHighlightKind = 1 + // Read-access of a symbol, like reading a variable. + Read DocumentHighlightKind = 2 + // Write-access of a symbol, like writing to a variable. + Write DocumentHighlightKind = 3 + // Predefined error codes. + ParseError ErrorCodes = -32700 + InvalidRequest ErrorCodes = -32600 + MethodNotFound ErrorCodes = -32601 + InvalidParams ErrorCodes = -32602 + InternalError ErrorCodes = -32603 + // Error code indicating that a server received a notification or + // request before the server has received the `initialize` request. + ServerNotInitialized ErrorCodes = -32002 + UnknownErrorCode ErrorCodes = -32001 + // Applying the workspace change is simply aborted if one of the changes provided + // fails. All operations executed before the failing operation stay executed. + Abort FailureHandlingKind = "abort" + // All operations are executed transactional. That means they either all + // succeed or no changes at all are applied to the workspace. + Transactional FailureHandlingKind = "transactional" + // If the workspace edit contains only textual file changes they are executed transactional. + // If resource changes (create, rename or delete file) are part of the change the failure + // handling strategy is abort. + TextOnlyTransactional FailureHandlingKind = "textOnlyTransactional" + // The client tries to undo the operations already executed. But there is no + // guarantee that this is succeeding. + Undo FailureHandlingKind = "undo" + // The file event type + // The file got created. + Created FileChangeType = 1 + // The file got changed. + Changed FileChangeType = 2 + // The file got deleted. + Deleted FileChangeType = 3 + // A pattern kind describing if a glob pattern matches a file a folder or + // both. + // + // @since 3.16.0 + // The pattern matches a file only. + FilePattern FileOperationPatternKind = "file" + // The pattern matches a folder only. + FolderPattern FileOperationPatternKind = "folder" + // A set of predefined range kinds. + // Folding range for a comment + Comment FoldingRangeKind = "comment" + // Folding range for an import or include + Imports FoldingRangeKind = "imports" + // Folding range for a region (e.g. `#region`) + Region FoldingRangeKind = "region" + // Inlay hint kinds. + // + // @since 3.17.0 + // An inlay hint that for a type annotation. + Type InlayHintKind = 1 + // An inlay hint that is for a parameter. + Parameter InlayHintKind = 2 + // Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. + // + // @since 3.18.0 + // @proposed + // Completion was triggered explicitly by a user gesture. + InlineInvoked InlineCompletionTriggerKind = 1 + // Completion was triggered automatically while editing. + InlineAutomatic InlineCompletionTriggerKind = 2 + // Defines whether the insert text in a completion item should be interpreted as + // plain text or a snippet. + // The primary text to be inserted is treated as a plain string. + PlainTextTextFormat InsertTextFormat = 1 + // The primary text to be inserted is treated as a snippet. + // + // A snippet can define tab stops and placeholders with `$1`, `$2` + // and `${3:foo}`. `$0` defines the final tab stop, it defaults to + // the end of the snippet. Placeholders with equal identifiers are linked, + // that is typing in one will update others too. + // + // See also: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#snippet_syntax + SnippetTextFormat InsertTextFormat = 2 + // How whitespace and indentation is handled during completion + // item insertion. + // + // @since 3.16.0 + // The insertion or replace strings is taken as it is. If the + // value is multi line the lines below the cursor will be + // inserted using the indentation defined in the string value. + // The client will not apply any kind of adjustments to the + // string. + AsIs InsertTextMode = 1 + // The editor adjusts leading whitespace of new lines so that + // they match the indentation up to the cursor of the line for + // which the item is accepted. + // + // Consider a line like this: <2tabs><3tabs>foo. Accepting a + // multi line completion item is indented using 2 tabs and all + // following lines inserted will be indented using 2 tabs as well. + AdjustIndentation InsertTextMode = 2 + // A request failed but it was syntactically correct, e.g the + // method name was known and the parameters were valid. The error + // message should contain human readable information about why + // the request failed. + // + // @since 3.17.0 + RequestFailed LSPErrorCodes = -32803 + // The server cancelled the request. This error code should + // only be used for requests that explicitly support being + // server cancellable. + // + // @since 3.17.0 + ServerCancelled LSPErrorCodes = -32802 + // The server detected that the content of a document got + // modified outside normal conditions. A server should + // NOT send this error code if it detects a content change + // in it unprocessed messages. The result even computed + // on an older state might still be useful for the client. + // + // If a client decides that a result is not of any use anymore + // the client should cancel the request. + ContentModified LSPErrorCodes = -32801 + // The client has canceled a request and a server as detected + // the cancel. + RequestCancelled LSPErrorCodes = -32800 + // Predefined Language kinds + // @since 3.18.0 + // @proposed + LangABAP LanguageKind = "abap" + LangWindowsBat LanguageKind = "bat" + LangBibTeX LanguageKind = "bibtex" + LangClojure LanguageKind = "clojure" + LangCoffeescript LanguageKind = "coffeescript" + LangC LanguageKind = "c" + LangCPP LanguageKind = "cpp" + LangCSharp LanguageKind = "csharp" + LangCSS LanguageKind = "css" + // @since 3.18.0 + // @proposed + LangD LanguageKind = "d" + // @since 3.18.0 + // @proposed + LangDelphi LanguageKind = "pascal" + LangDiff LanguageKind = "diff" + LangDart LanguageKind = "dart" + LangDockerfile LanguageKind = "dockerfile" + LangElixir LanguageKind = "elixir" + LangErlang LanguageKind = "erlang" + LangFSharp LanguageKind = "fsharp" + LangGitCommit LanguageKind = "git-commit" + LangGitRebase LanguageKind = "rebase" + LangGo LanguageKind = "go" + LangGroovy LanguageKind = "groovy" + LangHandlebars LanguageKind = "handlebars" + LangHTML LanguageKind = "html" + LangIni LanguageKind = "ini" + LangJava LanguageKind = "java" + LangJavaScript LanguageKind = "javascript" + LangJavaScriptReact LanguageKind = "javascriptreact" + LangJSON LanguageKind = "json" + LangLaTeX LanguageKind = "latex" + LangLess LanguageKind = "less" + LangLua LanguageKind = "lua" + LangMakefile LanguageKind = "makefile" + LangMarkdown LanguageKind = "markdown" + LangObjectiveC LanguageKind = "objective-c" + LangObjectiveCPP LanguageKind = "objective-cpp" + // @since 3.18.0 + // @proposed + LangPascal LanguageKind = "pascal" + LangPerl LanguageKind = "perl" + LangPerl6 LanguageKind = "perl6" + LangPHP LanguageKind = "php" + LangPowershell LanguageKind = "powershell" + LangPug LanguageKind = "jade" + LangPython LanguageKind = "python" + LangR LanguageKind = "r" + LangRazor LanguageKind = "razor" + LangRuby LanguageKind = "ruby" + LangRust LanguageKind = "rust" + LangSCSS LanguageKind = "scss" + LangSASS LanguageKind = "sass" + LangScala LanguageKind = "scala" + LangShaderLab LanguageKind = "shaderlab" + LangShellScript LanguageKind = "shellscript" + LangSQL LanguageKind = "sql" + LangSwift LanguageKind = "swift" + LangTypeScript LanguageKind = "typescript" + LangTypeScriptReact LanguageKind = "typescriptreact" + LangTeX LanguageKind = "tex" + LangVisualBasic LanguageKind = "vb" + LangXML LanguageKind = "xml" + LangXSL LanguageKind = "xsl" + LangYAML LanguageKind = "yaml" + // Describes the content type that a client supports in various + // result literals like `Hover`, `ParameterInfo` or `CompletionItem`. + // + // Please note that `MarkupKinds` must not start with a `$`. This kinds + // are reserved for internal usage. + // Plain text is supported as a content format + PlainText MarkupKind = "plaintext" + // Markdown is supported as a content format + Markdown MarkupKind = "markdown" + // The message type + // An error message. + Error MessageType = 1 + // A warning message. + Warning MessageType = 2 + // An information message. + Info MessageType = 3 + // A log message. + Log MessageType = 4 + // A debug message. + // + // @since 3.18.0 + // @proposed + Debug MessageType = 5 + // The moniker kind. + // + // @since 3.16.0 + // The moniker represent a symbol that is imported into a project + Import MonikerKind = "import" + // The moniker represents a symbol that is exported from a project + Export MonikerKind = "export" + // The moniker represents a symbol that is local to a project (e.g. a local + // variable of a function, a class not visible outside the project, ...) + Local MonikerKind = "local" + // A notebook cell kind. + // + // @since 3.17.0 + // A markup-cell is formatted source that is used for display. + Markup NotebookCellKind = 1 + // A code-cell is source code. + Code NotebookCellKind = 2 + // A set of predefined position encoding kinds. + // + // @since 3.17.0 + // Character offsets count UTF-8 code units (e.g. bytes). + UTF8 PositionEncodingKind = "utf-8" + // Character offsets count UTF-16 code units. + // + // This is the default and must always be supported + // by servers + UTF16 PositionEncodingKind = "utf-16" + // Character offsets count UTF-32 code units. + // + // Implementation note: these are the same as Unicode codepoints, + // so this `PositionEncodingKind` may also be used for an + // encoding-agnostic representation of character offsets. + UTF32 PositionEncodingKind = "utf-32" + // The client's default behavior is to select the identifier + // according the to language's syntax rule. + Identifier PrepareSupportDefaultBehavior = 1 + // Supports creating new files and folders. + Create ResourceOperationKind = "create" + // Supports renaming existing files and folders. + Rename ResourceOperationKind = "rename" + // Supports deleting existing files and folders. + Delete ResourceOperationKind = "delete" + // A set of predefined token modifiers. This set is not fixed + // an clients can specify additional token types via the + // corresponding client capabilities. + // + // @since 3.16.0 + ModDeclaration SemanticTokenModifiers = "declaration" + ModDefinition SemanticTokenModifiers = "definition" + ModReadonly SemanticTokenModifiers = "readonly" + ModStatic SemanticTokenModifiers = "static" + ModDeprecated SemanticTokenModifiers = "deprecated" + ModAbstract SemanticTokenModifiers = "abstract" + ModAsync SemanticTokenModifiers = "async" + ModModification SemanticTokenModifiers = "modification" + ModDocumentation SemanticTokenModifiers = "documentation" + ModDefaultLibrary SemanticTokenModifiers = "defaultLibrary" + // A set of predefined token types. This set is not fixed + // an clients can specify additional token types via the + // corresponding client capabilities. + // + // @since 3.16.0 + NamespaceType SemanticTokenTypes = "namespace" + // Represents a generic type. Acts as a fallback for types which can't be mapped to + // a specific type like class or enum. + TypeType SemanticTokenTypes = "type" + ClassType SemanticTokenTypes = "class" + EnumType SemanticTokenTypes = "enum" + InterfaceType SemanticTokenTypes = "interface" + StructType SemanticTokenTypes = "struct" + TypeParameterType SemanticTokenTypes = "typeParameter" + ParameterType SemanticTokenTypes = "parameter" + VariableType SemanticTokenTypes = "variable" + PropertyType SemanticTokenTypes = "property" + EnumMemberType SemanticTokenTypes = "enumMember" + EventType SemanticTokenTypes = "event" + FunctionType SemanticTokenTypes = "function" + MethodType SemanticTokenTypes = "method" + MacroType SemanticTokenTypes = "macro" + KeywordType SemanticTokenTypes = "keyword" + ModifierType SemanticTokenTypes = "modifier" + CommentType SemanticTokenTypes = "comment" + StringType SemanticTokenTypes = "string" + NumberType SemanticTokenTypes = "number" + RegexpType SemanticTokenTypes = "regexp" + OperatorType SemanticTokenTypes = "operator" + // @since 3.17.0 + DecoratorType SemanticTokenTypes = "decorator" + // How a signature help was triggered. + // + // @since 3.15.0 + // Signature help was invoked manually by the user or by a command. + SigInvoked SignatureHelpTriggerKind = 1 + // Signature help was triggered by a trigger character. + SigTriggerCharacter SignatureHelpTriggerKind = 2 + // Signature help was triggered by the cursor moving or by the document content changing. + SigContentChange SignatureHelpTriggerKind = 3 + // A symbol kind. + File SymbolKind = 1 + Module SymbolKind = 2 + Namespace SymbolKind = 3 + Package SymbolKind = 4 + Class SymbolKind = 5 + Method SymbolKind = 6 + Property SymbolKind = 7 + Field SymbolKind = 8 + Constructor SymbolKind = 9 + Enum SymbolKind = 10 + Interface SymbolKind = 11 + Function SymbolKind = 12 + Variable SymbolKind = 13 + Constant SymbolKind = 14 + String SymbolKind = 15 + Number SymbolKind = 16 + Boolean SymbolKind = 17 + Array SymbolKind = 18 + Object SymbolKind = 19 + Key SymbolKind = 20 + Null SymbolKind = 21 + EnumMember SymbolKind = 22 + Struct SymbolKind = 23 + Event SymbolKind = 24 + Operator SymbolKind = 25 + TypeParameter SymbolKind = 26 + // Symbol tags are extra annotations that tweak the rendering of a symbol. + // + // @since 3.16 + // Render a symbol as obsolete, usually using a strike-out. + DeprecatedSymbol SymbolTag = 1 + // Represents reasons why a text document is saved. + // Manually triggered, e.g. by the user pressing save, by starting debugging, + // or by an API call. + Manual TextDocumentSaveReason = 1 + // Automatic after a delay. + AfterDelay TextDocumentSaveReason = 2 + // When the editor lost focus. + FocusOut TextDocumentSaveReason = 3 + // Defines how the host (editor) should sync + // document changes to the language server. + // Documents should not be synced at all. + None TextDocumentSyncKind = 0 + // Documents are synced by always sending the full content + // of the document. + Full TextDocumentSyncKind = 1 + // Documents are synced by sending the full content on open. + // After that only incremental updates to the document are + // send. + Incremental TextDocumentSyncKind = 2 + Relative TokenFormat = "relative" + // Turn tracing off. + Off TraceValue = "off" + // Trace messages only. + Messages TraceValue = "messages" + // Verbose message tracing. + Verbose TraceValue = "verbose" + // Moniker uniqueness level to define scope of the moniker. + // + // @since 3.16.0 + // The moniker is only unique inside a document + Document UniquenessLevel = "document" + // The moniker is unique inside a project for which a dump got created + Project UniquenessLevel = "project" + // The moniker is unique inside the group to which a project belongs + Group UniquenessLevel = "group" + // The moniker is unique inside the moniker scheme. + Scheme UniquenessLevel = "scheme" + // The moniker is globally unique + Global UniquenessLevel = "global" + // Interested in create events. + WatchCreate WatchKind = 1 + // Interested in change events + WatchChange WatchKind = 2 + // Interested in delete events + WatchDelete WatchKind = 4 +) diff --git a/contribs/gnopls/internal/protocol/tsserver.go b/contribs/gnopls/internal/protocol/tsserver.go new file mode 100644 index 00000000000..b405aae1b89 --- /dev/null +++ b/contribs/gnopls/internal/protocol/tsserver.go @@ -0,0 +1,1334 @@ +// 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. + +// Code generated for LSP. DO NOT EDIT. + +package protocol + +// Code generated from protocol/metaModel.json at ref release/protocol/3.17.6-next.2 (hash 654dc9be6673c61476c28fda604406279c3258d7). +// https://github.com/microsoft/vscode-languageserver-node/blob/release/protocol/3.17.6-next.2/protocol/metaModel.json +// LSP metaData.version = 3.17.0. + +import ( + "context" + + "golang.org/x/tools/internal/jsonrpc2" +) + +type Server interface { + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#progress + Progress(context.Context, *ProgressParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#setTrace + SetTrace(context.Context, *SetTraceParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchy_incomingCalls + IncomingCalls(context.Context, *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchy_outgoingCalls + OutgoingCalls(context.Context, *CallHierarchyOutgoingCallsParams) ([]CallHierarchyOutgoingCall, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeAction_resolve + ResolveCodeAction(context.Context, *CodeAction) (*CodeAction, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#codeLens_resolve + ResolveCodeLens(context.Context, *CodeLens) (*CodeLens, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#completionItem_resolve + ResolveCompletionItem(context.Context, *CompletionItem) (*CompletionItem, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentLink_resolve + ResolveDocumentLink(context.Context, *DocumentLink) (*DocumentLink, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#exit + Exit(context.Context) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#initialize + Initialize(context.Context, *ParamInitialize) (*InitializeResult, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#initialized + Initialized(context.Context, *InitializedParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#inlayHint_resolve + Resolve(context.Context, *InlayHint) (*InlayHint, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocument_didChange + DidChangeNotebookDocument(context.Context, *DidChangeNotebookDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocument_didClose + DidCloseNotebookDocument(context.Context, *DidCloseNotebookDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocument_didOpen + DidOpenNotebookDocument(context.Context, *DidOpenNotebookDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#notebookDocument_didSave + DidSaveNotebookDocument(context.Context, *DidSaveNotebookDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#shutdown + Shutdown(context.Context) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_codeAction + CodeAction(context.Context, *CodeActionParams) ([]CodeAction, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_codeLens + CodeLens(context.Context, *CodeLensParams) ([]CodeLens, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_colorPresentation + ColorPresentation(context.Context, *ColorPresentationParams) ([]ColorPresentation, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_completion + Completion(context.Context, *CompletionParams) (*CompletionList, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_declaration + Declaration(context.Context, *DeclarationParams) (*Or_textDocument_declaration, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_definition + Definition(context.Context, *DefinitionParams) ([]Location, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_diagnostic + Diagnostic(context.Context, *string) (*string, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_didChange + DidChange(context.Context, *DidChangeTextDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_didClose + DidClose(context.Context, *DidCloseTextDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_didOpen + DidOpen(context.Context, *DidOpenTextDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_didSave + DidSave(context.Context, *DidSaveTextDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_documentColor + DocumentColor(context.Context, *DocumentColorParams) ([]ColorInformation, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_documentHighlight + DocumentHighlight(context.Context, *DocumentHighlightParams) ([]DocumentHighlight, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_documentLink + DocumentLink(context.Context, *DocumentLinkParams) ([]DocumentLink, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_documentSymbol + DocumentSymbol(context.Context, *DocumentSymbolParams) ([]interface{}, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_foldingRange + FoldingRange(context.Context, *FoldingRangeParams) ([]FoldingRange, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_formatting + Formatting(context.Context, *DocumentFormattingParams) ([]TextEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_hover + Hover(context.Context, *HoverParams) (*Hover, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_implementation + Implementation(context.Context, *ImplementationParams) ([]Location, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_inlayHint + InlayHint(context.Context, *InlayHintParams) ([]InlayHint, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_inlineCompletion + InlineCompletion(context.Context, *InlineCompletionParams) (*Or_Result_textDocument_inlineCompletion, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_inlineValue + InlineValue(context.Context, *InlineValueParams) ([]InlineValue, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_linkedEditingRange + LinkedEditingRange(context.Context, *LinkedEditingRangeParams) (*LinkedEditingRanges, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_moniker + Moniker(context.Context, *MonikerParams) ([]Moniker, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_onTypeFormatting + OnTypeFormatting(context.Context, *DocumentOnTypeFormattingParams) ([]TextEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_prepareCallHierarchy + PrepareCallHierarchy(context.Context, *CallHierarchyPrepareParams) ([]CallHierarchyItem, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_prepareRename + PrepareRename(context.Context, *PrepareRenameParams) (*PrepareRenameResult, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_prepareTypeHierarchy + PrepareTypeHierarchy(context.Context, *TypeHierarchyPrepareParams) ([]TypeHierarchyItem, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_rangeFormatting + RangeFormatting(context.Context, *DocumentRangeFormattingParams) ([]TextEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_rangesFormatting + RangesFormatting(context.Context, *DocumentRangesFormattingParams) ([]TextEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_references + References(context.Context, *ReferenceParams) ([]Location, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_rename + Rename(context.Context, *RenameParams) (*WorkspaceEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_selectionRange + SelectionRange(context.Context, *SelectionRangeParams) ([]SelectionRange, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_semanticTokens_full + SemanticTokensFull(context.Context, *SemanticTokensParams) (*SemanticTokens, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_semanticTokens_full_delta + SemanticTokensFullDelta(context.Context, *SemanticTokensDeltaParams) (interface{}, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_semanticTokens_range + SemanticTokensRange(context.Context, *SemanticTokensRangeParams) (*SemanticTokens, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_signatureHelp + SignatureHelp(context.Context, *SignatureHelpParams) (*SignatureHelp, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_typeDefinition + TypeDefinition(context.Context, *TypeDefinitionParams) ([]Location, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_willSave + WillSave(context.Context, *WillSaveTextDocumentParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_willSaveWaitUntil + WillSaveWaitUntil(context.Context, *WillSaveTextDocumentParams) ([]TextEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchy_subtypes + Subtypes(context.Context, *TypeHierarchySubtypesParams) ([]TypeHierarchyItem, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchy_supertypes + Supertypes(context.Context, *TypeHierarchySupertypesParams) ([]TypeHierarchyItem, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#window_workDoneProgress_cancel + WorkDoneProgressCancel(context.Context, *WorkDoneProgressCancelParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_diagnostic + DiagnosticWorkspace(context.Context, *WorkspaceDiagnosticParams) (*WorkspaceDiagnosticReport, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_didChangeConfiguration + DidChangeConfiguration(context.Context, *DidChangeConfigurationParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_didChangeWatchedFiles + DidChangeWatchedFiles(context.Context, *DidChangeWatchedFilesParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_didChangeWorkspaceFolders + DidChangeWorkspaceFolders(context.Context, *DidChangeWorkspaceFoldersParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_didCreateFiles + DidCreateFiles(context.Context, *CreateFilesParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_didDeleteFiles + DidDeleteFiles(context.Context, *DeleteFilesParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_didRenameFiles + DidRenameFiles(context.Context, *RenameFilesParams) error + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_executeCommand + ExecuteCommand(context.Context, *ExecuteCommandParams) (interface{}, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_symbol + Symbol(context.Context, *WorkspaceSymbolParams) ([]SymbolInformation, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_willCreateFiles + WillCreateFiles(context.Context, *CreateFilesParams) (*WorkspaceEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_willDeleteFiles + WillDeleteFiles(context.Context, *DeleteFilesParams) (*WorkspaceEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_willRenameFiles + WillRenameFiles(context.Context, *RenameFilesParams) (*WorkspaceEdit, error) + // See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspaceSymbol_resolve + ResolveWorkspaceSymbol(context.Context, *WorkspaceSymbol) (*WorkspaceSymbol, error) +} + +func serverDispatch(ctx context.Context, server Server, reply jsonrpc2.Replier, r jsonrpc2.Request) (bool, error) { + defer recoverHandlerPanic(r.Method()) + switch r.Method() { + case "$/progress": + var params ProgressParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.Progress(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "$/setTrace": + var params SetTraceParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.SetTrace(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "callHierarchy/incomingCalls": + var params CallHierarchyIncomingCallsParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.IncomingCalls(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "callHierarchy/outgoingCalls": + var params CallHierarchyOutgoingCallsParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.OutgoingCalls(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "codeAction/resolve": + var params CodeAction + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.ResolveCodeAction(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "codeLens/resolve": + var params CodeLens + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.ResolveCodeLens(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "completionItem/resolve": + var params CompletionItem + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.ResolveCompletionItem(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "documentLink/resolve": + var params DocumentLink + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.ResolveDocumentLink(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "exit": + err := server.Exit(ctx) + return true, reply(ctx, nil, err) + + case "initialize": + var params ParamInitialize + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Initialize(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "initialized": + var params InitializedParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.Initialized(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "inlayHint/resolve": + var params InlayHint + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Resolve(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "notebookDocument/didChange": + var params DidChangeNotebookDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidChangeNotebookDocument(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "notebookDocument/didClose": + var params DidCloseNotebookDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidCloseNotebookDocument(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "notebookDocument/didOpen": + var params DidOpenNotebookDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidOpenNotebookDocument(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "notebookDocument/didSave": + var params DidSaveNotebookDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidSaveNotebookDocument(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "shutdown": + err := server.Shutdown(ctx) + return true, reply(ctx, nil, err) + + case "textDocument/codeAction": + var params CodeActionParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.CodeAction(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/codeLens": + var params CodeLensParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.CodeLens(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/colorPresentation": + var params ColorPresentationParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.ColorPresentation(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/completion": + var params CompletionParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Completion(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/declaration": + var params DeclarationParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Declaration(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/definition": + var params DefinitionParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Definition(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/diagnostic": + var params string + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Diagnostic(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/didChange": + var params DidChangeTextDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidChange(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "textDocument/didClose": + var params DidCloseTextDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidClose(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "textDocument/didOpen": + var params DidOpenTextDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidOpen(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "textDocument/didSave": + var params DidSaveTextDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidSave(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "textDocument/documentColor": + var params DocumentColorParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.DocumentColor(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/documentHighlight": + var params DocumentHighlightParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.DocumentHighlight(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/documentLink": + var params DocumentLinkParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.DocumentLink(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/documentSymbol": + var params DocumentSymbolParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.DocumentSymbol(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/foldingRange": + var params FoldingRangeParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.FoldingRange(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/formatting": + var params DocumentFormattingParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Formatting(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/hover": + var params HoverParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Hover(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/implementation": + var params ImplementationParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Implementation(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/inlayHint": + var params InlayHintParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.InlayHint(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/inlineCompletion": + var params InlineCompletionParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.InlineCompletion(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/inlineValue": + var params InlineValueParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.InlineValue(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/linkedEditingRange": + var params LinkedEditingRangeParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.LinkedEditingRange(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/moniker": + var params MonikerParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Moniker(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/onTypeFormatting": + var params DocumentOnTypeFormattingParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.OnTypeFormatting(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/prepareCallHierarchy": + var params CallHierarchyPrepareParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.PrepareCallHierarchy(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/prepareRename": + var params PrepareRenameParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.PrepareRename(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/prepareTypeHierarchy": + var params TypeHierarchyPrepareParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.PrepareTypeHierarchy(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/rangeFormatting": + var params DocumentRangeFormattingParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.RangeFormatting(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/rangesFormatting": + var params DocumentRangesFormattingParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.RangesFormatting(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/references": + var params ReferenceParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.References(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/rename": + var params RenameParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Rename(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/selectionRange": + var params SelectionRangeParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.SelectionRange(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/semanticTokens/full": + var params SemanticTokensParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.SemanticTokensFull(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/semanticTokens/full/delta": + var params SemanticTokensDeltaParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.SemanticTokensFullDelta(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/semanticTokens/range": + var params SemanticTokensRangeParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.SemanticTokensRange(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/signatureHelp": + var params SignatureHelpParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.SignatureHelp(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/typeDefinition": + var params TypeDefinitionParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.TypeDefinition(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "textDocument/willSave": + var params WillSaveTextDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.WillSave(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "textDocument/willSaveWaitUntil": + var params WillSaveTextDocumentParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.WillSaveWaitUntil(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "typeHierarchy/subtypes": + var params TypeHierarchySubtypesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Subtypes(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "typeHierarchy/supertypes": + var params TypeHierarchySupertypesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Supertypes(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "window/workDoneProgress/cancel": + var params WorkDoneProgressCancelParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.WorkDoneProgressCancel(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "workspace/diagnostic": + var params WorkspaceDiagnosticParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.DiagnosticWorkspace(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "workspace/didChangeConfiguration": + var params DidChangeConfigurationParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidChangeConfiguration(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "workspace/didChangeWatchedFiles": + var params DidChangeWatchedFilesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidChangeWatchedFiles(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "workspace/didChangeWorkspaceFolders": + var params DidChangeWorkspaceFoldersParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidChangeWorkspaceFolders(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "workspace/didCreateFiles": + var params CreateFilesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidCreateFiles(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "workspace/didDeleteFiles": + var params DeleteFilesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidDeleteFiles(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "workspace/didRenameFiles": + var params RenameFilesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + err := server.DidRenameFiles(ctx, ¶ms) + return true, reply(ctx, nil, err) + + case "workspace/executeCommand": + var params ExecuteCommandParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.ExecuteCommand(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "workspace/symbol": + var params WorkspaceSymbolParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.Symbol(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "workspace/willCreateFiles": + var params CreateFilesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.WillCreateFiles(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "workspace/willDeleteFiles": + var params DeleteFilesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.WillDeleteFiles(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "workspace/willRenameFiles": + var params RenameFilesParams + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.WillRenameFiles(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + case "workspaceSymbol/resolve": + var params WorkspaceSymbol + if err := UnmarshalJSON(r.Params(), ¶ms); err != nil { + return true, sendParseError(ctx, reply, err) + } + resp, err := server.ResolveWorkspaceSymbol(ctx, ¶ms) + if err != nil { + return true, reply(ctx, nil, err) + } + return true, reply(ctx, resp, nil) + + default: + return false, nil + } +} + +func (s *serverDispatcher) Progress(ctx context.Context, params *ProgressParams) error { + return s.sender.Notify(ctx, "$/progress", params) +} +func (s *serverDispatcher) SetTrace(ctx context.Context, params *SetTraceParams) error { + return s.sender.Notify(ctx, "$/setTrace", params) +} +func (s *serverDispatcher) IncomingCalls(ctx context.Context, params *CallHierarchyIncomingCallsParams) ([]CallHierarchyIncomingCall, error) { + var result []CallHierarchyIncomingCall + if err := s.sender.Call(ctx, "callHierarchy/incomingCalls", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) OutgoingCalls(ctx context.Context, params *CallHierarchyOutgoingCallsParams) ([]CallHierarchyOutgoingCall, error) { + var result []CallHierarchyOutgoingCall + if err := s.sender.Call(ctx, "callHierarchy/outgoingCalls", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) ResolveCodeAction(ctx context.Context, params *CodeAction) (*CodeAction, error) { + var result *CodeAction + if err := s.sender.Call(ctx, "codeAction/resolve", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) ResolveCodeLens(ctx context.Context, params *CodeLens) (*CodeLens, error) { + var result *CodeLens + if err := s.sender.Call(ctx, "codeLens/resolve", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) ResolveCompletionItem(ctx context.Context, params *CompletionItem) (*CompletionItem, error) { + var result *CompletionItem + if err := s.sender.Call(ctx, "completionItem/resolve", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) ResolveDocumentLink(ctx context.Context, params *DocumentLink) (*DocumentLink, error) { + var result *DocumentLink + if err := s.sender.Call(ctx, "documentLink/resolve", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Exit(ctx context.Context) error { + return s.sender.Notify(ctx, "exit", nil) +} +func (s *serverDispatcher) Initialize(ctx context.Context, params *ParamInitialize) (*InitializeResult, error) { + var result *InitializeResult + if err := s.sender.Call(ctx, "initialize", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Initialized(ctx context.Context, params *InitializedParams) error { + return s.sender.Notify(ctx, "initialized", params) +} +func (s *serverDispatcher) Resolve(ctx context.Context, params *InlayHint) (*InlayHint, error) { + var result *InlayHint + if err := s.sender.Call(ctx, "inlayHint/resolve", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) DidChangeNotebookDocument(ctx context.Context, params *DidChangeNotebookDocumentParams) error { + return s.sender.Notify(ctx, "notebookDocument/didChange", params) +} +func (s *serverDispatcher) DidCloseNotebookDocument(ctx context.Context, params *DidCloseNotebookDocumentParams) error { + return s.sender.Notify(ctx, "notebookDocument/didClose", params) +} +func (s *serverDispatcher) DidOpenNotebookDocument(ctx context.Context, params *DidOpenNotebookDocumentParams) error { + return s.sender.Notify(ctx, "notebookDocument/didOpen", params) +} +func (s *serverDispatcher) DidSaveNotebookDocument(ctx context.Context, params *DidSaveNotebookDocumentParams) error { + return s.sender.Notify(ctx, "notebookDocument/didSave", params) +} +func (s *serverDispatcher) Shutdown(ctx context.Context) error { + return s.sender.Call(ctx, "shutdown", nil, nil) +} +func (s *serverDispatcher) CodeAction(ctx context.Context, params *CodeActionParams) ([]CodeAction, error) { + var result []CodeAction + if err := s.sender.Call(ctx, "textDocument/codeAction", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) CodeLens(ctx context.Context, params *CodeLensParams) ([]CodeLens, error) { + var result []CodeLens + if err := s.sender.Call(ctx, "textDocument/codeLens", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) ColorPresentation(ctx context.Context, params *ColorPresentationParams) ([]ColorPresentation, error) { + var result []ColorPresentation + if err := s.sender.Call(ctx, "textDocument/colorPresentation", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Completion(ctx context.Context, params *CompletionParams) (*CompletionList, error) { + var result *CompletionList + if err := s.sender.Call(ctx, "textDocument/completion", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Declaration(ctx context.Context, params *DeclarationParams) (*Or_textDocument_declaration, error) { + var result *Or_textDocument_declaration + if err := s.sender.Call(ctx, "textDocument/declaration", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Definition(ctx context.Context, params *DefinitionParams) ([]Location, error) { + var result []Location + if err := s.sender.Call(ctx, "textDocument/definition", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Diagnostic(ctx context.Context, params *string) (*string, error) { + var result *string + if err := s.sender.Call(ctx, "textDocument/diagnostic", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) DidChange(ctx context.Context, params *DidChangeTextDocumentParams) error { + return s.sender.Notify(ctx, "textDocument/didChange", params) +} +func (s *serverDispatcher) DidClose(ctx context.Context, params *DidCloseTextDocumentParams) error { + return s.sender.Notify(ctx, "textDocument/didClose", params) +} +func (s *serverDispatcher) DidOpen(ctx context.Context, params *DidOpenTextDocumentParams) error { + return s.sender.Notify(ctx, "textDocument/didOpen", params) +} +func (s *serverDispatcher) DidSave(ctx context.Context, params *DidSaveTextDocumentParams) error { + return s.sender.Notify(ctx, "textDocument/didSave", params) +} +func (s *serverDispatcher) DocumentColor(ctx context.Context, params *DocumentColorParams) ([]ColorInformation, error) { + var result []ColorInformation + if err := s.sender.Call(ctx, "textDocument/documentColor", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) DocumentHighlight(ctx context.Context, params *DocumentHighlightParams) ([]DocumentHighlight, error) { + var result []DocumentHighlight + if err := s.sender.Call(ctx, "textDocument/documentHighlight", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) DocumentLink(ctx context.Context, params *DocumentLinkParams) ([]DocumentLink, error) { + var result []DocumentLink + if err := s.sender.Call(ctx, "textDocument/documentLink", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) DocumentSymbol(ctx context.Context, params *DocumentSymbolParams) ([]interface{}, error) { + var result []interface{} + if err := s.sender.Call(ctx, "textDocument/documentSymbol", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) FoldingRange(ctx context.Context, params *FoldingRangeParams) ([]FoldingRange, error) { + var result []FoldingRange + if err := s.sender.Call(ctx, "textDocument/foldingRange", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Formatting(ctx context.Context, params *DocumentFormattingParams) ([]TextEdit, error) { + var result []TextEdit + if err := s.sender.Call(ctx, "textDocument/formatting", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Hover(ctx context.Context, params *HoverParams) (*Hover, error) { + var result *Hover + if err := s.sender.Call(ctx, "textDocument/hover", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Implementation(ctx context.Context, params *ImplementationParams) ([]Location, error) { + var result []Location + if err := s.sender.Call(ctx, "textDocument/implementation", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) InlayHint(ctx context.Context, params *InlayHintParams) ([]InlayHint, error) { + var result []InlayHint + if err := s.sender.Call(ctx, "textDocument/inlayHint", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) InlineCompletion(ctx context.Context, params *InlineCompletionParams) (*Or_Result_textDocument_inlineCompletion, error) { + var result *Or_Result_textDocument_inlineCompletion + if err := s.sender.Call(ctx, "textDocument/inlineCompletion", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) InlineValue(ctx context.Context, params *InlineValueParams) ([]InlineValue, error) { + var result []InlineValue + if err := s.sender.Call(ctx, "textDocument/inlineValue", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) LinkedEditingRange(ctx context.Context, params *LinkedEditingRangeParams) (*LinkedEditingRanges, error) { + var result *LinkedEditingRanges + if err := s.sender.Call(ctx, "textDocument/linkedEditingRange", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Moniker(ctx context.Context, params *MonikerParams) ([]Moniker, error) { + var result []Moniker + if err := s.sender.Call(ctx, "textDocument/moniker", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) OnTypeFormatting(ctx context.Context, params *DocumentOnTypeFormattingParams) ([]TextEdit, error) { + var result []TextEdit + if err := s.sender.Call(ctx, "textDocument/onTypeFormatting", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) PrepareCallHierarchy(ctx context.Context, params *CallHierarchyPrepareParams) ([]CallHierarchyItem, error) { + var result []CallHierarchyItem + if err := s.sender.Call(ctx, "textDocument/prepareCallHierarchy", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) PrepareRename(ctx context.Context, params *PrepareRenameParams) (*PrepareRenameResult, error) { + var result *PrepareRenameResult + if err := s.sender.Call(ctx, "textDocument/prepareRename", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) PrepareTypeHierarchy(ctx context.Context, params *TypeHierarchyPrepareParams) ([]TypeHierarchyItem, error) { + var result []TypeHierarchyItem + if err := s.sender.Call(ctx, "textDocument/prepareTypeHierarchy", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) RangeFormatting(ctx context.Context, params *DocumentRangeFormattingParams) ([]TextEdit, error) { + var result []TextEdit + if err := s.sender.Call(ctx, "textDocument/rangeFormatting", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) RangesFormatting(ctx context.Context, params *DocumentRangesFormattingParams) ([]TextEdit, error) { + var result []TextEdit + if err := s.sender.Call(ctx, "textDocument/rangesFormatting", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) References(ctx context.Context, params *ReferenceParams) ([]Location, error) { + var result []Location + if err := s.sender.Call(ctx, "textDocument/references", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Rename(ctx context.Context, params *RenameParams) (*WorkspaceEdit, error) { + var result *WorkspaceEdit + if err := s.sender.Call(ctx, "textDocument/rename", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) SelectionRange(ctx context.Context, params *SelectionRangeParams) ([]SelectionRange, error) { + var result []SelectionRange + if err := s.sender.Call(ctx, "textDocument/selectionRange", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) SemanticTokensFull(ctx context.Context, params *SemanticTokensParams) (*SemanticTokens, error) { + var result *SemanticTokens + if err := s.sender.Call(ctx, "textDocument/semanticTokens/full", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) SemanticTokensFullDelta(ctx context.Context, params *SemanticTokensDeltaParams) (interface{}, error) { + var result interface{} + if err := s.sender.Call(ctx, "textDocument/semanticTokens/full/delta", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) SemanticTokensRange(ctx context.Context, params *SemanticTokensRangeParams) (*SemanticTokens, error) { + var result *SemanticTokens + if err := s.sender.Call(ctx, "textDocument/semanticTokens/range", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) SignatureHelp(ctx context.Context, params *SignatureHelpParams) (*SignatureHelp, error) { + var result *SignatureHelp + if err := s.sender.Call(ctx, "textDocument/signatureHelp", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) TypeDefinition(ctx context.Context, params *TypeDefinitionParams) ([]Location, error) { + var result []Location + if err := s.sender.Call(ctx, "textDocument/typeDefinition", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) WillSave(ctx context.Context, params *WillSaveTextDocumentParams) error { + return s.sender.Notify(ctx, "textDocument/willSave", params) +} +func (s *serverDispatcher) WillSaveWaitUntil(ctx context.Context, params *WillSaveTextDocumentParams) ([]TextEdit, error) { + var result []TextEdit + if err := s.sender.Call(ctx, "textDocument/willSaveWaitUntil", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Subtypes(ctx context.Context, params *TypeHierarchySubtypesParams) ([]TypeHierarchyItem, error) { + var result []TypeHierarchyItem + if err := s.sender.Call(ctx, "typeHierarchy/subtypes", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Supertypes(ctx context.Context, params *TypeHierarchySupertypesParams) ([]TypeHierarchyItem, error) { + var result []TypeHierarchyItem + if err := s.sender.Call(ctx, "typeHierarchy/supertypes", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) WorkDoneProgressCancel(ctx context.Context, params *WorkDoneProgressCancelParams) error { + return s.sender.Notify(ctx, "window/workDoneProgress/cancel", params) +} +func (s *serverDispatcher) DiagnosticWorkspace(ctx context.Context, params *WorkspaceDiagnosticParams) (*WorkspaceDiagnosticReport, error) { + var result *WorkspaceDiagnosticReport + if err := s.sender.Call(ctx, "workspace/diagnostic", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) DidChangeConfiguration(ctx context.Context, params *DidChangeConfigurationParams) error { + return s.sender.Notify(ctx, "workspace/didChangeConfiguration", params) +} +func (s *serverDispatcher) DidChangeWatchedFiles(ctx context.Context, params *DidChangeWatchedFilesParams) error { + return s.sender.Notify(ctx, "workspace/didChangeWatchedFiles", params) +} +func (s *serverDispatcher) DidChangeWorkspaceFolders(ctx context.Context, params *DidChangeWorkspaceFoldersParams) error { + return s.sender.Notify(ctx, "workspace/didChangeWorkspaceFolders", params) +} +func (s *serverDispatcher) DidCreateFiles(ctx context.Context, params *CreateFilesParams) error { + return s.sender.Notify(ctx, "workspace/didCreateFiles", params) +} +func (s *serverDispatcher) DidDeleteFiles(ctx context.Context, params *DeleteFilesParams) error { + return s.sender.Notify(ctx, "workspace/didDeleteFiles", params) +} +func (s *serverDispatcher) DidRenameFiles(ctx context.Context, params *RenameFilesParams) error { + return s.sender.Notify(ctx, "workspace/didRenameFiles", params) +} +func (s *serverDispatcher) ExecuteCommand(ctx context.Context, params *ExecuteCommandParams) (interface{}, error) { + var result interface{} + if err := s.sender.Call(ctx, "workspace/executeCommand", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) Symbol(ctx context.Context, params *WorkspaceSymbolParams) ([]SymbolInformation, error) { + var result []SymbolInformation + if err := s.sender.Call(ctx, "workspace/symbol", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) WillCreateFiles(ctx context.Context, params *CreateFilesParams) (*WorkspaceEdit, error) { + var result *WorkspaceEdit + if err := s.sender.Call(ctx, "workspace/willCreateFiles", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) WillDeleteFiles(ctx context.Context, params *DeleteFilesParams) (*WorkspaceEdit, error) { + var result *WorkspaceEdit + if err := s.sender.Call(ctx, "workspace/willDeleteFiles", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) WillRenameFiles(ctx context.Context, params *RenameFilesParams) (*WorkspaceEdit, error) { + var result *WorkspaceEdit + if err := s.sender.Call(ctx, "workspace/willRenameFiles", params, &result); err != nil { + return nil, err + } + return result, nil +} +func (s *serverDispatcher) ResolveWorkspaceSymbol(ctx context.Context, params *WorkspaceSymbol) (*WorkspaceSymbol, error) { + var result *WorkspaceSymbol + if err := s.sender.Call(ctx, "workspaceSymbol/resolve", params, &result); err != nil { + return nil, err + } + return result, nil +} diff --git a/contribs/gnopls/internal/protocol/uri.go b/contribs/gnopls/internal/protocol/uri.go new file mode 100644 index 00000000000..86775b065f5 --- /dev/null +++ b/contribs/gnopls/internal/protocol/uri.go @@ -0,0 +1,220 @@ +// 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 protocol + +// This file declares URI, DocumentURI, and its methods. +// +// For the LSP definition of these types, see +// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#uri + +import ( + "fmt" + "net/url" + "path/filepath" + "strings" + "unicode" + + "golang.org/x/tools/gopls/internal/util/pathutil" +) + +// A DocumentURI is the URI of a client editor document. +// +// According to the LSP specification: +// +// Care should be taken to handle encoding in URIs. For +// example, some clients (such as VS Code) may encode colons +// in drive letters while others do not. The URIs below are +// both valid, but clients and servers should be consistent +// with the form they use themselves to ensure the other party +// doesn’t interpret them as distinct URIs. Clients and +// servers should not assume that each other are encoding the +// same way (for example a client encoding colons in drive +// letters cannot assume server responses will have encoded +// colons). The same applies to casing of drive letters - one +// party should not assume the other party will return paths +// with drive letters cased the same as it. +// +// file:///c:/project/readme.md +// file:///C%3A/project/readme.md +// +// This is done during JSON unmarshalling; +// see [DocumentURI.UnmarshalText] for details. +type DocumentURI string + +// A URI is an arbitrary URL (e.g. https), not necessarily a file. +type URI = string + +// UnmarshalText implements decoding of DocumentURI values. +// +// In particular, it implements a systematic correction of various odd +// features of the definition of DocumentURI in the LSP spec that +// appear to be workarounds for bugs in VS Code. For example, it may +// URI-encode the URI itself, so that colon becomes %3A, and it may +// send file://foo.go URIs that have two slashes (not three) and no +// hostname. +// +// We use UnmarshalText, not UnmarshalJSON, because it is called even +// for non-addressable values such as keys and values of map[K]V, +// where there is no pointer of type *K or *V on which to call +// UnmarshalJSON. (See Go issue #28189 for more detail.) +// +// Non-empty DocumentURIs are valid "file"-scheme URIs. +// The empty DocumentURI is valid. +func (uri *DocumentURI) UnmarshalText(data []byte) (err error) { + *uri, err = ParseDocumentURI(string(data)) + return +} + +// Path returns the file path for the given URI. +// +// DocumentURI("").Path() returns the empty string. +// +// Path panics if called on a URI that is not a valid filename. +func (uri DocumentURI) Path() string { + filename, err := filename(uri) + if err != nil { + // e.g. ParseRequestURI failed. + // + // This can only affect DocumentURIs created by + // direct string manipulation; all DocumentURIs + // received from the client pass through + // ParseRequestURI, which ensures validity. + panic(err) + } + return filepath.FromSlash(filename) +} + +// Dir returns the URI for the directory containing the receiver. +func (uri DocumentURI) Dir() DocumentURI { + // This function could be more efficiently implemented by avoiding any call + // to Path(), but at least consolidates URI manipulation. + return URIFromPath(filepath.Dir(uri.Path())) +} + +// Encloses reports whether uri's path, considered as a sequence of segments, +// is a prefix of file's path. +func (uri DocumentURI) Encloses(file DocumentURI) bool { + return pathutil.InDir(uri.Path(), file.Path()) +} + +func filename(uri DocumentURI) (string, error) { + if uri == "" { + return "", nil + } + + // This conservative check for the common case + // of a simple non-empty absolute POSIX filename + // avoids the allocation of a net.URL. + if strings.HasPrefix(string(uri), "file:///") { + rest := string(uri)[len("file://"):] // leave one slash + for i := 0; i < len(rest); i++ { + b := rest[i] + // Reject these cases: + if b < ' ' || b == 0x7f || // control character + b == '%' || b == '+' || // URI escape + b == ':' || // Windows drive letter + b == '@' || b == '&' || b == '?' { // authority or query + goto slow + } + } + return rest, nil + } +slow: + + u, err := url.ParseRequestURI(string(uri)) + if err != nil { + return "", err + } + if u.Scheme != fileScheme { + return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri) + } + // If the URI is a Windows URI, we trim the leading "/" and uppercase + // the drive letter, which will never be case sensitive. + if isWindowsDriveURIPath(u.Path) { + u.Path = strings.ToUpper(string(u.Path[1])) + u.Path[2:] + } + + return u.Path, nil +} + +// ParseDocumentURI interprets a string as a DocumentURI, applying VS +// Code workarounds; see [DocumentURI.UnmarshalText] for details. +func ParseDocumentURI(s string) (DocumentURI, error) { + if s == "" { + return "", nil + } + + if !strings.HasPrefix(s, "file://") { + return "", fmt.Errorf("DocumentURI scheme is not 'file': %s", s) + } + + // VS Code sends URLs with only two slashes, + // which are invalid. golang/go#39789. + if !strings.HasPrefix(s, "file:///") { + s = "file:///" + s[len("file://"):] + } + + // Even though the input is a URI, it may not be in canonical form. VS Code + // in particular over-escapes :, @, etc. Unescape and re-encode to canonicalize. + path, err := url.PathUnescape(s[len("file://"):]) + if err != nil { + return "", err + } + + // File URIs from Windows may have lowercase drive letters. + // Since drive letters are guaranteed to be case insensitive, + // we change them to uppercase to remain consistent. + // For example, file:///c:/x/y/z becomes file:///C:/x/y/z. + if isWindowsDriveURIPath(path) { + path = path[:1] + strings.ToUpper(string(path[1])) + path[2:] + } + u := url.URL{Scheme: fileScheme, Path: path} + return DocumentURI(u.String()), nil +} + +// URIFromPath returns DocumentURI for the supplied file path. +// Given "", it returns "". +func URIFromPath(path string) DocumentURI { + if path == "" { + return "" + } + if !isWindowsDrivePath(path) { + if abs, err := filepath.Abs(path); err == nil { + path = abs + } + } + // Check the file path again, in case it became absolute. + if isWindowsDrivePath(path) { + path = "/" + strings.ToUpper(string(path[0])) + path[1:] + } + path = filepath.ToSlash(path) + u := url.URL{ + Scheme: fileScheme, + Path: path, + } + return DocumentURI(u.String()) +} + +const fileScheme = "file" + +// isWindowsDrivePath returns true if the file path is of the form used by +// Windows. We check if the path begins with a drive letter, followed by a ":". +// For example: C:/x/y/z. +func isWindowsDrivePath(path string) bool { + if len(path) < 3 { + return false + } + return unicode.IsLetter(rune(path[0])) && path[1] == ':' +} + +// isWindowsDriveURIPath returns true if the file URI is of the format used by +// Windows URIs. The url.Parse package does not specially handle Windows paths +// (see golang/go#6027), so we check if the URI path has a drive prefix (e.g. "/C:"). +func isWindowsDriveURIPath(uri string) bool { + if len(uri) < 4 { + return false + } + return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':' +} diff --git a/contribs/gnopls/internal/protocol/uri_test.go b/contribs/gnopls/internal/protocol/uri_test.go new file mode 100644 index 00000000000..cad71ddc13c --- /dev/null +++ b/contribs/gnopls/internal/protocol/uri_test.go @@ -0,0 +1,134 @@ +// 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. + +//go:build !windows +// +build !windows + +package protocol_test + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// TestURIFromPath tests the conversion between URIs and filenames. The test cases +// include Windows-style URIs and filepaths, but we avoid having OS-specific +// tests by using only forward slashes, assuming that the standard library +// functions filepath.ToSlash and filepath.FromSlash do not need testing. +func TestURIFromPath(t *testing.T) { + for _, test := range []struct { + path, wantFile string + wantURI protocol.DocumentURI + }{ + { + path: ``, + wantFile: ``, + wantURI: protocol.DocumentURI(""), + }, + { + path: `C:/Windows/System32`, + wantFile: `C:/Windows/System32`, + wantURI: protocol.DocumentURI("file:///C:/Windows/System32"), + }, + { + path: `C:/Go/src/bob.go`, + wantFile: `C:/Go/src/bob.go`, + wantURI: protocol.DocumentURI("file:///C:/Go/src/bob.go"), + }, + { + path: `c:/Go/src/bob.go`, + wantFile: `C:/Go/src/bob.go`, + wantURI: protocol.DocumentURI("file:///C:/Go/src/bob.go"), + }, + { + path: `/path/to/dir`, + wantFile: `/path/to/dir`, + wantURI: protocol.DocumentURI("file:///path/to/dir"), + }, + { + path: `/a/b/c/src/bob.go`, + wantFile: `/a/b/c/src/bob.go`, + wantURI: protocol.DocumentURI("file:///a/b/c/src/bob.go"), + }, + { + path: `c:/Go/src/bob george/george/george.go`, + wantFile: `C:/Go/src/bob george/george/george.go`, + wantURI: protocol.DocumentURI("file:///C:/Go/src/bob%20george/george/george.go"), + }, + } { + got := protocol.URIFromPath(test.path) + if got != test.wantURI { + t.Errorf("URIFromPath(%q): got %q, expected %q", test.path, got, test.wantURI) + } + gotFilename := got.Path() + if gotFilename != test.wantFile { + t.Errorf("Filename(%q): got %q, expected %q", got, gotFilename, test.wantFile) + } + } +} + +func TestParseDocumentURI(t *testing.T) { + for _, test := range []struct { + input string + want string // string(DocumentURI) on success or error.Error() on failure + wantPath string // expected DocumentURI.Path on success + }{ + { + input: `file:///c:/Go/src/bob%20george/george/george.go`, + want: "file:///C:/Go/src/bob%20george/george/george.go", + wantPath: `C:/Go/src/bob george/george/george.go`, + }, + { + input: `file:///C%3A/Go/src/bob%20george/george/george.go`, + want: "file:///C:/Go/src/bob%20george/george/george.go", + wantPath: `C:/Go/src/bob george/george/george.go`, + }, + { + input: `file:///path/to/%25p%25ercent%25/per%25cent.go`, + want: `file:///path/to/%25p%25ercent%25/per%25cent.go`, + wantPath: `/path/to/%p%ercent%/per%cent.go`, + }, + { + input: `file:///C%3A/`, + want: `file:///C:/`, + wantPath: `C:/`, + }, + { + input: `file:///`, + want: `file:///`, + wantPath: `/`, + }, + { + input: `file://wsl%24/Ubuntu/home/wdcui/repo/VMEnclaves/cvm-runtime`, + want: `file:///wsl$/Ubuntu/home/wdcui/repo/VMEnclaves/cvm-runtime`, + wantPath: `/wsl$/Ubuntu/home/wdcui/repo/VMEnclaves/cvm-runtime`, + }, + { + input: "", + want: "", + wantPath: "", + }, + // Errors: + { + input: "https://go.dev/", + want: "DocumentURI scheme is not 'file': https://go.dev/", + }, + } { + uri, err := protocol.ParseDocumentURI(test.input) + var got string + if err != nil { + got = err.Error() + } else { + got = string(uri) + } + if got != test.want { + t.Errorf("ParseDocumentURI(%q): got %q, want %q", test.input, got, test.want) + } + if err == nil && uri.Path() != test.wantPath { + t.Errorf("DocumentURI(%s).Path = %q, want %q", uri, + uri.Path(), test.wantPath) + } + } +} diff --git a/contribs/gnopls/internal/protocol/uri_windows_test.go b/contribs/gnopls/internal/protocol/uri_windows_test.go new file mode 100644 index 00000000000..08471167a22 --- /dev/null +++ b/contribs/gnopls/internal/protocol/uri_windows_test.go @@ -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. + +//go:build windows +// +build windows + +package protocol_test + +import ( + "path/filepath" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// TestURIFromPath tests the conversion between URIs and filenames. The test cases +// include Windows-style URIs and filepaths, but we avoid having OS-specific +// tests by using only forward slashes, assuming that the standard library +// functions filepath.ToSlash and filepath.FromSlash do not need testing. +func TestURIFromPath(t *testing.T) { + rootPath, err := filepath.Abs("/") + if err != nil { + t.Fatal(err) + } + if len(rootPath) < 2 || rootPath[1] != ':' { + t.Fatalf("malformed root path %q", rootPath) + } + driveLetter := string(rootPath[0]) + + for _, test := range []struct { + path, wantFile string + wantURI protocol.DocumentURI + }{ + { + path: ``, + wantFile: ``, + wantURI: protocol.DocumentURI(""), + }, + { + path: `C:\Windows\System32`, + wantFile: `C:\Windows\System32`, + wantURI: protocol.DocumentURI("file:///C:/Windows/System32"), + }, + { + path: `C:\Go\src\bob.go`, + wantFile: `C:\Go\src\bob.go`, + wantURI: protocol.DocumentURI("file:///C:/Go/src/bob.go"), + }, + { + path: `c:\Go\src\bob.go`, + wantFile: `C:\Go\src\bob.go`, + wantURI: protocol.DocumentURI("file:///C:/Go/src/bob.go"), + }, + { + path: `\path\to\dir`, + wantFile: driveLetter + `:\path\to\dir`, + wantURI: protocol.DocumentURI("file:///" + driveLetter + ":/path/to/dir"), + }, + { + path: `\a\b\c\src\bob.go`, + wantFile: driveLetter + `:\a\b\c\src\bob.go`, + wantURI: protocol.DocumentURI("file:///" + driveLetter + ":/a/b/c/src/bob.go"), + }, + { + path: `c:\Go\src\bob george\george\george.go`, + wantFile: `C:\Go\src\bob george\george\george.go`, + wantURI: protocol.DocumentURI("file:///C:/Go/src/bob%20george/george/george.go"), + }, + } { + got := protocol.URIFromPath(test.path) + if got != test.wantURI { + t.Errorf("URIFromPath(%q): got %q, expected %q", test.path, got, test.wantURI) + } + gotFilename := got.Path() + if gotFilename != test.wantFile { + t.Errorf("Filename(%q): got %q, expected %q", got, gotFilename, test.wantFile) + } + } +} + +func TestParseDocumentURI(t *testing.T) { + for _, test := range []struct { + input string + want string // string(DocumentURI) on success or error.Error() on failure + wantPath string // expected DocumentURI.Path on success + }{ + { + input: `file:///c:/Go/src/bob%20george/george/george.go`, + want: "file:///C:/Go/src/bob%20george/george/george.go", + wantPath: `C:\Go\src\bob george\george\george.go`, + }, + { + input: `file:///C%3A/Go/src/bob%20george/george/george.go`, + want: "file:///C:/Go/src/bob%20george/george/george.go", + wantPath: `C:\Go\src\bob george\george\george.go`, + }, + { + input: `file:///c:/path/to/%25p%25ercent%25/per%25cent.go`, + want: `file:///C:/path/to/%25p%25ercent%25/per%25cent.go`, + wantPath: `C:\path\to\%p%ercent%\per%cent.go`, + }, + { + input: `file:///C%3A/`, + want: `file:///C:/`, + wantPath: `C:\`, + }, + { + input: `file:///`, + want: `file:///`, + wantPath: `\`, + }, + { + input: "", + want: "", + wantPath: "", + }, + // Errors: + { + input: "https://go.dev/", + want: "DocumentURI scheme is not 'file': https://go.dev/", + }, + } { + uri, err := protocol.ParseDocumentURI(test.input) + var got string + if err != nil { + got = err.Error() + } else { + got = string(uri) + } + if got != test.want { + t.Errorf("ParseDocumentURI(%q): got %q, want %q", test.input, got, test.want) + } + if err == nil && uri.Path() != test.wantPath { + t.Errorf("DocumentURI(%s).Path = %q, want %q", uri, + uri.Path(), test.wantPath) + } + } +} diff --git a/contribs/gnopls/internal/server/assets/common.css b/contribs/gnopls/internal/server/assets/common.css new file mode 100644 index 00000000000..3795412e1c9 --- /dev/null +++ b/contribs/gnopls/internal/server/assets/common.css @@ -0,0 +1,116 @@ +/* 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. + */ + +/* inspired by pkg.go.dev's typography.css */ + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + font-size: 1rem; + line-height: normal; +} + +h1 { + font-size: 1.5rem; +} + +h2 { + font-size: 1.375rem; +} + +h3 { + font-size: 1.25rem; +} + +h4 { + font-size: 1.125rem; +} + +h5 { + font-size: 1rem; +} + +h6 { + font-size: 0.875rem; +} + +h1, +h2, +h3, +h4 { + font-weight: 600; + line-height: 1.25em; + word-break: break-word; +} + +h5, +h6 { + font-weight: 500; + line-height: 1.3em; + word-break: break-word; +} + +p { + font-size: 1rem; + line-height: 1.5rem; + max-width: 60rem; +} + +strong { + font-weight: 600; +} + +code, +pre, +textarea.code { + font-family: Consolas, 'Liberation Mono', Menlo, monospace; + font-size: 0.875rem; + line-height: 1.5em; +} + +pre, +textarea.code { + background-color: #eee; + border: 3px; + border-radius: 3px; + color: black; + overflow-x: auto; + padding: 0.625rem; + tab-size: 4; + white-space: pre; +} + +button, +input, +select, +textarea { + font: inherit; +} + +a, +a:link, +a:visited { + color: rgb(0, 125, 156); + text-decoration: none; +} + +a:hover, +a:focus { + color: rgb(0, 125, 156); + text-decoration: underline; +} + +a:hover > * { + text-decoration: underline; +} + +#disconnected { + position: fixed; + top: 1em; + left: 1em; + display: none; /* initially */ + background-color: white; + border: thick solid red; + padding: 2em; +} diff --git a/contribs/gnopls/internal/server/assets/common.js b/contribs/gnopls/internal/server/assets/common.js new file mode 100644 index 00000000000..12334568998 --- /dev/null +++ b/contribs/gnopls/internal/server/assets/common.js @@ -0,0 +1,28 @@ +// 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. + +// httpGET requests a URL for its effects only. +// (It is needed for /open URLs; see objHTML.) +function httpGET(url) { + var x = new XMLHttpRequest(); + x.open("GET", url, true); + x.send(); + return false; // disable usual behavior +} + +// disconnect banner +window.addEventListener('load', function() { + // Create a hidden
    element. + var banner = document.createElement("div"); + banner.id = "disconnected"; + banner.innerText = "Gopls server has terminated. Page is inactive."; + document.body.appendChild(banner); + + // Start a GET /hang request. If it ever completes, the server + // has disconnected. Reveal the banner in that case. + var x = new XMLHttpRequest(); + x.open("GET", "/hang", true); + x.onloadend = () => { banner.style.display = "block"; }; + x.send(); +}); diff --git a/contribs/gnopls/internal/server/assets/favicon.ico b/contribs/gnopls/internal/server/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8d225846dbcda496ba803b828c4786b21cc84d01 GIT binary patch literal 5686 zcmeHL4Qx|Y6h33c%}oagh()0_%1;O(L4mbFbVLmNO!o_pK(h=QmG>Gj0Dp1$i%G?a*Xb00*Zl+=^xHy#gX9;;9klhC_gGfM^tl54Hq zN-B=8KPo?fAIN`q)__`Pf%@y10W~k(nR{Vd#p#Gnr1As!|Bv%uUN{Fw^JXYieKe`d zzZvR2Kj{8nsXc+#HYjwd{P07=cUC0hO6hzw+^+ZFU#~ld%O&$5WcRDz2Z_J6DiynP z-o~cPW!SXsAaZsV;;UUn$ox7FS(!_aw=JPt@tqZEu&%WsWleFXv~2yse;5DKQph*= z0WBARJ9fZv61lq%DA=*HR^dfsG{(L(8Pn%&P;XEja?6{y7f~8$JS+Rp)$Ff%YcI*RnhK&{cdmtNiVGadJ0XF?rG$Onq}K z@_)$jlK2eUTOeOv~?9P$ee01br-zOe1aZ|vm!mkZgAQy=i&<@?#(oSlO{eFu1`u&5Yv z-RG|Q@X?+!)$GyJJv#(#j!Ff;o^1<^Sjlj~7vExv+$&@y2WCUQRp4V0$jZw0*c5)) zY#dYF7oFmpro?)52kW0lYg2`Se>{Jd8vj0Te6wV<#7#3cZCD}U(((5=|C{~r9S#TL zXCz?A&=IgMO5^-DxbvUsr~7+Z8OBeD!pKphk(VdVM3I7j-j;;L3tksKIL#(wqVT~y z+4ySQ6r)^r=w19#_95D;t!S^Y0?rygyWR}3N9tXtoKJ6|_qg5Z+9&sWMa#uxe6!K4 zHmnWm$F3w-Qx236UpNW-bK|jQd5kJUQo<{+eV>3k;!LWscCi;{O{`618^Zof%XsX_uwc)YILw*( zvcijvF`O0q&+;~*(}7F|yE;#Z8;^{YCKPOs$LS-9xO!p%8qR*C=MEsn~7LW{xpL3+($fi^UQ=O^Tezb&|n!m~B)s-E@=cPlJ1Icj-i~H*-ws>HMPp zA8$6I;O?Sy@#&$HCioyy&a?PV!JjLXFBIbY#l0Hexx~M@G-~89ijzi*_XSFw6G@Ac z#?aDaiS}f?LEmPWiT|6>v0W0a+p?2(?pRI7@)uIk{v~wsP#Tr|yq-!6v#9Y*3ZZfl zp<2*lLTxIcCXM)v(bd{Aax}M6%Uuxvzob?f!qI!V72pRl5Xito$bep>^=cY@H_dzZ z()RDyPwTe-RM?4IGB29wF3w7Q=`{B z5 \ No newline at end of file diff --git a/contribs/gnopls/internal/server/call_hierarchy.go b/contribs/gnopls/internal/server/call_hierarchy.go new file mode 100644 index 00000000000..671d4f8c81c --- /dev/null +++ b/contribs/gnopls/internal/server/call_hierarchy.go @@ -0,0 +1,59 @@ +// 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 server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func (s *server) PrepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) { + ctx, done := event.Start(ctx, "lsp.Server.prepareCallHierarchy") + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + if snapshot.FileKind(fh) != file.Go { + return nil, nil // empty result + } + return golang.PrepareCallHierarchy(ctx, snapshot, fh, params.Position) +} + +func (s *server) IncomingCalls(ctx context.Context, params *protocol.CallHierarchyIncomingCallsParams) ([]protocol.CallHierarchyIncomingCall, error) { + ctx, done := event.Start(ctx, "lsp.Server.incomingCalls") + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.Item.URI) + if err != nil { + return nil, err + } + defer release() + if snapshot.FileKind(fh) != file.Go { + return nil, nil // empty result + } + return golang.IncomingCalls(ctx, snapshot, fh, params.Item.Range.Start) +} + +func (s *server) OutgoingCalls(ctx context.Context, params *protocol.CallHierarchyOutgoingCallsParams) ([]protocol.CallHierarchyOutgoingCall, error) { + ctx, done := event.Start(ctx, "lsp.Server.outgoingCalls") + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.Item.URI) + if err != nil { + return nil, err + } + defer release() + if snapshot.FileKind(fh) != file.Go { + return nil, nil // empty result + } + return golang.OutgoingCalls(ctx, snapshot, fh, params.Item.Range.Start) +} diff --git a/contribs/gnopls/internal/server/code_action.go b/contribs/gnopls/internal/server/code_action.go new file mode 100644 index 00000000000..5fc0fb6aa21 --- /dev/null +++ b/contribs/gnopls/internal/server/code_action.go @@ -0,0 +1,316 @@ +// Copyright 2018 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 server + +import ( + "context" + "fmt" + "slices" + "sort" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/mod" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/event" +) + +func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { + ctx, done := event.Start(ctx, "lsp.Server.codeAction") + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + uri := fh.URI() + kind := snapshot.FileKind(fh) + + // Determine the supported code action kinds for this file. + // + // We interpret CodeActionKinds hierarchically, so refactor.rewrite + // subsumes refactor.rewrite.change_quote, for example. + // See ../protocol/codeactionkind.go for some code action theory. + codeActionKinds, ok := snapshot.Options().SupportedCodeActions[kind] + if !ok { + return nil, fmt.Errorf("no supported code actions for %v file kind", kind) + } + + // The Only field of the context specifies which code actions + // the client wants. If Only is empty, assume the client wants + // all supported code actions. + if len(params.Context.Only) > 0 { + codeActionKinds = make(map[protocol.CodeActionKind]bool) + for _, kind := range params.Context.Only { + codeActionKinds[kind] = true + } + } + + // enabled reports whether the specified kind of code action is required. + enabled := func(kind protocol.CodeActionKind) bool { + // Given "refactor.rewrite.foo", check for it, + // then "refactor.rewrite", "refactor". + // A false map entry prunes the search for ancestors. + for { + if v, ok := codeActionKinds[kind]; ok { + return v + } + dot := strings.LastIndexByte(string(kind), '.') + if dot < 0 { + return false + } + + // The "source.test" code action shouldn't be + // returned to the client unless requested by + // an exact match in Only. + // + // This mechanism exists to avoid a distracting + // lightbulb (code action) on each Test function. + // These actions are unwanted in VS Code because it + // has Test Explorer, and in other editors because + // the UX of executeCommand is unsatisfactory for tests: + // it doesn't show the complete streaming output. + // See https://github.com/joaotavora/eglot/discussions/1402 + // for a better solution. See also + // https://github.com/golang/go/issues/67400. + // + // TODO(adonovan): consider instead switching on + // codeActionTriggerKind. Perhaps other noisy Source + // Actions should be guarded in the same way. + if kind == settings.GoTest { + return false // don't search ancestors + } + + kind = kind[:dot] + } + } + + switch kind { + case file.Mod: + var actions []protocol.CodeAction + + fixes, err := s.codeActionsMatchingDiagnostics(ctx, fh.URI(), snapshot, params.Context.Diagnostics, enabled) + if err != nil { + return nil, err + } + + // Group vulnerability fixes by their range, and select only the most + // appropriate upgrades. + // + // TODO(rfindley): can this instead be accomplished on the diagnosis side, + // so that code action handling remains uniform? + vulnFixes := make(map[protocol.Range][]protocol.CodeAction) + searchFixes: + for _, fix := range fixes { + for _, diag := range fix.Diagnostics { + if diag.Source == string(cache.Govulncheck) || diag.Source == string(cache.Vulncheck) { + vulnFixes[diag.Range] = append(vulnFixes[diag.Range], fix) + continue searchFixes + } + } + actions = append(actions, fix) + } + + for _, fixes := range vulnFixes { + fixes = mod.SelectUpgradeCodeActions(fixes) + actions = append(actions, fixes...) + } + + return actions, nil + + case file.Go: + // diagnostic-bundled code actions + // + // The diagnostics already have a UI presence (e.g. squiggly underline); + // the associated action may additionally show (in VS Code) as a lightbulb. + // Note s.codeActionsMatchingDiagnostics returns only fixes + // detected during the analysis phase. golang.CodeActions computes + // extra changes that can address some diagnostics. + actions, err := s.codeActionsMatchingDiagnostics(ctx, uri, snapshot, params.Context.Diagnostics, enabled) + if err != nil { + return nil, err + } + + // computed code actions (may include quickfixes from diagnostics) + trigger := protocol.CodeActionUnknownTrigger + if k := params.Context.TriggerKind; k != nil { // (some clients omit it) + trigger = *k + } + moreActions, err := golang.CodeActions(ctx, snapshot, fh, params.Range, params.Context.Diagnostics, enabled, trigger) + if err != nil { + return nil, err + } + actions = append(actions, moreActions...) + + // Don't suggest fixes for generated files, since they are generally + // not useful and some editors may apply them automatically on save. + // (Unfortunately there's no reliable way to distinguish fixes from + // queries, so we must list all kinds of queries here.) + if golang.IsGenerated(ctx, snapshot, uri) { + actions = slices.DeleteFunc(actions, func(a protocol.CodeAction) bool { + switch a.Kind { + case settings.GoTest, + settings.GoDoc, + settings.GoFreeSymbols, + settings.GoAssembly, + settings.GoplsDocFeatures: + return false // read-only query + } + return true // potential write operation + }) + } + + return actions, nil + + default: + // Unsupported file kind for a code action. + return nil, nil + } +} + +// ResolveCodeAction resolves missing Edit information (that is, computes the +// details of the necessary patch) in the given code action using the provided +// Data field of the CodeAction, which should contain the raw json of a protocol.Command. +// +// This should be called by the client before applying code actions, when the +// client has code action resolve support. +// +// This feature allows capable clients to preview and selectively apply the diff +// instead of applying the whole thing unconditionally through workspace/applyEdit. +func (s *server) ResolveCodeAction(ctx context.Context, ca *protocol.CodeAction) (*protocol.CodeAction, error) { + ctx, done := event.Start(ctx, "lsp.Server.resolveCodeAction") + defer done() + + // Only resolve the code action if there is Data provided. + var cmd protocol.Command + if ca.Data != nil { + if err := protocol.UnmarshalJSON(*ca.Data, &cmd); err != nil { + return nil, err + } + } + if cmd.Command != "" { + params := &protocol.ExecuteCommandParams{ + Command: cmd.Command, + Arguments: cmd.Arguments, + } + + handler := &commandHandler{ + s: s, + params: params, + } + edit, err := command.Dispatch(ctx, params, handler) + if err != nil { + return nil, err + } + var ok bool + if ca.Edit, ok = edit.(*protocol.WorkspaceEdit); !ok { + return nil, fmt.Errorf("unable to resolve code action %q", ca.Title) + } + } + return ca, nil +} + +// codeActionsMatchingDiagnostics creates code actions for the +// provided diagnostics, by unmarshalling actions bundled in the +// protocol.Diagnostic.Data field or, if there were none, by creating +// actions from edits associated with a matching Diagnostic from the +// set of stored diagnostics for this file. +func (s *server) codeActionsMatchingDiagnostics(ctx context.Context, uri protocol.DocumentURI, snapshot *cache.Snapshot, pds []protocol.Diagnostic, enabled func(protocol.CodeActionKind) bool) ([]protocol.CodeAction, error) { + var actions []protocol.CodeAction + var unbundled []protocol.Diagnostic // diagnostics without bundled code actions in their Data field + for _, pd := range pds { + bundled := cache.BundledLazyFixes(pd) + if len(bundled) > 0 { + for _, fix := range bundled { + if enabled(fix.Kind) { + actions = append(actions, fix) + } + } + } else { + // No bundled actions: keep searching for a match. + unbundled = append(unbundled, pd) + } + } + + for _, pd := range unbundled { + for _, sd := range s.findMatchingDiagnostics(uri, pd) { + diagActions, err := codeActionsForDiagnostic(ctx, snapshot, sd, &pd, enabled) + if err != nil { + return nil, err + } + actions = append(actions, diagActions...) + } + } + return actions, nil +} + +func codeActionsForDiagnostic(ctx context.Context, snapshot *cache.Snapshot, sd *cache.Diagnostic, pd *protocol.Diagnostic, enabled func(protocol.CodeActionKind) bool) ([]protocol.CodeAction, error) { + var actions []protocol.CodeAction + for _, fix := range sd.SuggestedFixes { + if !enabled(fix.ActionKind) { + continue + } + var changes []protocol.DocumentChange + for uri, edits := range fix.Edits { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + change := protocol.DocumentChangeEdit(fh, edits) + changes = append(changes, change) + } + actions = append(actions, protocol.CodeAction{ + Title: fix.Title, + Kind: fix.ActionKind, + Edit: protocol.NewWorkspaceEdit(changes...), + Command: fix.Command, + Diagnostics: []protocol.Diagnostic{*pd}, + }) + } + return actions, nil +} + +func (s *server) findMatchingDiagnostics(uri protocol.DocumentURI, pd protocol.Diagnostic) []*cache.Diagnostic { + s.diagnosticsMu.Lock() + defer s.diagnosticsMu.Unlock() + + var sds []*cache.Diagnostic + for _, viewDiags := range s.diagnostics[uri].byView { + for _, sd := range viewDiags.diagnostics { + sameDiagnostic := (pd.Message == strings.TrimSpace(sd.Message) && // extra space may have been trimmed when converting to protocol.Diagnostic + protocol.CompareRange(pd.Range, sd.Range) == 0 && + pd.Source == string(sd.Source)) + + if sameDiagnostic { + sds = append(sds, sd) + } + } + } + return sds +} + +func (s *server) getSupportedCodeActions() []protocol.CodeActionKind { + allCodeActionKinds := make(map[protocol.CodeActionKind]struct{}) + for _, kinds := range s.Options().SupportedCodeActions { + for kind := range kinds { + allCodeActionKinds[kind] = struct{}{} + } + } + var result []protocol.CodeActionKind + for kind := range allCodeActionKinds { + result = append(result, kind) + } + sort.Slice(result, func(i, j int) bool { + return result[i] < result[j] + }) + return result +} + +type unit = struct{} diff --git a/contribs/gnopls/internal/server/code_lens.go b/contribs/gnopls/internal/server/code_lens.go new file mode 100644 index 00000000000..67b359e866c --- /dev/null +++ b/contribs/gnopls/internal/server/code_lens.go @@ -0,0 +1,66 @@ +// 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 server + +import ( + "context" + "fmt" + "sort" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/mod" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/event" +) + +// CodeLens reports the set of available CodeLenses +// (range-associated commands) in the given file. +func (s *server) CodeLens(ctx context.Context, params *protocol.CodeLensParams) ([]protocol.CodeLens, error) { + ctx, done := event.Start(ctx, "lsp.Server.codeLens", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + var lensFuncs map[settings.CodeLensSource]cache.CodeLensSourceFunc + switch snapshot.FileKind(fh) { + case file.Mod: + lensFuncs = mod.CodeLensSources() + case file.Go: + lensFuncs = golang.CodeLensSources() + default: + // Unsupported file kind for a code lens. + return nil, nil + } + var lenses []protocol.CodeLens + for kind, lensFunc := range lensFuncs { + if !snapshot.Options().Codelenses[kind] { + continue + } + added, err := lensFunc(ctx, snapshot, fh) + // Code lens is called on every keystroke, so we should just operate in + // a best-effort mode, ignoring errors. + if err != nil { + event.Error(ctx, fmt.Sprintf("code lens %s failed", kind), err) + continue + } + lenses = append(lenses, added...) + } + sort.Slice(lenses, func(i, j int) bool { + a, b := lenses[i], lenses[j] + if cmp := protocol.CompareRange(a.Range, b.Range); cmp != 0 { + return cmp < 0 + } + return a.Command.Command < b.Command.Command + }) + return lenses, nil +} diff --git a/contribs/gnopls/internal/server/command.go b/contribs/gnopls/internal/server/command.go new file mode 100644 index 00000000000..352008ac43b --- /dev/null +++ b/contribs/gnopls/internal/server/command.go @@ -0,0 +1,1651 @@ +// 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 server + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "os" + "path/filepath" + "regexp" + "runtime" + "runtime/pprof" + "slices" + "sort" + "strings" + "sync" + + "golang.org/x/mod/modfile" + "golang.org/x/telemetry/counter" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/progress" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/vulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/scan" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/tokeninternal" + "golang.org/x/tools/internal/xcontext" +) + +func (s *server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { + ctx, done := event.Start(ctx, "lsp.Server.executeCommand") + defer done() + + var found bool + for _, name := range s.Options().SupportedCommands { + if name == params.Command { + found = true + break + } + } + if !found { + return nil, fmt.Errorf("%s is not a supported command", params.Command) + } + + handler := &commandHandler{ + s: s, + params: params, + } + return command.Dispatch(ctx, params, handler) +} + +type commandHandler struct { + s *server + params *protocol.ExecuteCommandParams +} + +func (h *commandHandler) Modules(ctx context.Context, args command.ModulesArgs) (command.ModulesResult, error) { + // keepModule filters modules based on the command args + keepModule := func(goMod protocol.DocumentURI) bool { + // Does the directory enclose the view's go.mod file? + if !args.Dir.Encloses(goMod) { + return false + } + + // Calculate the relative path + rel, err := filepath.Rel(args.Dir.Path(), goMod.Path()) + if err != nil { + return false // "can't happen" (see prior Encloses check) + } + + assert(filepath.Base(goMod.Path()) == "go.mod", fmt.Sprintf("invalid go.mod path: want go.mod, got %q", goMod.Path())) + + // Invariant: rel is a relative path without "../" segments and the last + // segment is "go.mod" + nparts := strings.Count(rel, string(filepath.Separator)) + return args.MaxDepth < 0 || nparts <= args.MaxDepth + } + + // Views may include: + // - go.work views containing one or more modules each; + // - go.mod views containing a single module each; + // - GOPATH and/or ad hoc views containing no modules. + // + // Retrieving a view via the request path would only work for a + // non-recursive query for a go.mod view, and even in that case + // [Session.SnapshotOf] doesn't work on directories. Thus we check every + // view. + var result command.ModulesResult + seen := map[protocol.DocumentURI]bool{} + for _, v := range h.s.session.Views() { + s, release, err := v.Snapshot() + if err != nil { + return command.ModulesResult{}, err + } + defer release() + + for _, modFile := range v.ModFiles() { + if !keepModule(modFile) { + continue + } + + // Deduplicate + if seen[modFile] { + continue + } + seen[modFile] = true + + fh, err := s.ReadFile(ctx, modFile) + if err != nil { + return command.ModulesResult{}, err + } + mod, err := s.ParseMod(ctx, fh) + if err != nil { + return command.ModulesResult{}, err + } + if mod.File.Module == nil { + continue // syntax contains errors + } + result.Modules = append(result.Modules, command.Module{ + Path: mod.File.Module.Mod.Path, + Version: mod.File.Module.Mod.Version, + GoMod: mod.URI, + }) + } + } + return result, nil +} + +func (h *commandHandler) Packages(ctx context.Context, args command.PackagesArgs) (command.PackagesResult, error) { + // Convert file arguments into directories + dirs := make([]protocol.DocumentURI, len(args.Files)) + for i, file := range args.Files { + if filepath.Ext(file.Path()) == ".go" { + dirs[i] = file.Dir() + } else { + dirs[i] = file + } + } + + keepPackage := func(pkg *metadata.Package) bool { + for _, file := range pkg.GoFiles { + for _, dir := range dirs { + if file.Dir() == dir || args.Recursive && dir.Encloses(file) { + return true + } + } + } + return false + } + + result := command.PackagesResult{ + Module: make(map[string]command.Module), + } + + err := h.run(ctx, commandConfig{ + progress: "Packages", + }, func(ctx context.Context, _ commandDeps) error { + for _, view := range h.s.session.Views() { + snapshot, release, err := view.Snapshot() + if err != nil { + return err + } + defer release() + + metas, err := snapshot.WorkspaceMetadata(ctx) + if err != nil { + return err + } + + // Filter out unwanted packages + metas = slices.DeleteFunc(metas, func(meta *metadata.Package) bool { + return meta.IsIntermediateTestVariant() || + !keepPackage(meta) + }) + + start := len(result.Packages) + for _, meta := range metas { + var mod command.Module + if meta.Module != nil { + mod = command.Module{ + Path: meta.Module.Path, + Version: meta.Module.Version, + GoMod: protocol.URIFromPath(meta.Module.GoMod), + } + result.Module[mod.Path] = mod // Overwriting is ok + } + + result.Packages = append(result.Packages, command.Package{ + Path: string(meta.PkgPath), + ForTest: string(meta.ForTest), + ModulePath: mod.Path, + }) + } + + if args.Mode&command.NeedTests == 0 { + continue + } + + // Make a single request to the index (per snapshot) to minimize the + // performance hit + var ids []cache.PackageID + for _, meta := range metas { + ids = append(ids, meta.ID) + } + + allTests, err := snapshot.Tests(ctx, ids...) + if err != nil { + return err + } + + for i, tests := range allTests { + pkg := &result.Packages[start+i] + fileByPath := map[protocol.DocumentURI]*command.TestFile{} + for _, test := range tests.All() { + test := command.TestCase{ + Name: test.Name, + Loc: test.Location, + } + + file, ok := fileByPath[test.Loc.URI] + if !ok { + f := command.TestFile{ + URI: test.Loc.URI, + } + i := len(pkg.TestFiles) + pkg.TestFiles = append(pkg.TestFiles, f) + file = &pkg.TestFiles[i] + fileByPath[test.Loc.URI] = file + } + file.Tests = append(file.Tests, test) + } + } + } + + return nil + }) + return result, err +} + +func (h *commandHandler) MaybePromptForTelemetry(ctx context.Context) error { + go h.s.maybePromptForTelemetry(ctx, true) + return nil +} + +func (*commandHandler) AddTelemetryCounters(_ context.Context, args command.AddTelemetryCountersArgs) error { + if len(args.Names) != len(args.Values) { + return fmt.Errorf("Names and Values must have the same length") + } + // invalid counter update requests will be silently dropped. (no audience) + for i, n := range args.Names { + v := args.Values[i] + if n == "" || v < 0 { + continue + } + counter.Add("fwd/"+n, v) + } + return nil +} + +// commandConfig configures common command set-up and execution. +type commandConfig struct { + requireSave bool // whether all files must be saved for the command to work + progress string // title to use for progress reporting. If empty, no progress will be reported. Mandatory for async commands. + forView string // view to resolve to a snapshot; incompatible with forURI + forURI protocol.DocumentURI // URI to resolve to a snapshot. If unset, snapshot will be nil. +} + +// commandDeps is evaluated from a commandConfig. Note that not all fields may +// be populated, depending on which configuration is set. See comments in-line +// for details. +type commandDeps struct { + snapshot *cache.Snapshot // present if cfg.forURI or forView was set + fh file.Handle // present if cfg.forURI was set + work *progress.WorkDone // present if cfg.progress was set +} + +type commandFunc func(context.Context, commandDeps) error + +// These strings are reported as the final WorkDoneProgressEnd message +// for each workspace/executeCommand request. +const ( + CommandCanceled = "canceled" + CommandFailed = "failed" + CommandCompleted = "completed" +) + +// run performs command setup for command execution, and invokes the given run +// function. If cfg.async is set, run executes the given func in a separate +// goroutine, and returns as soon as setup is complete and the goroutine is +// scheduled. +// +// Invariant: if the resulting error is non-nil, the given run func will +// (eventually) be executed exactly once. +func (c *commandHandler) run(ctx context.Context, cfg commandConfig, run commandFunc) (err error) { + if cfg.requireSave { + var unsaved []string + for _, overlay := range c.s.session.Overlays() { + if !overlay.SameContentsOnDisk() { + unsaved = append(unsaved, overlay.URI().Path()) + } + } + if len(unsaved) > 0 { + return fmt.Errorf("All files must be saved first (unsaved: %v).", unsaved) + } + } + var deps commandDeps + var release func() + if cfg.forURI != "" && cfg.forView != "" { + return bug.Errorf("internal error: forURI=%q, forView=%q", cfg.forURI, cfg.forView) + } + if cfg.forURI != "" { + deps.fh, deps.snapshot, release, err = c.s.fileOf(ctx, cfg.forURI) + if err != nil { + return err + } + + } else if cfg.forView != "" { + view, err := c.s.session.View(cfg.forView) + if err != nil { + return err + } + deps.snapshot, release, err = view.Snapshot() + if err != nil { + return err + } + + } else { + release = func() {} + } + // Inv: release() must be called exactly once after this point. + // In the async case, runcmd may outlive run(). + + ctx, cancel := context.WithCancel(xcontext.Detach(ctx)) + if cfg.progress != "" { + deps.work = c.s.progress.Start(ctx, cfg.progress, "Running...", c.params.WorkDoneToken, cancel) + } + runcmd := func() error { + defer release() + defer cancel() + err := run(ctx, deps) + if deps.work != nil { + switch { + case errors.Is(err, context.Canceled): + deps.work.End(ctx, CommandCanceled) + case err != nil: + event.Error(ctx, "command error", err) + deps.work.End(ctx, CommandFailed) + default: + deps.work.End(ctx, CommandCompleted) + } + } + return err + } + + if enum := command.Command(c.params.Command); enum.IsAsync() { + if cfg.progress == "" { + log.Fatalf("asynchronous command %q does not enable progress reporting", + enum) + } + go func() { + if err := runcmd(); err != nil { + showMessage(ctx, c.s.client, protocol.Error, err.Error()) + } + }() + return nil + } + return runcmd() +} + +func (c *commandHandler) ApplyFix(ctx context.Context, args command.ApplyFixArgs) (*protocol.WorkspaceEdit, error) { + var result *protocol.WorkspaceEdit + err := c.run(ctx, commandConfig{ + // Note: no progress here. Applying fixes should be quick. + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + changes, err := golang.ApplyFix(ctx, args.Fix, deps.snapshot, deps.fh, args.Range) + if err != nil { + return err + } + wsedit := protocol.NewWorkspaceEdit(changes...) + if args.ResolveEdits { + result = wsedit + return nil + } + resp, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ + Edit: *wsedit, + }) + if err != nil { + return err + } + if !resp.Applied { + return errors.New(resp.FailureReason) + } + return nil + }) + return result, err +} + +func (c *commandHandler) RegenerateCgo(ctx context.Context, args command.URIArg) error { + return c.run(ctx, commandConfig{ + progress: "Regenerating Cgo", + }, func(ctx context.Context, _ commandDeps) error { + return c.modifyState(ctx, FromRegenerateCgo, func() (*cache.Snapshot, func(), error) { + // Resetting the view causes cgo to be regenerated via `go list`. + v, err := c.s.session.ResetView(ctx, args.URI) + if err != nil { + return nil, nil, err + } + return v.Snapshot() + }) + }) +} + +// modifyState performs an operation that modifies the snapshot state. +// +// It causes a snapshot diagnosis for the provided ModificationSource. +func (c *commandHandler) modifyState(ctx context.Context, source ModificationSource, work func() (*cache.Snapshot, func(), error)) error { + var wg sync.WaitGroup // tracks work done on behalf of this function, incl. diagnostics + wg.Add(1) + defer wg.Done() + + // Track progress on this operation for testing. + if c.s.Options().VerboseWorkDoneProgress { + work := c.s.progress.Start(ctx, DiagnosticWorkTitle(source), "Calculating file diagnostics...", nil, nil) + go func() { + wg.Wait() + work.End(ctx, "Done.") + }() + } + snapshot, release, err := work() + if err != nil { + return err + } + wg.Add(1) + go func() { + // Diagnosing with the background context ensures new snapshots are fully + // diagnosed. + c.s.diagnoseSnapshot(snapshot.BackgroundContext(), snapshot, nil, 0) + release() + wg.Done() + }() + return nil +} + +func (c *commandHandler) CheckUpgrades(ctx context.Context, args command.CheckUpgradesArgs) error { + return c.run(ctx, commandConfig{ + forURI: args.URI, + progress: "Checking for upgrades", + }, func(ctx context.Context, deps commandDeps) error { + return c.modifyState(ctx, FromCheckUpgrades, func() (*cache.Snapshot, func(), error) { + upgrades, err := c.s.getUpgrades(ctx, deps.snapshot, args.URI, args.Modules) + if err != nil { + return nil, nil, err + } + return c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{ + ModuleUpgrades: map[protocol.DocumentURI]map[string]string{args.URI: upgrades}, + }) + }) + }) +} + +func (c *commandHandler) AddDependency(ctx context.Context, args command.DependencyArgs) error { + return c.GoGetModule(ctx, args) +} + +func (c *commandHandler) UpgradeDependency(ctx context.Context, args command.DependencyArgs) error { + return c.GoGetModule(ctx, args) +} + +func (c *commandHandler) ResetGoModDiagnostics(ctx context.Context, args command.ResetGoModDiagnosticsArgs) error { + return c.run(ctx, commandConfig{ + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + return c.modifyState(ctx, FromResetGoModDiagnostics, func() (*cache.Snapshot, func(), error) { + return c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{ + ModuleUpgrades: map[protocol.DocumentURI]map[string]string{ + deps.fh.URI(): nil, + }, + Vulns: map[protocol.DocumentURI]*vulncheck.Result{ + deps.fh.URI(): nil, + }, + }) + }) + }) +} + +func (c *commandHandler) GoGetModule(ctx context.Context, args command.DependencyArgs) error { + return c.run(ctx, commandConfig{ + progress: "Running go get", + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI, func(invoke func(...string) (*bytes.Buffer, error)) error { + return runGoGetModule(invoke, args.AddRequire, args.GoCmdArgs) + }) + }) +} + +// TODO(rFindley): UpdateGoSum, Tidy, and Vendor could probably all be one command. +func (c *commandHandler) UpdateGoSum(ctx context.Context, args command.URIArgs) error { + return c.run(ctx, commandConfig{ + progress: "Updating go.sum", + }, func(ctx context.Context, _ commandDeps) error { + for _, uri := range args.URIs { + fh, snapshot, release, err := c.s.fileOf(ctx, uri) + if err != nil { + return err + } + defer release() + if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error { + _, err := invoke("list", "all") + return err + }); err != nil { + return err + } + } + return nil + }) +} + +func (c *commandHandler) Tidy(ctx context.Context, args command.URIArgs) error { + return c.run(ctx, commandConfig{ + progress: "Running go mod tidy", + }, func(ctx context.Context, _ commandDeps) error { + for _, uri := range args.URIs { + fh, snapshot, release, err := c.s.fileOf(ctx, uri) + if err != nil { + return err + } + defer release() + if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error { + _, err := invoke("mod", "tidy") + return err + }); err != nil { + return err + } + } + return nil + }) +} + +func (c *commandHandler) Vendor(ctx context.Context, args command.URIArg) error { + return c.run(ctx, commandConfig{ + requireSave: true, // TODO(adonovan): probably not needed; but needs a test. + progress: "Running go mod vendor", + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + // Use RunGoCommandPiped here so that we don't compete with any other go + // command invocations. go mod vendor deletes modules.txt before recreating + // it, and therefore can run into file locking issues on Windows if that + // file is in use by another process, such as go list. + // + // If golang/go#44119 is resolved, go mod vendor will instead modify + // modules.txt in-place. In that case we could theoretically allow this + // command to run concurrently. + stderr := new(bytes.Buffer) + inv, cleanupInvocation, err := deps.snapshot.GoCommandInvocation(true, &gocommand.Invocation{ + Verb: "mod", + Args: []string{"vendor"}, + WorkingDir: filepath.Dir(args.URI.Path()), + }) + if err != nil { + return err + } + defer cleanupInvocation() + err = deps.snapshot.View().GoCommandRunner().RunPiped(ctx, *inv, &bytes.Buffer{}, stderr) + if err != nil { + return fmt.Errorf("running go mod vendor failed: %v\nstderr:\n%s", err, stderr.String()) + } + return nil + }) +} + +func (c *commandHandler) EditGoDirective(ctx context.Context, args command.EditGoDirectiveArgs) error { + return c.run(ctx, commandConfig{ + requireSave: true, // if go.mod isn't saved it could cause a problem + forURI: args.URI, + }, func(ctx context.Context, _ commandDeps) error { + fh, snapshot, release, err := c.s.fileOf(ctx, args.URI) + if err != nil { + return err + } + defer release() + if err := c.s.runGoModUpdateCommands(ctx, snapshot, fh.URI(), func(invoke func(...string) (*bytes.Buffer, error)) error { + _, err := invoke("mod", "edit", "-go", args.Version) + return err + }); err != nil { + return err + } + return nil + }) +} + +func (c *commandHandler) RemoveDependency(ctx context.Context, args command.RemoveDependencyArgs) error { + return c.run(ctx, commandConfig{ + progress: "Removing dependency", + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + // See the documentation for OnlyDiagnostic. + // + // TODO(rfindley): In Go 1.17+, we will be able to use the go command + // without checking if the module is tidy. + if args.OnlyDiagnostic { + return c.s.runGoModUpdateCommands(ctx, deps.snapshot, args.URI, func(invoke func(...string) (*bytes.Buffer, error)) error { + if err := runGoGetModule(invoke, false, []string{args.ModulePath + "@none"}); err != nil { + return err + } + _, err := invoke("mod", "tidy") + return err + }) + } + pm, err := deps.snapshot.ParseMod(ctx, deps.fh) + if err != nil { + return err + } + edits, err := dropDependency(pm, args.ModulePath) + if err != nil { + return err + } + response, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ + Edit: *protocol.NewWorkspaceEdit( + protocol.DocumentChangeEdit(deps.fh, edits)), + }) + if err != nil { + return err + } + if !response.Applied { + return fmt.Errorf("edits not applied because of %s", response.FailureReason) + } + return nil + }) +} + +// dropDependency returns the edits to remove the given require from the go.mod +// file. +func dropDependency(pm *cache.ParsedModule, modulePath string) ([]protocol.TextEdit, error) { + // We need a private copy of the parsed go.mod file, since we're going to + // modify it. + copied, err := modfile.Parse("", pm.Mapper.Content, nil) + if err != nil { + return nil, err + } + if err := copied.DropRequire(modulePath); err != nil { + return nil, err + } + copied.Cleanup() + newContent, err := copied.Format() + if err != nil { + return nil, err + } + // Calculate the edits to be made due to the change. + diff := diff.Bytes(pm.Mapper.Content, newContent) + return protocol.EditsFromDiffEdits(pm.Mapper, diff) +} + +// Test is an alias for RunTests (with splayed arguments). +func (c *commandHandler) Test(ctx context.Context, uri protocol.DocumentURI, tests, benchmarks []string) error { + return c.RunTests(ctx, command.RunTestsArgs{ + URI: uri, + Tests: tests, + Benchmarks: benchmarks, + }) +} + +func (c *commandHandler) Doc(ctx context.Context, loc protocol.Location) error { + return c.run(ctx, commandConfig{ + progress: "", // the operation should be fast + forURI: loc.URI, + }, func(ctx context.Context, deps commandDeps) error { + pkg, pgf, err := golang.NarrowestPackageForFile(ctx, deps.snapshot, loc.URI) + if err != nil { + return err + } + start, end, err := pgf.RangePos(loc.Range) + if err != nil { + return err + } + + // Start web server. + web, err := c.s.getWeb() + if err != nil { + return err + } + + // Compute package path and optional symbol fragment + // (e.g. "#Buffer.Len") from the the selection. + pkgpath, fragment, _ := golang.DocFragment(pkg, pgf, start, end) + + // Direct the client to open the /pkg page. + url := web.PkgURL(deps.snapshot.View().ID(), pkgpath, fragment) + openClientBrowser(ctx, c.s.client, url) + + return nil + }) +} + +func (c *commandHandler) RunTests(ctx context.Context, args command.RunTestsArgs) error { + return c.run(ctx, commandConfig{ + progress: "Running go test", // (asynchronous) + requireSave: true, // go test honors overlays, but tests themselves cannot + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + return c.runTests(ctx, deps.snapshot, deps.work, args.URI, args.Tests, args.Benchmarks) + }) +} + +func (c *commandHandler) runTests(ctx context.Context, snapshot *cache.Snapshot, work *progress.WorkDone, uri protocol.DocumentURI, tests, benchmarks []string) error { + // TODO: fix the error reporting when this runs async. + meta, err := golang.NarrowestMetadataForFile(ctx, snapshot, uri) + if err != nil { + return err + } + pkgPath := string(meta.ForTest) + + // create output + buf := &bytes.Buffer{} + ew := progress.NewEventWriter(ctx, "test") + out := io.MultiWriter(ew, progress.NewWorkDoneWriter(ctx, work), buf) + + // Run `go test -run Func` on each test. + var failedTests int + for _, funcName := range tests { + inv, cleanupInvocation, err := snapshot.GoCommandInvocation(false, &gocommand.Invocation{ + Verb: "test", + Args: []string{pkgPath, "-v", "-count=1", fmt.Sprintf("-run=^%s$", regexp.QuoteMeta(funcName))}, + WorkingDir: filepath.Dir(uri.Path()), + }) + if err != nil { + return err + } + defer cleanupInvocation() + if err := snapshot.View().GoCommandRunner().RunPiped(ctx, *inv, out, out); err != nil { + if errors.Is(err, context.Canceled) { + return err + } + failedTests++ + } + } + + // Run `go test -run=^$ -bench Func` on each test. + var failedBenchmarks int + for _, funcName := range benchmarks { + inv, cleanupInvocation, err := snapshot.GoCommandInvocation(false, &gocommand.Invocation{ + Verb: "test", + Args: []string{pkgPath, "-v", "-run=^$", fmt.Sprintf("-bench=^%s$", regexp.QuoteMeta(funcName))}, + WorkingDir: filepath.Dir(uri.Path()), + }) + if err != nil { + return err + } + defer cleanupInvocation() + if err := snapshot.View().GoCommandRunner().RunPiped(ctx, *inv, out, out); err != nil { + if errors.Is(err, context.Canceled) { + return err + } + failedBenchmarks++ + } + } + + var title string + if len(tests) > 0 && len(benchmarks) > 0 { + title = "tests and benchmarks" + } else if len(tests) > 0 { + title = "tests" + } else if len(benchmarks) > 0 { + title = "benchmarks" + } else { + return errors.New("No functions were provided") + } + message := fmt.Sprintf("all %s passed", title) + if failedTests > 0 && failedBenchmarks > 0 { + message = fmt.Sprintf("%d / %d tests failed and %d / %d benchmarks failed", failedTests, len(tests), failedBenchmarks, len(benchmarks)) + } else if failedTests > 0 { + message = fmt.Sprintf("%d / %d tests failed", failedTests, len(tests)) + } else if failedBenchmarks > 0 { + message = fmt.Sprintf("%d / %d benchmarks failed", failedBenchmarks, len(benchmarks)) + } + if failedTests > 0 || failedBenchmarks > 0 { + message += "\n" + buf.String() + } + + showMessage(ctx, c.s.client, protocol.Info, message) + + if failedTests > 0 || failedBenchmarks > 0 { + return errors.New("gopls.test command failed") + } + return nil +} + +func (c *commandHandler) Generate(ctx context.Context, args command.GenerateArgs) error { + title := "Running go generate ." + if args.Recursive { + title = "Running go generate ./..." + } + return c.run(ctx, commandConfig{ + requireSave: true, // commands executed by go generate cannot honor overlays + progress: title, + forURI: args.Dir, + }, func(ctx context.Context, deps commandDeps) error { + er := progress.NewEventWriter(ctx, "generate") + + pattern := "." + if args.Recursive { + pattern = "./..." + } + inv, cleanupInvocation, err := deps.snapshot.GoCommandInvocation(true, &gocommand.Invocation{ + Verb: "generate", + Args: []string{"-x", pattern}, + WorkingDir: args.Dir.Path(), + }) + if err != nil { + return err + } + defer cleanupInvocation() + stderr := io.MultiWriter(er, progress.NewWorkDoneWriter(ctx, deps.work)) + if err := deps.snapshot.View().GoCommandRunner().RunPiped(ctx, *inv, er, stderr); err != nil { + return err + } + return nil + }) +} + +func (c *commandHandler) GoGetPackage(ctx context.Context, args command.GoGetPackageArgs) error { + return c.run(ctx, commandConfig{ + forURI: args.URI, + progress: "Running go get", + }, func(ctx context.Context, deps commandDeps) error { + snapshot := deps.snapshot + modURI := snapshot.GoModForFile(args.URI) + if modURI == "" { + return fmt.Errorf("no go.mod file found for %s", args.URI) + } + tempDir, cleanupModDir, err := cache.TempModDir(ctx, snapshot, modURI) + if err != nil { + return fmt.Errorf("creating a temp go.mod: %v", err) + } + defer cleanupModDir() + + inv, cleanupInvocation, err := snapshot.GoCommandInvocation(true, &gocommand.Invocation{ + Verb: "list", + Args: []string{"-f", "{{.Module.Path}}@{{.Module.Version}}", "-mod=mod", "-modfile=" + filepath.Join(tempDir, "go.mod"), args.Pkg}, + Env: []string{"GOWORK=off"}, + WorkingDir: modURI.Dir().Path(), + }) + if err != nil { + return err + } + defer cleanupInvocation() + stdout, err := snapshot.View().GoCommandRunner().Run(ctx, *inv) + if err != nil { + return err + } + ver := strings.TrimSpace(stdout.String()) + return c.s.runGoModUpdateCommands(ctx, snapshot, args.URI, func(invoke func(...string) (*bytes.Buffer, error)) error { + if args.AddRequire { + if err := addModuleRequire(invoke, []string{ver}); err != nil { + return err + } + } + _, err := invoke(append([]string{"get", "-d"}, args.Pkg)...) + return err + }) + }) +} + +func (s *server) runGoModUpdateCommands(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, run func(invoke func(...string) (*bytes.Buffer, error)) error) error { + // TODO(rfindley): can/should this use findRootPattern? + modURI := snapshot.GoModForFile(uri) + if modURI == "" { + return fmt.Errorf("no go.mod file found for %s", uri.Path()) + } + newModBytes, newSumBytes, err := snapshot.RunGoModUpdateCommands(ctx, modURI, run) + if err != nil { + return err + } + sumURI := protocol.URIFromPath(strings.TrimSuffix(modURI.Path(), ".mod") + ".sum") + + modChange, err := computeEditChange(ctx, snapshot, modURI, newModBytes) + if err != nil { + return err + } + sumChange, err := computeEditChange(ctx, snapshot, sumURI, newSumBytes) + if err != nil { + return err + } + + var changes []protocol.DocumentChange + if modChange.Valid() { + changes = append(changes, modChange) + } + if sumChange.Valid() { + changes = append(changes, sumChange) + } + return applyChanges(ctx, s.client, changes) +} + +// computeEditChange computes the edit change required to transform the +// snapshot file specified by uri to the provided new content. +// Beware: returns a DocumentChange that is !Valid() if none were necessary. +// +// If the file is not open, computeEditChange simply writes the new content to +// disk. +// +// TODO(rfindley): fix this API asymmetry. It should be up to the caller to +// write the file or apply the edits. +func computeEditChange(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, newContent []byte) (protocol.DocumentChange, error) { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return protocol.DocumentChange{}, err + } + oldContent, err := fh.Content() + if err != nil && !os.IsNotExist(err) { + return protocol.DocumentChange{}, err + } + + if bytes.Equal(oldContent, newContent) { + return protocol.DocumentChange{}, nil // note: result is !Valid() + } + + // Sending a workspace edit to a closed file causes VS Code to open the + // file and leave it unsaved. We would rather apply the changes directly, + // especially to go.sum, which should be mostly invisible to the user. + if !snapshot.IsOpen(uri) { + err := os.WriteFile(uri.Path(), newContent, 0666) + return protocol.DocumentChange{}, err + } + + m := protocol.NewMapper(fh.URI(), oldContent) + diff := diff.Bytes(oldContent, newContent) + textedits, err := protocol.EditsFromDiffEdits(m, diff) + if err != nil { + return protocol.DocumentChange{}, err + } + return protocol.DocumentChangeEdit(fh, textedits), nil +} + +func applyChanges(ctx context.Context, cli protocol.Client, changes []protocol.DocumentChange) error { + if len(changes) == 0 { + return nil + } + response, err := cli.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ + Edit: *protocol.NewWorkspaceEdit(changes...), + }) + if err != nil { + return err + } + if !response.Applied { + return fmt.Errorf("edits not applied because of %s", response.FailureReason) + } + return nil +} + +func runGoGetModule(invoke func(...string) (*bytes.Buffer, error), addRequire bool, args []string) error { + if addRequire { + if err := addModuleRequire(invoke, args); err != nil { + return err + } + } + _, err := invoke(append([]string{"get", "-d"}, args...)...) + return err +} + +func addModuleRequire(invoke func(...string) (*bytes.Buffer, error), args []string) error { + // Using go get to create a new dependency results in an + // `// indirect` comment we may not want. The only way to avoid it + // is to add the require as direct first. Then we can use go get to + // update go.sum and tidy up. + _, err := invoke(append([]string{"mod", "edit", "-require"}, args...)...) + return err +} + +// TODO(rfindley): inline. +func (s *server) getUpgrades(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, modules []string) (map[string]string, error) { + inv, cleanup, err := snapshot.GoCommandInvocation(true, &gocommand.Invocation{ + Verb: "list", + // -mod=readonly is necessary when vendor is present (golang/go#66055) + Args: append([]string{"-mod=readonly", "-m", "-u", "-json"}, modules...), + WorkingDir: filepath.Dir(uri.Path()), + }) + if err != nil { + return nil, err + } + defer cleanup() + stdout, err := snapshot.View().GoCommandRunner().Run(ctx, *inv) + if err != nil { + return nil, err + } + + upgrades := map[string]string{} + for dec := json.NewDecoder(stdout); dec.More(); { + mod := &gocommand.ModuleJSON{} + if err := dec.Decode(mod); err != nil { + return nil, err + } + if mod.Update == nil { + continue + } + upgrades[mod.Path] = mod.Update.Version + } + return upgrades, nil +} + +func (c *commandHandler) GCDetails(ctx context.Context, uri protocol.DocumentURI) error { + return c.ToggleGCDetails(ctx, command.URIArg{URI: uri}) +} + +func (c *commandHandler) ToggleGCDetails(ctx context.Context, args command.URIArg) error { + return c.run(ctx, commandConfig{ + progress: "Toggling GC Details", + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + return c.modifyState(ctx, FromToggleGCDetails, func() (*cache.Snapshot, func(), error) { + meta, err := golang.NarrowestMetadataForFile(ctx, deps.snapshot, deps.fh.URI()) + if err != nil { + return nil, nil, err + } + wantDetails := !deps.snapshot.WantGCDetails(meta.ID) // toggle the gc details state + return c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{ + GCDetails: map[metadata.PackageID]bool{ + meta.ID: wantDetails, + }, + }) + }) + }) +} + +func (c *commandHandler) ListKnownPackages(ctx context.Context, args command.URIArg) (command.ListKnownPackagesResult, error) { + var result command.ListKnownPackagesResult + err := c.run(ctx, commandConfig{ + progress: "Listing packages", + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + pkgs, err := golang.KnownPackagePaths(ctx, deps.snapshot, deps.fh) + for _, pkg := range pkgs { + result.Packages = append(result.Packages, string(pkg)) + } + return err + }) + return result, err +} + +func (c *commandHandler) ListImports(ctx context.Context, args command.URIArg) (command.ListImportsResult, error) { + var result command.ListImportsResult + err := c.run(ctx, commandConfig{ + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + fh, err := deps.snapshot.ReadFile(ctx, args.URI) + if err != nil { + return err + } + pgf, err := deps.snapshot.ParseGo(ctx, fh, parsego.Header) + if err != nil { + return err + } + fset := tokeninternal.FileSetFor(pgf.Tok) + for _, group := range astutil.Imports(fset, pgf.File) { + for _, imp := range group { + if imp.Path == nil { + continue + } + var name string + if imp.Name != nil { + name = imp.Name.Name + } + result.Imports = append(result.Imports, command.FileImport{ + Path: string(metadata.UnquoteImportPath(imp)), + Name: name, + }) + } + } + meta, err := golang.NarrowestMetadataForFile(ctx, deps.snapshot, args.URI) + if err != nil { + return err // e.g. cancelled + } + for pkgPath := range meta.DepsByPkgPath { + result.PackageImports = append(result.PackageImports, + command.PackageImport{Path: string(pkgPath)}) + } + sort.Slice(result.PackageImports, func(i, j int) bool { + return result.PackageImports[i].Path < result.PackageImports[j].Path + }) + return nil + }) + return result, err +} + +func (c *commandHandler) AddImport(ctx context.Context, args command.AddImportArgs) error { + return c.run(ctx, commandConfig{ + progress: "Adding import", + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + edits, err := golang.AddImport(ctx, deps.snapshot, deps.fh, args.ImportPath) + if err != nil { + return fmt.Errorf("could not add import: %v", err) + } + r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ + Edit: *protocol.NewWorkspaceEdit( + protocol.DocumentChangeEdit(deps.fh, edits)), + }) + if err != nil { + return fmt.Errorf("could not apply import edits: %v", err) + } + if !r.Applied { + return fmt.Errorf("failed to apply edits: %v", r.FailureReason) + } + return nil + }) +} + +func (c *commandHandler) ExtractToNewFile(ctx context.Context, args protocol.Location) error { + return c.run(ctx, commandConfig{ + progress: "Extract to a new file", + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + edit, err := golang.ExtractToNewFile(ctx, deps.snapshot, deps.fh, args.Range) + if err != nil { + return err + } + resp, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{Edit: *edit}) + if err != nil { + return fmt.Errorf("could not apply edits: %v", err) + } + if !resp.Applied { + return fmt.Errorf("edits not applied: %s", resp.FailureReason) + } + return nil + }) +} + +func (c *commandHandler) StartDebugging(ctx context.Context, args command.DebuggingArgs) (result command.DebuggingResult, _ error) { + addr := args.Addr + if addr == "" { + addr = "localhost:0" + } + di := debug.GetInstance(ctx) + if di == nil { + return result, errors.New("internal error: server has no debugging instance") + } + listenedAddr, err := di.Serve(ctx, addr) + if err != nil { + return result, fmt.Errorf("starting debug server: %w", err) + } + result.URLs = []string{"http://" + listenedAddr} + openClientBrowser(ctx, c.s.client, result.URLs[0]) + return result, nil +} + +func (c *commandHandler) StartProfile(ctx context.Context, args command.StartProfileArgs) (result command.StartProfileResult, _ error) { + file, err := os.CreateTemp("", "gopls-profile-*") + if err != nil { + return result, fmt.Errorf("creating temp profile file: %v", err) + } + + c.s.ongoingProfileMu.Lock() + defer c.s.ongoingProfileMu.Unlock() + + if c.s.ongoingProfile != nil { + file.Close() // ignore error + return result, fmt.Errorf("profile already started (for %q)", c.s.ongoingProfile.Name()) + } + + if err := pprof.StartCPUProfile(file); err != nil { + file.Close() // ignore error + return result, fmt.Errorf("starting profile: %v", err) + } + + c.s.ongoingProfile = file + return result, nil +} + +func (c *commandHandler) StopProfile(ctx context.Context, args command.StopProfileArgs) (result command.StopProfileResult, _ error) { + c.s.ongoingProfileMu.Lock() + defer c.s.ongoingProfileMu.Unlock() + + prof := c.s.ongoingProfile + c.s.ongoingProfile = nil + + if prof == nil { + return result, fmt.Errorf("no ongoing profile") + } + + pprof.StopCPUProfile() + if err := prof.Close(); err != nil { + return result, fmt.Errorf("closing profile file: %v", err) + } + result.File = prof.Name() + return result, nil +} + +func (c *commandHandler) FetchVulncheckResult(ctx context.Context, arg command.URIArg) (map[protocol.DocumentURI]*vulncheck.Result, error) { + ret := map[protocol.DocumentURI]*vulncheck.Result{} + err := c.run(ctx, commandConfig{forURI: arg.URI}, func(ctx context.Context, deps commandDeps) error { + if deps.snapshot.Options().Vulncheck == settings.ModeVulncheckImports { + for _, modfile := range deps.snapshot.View().ModFiles() { + res, err := deps.snapshot.ModVuln(ctx, modfile) + if err != nil { + return err + } + ret[modfile] = res + } + } + // Overwrite if there is any govulncheck-based result. + for modfile, result := range deps.snapshot.Vulnerabilities() { + ret[modfile] = result + } + return nil + }) + return ret, err +} + +func (c *commandHandler) RunGovulncheck(ctx context.Context, args command.VulncheckArgs) (command.RunVulncheckResult, error) { + if args.URI == "" { + return command.RunVulncheckResult{}, errors.New("VulncheckArgs is missing URI field") + } + + // Return the workdone token so that clients can identify when this + // vulncheck invocation is complete. + // + // Since the run function executes asynchronously, we use a channel to + // synchronize the start of the run and return the token. + tokenChan := make(chan protocol.ProgressToken, 1) + err := c.run(ctx, commandConfig{ + progress: "govulncheck", // (asynchronous) + requireSave: true, // govulncheck cannot honor overlays + forURI: args.URI, + }, func(ctx context.Context, deps commandDeps) error { + tokenChan <- deps.work.Token() + + workDoneWriter := progress.NewWorkDoneWriter(ctx, deps.work) + dir := filepath.Dir(args.URI.Path()) + pattern := args.Pattern + + result, err := scan.RunGovulncheck(ctx, pattern, deps.snapshot, dir, workDoneWriter) + if err != nil { + return err + } + + snapshot, release, err := c.s.session.InvalidateView(ctx, deps.snapshot.View(), cache.StateChange{ + Vulns: map[protocol.DocumentURI]*vulncheck.Result{args.URI: result}, + }) + if err != nil { + return err + } + defer release() + + // Diagnosing with the background context ensures new snapshots are fully + // diagnosed. + c.s.diagnoseSnapshot(snapshot.BackgroundContext(), snapshot, nil, 0) + + affecting := make(map[string]bool, len(result.Entries)) + for _, finding := range result.Findings { + if len(finding.Trace) > 1 { // at least 2 frames if callstack exists (vulnerability, entry) + affecting[finding.OSV] = true + } + } + if len(affecting) == 0 { + showMessage(ctx, c.s.client, protocol.Info, "No vulnerabilities found") + return nil + } + affectingOSVs := make([]string, 0, len(affecting)) + for id := range affecting { + affectingOSVs = append(affectingOSVs, id) + } + sort.Strings(affectingOSVs) + + showMessage(ctx, c.s.client, protocol.Warning, fmt.Sprintf("Found %v", strings.Join(affectingOSVs, ", "))) + + return nil + }) + if err != nil { + return command.RunVulncheckResult{}, err + } + select { + case <-ctx.Done(): + return command.RunVulncheckResult{}, ctx.Err() + case token := <-tokenChan: + return command.RunVulncheckResult{Token: token}, nil + } +} + +// MemStats implements the MemStats command. It returns an error as a +// future-proof API, but the resulting error is currently always nil. +func (c *commandHandler) MemStats(ctx context.Context) (command.MemStatsResult, error) { + // GC a few times for stable results. + runtime.GC() + runtime.GC() + runtime.GC() + var m runtime.MemStats + runtime.ReadMemStats(&m) + return command.MemStatsResult{ + HeapAlloc: m.HeapAlloc, + HeapInUse: m.HeapInuse, + TotalAlloc: m.TotalAlloc, + }, nil +} + +// WorkspaceStats implements the WorkspaceStats command, reporting information +// about the current state of the loaded workspace for the current session. +func (c *commandHandler) WorkspaceStats(ctx context.Context) (command.WorkspaceStatsResult, error) { + var res command.WorkspaceStatsResult + res.Files = c.s.session.Cache().FileStats() + + for _, view := range c.s.session.Views() { + vs, err := collectViewStats(ctx, view) + if err != nil { + return res, err + } + res.Views = append(res.Views, vs) + } + return res, nil +} + +func collectViewStats(ctx context.Context, view *cache.View) (command.ViewStats, error) { + s, release, err := view.Snapshot() + if err != nil { + return command.ViewStats{}, err + } + defer release() + + allMD, err := s.AllMetadata(ctx) + if err != nil { + return command.ViewStats{}, err + } + allPackages := collectPackageStats(allMD) + + wsMD, err := s.WorkspaceMetadata(ctx) + if err != nil { + return command.ViewStats{}, err + } + workspacePackages := collectPackageStats(wsMD) + + var ids []golang.PackageID + for _, mp := range wsMD { + ids = append(ids, mp.ID) + } + + diags, err := s.PackageDiagnostics(ctx, ids...) + if err != nil { + return command.ViewStats{}, err + } + + ndiags := 0 + for _, d := range diags { + ndiags += len(d) + } + + return command.ViewStats{ + GoCommandVersion: view.GoVersionString(), + AllPackages: allPackages, + WorkspacePackages: workspacePackages, + Diagnostics: ndiags, + }, nil +} + +func collectPackageStats(mps []*metadata.Package) command.PackageStats { + var stats command.PackageStats + stats.Packages = len(mps) + modules := make(map[string]bool) + + for _, mp := range mps { + n := len(mp.CompiledGoFiles) + stats.CompiledGoFiles += n + if n > stats.LargestPackage { + stats.LargestPackage = n + } + if mp.Module != nil { + modules[mp.Module.Path] = true + } + } + stats.Modules = len(modules) + + return stats +} + +// RunGoWorkCommand invokes `go work ` with the provided arguments. +// +// args.InitFirst controls whether to first run `go work init`. This allows a +// single command to both create and recursively populate a go.work file -- as +// of writing there is no `go work init -r`. +// +// Some thought went into implementing this command. Unlike the go.mod commands +// above, this command simply invokes the go command and relies on the client +// to notify gopls of file changes via didChangeWatchedFile notifications. +// We could instead run these commands with GOWORK set to a temp file, but that +// poses the following problems: +// - directory locations in the resulting temp go.work file will be computed +// relative to the directory containing that go.work. If the go.work is in a +// tempdir, the directories will need to be translated to/from that dir. +// - it would be simpler to use a temp go.work file in the workspace +// directory, or whichever directory contains the real go.work file, but +// that sets a bad precedent of writing to a user-owned directory. We +// shouldn't start doing that. +// - Sending workspace edits to create a go.work file would require using +// the CreateFile resource operation, which would need to be tested in every +// client as we haven't used it before. We don't have time for that right +// now. +// +// Therefore, we simply require that the current go.work file is saved (if it +// exists), and delegate to the go command. +func (c *commandHandler) RunGoWorkCommand(ctx context.Context, args command.RunGoWorkArgs) error { + return c.run(ctx, commandConfig{ + progress: "Running go work command", + forView: args.ViewID, + }, func(ctx context.Context, deps commandDeps) (runErr error) { + snapshot := deps.snapshot + view := snapshot.View() + viewDir := snapshot.Folder().Path() + + if view.Type() != cache.GoWorkView && view.GoWork() != "" { + // If we are not using an existing go.work file, GOWORK must be explicitly off. + // TODO(rfindley): what about GO111MODULE=off? + return fmt.Errorf("cannot modify go.work files when GOWORK=off") + } + + var gowork string + // If the user has explicitly set GOWORK=off, we should warn them + // explicitly and avoid potentially misleading errors below. + if view.GoWork() != "" { + gowork = view.GoWork().Path() + fh, err := snapshot.ReadFile(ctx, view.GoWork()) + if err != nil { + return err // e.g. canceled + } + if !fh.SameContentsOnDisk() { + return fmt.Errorf("must save workspace file %s before running go work commands", view.GoWork()) + } + } else { + if !args.InitFirst { + // If go.work does not exist, we should have detected that and asked + // for InitFirst. + return bug.Errorf("internal error: cannot run go work command: required go.work file not found") + } + gowork = filepath.Join(viewDir, "go.work") + if err := c.invokeGoWork(ctx, viewDir, gowork, []string{"init"}); err != nil { + return fmt.Errorf("running `go work init`: %v", err) + } + } + + return c.invokeGoWork(ctx, viewDir, gowork, args.Args) + }) +} + +func (c *commandHandler) invokeGoWork(ctx context.Context, viewDir, gowork string, args []string) error { + inv := gocommand.Invocation{ + Verb: "work", + Args: args, + WorkingDir: viewDir, + Env: append(os.Environ(), fmt.Sprintf("GOWORK=%s", gowork)), + } + if _, err := c.s.session.GoCommandRunner().Run(ctx, inv); err != nil { + return fmt.Errorf("running go work command: %v", err) + } + return nil +} + +// showMessage causes the client to show a progress or error message. +// +// It reports whether it succeeded. If it fails, it writes an error to +// the server log, so most callers can safely ignore the result. +func showMessage(ctx context.Context, cli protocol.Client, typ protocol.MessageType, message string) bool { + err := cli.ShowMessage(ctx, &protocol.ShowMessageParams{ + Type: typ, + Message: message, + }) + if err != nil { + event.Error(ctx, "client.showMessage: %v", err) + return false + } + return true +} + +// openClientBrowser causes the LSP client to open the specified URL +// in an external browser. +func openClientBrowser(ctx context.Context, cli protocol.Client, url protocol.URI) { + showDocumentImpl(ctx, cli, url, nil) +} + +// openClientEditor causes the LSP client to open the specified document +// and select the indicated range. +// +// Note that VS Code 1.87.2 doesn't currently raise the window; this is +// https://github.com/microsoft/vscode/issues/207634 +func openClientEditor(ctx context.Context, cli protocol.Client, loc protocol.Location) { + showDocumentImpl(ctx, cli, protocol.URI(loc.URI), &loc.Range) +} + +func showDocumentImpl(ctx context.Context, cli protocol.Client, url protocol.URI, rangeOpt *protocol.Range) { + // In principle we shouldn't send a showDocument request to a + // client that doesn't support it, as reported by + // ShowDocumentClientCapabilities. But even clients that do + // support it may defer the real work of opening the document + // asynchronously, to avoid deadlocks due to rentrancy. + // + // For example: client sends request to server; server sends + // showDocument to client; client opens editor; editor causes + // new RPC to be sent to server, which is still busy with + // previous request. (This happens in eglot.) + // + // So we can't rely on the success/failure information. + // That's the reason this function doesn't return an error. + + // "External" means run the system-wide handler (e.g. open(1) + // on macOS or xdg-open(1) on Linux) for this URL, ignoring + // TakeFocus and Selection. Note that this may still end up + // opening the same editor (e.g. VSCode) for a file: URL. + res, err := cli.ShowDocument(ctx, &protocol.ShowDocumentParams{ + URI: url, + External: rangeOpt == nil, + TakeFocus: true, + Selection: rangeOpt, // optional + }) + if err != nil { + event.Error(ctx, "client.showDocument: %v", err) + } else if res != nil && !res.Success { + event.Log(ctx, fmt.Sprintf("client declined to open document %v", url)) + } +} + +func (c *commandHandler) ChangeSignature(ctx context.Context, args command.ChangeSignatureArgs) (*protocol.WorkspaceEdit, error) { + var result *protocol.WorkspaceEdit + err := c.run(ctx, commandConfig{ + forURI: args.RemoveParameter.URI, + }, func(ctx context.Context, deps commandDeps) error { + // For now, gopls only supports removing unused parameters. + docedits, err := golang.RemoveUnusedParameter(ctx, deps.fh, args.RemoveParameter.Range, deps.snapshot) + if err != nil { + return err + } + wsedit := protocol.NewWorkspaceEdit(docedits...) + if args.ResolveEdits { + result = wsedit + return nil + } + r, err := c.s.client.ApplyEdit(ctx, &protocol.ApplyWorkspaceEditParams{ + Edit: *wsedit, + }) + if !r.Applied { + return fmt.Errorf("failed to apply edits: %v", r.FailureReason) + } + return nil + }) + return result, err +} + +func (c *commandHandler) DiagnoseFiles(ctx context.Context, args command.DiagnoseFilesArgs) error { + return c.run(ctx, commandConfig{ + progress: "Diagnose files", + }, func(ctx context.Context, _ commandDeps) error { + + // TODO(rfindley): even better would be textDocument/diagnostics (golang/go#60122). + // Though note that implementing pull diagnostics may cause some servers to + // request diagnostics in an ad-hoc manner, and break our intentional pacing. + + ctx, done := event.Start(ctx, "lsp.server.DiagnoseFiles") + defer done() + + snapshots := make(map[*cache.Snapshot]bool) + for _, uri := range args.Files { + fh, snapshot, release, err := c.s.fileOf(ctx, uri) + if err != nil { + return err + } + if snapshots[snapshot] || snapshot.FileKind(fh) != file.Go { + release() + continue + } + defer release() + snapshots[snapshot] = true + } + + var wg sync.WaitGroup + for snapshot := range snapshots { + snapshot := snapshot + wg.Add(1) + go func() { + defer wg.Done() + + // Use the operation context for diagnosis, rather than + // snapshot.BackgroundContext, because this operation does not create + // new snapshots (so they should also be diagnosed by other means). + c.s.diagnoseSnapshot(ctx, snapshot, nil, 0) + }() + } + wg.Wait() + + return nil + }) +} + +func (c *commandHandler) Views(ctx context.Context) ([]command.View, error) { + var summaries []command.View + for _, view := range c.s.session.Views() { + summaries = append(summaries, command.View{ + ID: view.ID(), + Type: view.Type().String(), + Root: view.Root(), + Folder: view.Folder().Dir, + EnvOverlay: view.EnvOverlay(), + }) + } + return summaries, nil +} + +func (c *commandHandler) FreeSymbols(ctx context.Context, viewID string, loc protocol.Location) error { + web, err := c.s.getWeb() + if err != nil { + return err + } + url := web.freesymbolsURL(viewID, loc) + openClientBrowser(ctx, c.s.client, url) + return nil +} + +func (c *commandHandler) Assembly(ctx context.Context, viewID, packageID, symbol string) error { + web, err := c.s.getWeb() + if err != nil { + return err + } + url := web.assemblyURL(viewID, packageID, symbol) + openClientBrowser(ctx, c.s.client, url) + return nil +} + +func (c *commandHandler) ClientOpenURL(ctx context.Context, url string) error { + openClientBrowser(ctx, c.s.client, url) + return nil +} + +func (c *commandHandler) ScanImports(ctx context.Context) error { + for _, v := range c.s.session.Views() { + v.ScanImports() + } + return nil +} diff --git a/contribs/gnopls/internal/server/completion.go b/contribs/gnopls/internal/server/completion.go new file mode 100644 index 00000000000..079db865fb5 --- /dev/null +++ b/contribs/gnopls/internal/server/completion.go @@ -0,0 +1,201 @@ +// Copyright 2018 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 server + +import ( + "context" + "fmt" + "strings" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/golang/completion" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/telemetry" + "golang.org/x/tools/gopls/internal/template" + "golang.org/x/tools/gopls/internal/work" + "golang.org/x/tools/internal/event" +) + +func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (_ *protocol.CompletionList, rerr error) { + recordLatency := telemetry.StartLatencyTimer("completion") + defer func() { + recordLatency(ctx, rerr) + }() + + ctx, done := event.Start(ctx, "lsp.Server.completion", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + var candidates []completion.CompletionItem + var surrounding *completion.Selection + switch snapshot.FileKind(fh) { + case file.Go: + candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context) + case file.Mod: + candidates, surrounding = nil, nil + case file.Work: + cl, err := work.Completion(ctx, snapshot, fh, params.Position) + if err != nil { + break + } + return cl, nil + case file.Tmpl: + var cl *protocol.CompletionList + cl, err = template.Completion(ctx, snapshot, fh, params.Position, params.Context) + if err != nil { + break // use common error handling, candidates==nil + } + return cl, nil + } + if err != nil { + event.Error(ctx, "no completions found", err, label.Position.Of(params.Position)) + } + if candidates == nil || surrounding == nil { + complEmpty.Inc() + return &protocol.CompletionList{ + IsIncomplete: true, + Items: []protocol.CompletionItem{}, + }, nil + } + + // When using deep completions/fuzzy matching, report results as incomplete so + // client fetches updated completions after every key stroke. + options := snapshot.Options() + incompleteResults := options.DeepCompletion || options.Matcher == settings.Fuzzy + + items, err := toProtocolCompletionItems(candidates, surrounding, options) + if err != nil { + return nil, err + } + if snapshot.FileKind(fh) == file.Go { + s.saveLastCompletion(fh.URI(), fh.Version(), items, params.Position) + } + + if len(items) > 10 { + // TODO(pjw): long completions are ok for field lists + complLong.Inc() + } else { + complShort.Inc() + } + return &protocol.CompletionList{ + IsIncomplete: incompleteResults, + Items: items, + }, nil +} + +func (s *server) saveLastCompletion(uri protocol.DocumentURI, version int32, items []protocol.CompletionItem, pos protocol.Position) { + s.efficacyMu.Lock() + defer s.efficacyMu.Unlock() + s.efficacyVersion = version + s.efficacyURI = uri + s.efficacyPos = pos + s.efficacyItems = items +} + +func toProtocolCompletionItems(candidates []completion.CompletionItem, surrounding *completion.Selection, options *settings.Options) ([]protocol.CompletionItem, error) { + replaceRng, err := surrounding.Range() + if err != nil { + return nil, err + } + insertRng0, err := surrounding.PrefixRange() + if err != nil { + return nil, err + } + suffix := surrounding.Suffix() + + var ( + items = make([]protocol.CompletionItem, 0, len(candidates)) + numDeepCompletionsSeen int + ) + for i, candidate := range candidates { + // Limit the number of deep completions to not overwhelm the user in cases + // with dozens of deep completion matches. + if candidate.Depth > 0 { + if !options.DeepCompletion { + continue + } + if numDeepCompletionsSeen >= completion.MaxDeepCompletions { + continue + } + numDeepCompletionsSeen++ + } + insertText := candidate.InsertText + if options.InsertTextFormat == protocol.SnippetTextFormat { + insertText = candidate.Snippet() + } + + // This can happen if the client has snippets disabled but the + // candidate only supports snippet insertion. + if insertText == "" { + continue + } + + doc := &protocol.Or_CompletionItem_documentation{ + Value: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: golang.CommentToMarkdown(candidate.Documentation, options), + }, + } + if options.PreferredContentFormat != protocol.Markdown { + doc.Value = candidate.Documentation + } + var edits *protocol.Or_CompletionItem_textEdit + if options.InsertReplaceSupported { + insertRng := insertRng0 + if suffix == "" || strings.Contains(insertText, suffix) { + insertRng = replaceRng + } + // Insert and Replace ranges share the same start position and + // the same text edit but the end position may differ. + // See the comment for the CompletionItem's TextEdit field. + // https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol#CompletionItem + edits = &protocol.Or_CompletionItem_textEdit{ + Value: protocol.InsertReplaceEdit{ + NewText: insertText, + Insert: insertRng, // replace up to the cursor position. + Replace: replaceRng, + }, + } + } else { + edits = &protocol.Or_CompletionItem_textEdit{ + Value: protocol.TextEdit{ + NewText: insertText, + Range: replaceRng, + }, + } + } + item := protocol.CompletionItem{ + Label: candidate.Label, + Detail: candidate.Detail, + Kind: candidate.Kind, + TextEdit: edits, + InsertTextFormat: &options.InsertTextFormat, + AdditionalTextEdits: candidate.AdditionalTextEdits, + // This is a hack so that the client sorts completion results in the order + // according to their score. This can be removed upon the resolution of + // https://github.com/Microsoft/language-server-protocol/issues/348. + SortText: fmt.Sprintf("%05d", i), + + // Trim operators (VSCode doesn't like weird characters in + // filterText). + FilterText: strings.TrimLeft(candidate.InsertText, "&*"), + + Preselect: i == 0, + Documentation: doc, + Tags: protocol.NonNilSlice(candidate.Tags), + Deprecated: candidate.Deprecated, + } + items = append(items, item) + } + return items, nil +} diff --git a/contribs/gnopls/internal/server/counters.go b/contribs/gnopls/internal/server/counters.go new file mode 100644 index 00000000000..dc403faa694 --- /dev/null +++ b/contribs/gnopls/internal/server/counters.go @@ -0,0 +1,28 @@ +// 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 server + +import "golang.org/x/telemetry/counter" + +// Proposed counters for evaluating gopls code completion. +var ( + complEmpty = counter.New("gopls/completion/len:0") // count empty suggestions + complShort = counter.New("gopls/completion/len:<=10") // not empty, not long + complLong = counter.New("gopls/completion/len:>10") // returning more than 10 items + + changeFull = counter.New("gopls/completion/used:unknown") // full file change in didChange + complUnused = counter.New("gopls/completion/used:no") // did not use a completion + complUsed = counter.New("gopls/completion/used:yes") // used a completion + + // exported so tests can verify that counters are incremented + CompletionCounters = []*counter.Counter{ + complEmpty, + complShort, + complLong, + changeFull, + complUnused, + complUsed, + } +) diff --git a/contribs/gnopls/internal/server/debug.go b/contribs/gnopls/internal/server/debug.go new file mode 100644 index 00000000000..734df8682a7 --- /dev/null +++ b/contribs/gnopls/internal/server/debug.go @@ -0,0 +1,12 @@ +// 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 server + +// assert panics with the given msg if cond is not true. +func assert(cond bool, msg string) { + if !cond { + panic(msg) + } +} diff --git a/contribs/gnopls/internal/server/definition.go b/contribs/gnopls/internal/server/definition.go new file mode 100644 index 00000000000..7b4df3c7c07 --- /dev/null +++ b/contribs/gnopls/internal/server/definition.go @@ -0,0 +1,61 @@ +// 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. + +package server + +import ( + "context" + "fmt" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/telemetry" + "golang.org/x/tools/gopls/internal/template" + "golang.org/x/tools/internal/event" +) + +func (s *server) Definition(ctx context.Context, params *protocol.DefinitionParams) (_ []protocol.Location, rerr error) { + recordLatency := telemetry.StartLatencyTimer("definition") + defer func() { + recordLatency(ctx, rerr) + }() + + ctx, done := event.Start(ctx, "lsp.Server.definition", label.URI.Of(params.TextDocument.URI)) + defer done() + + // TODO(rfindley): definition requests should be multiplexed across all views. + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + switch kind := snapshot.FileKind(fh); kind { + case file.Tmpl: + return template.Definition(snapshot, fh, params.Position) + case file.Go: + return golang.Definition(ctx, snapshot, fh, params.Position) + default: + return nil, fmt.Errorf("can't find definitions for file type %s", kind) + } +} + +func (s *server) TypeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) { + ctx, done := event.Start(ctx, "lsp.Server.typeDefinition", label.URI.Of(params.TextDocument.URI)) + defer done() + + // TODO(rfindley): type definition requests should be multiplexed across all views. + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + switch kind := snapshot.FileKind(fh); kind { + case file.Go: + return golang.TypeDefinition(ctx, snapshot, fh, params.Position) + default: + return nil, fmt.Errorf("can't find type definitions for file type %s", kind) + } +} diff --git a/contribs/gnopls/internal/server/diagnostics.go b/contribs/gnopls/internal/server/diagnostics.go new file mode 100644 index 00000000000..a4466e2fc76 --- /dev/null +++ b/contribs/gnopls/internal/server/diagnostics.go @@ -0,0 +1,990 @@ +// Copyright 2018 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 server + +import ( + "context" + "crypto/sha256" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/mod" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/template" + "golang.org/x/tools/gopls/internal/util/moremaps" + "golang.org/x/tools/gopls/internal/work" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/event/keys" +) + +// fileDiagnostics holds the current state of published diagnostics for a file. +type fileDiagnostics struct { + publishedHash file.Hash // hash of the last set of diagnostics published for this URI + mustPublish bool // if set, publish diagnostics even if they haven't changed + + // Orphaned file diagnostics are not necessarily associated with any *View + // (since they are orphaned). Instead, keep track of the modification ID at + // which they were orphaned (see server.lastModificationID). + orphanedAt uint64 // modification ID at which this file was orphaned. + orphanedFileDiagnostics []*cache.Diagnostic + + // Files may have their diagnostics computed by multiple views, and so + // diagnostics are organized by View. See the documentation for update for more + // details about how the set of file diagnostics evolves over time. + byView map[*cache.View]viewDiagnostics +} + +// viewDiagnostics holds a set of file diagnostics computed from a given View. +type viewDiagnostics struct { + snapshot uint64 // snapshot sequence ID + version int32 // file version + diagnostics []*cache.Diagnostic +} + +// common types; for brevity +type ( + viewSet = map[*cache.View]unit + diagMap = map[protocol.DocumentURI][]*cache.Diagnostic +) + +// hashDiagnostic computes a hash to identify a diagnostic. +// The hash is for deduplicating within a file, +// so it need not incorporate d.URI. +func hashDiagnostic(d *cache.Diagnostic) file.Hash { + h := sha256.New() + for _, t := range d.Tags { + fmt.Fprintf(h, "tag: %s\n", t) + } + for _, r := range d.Related { + fmt.Fprintf(h, "related: %s %s %s\n", r.Location.URI, r.Message, r.Location.Range) + } + fmt.Fprintf(h, "code: %s\n", d.Code) + fmt.Fprintf(h, "codeHref: %s\n", d.CodeHref) + fmt.Fprintf(h, "message: %s\n", d.Message) + fmt.Fprintf(h, "range: %s\n", d.Range) + fmt.Fprintf(h, "severity: %s\n", d.Severity) + fmt.Fprintf(h, "source: %s\n", d.Source) + if d.BundledFixes != nil { + fmt.Fprintf(h, "fixes: %s\n", *d.BundledFixes) + } + var hash [sha256.Size]byte + h.Sum(hash[:0]) + return hash +} + +func sortDiagnostics(d []*cache.Diagnostic) { + sort.Slice(d, func(i int, j int) bool { + a, b := d[i], d[j] + if r := protocol.CompareRange(a.Range, b.Range); r != 0 { + return r < 0 + } + if a.Source != b.Source { + return a.Source < b.Source + } + return a.Message < b.Message + }) +} + +func (s *server) diagnoseChangedViews(ctx context.Context, modID uint64, lastChange map[*cache.View][]protocol.DocumentURI, cause ModificationSource) { + // Collect views needing diagnosis. + s.modificationMu.Lock() + needsDiagnosis := moremaps.KeySlice(s.viewsToDiagnose) + s.modificationMu.Unlock() + + // Diagnose views concurrently. + var wg sync.WaitGroup + for _, v := range needsDiagnosis { + v := v + snapshot, release, err := v.Snapshot() + if err != nil { + s.modificationMu.Lock() + // The View is shut down. Unlike below, no need to check + // s.needsDiagnosis[v], since the view can never be diagnosed. + delete(s.viewsToDiagnose, v) + s.modificationMu.Unlock() + continue + } + + // Collect uris for fast diagnosis. We only care about the most recent + // change here, because this is just an optimization for the case where the + // user is actively editing a single file. + uris := lastChange[v] + if snapshot.Options().DiagnosticsTrigger == settings.DiagnosticsOnSave && cause == FromDidChange { + // The user requested to update the diagnostics only on save. + // Do not diagnose yet. + release() + continue + } + + wg.Add(1) + go func(snapshot *cache.Snapshot, uris []protocol.DocumentURI) { + defer release() + defer wg.Done() + s.diagnoseSnapshot(ctx, snapshot, uris, snapshot.Options().DiagnosticsDelay) + s.modificationMu.Lock() + + // Only remove v from s.viewsToDiagnose if the context is not cancelled. + // This ensures that the snapshot was not cloned before its state was + // fully evaluated, and therefore avoids missing a change that was + // irrelevant to an incomplete snapshot. + // + // See the documentation for s.viewsToDiagnose for details. + if ctx.Err() == nil && s.viewsToDiagnose[v] <= modID { + delete(s.viewsToDiagnose, v) + } + s.modificationMu.Unlock() + }(snapshot, uris) + } + + wg.Wait() + + // Diagnose orphaned files for the session. + orphanedFileDiagnostics, err := s.session.OrphanedFileDiagnostics(ctx) + if err == nil { + err = s.updateOrphanedFileDiagnostics(ctx, modID, orphanedFileDiagnostics) + } + if err != nil { + if ctx.Err() == nil { + event.Error(ctx, "warning: while diagnosing orphaned files", err) + } + } +} + +// diagnoseSnapshot computes and publishes diagnostics for the given snapshot. +// +// If delay is non-zero, computing diagnostics does not start until after this +// delay has expired, to allow work to be cancelled by subsequent changes. +// +// If changedURIs is non-empty, it is a set of recently changed files that +// should be diagnosed immediately, and onDisk reports whether these file +// changes came from a change to on-disk files. +// +// If the provided context is cancelled, diagnostics may be partially +// published. Therefore, the provided context should only be cancelled if there +// will be a subsequent operation to make diagnostics consistent. In general, +// if an operation creates a new snapshot, it is responsible for ensuring that +// snapshot (or a subsequent snapshot in the same View) is eventually +// diagnosed. +func (s *server) diagnoseSnapshot(ctx context.Context, snapshot *cache.Snapshot, changedURIs []protocol.DocumentURI, delay time.Duration) { + ctx, done := event.Start(ctx, "Server.diagnoseSnapshot", snapshot.Labels()...) + defer done() + + if delay > 0 { + // 2-phase diagnostics. + // + // The first phase just parses and type-checks (but + // does not analyze) packages directly affected by + // file modifications. + // + // The second phase runs after the delay, and does everything. + // + // We wait a brief delay before the first phase, to allow higher priority + // work such as autocompletion to acquire the type checking mutex (though + // typically both diagnosing changed files and performing autocompletion + // will be doing the same work: recomputing active packages). + const minDelay = 20 * time.Millisecond + select { + case <-time.After(minDelay): + case <-ctx.Done(): + return + } + + if len(changedURIs) > 0 { + diagnostics, err := s.diagnoseChangedFiles(ctx, snapshot, changedURIs) + if err != nil { + if ctx.Err() == nil { + event.Error(ctx, "warning: while diagnosing changed files", err, snapshot.Labels()...) + } + return + } + s.updateDiagnostics(ctx, snapshot, diagnostics, false) + } + + if delay < minDelay { + delay = 0 + } else { + delay -= minDelay + } + + select { + case <-time.After(delay): + case <-ctx.Done(): + return + } + } + + diagnostics, err := s.diagnose(ctx, snapshot) + if err != nil { + if ctx.Err() == nil { + event.Error(ctx, "warning: while diagnosing snapshot", err, snapshot.Labels()...) + } + return + } + s.updateDiagnostics(ctx, snapshot, diagnostics, true) +} + +func (s *server) diagnoseChangedFiles(ctx context.Context, snapshot *cache.Snapshot, uris []protocol.DocumentURI) (diagMap, error) { + ctx, done := event.Start(ctx, "Server.diagnoseChangedFiles", snapshot.Labels()...) + defer done() + + toDiagnose := make(map[metadata.PackageID]*metadata.Package) + for _, uri := range uris { + // If the file is not open, don't diagnose its package. + // + // We don't care about fast diagnostics for files that are no longer open, + // because the user isn't looking at them. Also, explicitly requesting a + // package can lead to "command-line-arguments" packages if the file isn't + // covered by the current View. By avoiding requesting packages for e.g. + // unrelated file movement, we can minimize these unnecessary packages. + if !snapshot.IsOpen(uri) { + continue + } + // If the file is not known to the snapshot (e.g., if it was deleted), + // don't diagnose it. + if snapshot.FindFile(uri) == nil { + continue + } + + // Don't request type-checking for builtin.go: it's not a real package. + if snapshot.IsBuiltin(uri) { + continue + } + + // Don't diagnose files that are ignored by `go list` (e.g. testdata). + if snapshot.IgnoredFile(uri) { + continue + } + + // Find all packages that include this file and diagnose them in parallel. + meta, err := golang.NarrowestMetadataForFile(ctx, snapshot, uri) + if err != nil { + if ctx.Err() != nil { + return nil, ctx.Err() + } + // TODO(findleyr): we should probably do something with the error here, + // but as of now this can fail repeatedly if load fails, so can be too + // noisy to log (and we'll handle things later in the slow pass). + continue + } + // golang/go#65801: only diagnose changes to workspace packages. Otherwise, + // diagnostics will be unstable, as the slow-path diagnostics will erase + // them. + if snapshot.IsWorkspacePackage(ctx, meta.ID) { + toDiagnose[meta.ID] = meta + } + } + diags, err := snapshot.PackageDiagnostics(ctx, moremaps.KeySlice(toDiagnose)...) + if err != nil { + if ctx.Err() == nil { + event.Error(ctx, "warning: diagnostics failed", err, snapshot.Labels()...) + } + return nil, err + } + // golang/go#59587: guarantee that we compute type-checking diagnostics + // for every compiled package file, otherwise diagnostics won't be quickly + // cleared following a fix. + for _, meta := range toDiagnose { + for _, uri := range meta.CompiledGoFiles { + if _, ok := diags[uri]; !ok { + diags[uri] = nil + } + } + } + return diags, nil +} + +func (s *server) diagnose(ctx context.Context, snapshot *cache.Snapshot) (diagMap, error) { + ctx, done := event.Start(ctx, "Server.diagnose", snapshot.Labels()...) + defer done() + + // Wait for a free diagnostics slot. + // TODO(adonovan): opt: shouldn't it be the analysis implementation's + // job to de-dup and limit resource consumption? In any case this + // function spends most its time waiting for awaitLoaded, at + // least initially. + select { + case <-ctx.Done(): + return nil, ctx.Err() + case s.diagnosticsSema <- struct{}{}: + } + defer func() { + <-s.diagnosticsSema + }() + + var ( + diagnosticsMu sync.Mutex + diagnostics = make(diagMap) + ) + // common code for dispatching diagnostics + store := func(operation string, diagsByFile diagMap, err error) { + if err != nil { + if ctx.Err() == nil { + event.Error(ctx, "warning: while "+operation, err, snapshot.Labels()...) + } + return + } + diagnosticsMu.Lock() + defer diagnosticsMu.Unlock() + for uri, diags := range diagsByFile { + diagnostics[uri] = append(diagnostics[uri], diags...) + } + } + + // Diagnostics below are organized by increasing specificity: + // go.work > mod > mod upgrade > mod vuln > package, etc. + + // Diagnose go.work file. + workReports, workErr := work.Diagnostics(ctx, snapshot) + if ctx.Err() != nil { + return nil, ctx.Err() + } + store("diagnosing go.work file", workReports, workErr) + + // Diagnose go.mod file. + modReports, modErr := mod.ParseDiagnostics(ctx, snapshot) + if ctx.Err() != nil { + return nil, ctx.Err() + } + store("diagnosing go.mod file", modReports, modErr) + + // Diagnose go.mod upgrades. + upgradeReports, upgradeErr := mod.UpgradeDiagnostics(ctx, snapshot) + if ctx.Err() != nil { + return nil, ctx.Err() + } + store("diagnosing go.mod upgrades", upgradeReports, upgradeErr) + + // Diagnose vulnerabilities. + vulnReports, vulnErr := mod.VulnerabilityDiagnostics(ctx, snapshot) + if ctx.Err() != nil { + return nil, ctx.Err() + } + store("diagnosing vulnerabilities", vulnReports, vulnErr) + + workspacePkgs, err := snapshot.WorkspaceMetadata(ctx) + if s.shouldIgnoreError(snapshot, err) { + return diagnostics, ctx.Err() + } + + initialErr := snapshot.InitializationError() + if ctx.Err() != nil { + // Don't update initialization status if the context is cancelled. + return nil, ctx.Err() + } + + if initialErr != nil { + store("critical error", initialErr.Diagnostics, nil) + } + + // Show the error as a progress error report so that it appears in the + // status bar. If a client doesn't support progress reports, the error + // will still be shown as a ShowMessage. If there is no error, any running + // error progress reports will be closed. + statusErr := initialErr + if len(snapshot.Overlays()) == 0 { + // Don't report a hanging status message if there are no open files at this + // snapshot. + statusErr = nil + } + s.updateCriticalErrorStatus(ctx, snapshot, statusErr) + + // Diagnose template (.tmpl) files. + tmplReports := template.Diagnostics(snapshot) + // NOTE(rfindley): typeCheckSource is not accurate here. + // (but this will be gone soon anyway). + store("diagnosing templates", tmplReports, nil) + + // If there are no workspace packages, there is nothing to diagnose and + // there are no orphaned files. + if len(workspacePkgs) == 0 { + return diagnostics, nil + } + + var wg sync.WaitGroup // for potentially slow operations below + + // Maybe run go mod tidy (if it has been invalidated). + // + // Since go mod tidy can be slow, we run it concurrently to diagnostics. + wg.Add(1) + go func() { + defer wg.Done() + modTidyReports, err := mod.TidyDiagnostics(ctx, snapshot) + store("running go mod tidy", modTidyReports, err) + }() + + // Run type checking and go/analysis diagnosis of packages in parallel. + // + // For analysis, we use the *widest* package for each open file, + // for two reasons: + // + // - Correctness: some analyzers (e.g. unusedparam) depend + // on it. If applied to a non-test package for which a + // corresponding test package exists, they make assumptions + // that are falsified in the test package, for example that + // all references to unexported symbols are visible to the + // analysis. + // + // - Efficiency: it may yield a smaller covering set of + // PackageIDs for a given set of files. For example, {x.go, + // x_test.go} is covered by the single package x_test using + // "widest". (Using "narrowest", it would be covered only by + // the pair of packages {x, x_test}, Originally we used all + // covering packages, so {x.go} alone would be analyzed + // twice.) + var ( + toDiagnose = make(map[metadata.PackageID]*metadata.Package) + toAnalyze = make(map[metadata.PackageID]*metadata.Package) + + // secondary index, used to eliminate narrower packages. + toAnalyzeWidest = make(map[golang.PackagePath]*metadata.Package) + ) + for _, mp := range workspacePkgs { + var hasNonIgnored, hasOpenFile bool + for _, uri := range mp.CompiledGoFiles { + if !hasNonIgnored && !snapshot.IgnoredFile(uri) { + hasNonIgnored = true + } + if !hasOpenFile && snapshot.IsOpen(uri) { + hasOpenFile = true + } + } + if hasNonIgnored { + toDiagnose[mp.ID] = mp + if hasOpenFile { + if prev, ok := toAnalyzeWidest[mp.PkgPath]; ok { + if len(prev.CompiledGoFiles) >= len(mp.CompiledGoFiles) { + // Previous entry is not narrower; keep it. + continue + } + // Evict previous (narrower) entry. + delete(toAnalyze, prev.ID) + } + toAnalyze[mp.ID] = mp + toAnalyzeWidest[mp.PkgPath] = mp + } + } + } + + wg.Add(1) + go func() { + defer wg.Done() + gcDetailsReports, err := s.gcDetailsDiagnostics(ctx, snapshot, toDiagnose) + store("collecting gc_details", gcDetailsReports, err) + }() + + // Package diagnostics and analysis diagnostics must both be computed and + // merged before they can be reported. + var pkgDiags, analysisDiags diagMap + // Collect package diagnostics. + wg.Add(1) + go func() { + defer wg.Done() + var err error + pkgDiags, err = snapshot.PackageDiagnostics(ctx, moremaps.KeySlice(toDiagnose)...) + if err != nil { + event.Error(ctx, "warning: diagnostics failed", err, snapshot.Labels()...) + } + }() + + // Get diagnostics from analysis framework. + // This includes type-error analyzers, which suggest fixes to compiler errors. + wg.Add(1) + go func() { + defer wg.Done() + var err error + // TODO(rfindley): here and above, we should avoid using the first result + // if err is non-nil (though as of today it's OK). + analysisDiags, err = golang.Analyze(ctx, snapshot, toAnalyze, s.progress) + if err != nil { + event.Error(ctx, "warning: analyzing package", err, append(snapshot.Labels(), label.Package.Of(keys.Join(moremaps.KeySlice(toDiagnose))))...) + return + } + }() + + wg.Wait() + + // Merge analysis diagnostics with package diagnostics, and store the + // resulting analysis diagnostics. + for uri, adiags := range analysisDiags { + tdiags := pkgDiags[uri] + var tdiags2, adiags2 []*cache.Diagnostic + combineDiagnostics(tdiags, adiags, &tdiags2, &adiags2) + pkgDiags[uri] = tdiags2 + analysisDiags[uri] = adiags2 + } + store("type checking", pkgDiags, nil) // error reported above + store("analyzing packages", analysisDiags, nil) // error reported above + + return diagnostics, nil +} + +func (s *server) gcDetailsDiagnostics(ctx context.Context, snapshot *cache.Snapshot, toDiagnose map[metadata.PackageID]*metadata.Package) (diagMap, error) { + // Process requested gc_details diagnostics. + // + // TODO(rfindley): This should memoize its results if the package has not changed. + // Consider that these points, in combination with the note below about + // races, suggest that gc_details should be tracked on the Snapshot. + var toGCDetail map[metadata.PackageID]*metadata.Package + for _, mp := range toDiagnose { + if snapshot.WantGCDetails(mp.ID) { + if toGCDetail == nil { + toGCDetail = make(map[metadata.PackageID]*metadata.Package) + } + toGCDetail[mp.ID] = mp + } + } + + diagnostics := make(diagMap) + for _, mp := range toGCDetail { + gcReports, err := golang.GCOptimizationDetails(ctx, snapshot, mp) + if err != nil { + event.Error(ctx, "warning: gc details", err, append(snapshot.Labels(), label.Package.Of(string(mp.ID)))...) + continue + } + for uri, diags := range gcReports { + diagnostics[uri] = append(diagnostics[uri], diags...) + } + } + return diagnostics, nil +} + +// combineDiagnostics combines and filters list/parse/type diagnostics from +// tdiags with adiags, and appends the two lists to *outT and *outA, +// respectively. +// +// Type-error analyzers produce diagnostics that are redundant +// with type checker diagnostics, but more detailed (e.g. fixes). +// Rather than report two diagnostics for the same problem, +// we combine them by augmenting the type-checker diagnostic +// and discarding the analyzer diagnostic. +// +// If an analysis diagnostic has the same range and message as +// a list/parse/type diagnostic, the suggested fix information +// (et al) of the latter is merged into a copy of the former. +// This handles the case where a type-error analyzer suggests +// a fix to a type error, and avoids duplication. +// +// The use of out-slices, though irregular, allows the caller to +// easily choose whether to keep the results separate or combined. +// +// The arguments are not modified. +func combineDiagnostics(tdiags []*cache.Diagnostic, adiags []*cache.Diagnostic, outT, outA *[]*cache.Diagnostic) { + + // Build index of (list+parse+)type errors. + type key struct { + Range protocol.Range + message string + } + index := make(map[key]int) // maps (Range,Message) to index in tdiags slice + for i, diag := range tdiags { + index[key{diag.Range, diag.Message}] = i + } + + // Filter out analysis diagnostics that match type errors, + // retaining their suggested fix (etc) fields. + for _, diag := range adiags { + if i, ok := index[key{diag.Range, diag.Message}]; ok { + copy := *tdiags[i] + copy.SuggestedFixes = diag.SuggestedFixes + copy.Tags = diag.Tags + tdiags[i] = © + continue + } + + *outA = append(*outA, diag) + } + + *outT = append(*outT, tdiags...) +} + +// mustPublishDiagnostics marks the uri as needing publication, independent of +// whether the published contents have changed. +// +// This can be used for ensuring gopls publishes diagnostics after certain file +// events. +func (s *server) mustPublishDiagnostics(uri protocol.DocumentURI) { + s.diagnosticsMu.Lock() + defer s.diagnosticsMu.Unlock() + + if s.diagnostics[uri] == nil { + s.diagnostics[uri] = new(fileDiagnostics) + } + s.diagnostics[uri].mustPublish = true +} + +const WorkspaceLoadFailure = "Error loading workspace" + +// updateCriticalErrorStatus updates the critical error progress notification +// based on err. +// +// If err is nil, or if there are no open files, it clears any existing error +// progress report. +func (s *server) updateCriticalErrorStatus(ctx context.Context, snapshot *cache.Snapshot, err *cache.InitializationError) { + s.criticalErrorStatusMu.Lock() + defer s.criticalErrorStatusMu.Unlock() + + // Remove all newlines so that the error message can be formatted in a + // status bar. + var errMsg string + if err != nil { + errMsg = strings.ReplaceAll(err.MainError.Error(), "\n", " ") + } + + if s.criticalErrorStatus == nil { + if errMsg != "" { + event.Error(ctx, "errors loading workspace", err.MainError, snapshot.Labels()...) + s.criticalErrorStatus = s.progress.Start(ctx, WorkspaceLoadFailure, errMsg, nil, nil) + } + return + } + + // If an error is already shown to the user, update it or mark it as + // resolved. + if errMsg == "" { + s.criticalErrorStatus.End(ctx, "Done.") + s.criticalErrorStatus = nil + } else { + s.criticalErrorStatus.Report(ctx, errMsg, 0) + } +} + +// updateDiagnostics records the result of diagnosing a snapshot, and publishes +// any diagnostics that need to be updated on the client. +func (s *server) updateDiagnostics(ctx context.Context, snapshot *cache.Snapshot, diagnostics diagMap, final bool) { + ctx, done := event.Start(ctx, "Server.publishDiagnostics") + defer done() + + s.diagnosticsMu.Lock() + defer s.diagnosticsMu.Unlock() + + // Before updating any diagnostics, check that the context (i.e. snapshot + // background context) is not cancelled. + // + // If not, then we know that we haven't started diagnosing the next snapshot, + // because the previous snapshot is cancelled before the next snapshot is + // returned from Invalidate. + // + // Therefore, even if we publish stale diagnostics here, they should + // eventually be overwritten with accurate diagnostics. + // + // TODO(rfindley): refactor the API to force that snapshots are diagnosed + // after they are created. + if ctx.Err() != nil { + return + } + + // golang/go#65312: since the set of diagnostics depends on the set of views, + // we get the views *after* locking diagnosticsMu. This ensures that + // updateDiagnostics does not incorrectly delete diagnostics that have been + // set for an existing view that was created between the call to + // s.session.Views() and updateDiagnostics. + viewMap := make(viewSet) + for _, v := range s.session.Views() { + viewMap[v] = unit{} + } + + // updateAndPublish updates diagnostics for a file, checking both the latest + // diagnostics for the current snapshot, as well as reconciling the set of + // views. + updateAndPublish := func(uri protocol.DocumentURI, f *fileDiagnostics, diags []*cache.Diagnostic) error { + current, ok := f.byView[snapshot.View()] + // Update the stored diagnostics if: + // 1. we've never seen diagnostics for this view, + // 2. diagnostics are for an older snapshot, or + // 3. we're overwriting with final diagnostics + // + // In other words, we shouldn't overwrite existing diagnostics for a + // snapshot with non-final diagnostics. This avoids the race described at + // https://github.com/golang/go/issues/64765#issuecomment-1890144575. + if !ok || current.snapshot < snapshot.SequenceID() || (current.snapshot == snapshot.SequenceID() && final) { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return err + } + current = viewDiagnostics{ + snapshot: snapshot.SequenceID(), + version: fh.Version(), + diagnostics: diags, + } + if f.byView == nil { + f.byView = make(map[*cache.View]viewDiagnostics) + } + f.byView[snapshot.View()] = current + } + + return s.publishFileDiagnosticsLocked(ctx, viewMap, uri, current.version, f) + } + + seen := make(map[protocol.DocumentURI]bool) + for uri, diags := range diagnostics { + f, ok := s.diagnostics[uri] + if !ok { + f = new(fileDiagnostics) + s.diagnostics[uri] = f + } + seen[uri] = true + if err := updateAndPublish(uri, f, diags); err != nil { + if ctx.Err() != nil { + return + } else { + event.Error(ctx, "updateDiagnostics: failed to deliver diagnostics", err, label.URI.Of(uri)) + } + } + } + + // TODO(rfindley): perhaps we should clean up files that have no diagnostics. + // One could imagine a large operation generating diagnostics for a great + // number of files, after which gopls has to do more bookkeeping into the + // future. + if final { + for uri, f := range s.diagnostics { + if !seen[uri] { + if err := updateAndPublish(uri, f, nil); err != nil { + if ctx.Err() != nil { + return + } else { + event.Error(ctx, "updateDiagnostics: failed to deliver diagnostics", err, label.URI.Of(uri)) + } + } + } + } + } +} + +// updateOrphanedFileDiagnostics records and publishes orphaned file +// diagnostics as a given modification time. +func (s *server) updateOrphanedFileDiagnostics(ctx context.Context, modID uint64, diagnostics diagMap) error { + views := s.session.Views() + viewSet := make(viewSet) + for _, v := range views { + viewSet[v] = unit{} + } + + s.diagnosticsMu.Lock() + defer s.diagnosticsMu.Unlock() + + for uri, diags := range diagnostics { + f, ok := s.diagnostics[uri] + if !ok { + f = new(fileDiagnostics) + s.diagnostics[uri] = f + } + if f.orphanedAt > modID { + continue + } + f.orphanedAt = modID + f.orphanedFileDiagnostics = diags + // TODO(rfindley): the version of this file is potentially inaccurate; + // nevertheless, it should be eventually consistent, because all + // modifications are diagnosed. + fh, err := s.session.ReadFile(ctx, uri) + if err != nil { + return err + } + if err := s.publishFileDiagnosticsLocked(ctx, viewSet, uri, fh.Version(), f); err != nil { + return err + } + } + + // Clear any stale orphaned file diagnostics. + for uri, f := range s.diagnostics { + if f.orphanedAt < modID { + f.orphanedFileDiagnostics = nil + } + fh, err := s.session.ReadFile(ctx, uri) + if err != nil { + return err + } + if err := s.publishFileDiagnosticsLocked(ctx, viewSet, uri, fh.Version(), f); err != nil { + return err + } + } + return nil +} + +// publishFileDiagnosticsLocked publishes a fileDiagnostics value, while holding s.diagnosticsMu. +// +// If the publication succeeds, it updates f.publishedHash and f.mustPublish. +func (s *server) publishFileDiagnosticsLocked(ctx context.Context, views viewSet, uri protocol.DocumentURI, version int32, f *fileDiagnostics) error { + // We add a disambiguating suffix (e.g. " [darwin,arm64]") to + // each diagnostic that doesn't occur in the default view; + // see golang/go#65496. + type diagSuffix struct { + diag *cache.Diagnostic + suffix string // "" for default build (or orphans) + } + + // diagSuffixes records the set of view suffixes for a given diagnostic. + diagSuffixes := make(map[file.Hash][]diagSuffix) + add := func(diag *cache.Diagnostic, suffix string) { + h := hashDiagnostic(diag) + diagSuffixes[h] = append(diagSuffixes[h], diagSuffix{diag, suffix}) + } + + // Construct the inverse mapping, from diagnostic (hash) to its suffixes (views). + for _, diag := range f.orphanedFileDiagnostics { + add(diag, "") + } + + var allViews []*cache.View + for view, viewDiags := range f.byView { + if _, ok := views[view]; !ok { + delete(f.byView, view) // view no longer exists + continue + } + if viewDiags.version != version { + continue // a payload of diagnostics applies to a specific file version + } + allViews = append(allViews, view) + } + + // Only report diagnostics from relevant views for a file. This avoids + // spurious import errors when a view has only a partial set of dependencies + // for a package (golang/go#66425). + // + // It's ok to use the session to derive the eligible views, because we + // publish diagnostics following any state change, so the set of relevant + // views is eventually consistent. + relevantViews, err := cache.RelevantViews(ctx, s.session, uri, allViews) + if err != nil { + return err + } + + if len(relevantViews) == 0 { + // If we have no preferred diagnostics for a given file (i.e., the file is + // not naturally nested within a view), then all diagnostics should be + // considered valid. + // + // This could arise if the user jumps to definition outside the workspace. + // There is no view that owns the file, so its diagnostics are valid from + // any view. + relevantViews = allViews + } + + for _, view := range relevantViews { + viewDiags := f.byView[view] + // Compute the view's suffix (e.g. " [darwin,arm64]"). + var suffix string + { + var words []string + if view.GOOS() != runtime.GOOS { + words = append(words, view.GOOS()) + } + if view.GOARCH() != runtime.GOARCH { + words = append(words, view.GOARCH()) + } + if len(words) > 0 { + suffix = fmt.Sprintf(" [%s]", strings.Join(words, ",")) + } + } + + for _, diag := range viewDiags.diagnostics { + add(diag, suffix) + } + } + + // De-dup diagnostics across views by hash, and sort. + var ( + hash file.Hash + unique []*cache.Diagnostic + ) + for h, items := range diagSuffixes { + // Sort the items by ascending suffix, so that the + // default view (if present) is first. + // (The others are ordered arbitrarily.) + sort.Slice(items, func(i, j int) bool { + return items[i].suffix < items[j].suffix + }) + + // If the diagnostic was not present in + // the default view, add the view suffix. + first := items[0] + if first.suffix != "" { + diag2 := *first.diag // shallow copy + diag2.Message += first.suffix + first.diag = &diag2 + h = hashDiagnostic(&diag2) // update the hash + } + + hash.XORWith(h) + unique = append(unique, first.diag) + } + sortDiagnostics(unique) + + // Publish, if necessary. + if hash != f.publishedHash || f.mustPublish { + if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{ + Diagnostics: toProtocolDiagnostics(unique), + URI: uri, + Version: version, + }); err != nil { + return err + } + f.publishedHash = hash + f.mustPublish = false + } + return nil +} + +func toProtocolDiagnostics(diagnostics []*cache.Diagnostic) []protocol.Diagnostic { + reports := []protocol.Diagnostic{} + for _, diag := range diagnostics { + pdiag := protocol.Diagnostic{ + // diag.Message might start with \n or \t + Message: strings.TrimSpace(diag.Message), + Range: diag.Range, + Severity: diag.Severity, + Source: string(diag.Source), + Tags: protocol.NonNilSlice(diag.Tags), + RelatedInformation: diag.Related, + Data: diag.BundledFixes, + } + if diag.Code != "" { + pdiag.Code = diag.Code + } + if diag.CodeHref != "" { + pdiag.CodeDescription = &protocol.CodeDescription{Href: diag.CodeHref} + } + reports = append(reports, pdiag) + } + return reports +} + +func (s *server) shouldIgnoreError(snapshot *cache.Snapshot, err error) bool { + if err == nil { // if there is no error at all + return false + } + if errors.Is(err, context.Canceled) { + return true + } + // If the folder has no Go code in it, we shouldn't spam the user with a warning. + // TODO(rfindley): surely it is not correct to walk the folder here just to + // suppress diagnostics, every time we compute diagnostics. + var hasGo bool + _ = filepath.Walk(snapshot.Folder().Path(), func(_ string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !strings.HasSuffix(info.Name(), ".go") { + return nil + } + hasGo = true + return errors.New("done") + }) + return !hasGo +} diff --git a/contribs/gnopls/internal/server/folding_range.go b/contribs/gnopls/internal/server/folding_range.go new file mode 100644 index 00000000000..0ad00e54c8d --- /dev/null +++ b/contribs/gnopls/internal/server/folding_range.go @@ -0,0 +1,49 @@ +// 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. + +package server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func (s *server) FoldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) { + ctx, done := event.Start(ctx, "lsp.Server.foldingRange", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + if snapshot.FileKind(fh) != file.Go { + return nil, nil // empty result + } + ranges, err := golang.FoldingRange(ctx, snapshot, fh, snapshot.Options().LineFoldingOnly) + if err != nil { + return nil, err + } + return toProtocolFoldingRanges(ranges) +} + +func toProtocolFoldingRanges(ranges []*golang.FoldingRangeInfo) ([]protocol.FoldingRange, error) { + result := make([]protocol.FoldingRange, 0, len(ranges)) + for _, info := range ranges { + rng := info.MappedRange.Range() + result = append(result, protocol.FoldingRange{ + StartLine: rng.Start.Line, + StartCharacter: rng.Start.Character, + EndLine: rng.End.Line, + EndCharacter: rng.End.Character, + Kind: string(info.Kind), + }) + } + return result, nil +} diff --git a/contribs/gnopls/internal/server/format.go b/contribs/gnopls/internal/server/format.go new file mode 100644 index 00000000000..1e6344dcff4 --- /dev/null +++ b/contribs/gnopls/internal/server/format.go @@ -0,0 +1,38 @@ +// Copyright 2018 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 server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/mod" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/work" + "golang.org/x/tools/internal/event" +) + +func (s *server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { + ctx, done := event.Start(ctx, "lsp.Server.formatting", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + switch snapshot.FileKind(fh) { + case file.Mod: + return mod.Format(ctx, snapshot, fh) + case file.Go: + return golang.Format(ctx, snapshot, fh) + case file.Work: + return work.Format(ctx, snapshot, fh) + } + return nil, nil // empty result +} diff --git a/contribs/gnopls/internal/server/general.go b/contribs/gnopls/internal/server/general.go new file mode 100644 index 00000000000..e330bd5bbc3 --- /dev/null +++ b/contribs/gnopls/internal/server/general.go @@ -0,0 +1,685 @@ +// 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. + +package server + +// This file defines server methods related to initialization, +// options, shutdown, and exit. + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "go/build" + "os" + "path" + "path/filepath" + "sort" + "strings" + "sync" + + "golang.org/x/telemetry/counter" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug" + debuglog "golang.org/x/tools/gopls/internal/debug/log" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/goversion" + "golang.org/x/tools/gopls/internal/util/moremaps" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/jsonrpc2" +) + +func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { + ctx, done := event.Start(ctx, "lsp.Server.initialize") + defer done() + + var clientName string + if params != nil && params.ClientInfo != nil { + clientName = params.ClientInfo.Name + } + recordClientInfo(clientName) + + s.stateMu.Lock() + if s.state >= serverInitializing { + defer s.stateMu.Unlock() + return nil, fmt.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state) + } + s.state = serverInitializing + s.stateMu.Unlock() + + // For uniqueness, use the gopls PID rather than params.ProcessID (the client + // pid). Some clients might start multiple gopls servers, though they + // probably shouldn't. + pid := os.Getpid() + s.tempDir = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.%s", pid, s.session.ID())) + err := os.Mkdir(s.tempDir, 0700) + if err != nil { + // MkdirTemp could fail due to permissions issues. This is a problem with + // the user's environment, but should not block gopls otherwise behaving. + // All usage of s.tempDir should be predicated on having a non-empty + // s.tempDir. + event.Error(ctx, "creating temp dir", err) + s.tempDir = "" + } + s.progress.SetSupportsWorkDoneProgress(params.Capabilities.Window.WorkDoneProgress) + + options := s.Options().Clone() + // TODO(rfindley): eliminate this defer. + defer func() { s.SetOptions(options) }() + + s.handleOptionErrors(ctx, options.Set(params.InitializationOptions)) + options.ForClientCapabilities(params.ClientInfo, params.Capabilities) + + if options.ShowBugReports { + // Report the next bug that occurs on the server. + bug.Handle(func(b bug.Bug) { + msg := &protocol.ShowMessageParams{ + Type: protocol.Error, + Message: fmt.Sprintf("A bug occurred on the server: %s\nLocation:%s", b.Description, b.Key), + } + go s.eventuallyShowMessage(context.Background(), msg) + }) + } + + folders := params.WorkspaceFolders + if len(folders) == 0 { + if params.RootURI != "" { + folders = []protocol.WorkspaceFolder{{ + URI: string(params.RootURI), + Name: path.Base(params.RootURI.Path()), + }} + } + } + s.pendingFolders = append(s.pendingFolders, folders...) + + var codeActionProvider interface{} = true + if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 { + // If the client has specified CodeActionLiteralSupport, + // send the code actions we support. + // + // Using CodeActionOptions is only valid if codeActionLiteralSupport is set. + codeActionProvider = &protocol.CodeActionOptions{ + CodeActionKinds: s.getSupportedCodeActions(), + ResolveProvider: true, + } + } + var renameOpts interface{} = true + if r := params.Capabilities.TextDocument.Rename; r != nil && r.PrepareSupport { + renameOpts = protocol.RenameOptions{ + PrepareProvider: r.PrepareSupport, + } + } + + versionInfo := debug.VersionInfo() + + goplsVersion, err := json.Marshal(versionInfo) + if err != nil { + return nil, err + } + + return &protocol.InitializeResult{ + Capabilities: protocol.ServerCapabilities{ + CallHierarchyProvider: &protocol.Or_ServerCapabilities_callHierarchyProvider{Value: true}, + CodeActionProvider: codeActionProvider, + CodeLensProvider: &protocol.CodeLensOptions{}, // must be non-nil to enable the code lens capability + CompletionProvider: &protocol.CompletionOptions{ + TriggerCharacters: []string{"."}, + }, + DefinitionProvider: &protocol.Or_ServerCapabilities_definitionProvider{Value: true}, + TypeDefinitionProvider: &protocol.Or_ServerCapabilities_typeDefinitionProvider{Value: true}, + ImplementationProvider: &protocol.Or_ServerCapabilities_implementationProvider{Value: true}, + DocumentFormattingProvider: &protocol.Or_ServerCapabilities_documentFormattingProvider{Value: true}, + DocumentSymbolProvider: &protocol.Or_ServerCapabilities_documentSymbolProvider{Value: true}, + WorkspaceSymbolProvider: &protocol.Or_ServerCapabilities_workspaceSymbolProvider{Value: true}, + ExecuteCommandProvider: &protocol.ExecuteCommandOptions{ + Commands: protocol.NonNilSlice(options.SupportedCommands), + }, + FoldingRangeProvider: &protocol.Or_ServerCapabilities_foldingRangeProvider{Value: true}, + HoverProvider: &protocol.Or_ServerCapabilities_hoverProvider{Value: true}, + DocumentHighlightProvider: &protocol.Or_ServerCapabilities_documentHighlightProvider{Value: true}, + DocumentLinkProvider: &protocol.DocumentLinkOptions{}, + InlayHintProvider: protocol.InlayHintOptions{}, + ReferencesProvider: &protocol.Or_ServerCapabilities_referencesProvider{Value: true}, + RenameProvider: renameOpts, + SelectionRangeProvider: &protocol.Or_ServerCapabilities_selectionRangeProvider{Value: true}, + SemanticTokensProvider: protocol.SemanticTokensOptions{ + Range: &protocol.Or_SemanticTokensOptions_range{Value: true}, + Full: &protocol.Or_SemanticTokensOptions_full{Value: true}, + Legend: protocol.SemanticTokensLegend{ + TokenTypes: protocol.NonNilSlice(options.SemanticTypes), + TokenModifiers: protocol.NonNilSlice(options.SemanticMods), + }, + }, + SignatureHelpProvider: &protocol.SignatureHelpOptions{ + TriggerCharacters: []string{"(", ","}, + }, + TextDocumentSync: &protocol.TextDocumentSyncOptions{ + Change: protocol.Incremental, + OpenClose: true, + Save: &protocol.SaveOptions{ + IncludeText: false, + }, + }, + Workspace: &protocol.WorkspaceOptions{ + WorkspaceFolders: &protocol.WorkspaceFolders5Gn{ + Supported: true, + ChangeNotifications: "workspace/didChangeWorkspaceFolders", + }, + }, + }, + ServerInfo: &protocol.ServerInfo{ + Name: "gopls", + Version: string(goplsVersion), + }, + }, nil +} + +func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { + ctx, done := event.Start(ctx, "lsp.Server.initialized") + defer done() + + s.stateMu.Lock() + if s.state >= serverInitialized { + defer s.stateMu.Unlock() + return fmt.Errorf("%w: initialized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state) + } + s.state = serverInitialized + s.stateMu.Unlock() + + for _, not := range s.notifications { + s.client.ShowMessage(ctx, not) + } + s.notifications = nil + + s.addFolders(ctx, s.pendingFolders) + + s.pendingFolders = nil + s.checkViewGoVersions() + + var registrations []protocol.Registration + options := s.Options() + if options.ConfigurationSupported && options.DynamicConfigurationSupported { + registrations = append(registrations, protocol.Registration{ + ID: "workspace/didChangeConfiguration", + Method: "workspace/didChangeConfiguration", + }) + } + if len(registrations) > 0 { + if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{ + Registrations: registrations, + }); err != nil { + return err + } + } + + // Ask (maybe) about enabling telemetry. Do this asynchronously, as it's OK + // for users to ignore or dismiss the question. + go s.maybePromptForTelemetry(ctx, options.TelemetryPrompt) + + return nil +} + +// checkViewGoVersions checks whether any Go version used by a view is too old, +// raising a showMessage notification if so. +// +// It should be called after views change. +func (s *server) checkViewGoVersions() { + oldestVersion, fromBuild := go1Point(), true + for _, view := range s.session.Views() { + viewVersion := view.GoVersion() + if oldestVersion == -1 || viewVersion < oldestVersion { + oldestVersion, fromBuild = viewVersion, false + } + if viewVersion >= 0 { + counter.Inc(fmt.Sprintf("gopls/goversion:1.%d", viewVersion)) + } + } + + if msg, isError := goversion.Message(oldestVersion, fromBuild); msg != "" { + mType := protocol.Warning + if isError { + mType = protocol.Error + } + s.eventuallyShowMessage(context.Background(), &protocol.ShowMessageParams{ + Type: mType, + Message: msg, + }) + } +} + +// go1Point returns the x in Go 1.x. If an error occurs extracting the go +// version, it returns -1. +// +// Copied from the testenv package. +func go1Point() int { + for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- { + var version int + if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil { + continue + } + return version + } + return -1 +} + +// addFolders adds the specified list of "folders" (that's Windows for +// directories) to the session. It does not return an error, though it +// may report an error to the client over LSP if one or more folders +// had problems, for example, folders with unsupported file system. +func (s *server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) { + originalViews := len(s.session.Views()) + viewErrors := make(map[protocol.URI]error) + + // Skip non-'file' scheme, or invalid workspace folders, + // and log them form error reports. + // VS Code's file system API + // (https://code.visualstudio.com/api/references/vscode-api#FileSystem) + // allows extension to define their own schemes and register + // them with the workspace. We've seen gitlens://, decompileFs://, etc + // but the list can grow over time. + var filtered []protocol.WorkspaceFolder + for _, f := range folders { + if _, err := protocol.ParseDocumentURI(f.URI); err != nil { + debuglog.Warning.Logf(ctx, "skip adding virtual folder %q - invalid folder URI: %v", f.Name, err) + continue + } + filtered = append(filtered, f) + } + folders = filtered + + var ndiagnose sync.WaitGroup // number of unfinished diagnose calls + if s.Options().VerboseWorkDoneProgress { + work := s.progress.Start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil) + defer func() { + go func() { + ndiagnose.Wait() + work.End(ctx, "Done.") + }() + }() + } + // Only one view gets to have a workspace. + var nsnapshots sync.WaitGroup // number of unfinished snapshot initializations + for _, folder := range folders { + uri, err := protocol.ParseDocumentURI(folder.URI) + if err != nil { + viewErrors[folder.URI] = fmt.Errorf("invalid folder URI: %v", err) + continue + } + work := s.progress.Start(ctx, "Setting up workspace", "Loading packages...", nil, nil) + snapshot, release, err := s.addView(ctx, folder.Name, uri) + if err != nil { + if err == cache.ErrViewExists { + continue + } + viewErrors[folder.URI] = err + work.End(ctx, fmt.Sprintf("Error loading packages: %s", err)) + continue + } + // Inv: release() must be called once. + + // Initialize snapshot asynchronously. + initialized := make(chan struct{}) + nsnapshots.Add(1) + go func() { + snapshot.AwaitInitialized(ctx) + work.End(ctx, "Finished loading packages.") + nsnapshots.Done() + close(initialized) // signal + }() + + // Diagnose the newly created view asynchronously. + ndiagnose.Add(1) + go func() { + s.diagnoseSnapshot(snapshot.BackgroundContext(), snapshot, nil, 0) + <-initialized + release() + ndiagnose.Done() + }() + } + + // Wait for snapshots to be initialized so that all files are known. + // (We don't need to wait for diagnosis to finish.) + nsnapshots.Wait() + + // Register for file watching notifications, if they are supported. + if err := s.updateWatchedDirectories(ctx); err != nil { + event.Error(ctx, "failed to register for file watching notifications", err) + } + + // Report any errors using the protocol. + if len(viewErrors) > 0 { + errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews) + for uri, err := range viewErrors { + errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err) + } + showMessage(ctx, s.client, protocol.Error, errMsg) + } +} + +// updateWatchedDirectories compares the current set of directories to watch +// with the previously registered set of directories. If the set of directories +// has changed, we unregister and re-register for file watching notifications. +// updatedSnapshots is the set of snapshots that have been updated. +func (s *server) updateWatchedDirectories(ctx context.Context) error { + patterns := s.session.FileWatchingGlobPatterns(ctx) + + s.watchedGlobPatternsMu.Lock() + defer s.watchedGlobPatternsMu.Unlock() + + // Nothing to do if the set of workspace directories is unchanged. + if moremaps.SameKeys(s.watchedGlobPatterns, patterns) { + return nil + } + + // If the set of directories to watch has changed, register the updates and + // unregister the previously watched directories. This ordering avoids a + // period where no files are being watched. Still, if a user makes on-disk + // changes before these updates are complete, we may miss them for the new + // directories. + prevID := s.watchRegistrationCount - 1 + if err := s.registerWatchedDirectoriesLocked(ctx, patterns); err != nil { + return err + } + if prevID >= 0 { + return s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{ + Unregisterations: []protocol.Unregistration{{ + ID: watchedFilesCapabilityID(prevID), + Method: "workspace/didChangeWatchedFiles", + }}, + }) + } + return nil +} + +func watchedFilesCapabilityID(id int) string { + return fmt.Sprintf("workspace/didChangeWatchedFiles-%d", id) +} + +// registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles +// registrations to the client and updates s.watchedDirectories. +// The caller must not subsequently mutate patterns. +func (s *server) registerWatchedDirectoriesLocked(ctx context.Context, patterns map[protocol.RelativePattern]unit) error { + if !s.Options().DynamicWatchedFilesSupported { + return nil + } + + supportsRelativePatterns := s.Options().RelativePatternsSupported + + s.watchedGlobPatterns = patterns + watchers := make([]protocol.FileSystemWatcher, 0, len(patterns)) // must be a slice + val := protocol.WatchChange | protocol.WatchDelete | protocol.WatchCreate + for pattern := range patterns { + var value any + if supportsRelativePatterns && pattern.BaseURI != "" { + value = pattern + } else { + p := pattern.Pattern + if pattern.BaseURI != "" { + p = path.Join(filepath.ToSlash(pattern.BaseURI.Path()), p) + } + value = p + } + watchers = append(watchers, protocol.FileSystemWatcher{ + GlobPattern: protocol.GlobPattern{Value: value}, + Kind: &val, + }) + } + + if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{ + Registrations: []protocol.Registration{{ + ID: watchedFilesCapabilityID(s.watchRegistrationCount), + Method: "workspace/didChangeWatchedFiles", + RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{ + Watchers: watchers, + }, + }}, + }); err != nil { + return err + } + s.watchRegistrationCount++ + return nil +} + +// Options returns the current server options. +// +// The caller must not modify the result. +func (s *server) Options() *settings.Options { + s.optionsMu.Lock() + defer s.optionsMu.Unlock() + return s.options +} + +// SetOptions sets the current server options. +// +// The caller must not subsequently modify the options. +func (s *server) SetOptions(opts *settings.Options) { + s.optionsMu.Lock() + defer s.optionsMu.Unlock() + s.options = opts +} + +func (s *server) newFolder(ctx context.Context, folder protocol.DocumentURI, name string, opts *settings.Options) (*cache.Folder, error) { + env, err := cache.FetchGoEnv(ctx, folder, opts) + if err != nil { + return nil, err + } + + // Increment folder counters. + switch { + case env.GOTOOLCHAIN == "auto" || strings.Contains(env.GOTOOLCHAIN, "+auto"): + counter.Inc("gopls/gotoolchain:auto") + case env.GOTOOLCHAIN == "path" || strings.Contains(env.GOTOOLCHAIN, "+path"): + counter.Inc("gopls/gotoolchain:path") + case env.GOTOOLCHAIN == "local": // local+auto and local+path handled above + counter.Inc("gopls/gotoolchain:local") + default: + counter.Inc("gopls/gotoolchain:other") + } + + // Record whether a driver is in use so that it appears in the + // user's telemetry upload. Although we can't correlate the + // driver information with the crash or bug.Report at the + // granularity of the process instance, users that use a + // driver tend to do so most of the time, so we'll get a + // strong clue. See #60890 for an example of an issue where + // this information would have been helpful. + if env.EffectiveGOPACKAGESDRIVER != "" { + counter.Inc("gopls/gopackagesdriver") + } + + return &cache.Folder{ + Dir: folder, + Name: name, + Options: opts, + Env: *env, + }, nil +} + +// fetchFolderOptions makes a workspace/configuration request for the given +// folder, and populates options with the result. +// +// If folder is "", fetchFolderOptions makes an unscoped request. +func (s *server) fetchFolderOptions(ctx context.Context, folder protocol.DocumentURI) (*settings.Options, error) { + opts := s.Options() + if !opts.ConfigurationSupported { + return opts, nil + } + var scopeURI *string + if folder != "" { + scope := string(folder) + scopeURI = &scope + } + configs, err := s.client.Configuration(ctx, &protocol.ParamConfiguration{ + Items: []protocol.ConfigurationItem{{ + ScopeURI: scopeURI, + Section: "gopls", + }}, + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err) + } + + opts = opts.Clone() + for _, config := range configs { + s.handleOptionErrors(ctx, opts.Set(config)) + } + return opts, nil +} + +func (s *server) eventuallyShowMessage(ctx context.Context, msg *protocol.ShowMessageParams) { + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.state == serverInitialized { + _ = s.client.ShowMessage(ctx, msg) // ignore error + } + s.notifications = append(s.notifications, msg) +} + +func (s *server) handleOptionErrors(ctx context.Context, optionErrors []error) { + var warnings, errs []string + for _, err := range optionErrors { + if err == nil { + panic("nil error passed to handleOptionErrors") + } + if errors.Is(err, new(settings.SoftError)) { + warnings = append(warnings, err.Error()) + } else { + errs = append(errs, err.Error()) + } + } + + // Sort messages, but put errors first. + // + // Having stable content for the message allows clients to de-duplicate. This + // matters because we may send duplicate warnings for clients that support + // dynamic configuration: one for the initial settings, and then more for the + // individual viewsettings. + var msgs []string + msgType := protocol.Warning + if len(errs) > 0 { + msgType = protocol.Error + sort.Strings(errs) + msgs = append(msgs, errs...) + } + if len(warnings) > 0 { + sort.Strings(warnings) + msgs = append(msgs, warnings...) + } + + if len(msgs) > 0 { + // Settings + combined := "Invalid settings: " + strings.Join(msgs, "; ") + params := &protocol.ShowMessageParams{ + Type: msgType, + Message: combined, + } + s.eventuallyShowMessage(ctx, params) + } +} + +// fileOf returns the file for a given URI and its snapshot. +// On success, the returned function must be called to release the snapshot. +func (s *server) fileOf(ctx context.Context, uri protocol.DocumentURI) (file.Handle, *cache.Snapshot, func(), error) { + snapshot, release, err := s.session.SnapshotOf(ctx, uri) + if err != nil { + return nil, nil, nil, err + } + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + release() + return nil, nil, nil, err + } + return fh, snapshot, release, nil +} + +// shutdown implements the 'shutdown' LSP handler. It releases resources +// associated with the server and waits for all ongoing work to complete. +func (s *server) Shutdown(ctx context.Context) error { + ctx, done := event.Start(ctx, "lsp.Server.shutdown") + defer done() + + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.state < serverInitialized { + event.Log(ctx, "server shutdown without initialization") + } + if s.state != serverShutDown { + // Wait for the webserver (if any) to finish. + if s.web != nil { + s.web.server.Shutdown(ctx) + } + + // drop all the active views + s.session.Shutdown(ctx) + s.state = serverShutDown + if s.tempDir != "" { + if err := os.RemoveAll(s.tempDir); err != nil { + event.Error(ctx, "removing temp dir", err) + } + } + } + return nil +} + +func (s *server) Exit(ctx context.Context) error { + ctx, done := event.Start(ctx, "lsp.Server.exit") + defer done() + + s.stateMu.Lock() + defer s.stateMu.Unlock() + + s.client.Close() + + if s.state != serverShutDown { + // TODO: We should be able to do better than this. + os.Exit(1) + } + // We don't terminate the process on a normal exit, we just allow it to + // close naturally if needed after the connection is closed. + return nil +} + +// recordClientInfo records gopls client info. +func recordClientInfo(clientName string) { + key := "gopls/client:other" + switch clientName { + case "Visual Studio Code": + key = "gopls/client:vscode" + case "Visual Studio Code - Insiders": + key = "gopls/client:vscode-insiders" + case "VSCodium": + key = "gopls/client:vscodium" + case "code-server": + // https://github.com/coder/code-server/blob/3cb92edc76ecc2cfa5809205897d93d4379b16a6/ci/build/build-vscode.sh#L19 + key = "gopls/client:code-server" + case "Eglot": + // https://lists.gnu.org/archive/html/bug-gnu-emacs/2023-03/msg00954.html + key = "gopls/client:eglot" + case "govim": + // https://github.com/govim/govim/pull/1189 + key = "gopls/client:govim" + case "Neovim": + // https://github.com/neovim/neovim/blob/42333ea98dfcd2994ee128a3467dfe68205154cd/runtime/lua/vim/lsp.lua#L1361 + key = "gopls/client:neovim" + case "coc.nvim": + // https://github.com/neoclide/coc.nvim/blob/3dc6153a85ed0f185abec1deb972a66af3fbbfb4/src/language-client/client.ts#L994 + key = "gopls/client:coc.nvim" + case "Sublime Text LSP": + // https://github.com/sublimelsp/LSP/blob/e608f878e7e9dd34aabe4ff0462540fadcd88fcc/plugin/core/sessions.py#L493 + key = "gopls/client:sublimetext" + default: + // Accumulate at least a local counter for an unknown + // client name, but also fall through to count it as + // ":other" for collection. + if clientName != "" { + counter.New(fmt.Sprintf("gopls/client-other:%s", clientName)).Inc() + } + } + counter.Inc(key) +} diff --git a/contribs/gnopls/internal/server/highlight.go b/contribs/gnopls/internal/server/highlight.go new file mode 100644 index 00000000000..35ffc2db2f5 --- /dev/null +++ b/contribs/gnopls/internal/server/highlight.go @@ -0,0 +1,39 @@ +// 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. + +package server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/template" + "golang.org/x/tools/internal/event" +) + +func (s *server) DocumentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) { + ctx, done := event.Start(ctx, "lsp.Server.documentHighlight", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + switch snapshot.FileKind(fh) { + case file.Tmpl: + return template.Highlight(ctx, snapshot, fh, params.Position) + case file.Go: + rngs, err := golang.Highlight(ctx, snapshot, fh, params.Position) + if err != nil { + event.Error(ctx, "no highlight", err) + } + return rngs, nil + } + return nil, nil // empty result +} diff --git a/contribs/gnopls/internal/server/hover.go b/contribs/gnopls/internal/server/hover.go new file mode 100644 index 00000000000..80c35c09565 --- /dev/null +++ b/contribs/gnopls/internal/server/hover.go @@ -0,0 +1,59 @@ +// 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. + +package server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/mod" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/telemetry" + "golang.org/x/tools/gopls/internal/template" + "golang.org/x/tools/gopls/internal/work" + "golang.org/x/tools/internal/event" +) + +func (s *server) Hover(ctx context.Context, params *protocol.HoverParams) (_ *protocol.Hover, rerr error) { + recordLatency := telemetry.StartLatencyTimer("hover") + defer func() { + recordLatency(ctx, rerr) + }() + + ctx, done := event.Start(ctx, "lsp.Server.hover", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + switch snapshot.FileKind(fh) { + case file.Mod: + return mod.Hover(ctx, snapshot, fh, params.Position) + case file.Go: + var pkgURL func(path golang.PackagePath, fragment string) protocol.URI + if snapshot.Options().LinksInHover == settings.LinksInHover_Gopls { + web, err := s.getWeb() + if err != nil { + event.Error(ctx, "failed to start web server", err) + } else { + pkgURL = func(path golang.PackagePath, fragment string) protocol.URI { + return web.PkgURL(snapshot.View().ID(), path, fragment) + } + } + } + return golang.Hover(ctx, snapshot, fh, params.Position, pkgURL) + case file.Tmpl: + return template.Hover(ctx, snapshot, fh, params.Position) + case file.Work: + return work.Hover(ctx, snapshot, fh, params.Position) + } + return nil, nil // empty result +} diff --git a/contribs/gnopls/internal/server/implementation.go b/contribs/gnopls/internal/server/implementation.go new file mode 100644 index 00000000000..9e61ebc4d88 --- /dev/null +++ b/contribs/gnopls/internal/server/implementation.go @@ -0,0 +1,36 @@ +// 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. + +package server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/telemetry" + "golang.org/x/tools/internal/event" +) + +func (s *server) Implementation(ctx context.Context, params *protocol.ImplementationParams) (_ []protocol.Location, rerr error) { + recordLatency := telemetry.StartLatencyTimer("implementation") + defer func() { + recordLatency(ctx, rerr) + }() + + ctx, done := event.Start(ctx, "lsp.Server.implementation", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + if snapshot.FileKind(fh) != file.Go { + return nil, nil // empty result + } + return golang.Implementation(ctx, snapshot, fh, params.Position) +} diff --git a/contribs/gnopls/internal/server/inlay_hint.go b/contribs/gnopls/internal/server/inlay_hint.go new file mode 100644 index 00000000000..fca8bcbc1c8 --- /dev/null +++ b/contribs/gnopls/internal/server/inlay_hint.go @@ -0,0 +1,35 @@ +// 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 server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/mod" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func (s *server) InlayHint(ctx context.Context, params *protocol.InlayHintParams) ([]protocol.InlayHint, error) { + ctx, done := event.Start(ctx, "lsp.Server.inlayHint", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + switch snapshot.FileKind(fh) { + case file.Mod: + return mod.InlayHint(ctx, snapshot, fh, params.Range) + case file.Go: + return golang.InlayHint(ctx, snapshot, fh, params.Range) + } + return nil, nil // empty result +} diff --git a/contribs/gnopls/internal/server/link.go b/contribs/gnopls/internal/server/link.go new file mode 100644 index 00000000000..13097d89887 --- /dev/null +++ b/contribs/gnopls/internal/server/link.go @@ -0,0 +1,286 @@ +// Copyright 2018 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 server + +import ( + "bytes" + "context" + "fmt" + "go/ast" + "go/token" + "net/url" + "regexp" + "strings" + "sync" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/event" + "mvdan.cc/xurls/v2" +) + +func (s *server) DocumentLink(ctx context.Context, params *protocol.DocumentLinkParams) (links []protocol.DocumentLink, err error) { + ctx, done := event.Start(ctx, "lsp.Server.documentLink") + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + switch snapshot.FileKind(fh) { + case file.Mod: + links, err = modLinks(ctx, snapshot, fh) + case file.Go: + links, err = goLinks(ctx, snapshot, fh) + } + // Don't return errors for document links. + if err != nil { + event.Error(ctx, "failed to compute document links", err, label.URI.Of(fh.URI())) + return nil, nil // empty result + } + return links, nil // may be empty (for other file types) +} + +func modLinks(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.DocumentLink, error) { + pm, err := snapshot.ParseMod(ctx, fh) + if err != nil { + return nil, err + } + + var links []protocol.DocumentLink + for _, req := range pm.File.Require { + if req.Syntax == nil { + continue + } + // See golang/go#36998: don't link to modules matching GOPRIVATE. + if snapshot.IsGoPrivatePath(req.Mod.Path) { + continue + } + dep := []byte(req.Mod.Path) + start, end := req.Syntax.Start.Byte, req.Syntax.End.Byte + i := bytes.Index(pm.Mapper.Content[start:end], dep) + if i == -1 { + continue + } + // Shift the start position to the location of the + // dependency within the require statement. + target := cache.BuildLink(snapshot.Options().LinkTarget, "mod/"+req.Mod.String(), "") + l, err := toProtocolLink(pm.Mapper, target, start+i, start+i+len(dep)) + if err != nil { + return nil, err + } + links = append(links, l) + } + // TODO(ridersofrohan): handle links for replace and exclude directives. + if syntax := pm.File.Syntax; syntax == nil { + return links, nil + } + + // Get all the links that are contained in the comments of the file. + urlRegexp := xurls.Relaxed() + for _, expr := range pm.File.Syntax.Stmt { + comments := expr.Comment() + if comments == nil { + continue + } + for _, section := range [][]modfile.Comment{comments.Before, comments.Suffix, comments.After} { + for _, comment := range section { + l, err := findLinksInString(urlRegexp, comment.Token, comment.Start.Byte, pm.Mapper) + if err != nil { + return nil, err + } + links = append(links, l...) + } + } + } + return links, nil +} + +// goLinks returns the set of hyperlink annotations for the specified Go file. +func goLinks(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.DocumentLink, error) { + + pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full) + if err != nil { + return nil, err + } + + var links []protocol.DocumentLink + + // Create links for import specs. + if snapshot.Options().ImportShortcut.ShowLinks() { + + // If links are to pkg.go.dev, append module version suffixes. + // This requires the import map from the package metadata. Ignore errors. + var depsByImpPath map[golang.ImportPath]golang.PackageID + if strings.ToLower(snapshot.Options().LinkTarget) == "pkg.go.dev" { + if meta, err := golang.NarrowestMetadataForFile(ctx, snapshot, fh.URI()); err == nil { + depsByImpPath = meta.DepsByImpPath + } + } + + for _, imp := range pgf.File.Imports { + importPath := metadata.UnquoteImportPath(imp) + if importPath == "" { + continue // bad import + } + // See golang/go#36998: don't link to modules matching GOPRIVATE. + if snapshot.IsGoPrivatePath(string(importPath)) { + continue + } + + urlPath := string(importPath) + + // For pkg.go.dev, append module version suffix to package import path. + if mp := snapshot.Metadata(depsByImpPath[importPath]); mp != nil && mp.Module != nil && mp.Module.Path != "" && mp.Module.Version != "" { + urlPath = strings.Replace(urlPath, mp.Module.Path, mp.Module.Path+"@"+mp.Module.Version, 1) + } + + start, end, err := safetoken.Offsets(pgf.Tok, imp.Path.Pos(), imp.Path.End()) + if err != nil { + return nil, err + } + targetURL := cache.BuildLink(snapshot.Options().LinkTarget, urlPath, "") + // Account for the quotation marks in the positions. + l, err := toProtocolLink(pgf.Mapper, targetURL, start+len(`"`), end-len(`"`)) + if err != nil { + return nil, err + } + links = append(links, l) + } + } + + urlRegexp := xurls.Relaxed() + + // Gather links found in string literals. + var str []*ast.BasicLit + ast.Inspect(pgf.File, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.ImportSpec: + return false // don't process import strings again + case *ast.BasicLit: + if n.Kind == token.STRING { + str = append(str, n) + } + } + return true + }) + for _, s := range str { + strOffset, err := safetoken.Offset(pgf.Tok, s.Pos()) + if err != nil { + return nil, err + } + l, err := findLinksInString(urlRegexp, s.Value, strOffset, pgf.Mapper) + if err != nil { + return nil, err + } + links = append(links, l...) + } + + // Gather links found in comments. + for _, commentGroup := range pgf.File.Comments { + for _, comment := range commentGroup.List { + commentOffset, err := safetoken.Offset(pgf.Tok, comment.Pos()) + if err != nil { + return nil, err + } + l, err := findLinksInString(urlRegexp, comment.Text, commentOffset, pgf.Mapper) + if err != nil { + return nil, err + } + links = append(links, l...) + } + } + + return links, nil +} + +// acceptedSchemes controls the schemes that URLs must have to be shown to the +// user. Other schemes can't be opened by LSP clients, so linkifying them is +// distracting. See golang/go#43990. +var acceptedSchemes = map[string]bool{ + "http": true, + "https": true, +} + +// urlRegexp is the user-supplied regular expression to match URL. +// srcOffset is the start offset of 'src' within m's file. +func findLinksInString(urlRegexp *regexp.Regexp, src string, srcOffset int, m *protocol.Mapper) ([]protocol.DocumentLink, error) { + var links []protocol.DocumentLink + for _, index := range urlRegexp.FindAllIndex([]byte(src), -1) { + start, end := index[0], index[1] + link := src[start:end] + linkURL, err := url.Parse(link) + // Fallback: Linkify IP addresses as suggested in golang/go#18824. + if err != nil { + linkURL, err = url.Parse("//" + link) + // Not all potential links will be valid, so don't return this error. + if err != nil { + continue + } + } + // If the URL has no scheme, use https. + if linkURL.Scheme == "" { + linkURL.Scheme = "https" + } + if !acceptedSchemes[linkURL.Scheme] { + continue + } + + l, err := toProtocolLink(m, linkURL.String(), srcOffset+start, srcOffset+end) + if err != nil { + return nil, err + } + links = append(links, l) + } + // Handle golang/go#1234-style links. + r := getIssueRegexp() + for _, index := range r.FindAllIndex([]byte(src), -1) { + start, end := index[0], index[1] + matches := r.FindStringSubmatch(src) + if len(matches) < 4 { + continue + } + org, repo, number := matches[1], matches[2], matches[3] + targetURL := fmt.Sprintf("https://github.com/%s/%s/issues/%s", org, repo, number) + l, err := toProtocolLink(m, targetURL, srcOffset+start, srcOffset+end) + if err != nil { + return nil, err + } + links = append(links, l) + } + return links, nil +} + +func getIssueRegexp() *regexp.Regexp { + once.Do(func() { + issueRegexp = regexp.MustCompile(`(\w+)/([\w-]+)#([0-9]+)`) + }) + return issueRegexp +} + +var ( + once sync.Once + issueRegexp *regexp.Regexp +) + +func toProtocolLink(m *protocol.Mapper, targetURL string, start, end int) (protocol.DocumentLink, error) { + rng, err := m.OffsetRange(start, end) + if err != nil { + return protocol.DocumentLink{}, err + } + return protocol.DocumentLink{ + Range: rng, + Target: &targetURL, + }, nil +} diff --git a/contribs/gnopls/internal/server/prompt.go b/contribs/gnopls/internal/server/prompt.go new file mode 100644 index 00000000000..66329784a6f --- /dev/null +++ b/contribs/gnopls/internal/server/prompt.go @@ -0,0 +1,419 @@ +// 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 server + +import ( + "context" + "fmt" + "math/rand" + "os" + "path/filepath" + "strconv" + "time" + + "golang.org/x/telemetry" + "golang.org/x/telemetry/counter" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +// promptTimeout is the amount of time we wait for an ongoing prompt before +// prompting again. This gives the user time to reply. However, at some point +// we must assume that the client is not displaying the prompt, the user is +// ignoring it, or the prompt has been disrupted in some way (e.g. by a gopls +// crash). +const promptTimeout = 24 * time.Hour + +// gracePeriod is the amount of time we wait before sufficient telemetry data +// is accumulated in the local directory, so users can have time to review +// what kind of information will be collected and uploaded when prompting starts. +const gracePeriod = 7 * 24 * time.Hour + +// samplesPerMille is the prompt probability. +// Token is an integer between [1, 1000] and is assigned when maybePromptForTelemetry +// is called first time. Only the user with a token ∈ [1, samplesPerMille] +// will be considered for prompting. +const samplesPerMille = 10 // 1% sample rate + +// The following constants are used for testing telemetry integration. +const ( + TelemetryPromptWorkTitle = "Checking telemetry prompt" // progress notification title, for awaiting in tests + GoplsConfigDirEnvvar = "GOPLS_CONFIG_DIR" // overridden for testing + FakeTelemetryModefileEnvvar = "GOPLS_FAKE_TELEMETRY_MODEFILE" // overridden for testing + FakeSamplesPerMille = "GOPLS_FAKE_SAMPLES_PER_MILLE" // overridden for testing + TelemetryYes = "Yes, I'd like to help." + TelemetryNo = "No, thanks." +) + +// The following environment variables may be set by the client. +// Exported for testing telemetry integration. +const ( + GoTelemetryGoplsClientStartTimeEnvvar = "GOTELEMETRY_GOPLS_CLIENT_START_TIME" // telemetry start time recored in client + GoTelemetryGoplsClientTokenEnvvar = "GOTELEMETRY_GOPLS_CLIENT_TOKEN" // sampling token +) + +// getenv returns the effective environment variable value for the provided +// key, looking up the key in the session environment before falling back on +// the process environment. +func (s *server) getenv(key string) string { + if v, ok := s.Options().Env[key]; ok { + return v + } + return os.Getenv(key) +} + +// configDir returns the root of the gopls configuration dir. By default this +// is os.UserConfigDir/gopls, but it may be overridden for tests. +func (s *server) configDir() (string, error) { + if d := s.getenv(GoplsConfigDirEnvvar); d != "" { + return d, nil + } + userDir, err := os.UserConfigDir() + if err != nil { + return "", err + } + return filepath.Join(userDir, "gopls"), nil +} + +// telemetryMode returns the current effective telemetry mode. +// By default this is x/telemetry.Mode(), but it may be overridden for tests. +func (s *server) telemetryMode() string { + if fake := s.getenv(FakeTelemetryModefileEnvvar); fake != "" { + if data, err := os.ReadFile(fake); err == nil { + return string(data) + } + return "local" + } + return telemetry.Mode() +} + +// setTelemetryMode sets the current telemetry mode. +// By default this calls x/telemetry.SetMode, but it may be overridden for +// tests. +func (s *server) setTelemetryMode(mode string) error { + if fake := s.getenv(FakeTelemetryModefileEnvvar); fake != "" { + return os.WriteFile(fake, []byte(mode), 0666) + } + return telemetry.SetMode(mode) +} + +// maybePromptForTelemetry 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". +// +// The actual conditions for prompting are defensive, erring on the side of not +// prompting. +// If enabled is false, this will not prompt the user in any condition, +// but will send work progress reports to help testing. +func (s *server) maybePromptForTelemetry(ctx context.Context, enabled bool) { + if s.Options().VerboseWorkDoneProgress { + work := s.progress.Start(ctx, TelemetryPromptWorkTitle, "Checking if gopls should prompt about telemetry...", nil, nil) + defer work.End(ctx, "Done.") + } + + errorf := func(format string, args ...any) { + err := fmt.Errorf(format, args...) + event.Error(ctx, "telemetry prompt failed", err) + } + + // Only prompt if we can read/write the prompt config file. + configDir, err := s.configDir() + if err != nil { + errorf("unable to determine config dir: %v", err) + return + } + + // Read the current prompt file. + + var ( + promptDir = filepath.Join(configDir, "prompt") // prompt configuration directory + promptFile = filepath.Join(promptDir, "telemetry") // telemetry prompt file + ) + + // prompt states, stored in the prompt file + const ( + pUnknown = "" // first time + pNotReady = "-" // user is not asked yet (either not sampled or not past the grace period) + pYes = "yes" // user said yes + pNo = "no" // user said no + pPending = "pending" // current prompt is still pending + pFailed = "failed" // prompt was asked but failed + ) + validStates := map[string]bool{ + pNotReady: true, + pYes: true, + pNo: true, + pPending: true, + pFailed: true, + } + + // Parse the current prompt file. + var ( + state = pUnknown + attempts = 0 // number of times we've asked already + + // the followings are recorded after gopls v0.17+. + token = 0 // valid token is [1, 1000] + creationTime int64 // unix time sec + ) + if content, err := os.ReadFile(promptFile); err == nil { + if n, _ := fmt.Sscanf(string(content), "%s %d %d %d", &state, &attempts, &creationTime, &token); (n == 2 || n == 4) && validStates[state] { + // successfully parsed! + // ~ v0.16: must have only two fields, state and attempts. + // v0.17 ~: must have all four fields. + } else { + state, attempts, creationTime, token = pUnknown, 0, 0, 0 + errorf("malformed prompt result %q", string(content)) + } + } else if !os.IsNotExist(err) { + errorf("reading prompt file: %v", err) + // Something went wrong. Since we don't know how many times we've asked the + // prompt, err on the side of not asking. + // + // But record this in telemetry, in case some users enable telemetry by + // other means. + counter.New("gopls/telemetryprompt/corrupted").Inc() + return + } + + counter.New(fmt.Sprintf("gopls/telemetryprompt/attempts:%d", attempts)).Inc() + + // Check terminal conditions. + + if state == pYes { + // Prompt has been accepted. + // + // We record this counter for every gopls session, rather than when the + // prompt actually accepted below, because if we only recorded it in the + // counter file at the time telemetry is enabled, we'd never upload it, + // because we exclude any counter files that overlap with a time period + // that has telemetry uploading is disabled. + counter.New("gopls/telemetryprompt/accepted").Inc() + return + } + if state == pNo { + // Prompt has been declined. In most cases, this means we'll never see the + // counter below, but it's possible that the user may enable telemetry by + // other means later on. If we see a significant number of users that have + // accepted telemetry but declined the prompt, it may be an indication that + // the prompt is not working well. + counter.New("gopls/telemetryprompt/declined").Inc() + return + } + if attempts >= 5 { // pPending or pFailed + // We've tried asking enough; give up. Record that the prompt expired, in + // case the user decides to enable telemetry by other means later on. + // (see also the pNo case). + counter.New("gopls/telemetryprompt/expired").Inc() + return + } + + // We only check enabled after (1) the work progress is started, and (2) the + // prompt file has been read. (1) is for testing purposes, and (2) is so that + // we record the "gopls/telemetryprompt/accepted" counter for every session. + if !enabled { + return // prompt is disabled + } + + if s.telemetryMode() == "on" || s.telemetryMode() == "off" { + // Telemetry is already on or explicitly off -- nothing to ask about. + return + } + + // Transition: pUnknown -> pNotReady + if state == pUnknown { + // First time; we need to make the prompt dir. + if err := os.MkdirAll(promptDir, 0777); err != nil { + errorf("creating prompt dir: %v", err) + return + } + state = pNotReady + } + + // Correct missing values. + if creationTime == 0 { + creationTime = time.Now().Unix() + if v := s.getenv(GoTelemetryGoplsClientStartTimeEnvvar); v != "" { + if sec, err := strconv.ParseInt(v, 10, 64); err == nil && sec > 0 { + creationTime = sec + } + } + } + if token == 0 { + token = rand.Intn(1000) + 1 + if v := s.getenv(GoTelemetryGoplsClientTokenEnvvar); v != "" { + if tok, err := strconv.Atoi(v); err == nil && 1 <= tok && tok <= 1000 { + token = tok + } + } + } + + // Transition: pNotReady -> pPending if sampled + if state == pNotReady { + threshold := samplesPerMille + if v := s.getenv(FakeSamplesPerMille); v != "" { + if t, err := strconv.Atoi(v); err == nil { + threshold = t + } + } + if token <= threshold && time.Now().Unix()-creationTime > gracePeriod.Milliseconds()/1000 { + state = pPending + } + } + + // Acquire the lock and write the updated state to the prompt file before actually + // prompting. + // + // This ensures that the prompt file is writeable, and that we increment the + // attempt counter before we prompt, so that we don't end up in a failure + // mode where we keep prompting and then failing to record the response. + + release, ok, err := acquireLockFile(promptFile) + if err != nil { + errorf("acquiring prompt: %v", err) + return + } + if !ok { + // Another process is making decision. + return + } + defer release() + + if state != pNotReady { // pPending or pFailed + attempts++ + } + + pendingContent := []byte(fmt.Sprintf("%s %d %d %d", state, attempts, creationTime, token)) + if err := os.WriteFile(promptFile, pendingContent, 0666); err != nil { + errorf("writing pending state: %v", err) + return + } + + if state == pNotReady { + return + } + + var prompt = `Go telemetry helps us improve Go by periodically sending anonymous metrics and crash reports to the Go team. Learn more at https://go.dev/doc/telemetry. + +Would you like to enable Go telemetry? +` + if s.Options().LinkifyShowMessage { + prompt = `Go telemetry helps us improve Go by periodically sending anonymous metrics and crash reports to the Go team. Learn more at [go.dev/doc/telemetry](https://go.dev/doc/telemetry). + +Would you like to enable Go telemetry? +` + } + // TODO(rfindley): investigate a "tell me more" action in combination with ShowDocument. + params := &protocol.ShowMessageRequestParams{ + Type: protocol.Info, + Message: prompt, + Actions: []protocol.MessageActionItem{ + {Title: TelemetryYes}, + {Title: TelemetryNo}, + }, + } + + item, err := s.client.ShowMessageRequest(ctx, params) + if err != nil { + errorf("ShowMessageRequest failed: %v", err) + // Defensive: ensure item == nil for the logic below. + item = nil + } + + message := func(typ protocol.MessageType, msg string) { + if !showMessage(ctx, s.client, typ, msg) { + // Make sure we record that "telemetry prompt failed". + errorf("showMessage failed: %v", err) + } + } + + result := pFailed + if item == nil { + // e.g. dialog was dismissed + errorf("no response") + } else { + // Response matches MessageActionItem.Title. + switch item.Title { + case TelemetryYes: + result = pYes + if err := s.setTelemetryMode("on"); err == nil { + message(protocol.Info, telemetryOnMessage(s.Options().LinkifyShowMessage)) + } else { + errorf("enabling telemetry failed: %v", err) + msg := fmt.Sprintf("Failed to enable Go telemetry: %v\nTo enable telemetry manually, please run `go run golang.org/x/telemetry/cmd/gotelemetry@latest on`", err) + message(protocol.Error, msg) + } + + case TelemetryNo: + result = pNo + default: + errorf("unrecognized response %q", item.Title) + message(protocol.Error, fmt.Sprintf("Unrecognized response %q", item.Title)) + } + } + resultContent := []byte(fmt.Sprintf("%s %d %d %d", result, attempts, creationTime, token)) + if err := os.WriteFile(promptFile, resultContent, 0666); err != nil { + errorf("error writing result state to prompt file: %v", err) + } +} + +func telemetryOnMessage(linkify bool) string { + format := `Thank you. Telemetry uploading is now enabled. + +To disable telemetry uploading, run %s. +` + var runCmd = "`go run golang.org/x/telemetry/cmd/gotelemetry@latest local`" + if linkify { + runCmd = "[gotelemetry local](https://golang.org/x/telemetry/cmd/gotelemetry)" + } + return fmt.Sprintf(format, runCmd) +} + +// acquireLockFile attempts to "acquire a lock" for writing to path. +// +// This is achieved by creating an exclusive lock file at .lock. Lock +// files expire after a period, at which point acquireLockFile will remove and +// recreate the lock file. +// +// acquireLockFile fails if path is in a directory that doesn't exist. +func acquireLockFile(path string) (func(), bool, error) { + lockpath := path + ".lock" + fi, err := os.Stat(lockpath) + if err == nil { + if time.Since(fi.ModTime()) > promptTimeout { + _ = os.Remove(lockpath) // ignore error + } else { + return nil, false, nil + } + } else if !os.IsNotExist(err) { + return nil, false, fmt.Errorf("statting lockfile: %v", err) + } + + f, err := os.OpenFile(lockpath, os.O_CREATE|os.O_EXCL, 0666) + if err != nil { + if os.IsExist(err) { + return nil, false, nil + } + return nil, false, fmt.Errorf("creating lockfile: %v", err) + } + fi, err = f.Stat() + if err != nil { + return nil, false, err + } + release := func() { + _ = f.Close() // ignore error + fi2, err := os.Stat(lockpath) + if err == nil && os.SameFile(fi, fi2) { + // Only clean up the lockfile if it's the same file we created. + // Otherwise, our lock has expired and something else has the lock. + // + // There's a race here, in that the file could have changed since the + // stat above; but given that we've already waited 24h this is extremely + // unlikely, and acceptable. + _ = os.Remove(lockpath) + } + } + return release, true, nil +} diff --git a/contribs/gnopls/internal/server/prompt_test.go b/contribs/gnopls/internal/server/prompt_test.go new file mode 100644 index 00000000000..f4484cb6437 --- /dev/null +++ b/contribs/gnopls/internal/server/prompt_test.go @@ -0,0 +1,82 @@ +// 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 server + +import ( + "path/filepath" + "sync" + "sync/atomic" + "testing" +) + +func TestAcquireFileLock(t *testing.T) { + name := filepath.Join(t.TempDir(), "config.json") + + const concurrency = 100 + var acquired int32 + var releasers [concurrency]func() + defer func() { + for _, r := range releasers { + if r != nil { + r() + } + } + }() + + var wg sync.WaitGroup + for i := range releasers { + i := i + wg.Add(1) + go func() { + defer wg.Done() + + release, ok, err := acquireLockFile(name) + if err != nil { + t.Errorf("Acquire failed: %v", err) + return + } + if ok { + atomic.AddInt32(&acquired, 1) + releasers[i] = release + } + }() + } + + wg.Wait() + + if acquired != 1 { + t.Errorf("Acquire succeeded %d times, expected exactly 1", acquired) + } +} + +func TestReleaseAndAcquireFileLock(t *testing.T) { + name := filepath.Join(t.TempDir(), "config.json") + + acquire := func() (func(), bool) { + t.Helper() + release, ok, err := acquireLockFile(name) + if err != nil { + t.Fatal(err) + } + return release, ok + } + + release, ok := acquire() + if !ok { + t.Fatal("failed to Acquire") + } + if release2, ok := acquire(); ok { + release() + release2() + t.Fatalf("Acquire succeeded unexpectedly") + } + + release() + release3, ok := acquire() + release3() + if !ok { + t.Fatalf("failed to Acquire") + } +} diff --git a/contribs/gnopls/internal/server/references.go b/contribs/gnopls/internal/server/references.go new file mode 100644 index 00000000000..f5019693946 --- /dev/null +++ b/contribs/gnopls/internal/server/references.go @@ -0,0 +1,40 @@ +// 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. + +package server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/telemetry" + "golang.org/x/tools/gopls/internal/template" + "golang.org/x/tools/internal/event" +) + +func (s *server) References(ctx context.Context, params *protocol.ReferenceParams) (_ []protocol.Location, rerr error) { + recordLatency := telemetry.StartLatencyTimer("references") + defer func() { + recordLatency(ctx, rerr) + }() + + ctx, done := event.Start(ctx, "lsp.Server.references", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + switch snapshot.FileKind(fh) { + case file.Tmpl: + return template.References(ctx, snapshot, fh, params) + case file.Go: + return golang.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration) + } + return nil, nil // empty result +} diff --git a/contribs/gnopls/internal/server/rename.go b/contribs/gnopls/internal/server/rename.go new file mode 100644 index 00000000000..93b2ac6f9c4 --- /dev/null +++ b/contribs/gnopls/internal/server/rename.go @@ -0,0 +1,96 @@ +// 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. + +package server + +import ( + "context" + "fmt" + "path/filepath" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func (s *server) Rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) { + ctx, done := event.Start(ctx, "lsp.Server.rename", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + if kind := snapshot.FileKind(fh); kind != file.Go { + return nil, fmt.Errorf("cannot rename in file of type %s", kind) + } + + // Because we don't handle directory renaming within golang.Rename, golang.Rename returns + // boolean value isPkgRenaming to determine whether an DocumentChanges of type RenameFile should + // be added to the return protocol.WorkspaceEdit value. + edits, isPkgRenaming, err := golang.Rename(ctx, snapshot, fh, params.Position, params.NewName) + if err != nil { + return nil, err + } + + var changes []protocol.DocumentChange + for uri, e := range edits { + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + change := protocol.DocumentChangeEdit(fh, e) + changes = append(changes, change) + } + + if isPkgRenaming { + // Update the last component of the file's enclosing directory. + oldDir := filepath.Dir(fh.URI().Path()) + newDir := filepath.Join(filepath.Dir(oldDir), params.NewName) + change := protocol.DocumentChangeRename( + protocol.URIFromPath(oldDir), + protocol.URIFromPath(newDir)) + changes = append(changes, change) + } + + return protocol.NewWorkspaceEdit(changes...), nil +} + +// PrepareRename implements the textDocument/prepareRename handler. It may +// return (nil, nil) if there is no rename at the cursor position, but it is +// not desirable to display an error to the user. +// +// TODO(rfindley): why wouldn't we want to show an error to the user, if the +// user initiated a rename request at the cursor? +func (s *server) PrepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.PrepareRenamePlaceholder, error) { + ctx, done := event.Start(ctx, "lsp.Server.prepareRename", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + if kind := snapshot.FileKind(fh); kind != file.Go { + return nil, fmt.Errorf("cannot rename in file of type %s", kind) + } + + // Do not return errors here, as it adds clutter. + // Returning a nil result means there is not a valid rename. + item, usererr, err := golang.PrepareRename(ctx, snapshot, fh, params.Position) + if err != nil { + // Return usererr here rather than err, to avoid cluttering the UI with + // internal error details. + return nil, usererr + } + return &protocol.PrepareRenamePlaceholder{ + Range: item.Range, + Placeholder: item.Text, + }, nil +} diff --git a/contribs/gnopls/internal/server/selection_range.go b/contribs/gnopls/internal/server/selection_range.go new file mode 100644 index 00000000000..042812217f3 --- /dev/null +++ b/contribs/gnopls/internal/server/selection_range.go @@ -0,0 +1,75 @@ +// 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 server + +import ( + "context" + "fmt" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/gopls/internal/cache/parsego" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +// selectionRange defines the textDocument/selectionRange feature, +// which, given a list of positions within a file, +// reports a linked list of enclosing syntactic blocks, innermost first. +// +// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange. +// +// This feature can be used by a client to implement "expand selection" in a +// language-aware fashion. Multiple input positions are supported to allow +// for multiple cursors, and the entire path up to the whole document is +// returned for each cursor to avoid multiple round-trips when the user is +// likely to issue this command multiple times in quick succession. +func (s *server) SelectionRange(ctx context.Context, params *protocol.SelectionRangeParams) ([]protocol.SelectionRange, error) { + ctx, done := event.Start(ctx, "lsp.Server.selectionRange") + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + if kind := snapshot.FileKind(fh); kind != file.Go { + return nil, fmt.Errorf("SelectionRange not supported for file of type %s", kind) + } + + pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full) + if err != nil { + return nil, err + } + + result := make([]protocol.SelectionRange, len(params.Positions)) + for i, protocolPos := range params.Positions { + pos, err := pgf.PositionPos(protocolPos) + if err != nil { + return nil, err + } + + path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos) + + tail := &result[i] // tail of the Parent linked list, built head first + + for j, node := range path { + rng, err := pgf.NodeRange(node) + if err != nil { + return nil, err + } + + // Add node to tail. + if j > 0 { + tail.Parent = &protocol.SelectionRange{} + tail = tail.Parent + } + tail.Range = rng + } + } + + return result, nil +} diff --git a/contribs/gnopls/internal/server/semantic.go b/contribs/gnopls/internal/server/semantic.go new file mode 100644 index 00000000000..f746593a3dd --- /dev/null +++ b/contribs/gnopls/internal/server/semantic.go @@ -0,0 +1,56 @@ +// 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 server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/template" + "golang.org/x/tools/internal/event" +) + +func (s *server) SemanticTokensFull(ctx context.Context, params *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) { + return s.semanticTokens(ctx, params.TextDocument, nil) +} + +func (s *server) SemanticTokensRange(ctx context.Context, params *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) { + return s.semanticTokens(ctx, params.TextDocument, ¶ms.Range) +} + +func (s *server) semanticTokens(ctx context.Context, td protocol.TextDocumentIdentifier, rng *protocol.Range) (*protocol.SemanticTokens, error) { + ctx, done := event.Start(ctx, "lsp.Server.semanticTokens", label.URI.Of(td.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, td.URI) + if err != nil { + return nil, err + } + defer release() + + if snapshot.Options().SemanticTokens { + switch snapshot.FileKind(fh) { + case file.Tmpl: + return template.SemanticTokens(ctx, snapshot, fh.URI()) + case file.Go: + return golang.SemanticTokens(ctx, snapshot, fh, rng) + } + } + + // Not enabled, or unsupported file type: return empty result. + // + // Returning an empty response is necessary to invalidate + // semantic tokens in VS Code (and perhaps other editors). + // Previously, we returned an error, but that had the side effect + // of noisy "semantictokens are disabled" logs on every keystroke. + // + // We must return a non-nil Data slice for JSON serialization. + // We do not return an empty field with "omitempty" set, + // as it is not marked optional in the protocol (golang/go#67885). + return &protocol.SemanticTokens{Data: []uint32{}}, nil +} diff --git a/contribs/gnopls/internal/server/server.go b/contribs/gnopls/internal/server/server.go new file mode 100644 index 00000000000..80e64bb996c --- /dev/null +++ b/contribs/gnopls/internal/server/server.go @@ -0,0 +1,540 @@ +// Copyright 2018 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 server defines gopls' implementation of the LSP server +// interface, [protocol.Server]. Call [New] to create an instance. +package server + +import ( + "context" + "crypto/rand" + "embed" + "encoding/base64" + "fmt" + "log" + "net" + "net/http" + "net/url" + "os" + paths "path" + "strconv" + "strings" + "sync" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cache/metadata" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/progress" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/event" +) + +// New creates an LSP server and binds it to handle incoming client +// messages on the supplied stream. +func New(session *cache.Session, client protocol.ClientCloser, options *settings.Options) protocol.Server { + const concurrentAnalyses = 1 + // If this assignment fails to compile after a protocol + // upgrade, it means that one or more new methods need new + // stub declarations in unimplemented.go. + return &server{ + diagnostics: make(map[protocol.DocumentURI]*fileDiagnostics), + watchedGlobPatterns: nil, // empty + changedFiles: make(map[protocol.DocumentURI]unit), + session: session, + client: client, + diagnosticsSema: make(chan unit, concurrentAnalyses), + progress: progress.NewTracker(client), + options: options, + viewsToDiagnose: make(map[*cache.View]uint64), + } +} + +type serverState int + +const ( + serverCreated = serverState(iota) + serverInitializing // set once the server has received "initialize" request + serverInitialized // set once the server has received "initialized" request + serverShutDown +) + +func (s serverState) String() string { + switch s { + case serverCreated: + return "created" + case serverInitializing: + return "initializing" + case serverInitialized: + return "initialized" + case serverShutDown: + return "shutDown" + } + return fmt.Sprintf("(unknown state: %d)", int(s)) +} + +// server implements the protocol.server interface. +type server struct { + client protocol.ClientCloser + + stateMu sync.Mutex + state serverState + // notifications generated before serverInitialized + notifications []*protocol.ShowMessageParams + + session *cache.Session + + tempDir string + + // changedFiles tracks files for which there has been a textDocument/didChange. + changedFilesMu sync.Mutex + changedFiles map[protocol.DocumentURI]unit + + // folders is only valid between initialize and initialized, and holds the + // set of folders to build views for when we are ready. + // Only the valid, non-empty 'file'-scheme URIs will be added. + pendingFolders []protocol.WorkspaceFolder + + // watchedGlobPatterns is the set of glob patterns that we have requested + // the client watch on disk. It will be updated as the set of directories + // that the server should watch changes. + // The map field may be reassigned but the map is immutable. + watchedGlobPatternsMu sync.Mutex + watchedGlobPatterns map[protocol.RelativePattern]unit + watchRegistrationCount int + + diagnosticsMu sync.Mutex // guards map and its values + diagnostics map[protocol.DocumentURI]*fileDiagnostics + + // diagnosticsSema limits the concurrency of diagnostics runs, which can be + // expensive. + diagnosticsSema chan unit + + progress *progress.Tracker + + // When the workspace fails to load, we show its status through a progress + // report with an error message. + criticalErrorStatusMu sync.Mutex + criticalErrorStatus *progress.WorkDone + + // Track an ongoing CPU profile created with the StartProfile command and + // terminated with the StopProfile command. + ongoingProfileMu sync.Mutex + ongoingProfile *os.File // if non-nil, an ongoing profile is writing to this file + + // Track most recently requested options. + optionsMu sync.Mutex + options *settings.Options + + // Track the most recent completion results, for measuring completion efficacy + efficacyMu sync.Mutex + efficacyURI protocol.DocumentURI + efficacyVersion int32 + efficacyItems []protocol.CompletionItem + efficacyPos protocol.Position + + // Web server (for package documentation, etc) associated with this + // LSP server. Opened on demand, and closed during LSP Shutdown. + webOnce sync.Once + web *web + webErr error + + // # Modification tracking and diagnostics + // + // For the purpose of tracking diagnostics, we need a monotonically + // increasing clock. Each time a change occurs on the server, this clock is + // incremented and the previous diagnostics pass is cancelled. When the + // changed is processed, the Session (via DidModifyFiles) determines which + // Views are affected by the change and these views are added to the + // viewsToDiagnose set. Then the server calls diagnoseChangedViews + // in a separate goroutine. Any Views that successfully complete their + // diagnostics are removed from the viewsToDiagnose set, provided they haven't + // been subsequently marked for re-diagnosis (as determined by the latest + // modificationID referenced by viewsToDiagnose). + // + // In this way, we enforce eventual completeness of the diagnostic set: any + // views requiring diagnosis are diagnosed, though possibly at a later point + // in time. Notably, the logic in Session.DidModifyFiles to determines if a + // view needs diagnosis considers whether any packages in the view were + // invalidated. Consider the following sequence of snapshots for a given view + // V: + // + // C1 C2 + // S1 -> S2 -> S3 + // + // In this case, suppose that S1 was fully type checked, and then two changes + // C1 and C2 occur in rapid succession, to a file in their package graph but + // perhaps not enclosed by V's root. In this case, the logic of + // DidModifyFiles will detect that V needs to be reloaded following C1. In + // order for our eventual consistency to be sound, we need to avoid the race + // where S2 is being diagnosed, C2 arrives, and S3 is not detected as needing + // diagnosis because the relevant package has not yet been computed in S2. To + // achieve this, we only remove V from viewsToDiagnose if the diagnosis of S2 + // completes before C2 is processed, which we can confirm by checking + // S2.BackgroundContext(). + modificationMu sync.Mutex + cancelPrevDiagnostics func() + viewsToDiagnose map[*cache.View]uint64 // View -> modification at which it last required diagnosis + lastModificationID uint64 // incrementing clock +} + +func (s *server) WorkDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error { + ctx, done := event.Start(ctx, "lsp.Server.workDoneProgressCancel") + defer done() + + return s.progress.Cancel(params.Token) +} + +// web encapsulates the web server associated with an LSP server. +// It is used for package documentation and other queries +// where HTML makes more sense than a client editor UI. +// +// Example URL: +// +// http://127.0.0.1:PORT/gopls/SECRET/... +// +// where +// - PORT is the random port number; +// - "gopls" helps the reader guess which program is the server; +// - SECRET is the 64-bit token; and +// - ... is the material part of the endpoint. +// +// Valid endpoints: +// +// open?file=%s&line=%d&col=%d - open a file +// pkg/PKGPATH?view=%s - show doc for package in a given view +// assembly?pkg=%s&view=%s&symbol=%s - show assembly of specified func symbol +// freesymbols?file=%s&range=%d:%d:%d:%d:&view=%s - show report of free symbols +type web struct { + server *http.Server + addr url.URL // "http://127.0.0.1:PORT/gopls/SECRET" + mux *http.ServeMux +} + +// getWeb returns the web server associated with this +// LSP server, creating it on first request. +func (s *server) getWeb() (*web, error) { + s.webOnce.Do(func() { + s.web, s.webErr = s.initWeb() + }) + return s.web, s.webErr +} + +// initWeb starts the local web server through which gopls +// serves package documentation and suchlike. +// +// Clients should use [getWeb]. +func (s *server) initWeb() (*web, error) { + // Use 64 random bits as the base of the URL namespace. + // This ensures that URLs are unguessable to any local + // processes that connect to the server, preventing + // exfiltration of source code. + // + // (Note: depending on the LSP client, URLs that are passed to + // it via showDocument and that result in the opening of a + // browser tab may be transiently published through the argv + // array of the open(1) or xdg-open(1) command.) + token := make([]byte, 8) + if _, err := rand.Read(token); err != nil { + return nil, fmt.Errorf("generating secret token: %v", err) + } + + // Pick any free port. + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + + // -- There should be no early returns after this point. -- + + // The root mux is not authenticated. + rootMux := http.NewServeMux() + rootMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + http.Error(w, "request URI lacks authentication segment", http.StatusUnauthorized) + }) + rootMux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, req *http.Request) { + http.Redirect(w, req, "/assets/favicon.ico", http.StatusMovedPermanently) + }) + rootMux.HandleFunc("/hang", func(w http.ResponseWriter, req *http.Request) { + // This endpoint hangs until cancelled. + // It is used by JS to detect server disconnect. + <-req.Context().Done() + }) + rootMux.Handle("/assets/", http.FileServer(http.FS(assets))) + + secret := "/gopls/" + base64.RawURLEncoding.EncodeToString(token) + webMux := http.NewServeMux() + rootMux.Handle(secret+"/", withPanicHandler(http.StripPrefix(secret, webMux))) + + webServer := &http.Server{Addr: listener.Addr().String(), Handler: rootMux} + go func() { + // This should run until LSP Shutdown, at which point + // it will return ErrServerClosed. Any other error + // means it failed to start. + if err := webServer.Serve(listener); err != nil { + if err != http.ErrServerClosed { + log.Print(err) + } + } + }() + + web := &web{ + server: webServer, + addr: url.URL{Scheme: "http", Host: webServer.Addr, Path: secret}, + mux: webMux, + } + + // The /src handler allows the browser to request that the + // LSP client editor open a file; see web.SrcURL. + webMux.HandleFunc("/src", func(w http.ResponseWriter, req *http.Request) { + if err := req.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + uri := protocol.URIFromPath(req.Form.Get("file")) + line, _ := strconv.Atoi(req.Form.Get("line")) // 1-based + col, _ := strconv.Atoi(req.Form.Get("col")) // 1-based UTF-8 + posn := protocol.Position{ + Line: uint32(line - 1), + Character: uint32(col - 1), // TODO(adonovan): map to UTF-16 + } + openClientEditor(req.Context(), s.client, protocol.Location{ + URI: uri, + Range: protocol.Range{Start: posn, End: posn}, + }) + }) + + // The /pkg/PATH&view=... handler shows package documentation for PATH. + webMux.Handle("/pkg/", http.StripPrefix("/pkg/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + ctx := req.Context() + if err := req.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Get snapshot of specified view. + view, err := s.session.View(req.Form.Get("view")) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + snapshot, release, err := view.Snapshot() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer release() + + // Find package by path. + var found *metadata.Package + for _, mp := range snapshot.MetadataGraph().Packages { + if string(mp.PkgPath) == req.URL.Path && mp.ForTest == "" { + found = mp + break + } + } + if found == nil { + // TODO(adonovan): what should we do for external test packages? + http.Error(w, "package not found", http.StatusNotFound) + return + } + + // Type-check the package and render its documentation. + pkgs, err := snapshot.TypeCheck(ctx, found.ID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + content, err := golang.PackageDocHTML(view.ID(), pkgs[0], web) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(content) + }))) + + // The /freesymbols?file=...&range=...&view=... handler shows + // free symbols referenced by the selection. + webMux.HandleFunc("/freesymbols", func(w http.ResponseWriter, req *http.Request) { + ctx := req.Context() + if err := req.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Get snapshot of specified view. + view, err := s.session.View(req.Form.Get("view")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + snapshot, release, err := view.Snapshot() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer release() + + // Get selection range and type-check. + loc := protocol.Location{ + URI: protocol.DocumentURI(req.Form.Get("file")), + } + if _, err := fmt.Sscanf(req.Form.Get("range"), "%d:%d:%d:%d", + &loc.Range.Start.Line, + &loc.Range.Start.Character, + &loc.Range.End.Line, + &loc.Range.End.Character, + ); err != nil { + http.Error(w, "invalid range", http.StatusInternalServerError) + return + } + pkg, pgf, err := golang.NarrowestPackageForFile(ctx, snapshot, loc.URI) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + start, end, err := pgf.RangePos(loc.Range) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Produce report. + html := golang.FreeSymbolsHTML(view.ID(), pkg, pgf, start, end, web) + w.Write(html) + }) + + // The /assembly?pkg=...&view=...&symbol=... handler shows + // the assembly of the current function. + webMux.HandleFunc("/assembly", func(w http.ResponseWriter, req *http.Request) { + ctx := req.Context() + if err := req.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Get parameters. + var ( + viewID = req.Form.Get("view") + pkgID = metadata.PackageID(req.Form.Get("pkg")) + symbol = req.Form.Get("symbol") + ) + if viewID == "" || pkgID == "" || symbol == "" { + http.Error(w, "/assembly requires view, pkg, symbol", http.StatusBadRequest) + return + } + + // Get snapshot of specified view. + view, err := s.session.View(viewID) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + snapshot, release, err := view.Snapshot() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer release() + + pkgs, err := snapshot.TypeCheck(ctx, pkgID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + pkg := pkgs[0] + + // Produce report. + html, err := golang.AssemblyHTML(ctx, snapshot, pkg, symbol, web) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(html) + }) + + return web, nil +} + +// assets holds our static web server content. +// +//go:embed assets/* +var assets embed.FS + +// SrcURL returns a /src URL that, when visited, causes the client +// editor to open the specified file/line/column (in 1-based UTF-8 +// coordinates). +// +// (Rendering may generate hundreds of positions across files of many +// packages, so don't convert to LSP coordinates yet: wait until the +// URL is opened.) +func (w *web) SrcURL(filename string, line, col8 int) protocol.URI { + return w.url( + "src", + fmt.Sprintf("file=%s&line=%d&col=%d", url.QueryEscape(filename), line, col8), + "") +} + +// PkgURL returns a /pkg URL for the documentation of the specified package. +// The optional fragment must be of the form "Println" or "Buffer.WriteString". +func (w *web) PkgURL(viewID string, path golang.PackagePath, fragment string) protocol.URI { + return w.url( + "pkg/"+string(path), + "view="+url.QueryEscape(viewID), + fragment) +} + +// freesymbolsURL returns a /freesymbols URL for a report +// on the free symbols referenced within the selection span (loc). +func (w *web) freesymbolsURL(viewID string, loc protocol.Location) protocol.URI { + return w.url( + "freesymbols", + fmt.Sprintf("file=%s&range=%d:%d:%d:%d&view=%s", + url.QueryEscape(string(loc.URI)), + loc.Range.Start.Line, + loc.Range.Start.Character, + loc.Range.End.Line, + loc.Range.End.Character, + url.QueryEscape(viewID)), + "") +} + +// assemblyURL returns the URL of an assembly listing of the specified function symbol. +func (w *web) assemblyURL(viewID, packageID, symbol string) protocol.URI { + return w.url( + "assembly", + fmt.Sprintf("view=%s&pkg=%s&symbol=%s", + url.QueryEscape(viewID), + url.QueryEscape(packageID), + url.QueryEscape(symbol)), + "") +} + +// url returns a URL by joining a relative path, an (encoded) query, +// and an (unencoded) fragment onto the authenticated base URL of the +// web server. +func (w *web) url(path, query, fragment string) protocol.URI { + url2 := w.addr + url2.Path = paths.Join(url2.Path, strings.TrimPrefix(path, "/")) + url2.RawQuery = query + url2.Fragment = fragment + return protocol.URI(url2.String()) +} + +// withPanicHandler wraps an HTTP handler with telemetry-reporting of +// panics that would otherwise be silently recovered by the net/http +// root handler. +func withPanicHandler(h http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + panicked := true + defer func() { + if panicked { + bug.Report("panic in HTTP handler") + } + }() + h.ServeHTTP(w, req) + panicked = false + } +} diff --git a/contribs/gnopls/internal/server/signature_help.go b/contribs/gnopls/internal/server/signature_help.go new file mode 100644 index 00000000000..addcfe1e262 --- /dev/null +++ b/contribs/gnopls/internal/server/signature_help.go @@ -0,0 +1,48 @@ +// Copyright 2018 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 server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func (s *server) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { + ctx, done := event.Start(ctx, "lsp.Server.signatureHelp", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + if snapshot.FileKind(fh) != file.Go { + return nil, nil // empty result + } + + info, activeParameter, err := golang.SignatureHelp(ctx, snapshot, fh, params.Position) + if err != nil { + // TODO(rfindley): is this correct? Apparently, returning an error from + // signatureHelp is distracting in some editors, though I haven't confirmed + // that recently. + // + // It's unclear whether we still need to avoid returning this error result. + event.Error(ctx, "signature help failed", err, label.Position.Of(params.Position)) + return nil, nil + } + if info == nil { + return nil, nil + } + return &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{*info}, + ActiveParameter: uint32(activeParameter), + }, nil +} diff --git a/contribs/gnopls/internal/server/symbols.go b/contribs/gnopls/internal/server/symbols.go new file mode 100644 index 00000000000..e35b2c75451 --- /dev/null +++ b/contribs/gnopls/internal/server/symbols.go @@ -0,0 +1,62 @@ +// 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. + +package server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/template" + "golang.org/x/tools/internal/event" +) + +func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]any, error) { + ctx, done := event.Start(ctx, "lsp.Server.documentSymbol", label.URI.Of(params.TextDocument.URI)) + defer done() + + fh, snapshot, release, err := s.fileOf(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer release() + + var docSymbols []protocol.DocumentSymbol + switch snapshot.FileKind(fh) { + case file.Tmpl: + docSymbols, err = template.DocumentSymbols(snapshot, fh) + case file.Go: + docSymbols, err = golang.DocumentSymbols(ctx, snapshot, fh) + default: + return nil, nil // empty result + } + if err != nil { + event.Error(ctx, "DocumentSymbols failed", err) + return nil, nil // empty result + } + // Convert the symbols to an interface array. + // TODO: Remove this once the lsp deprecates SymbolInformation. + symbols := make([]any, len(docSymbols)) + for i, s := range docSymbols { + if snapshot.Options().HierarchicalDocumentSymbolSupport { + symbols[i] = s + continue + } + // If the client does not support hierarchical document symbols, then + // we need to be backwards compatible for now and return SymbolInformation. + symbols[i] = protocol.SymbolInformation{ + Name: s.Name, + Kind: s.Kind, + Deprecated: s.Deprecated, + Location: protocol.Location{ + URI: params.TextDocument.URI, + Range: s.Range, + }, + } + } + return symbols, nil +} diff --git a/contribs/gnopls/internal/server/text_synchronization.go b/contribs/gnopls/internal/server/text_synchronization.go new file mode 100644 index 00000000000..257eadbbf41 --- /dev/null +++ b/contribs/gnopls/internal/server/text_synchronization.go @@ -0,0 +1,421 @@ +// 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. + +package server + +import ( + "bytes" + "context" + "errors" + "fmt" + "path/filepath" + "strings" + "sync" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/label" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/xcontext" +) + +// ModificationSource identifies the origin of a change. +type ModificationSource int + +const ( + // FromDidOpen is from a didOpen notification. + FromDidOpen = ModificationSource(iota) + + // FromDidChange is from a didChange notification. + FromDidChange + + // FromDidChangeWatchedFiles is from didChangeWatchedFiles notification. + FromDidChangeWatchedFiles + + // FromDidSave is from a didSave notification. + FromDidSave + + // FromDidClose is from a didClose notification. + FromDidClose + + // FromDidChangeConfiguration is from a didChangeConfiguration notification. + FromDidChangeConfiguration + + // FromRegenerateCgo refers to file modifications caused by regenerating + // the cgo sources for the workspace. + FromRegenerateCgo + + // FromInitialWorkspaceLoad refers to the loading of all packages in the + // workspace when the view is first created. + FromInitialWorkspaceLoad + + // FromCheckUpgrades refers to state changes resulting from the CheckUpgrades + // command, which queries module upgrades. + FromCheckUpgrades + + // FromResetGoModDiagnostics refers to state changes resulting from the + // ResetGoModDiagnostics command. + FromResetGoModDiagnostics + + // FromToggleGCDetails refers to state changes resulting from toggling + // gc_details on or off for a package. + FromToggleGCDetails +) + +func (m ModificationSource) String() string { + switch m { + case FromDidOpen: + return "opened files" + case FromDidChange: + return "changed files" + case FromDidChangeWatchedFiles: + return "files changed on disk" + case FromDidSave: + return "saved files" + case FromDidClose: + return "close files" + case FromRegenerateCgo: + return "regenerate cgo" + case FromInitialWorkspaceLoad: + return "initial workspace load" + case FromCheckUpgrades: + return "from check upgrades" + case FromResetGoModDiagnostics: + return "from resetting go.mod diagnostics" + default: + return "unknown file modification" + } +} + +func (s *server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error { + ctx, done := event.Start(ctx, "lsp.Server.didOpen", label.URI.Of(params.TextDocument.URI)) + defer done() + + uri := params.TextDocument.URI + // There may not be any matching view in the current session. If that's + // the case, try creating a new view based on the opened file path. + // + // TODO(golang/go#57979): revisit creating a folder here. We should separate + // the logic for managing folders from the logic for managing views. But it + // does make sense to ensure at least one workspace folder the first time a + // file is opened, and we can't do that inside didModifyFiles because we + // don't want to request configuration while holding a lock. + if len(s.session.Views()) == 0 { + dir := filepath.Dir(uri.Path()) + s.addFolders(ctx, []protocol.WorkspaceFolder{{ + URI: string(protocol.URIFromPath(dir)), + Name: filepath.Base(dir), + }}) + } + return s.didModifyFiles(ctx, []file.Modification{{ + URI: uri, + Action: file.Open, + Version: params.TextDocument.Version, + Text: []byte(params.TextDocument.Text), + LanguageID: params.TextDocument.LanguageID, + }}, FromDidOpen) +} + +func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error { + ctx, done := event.Start(ctx, "lsp.Server.didChange", label.URI.Of(params.TextDocument.URI)) + defer done() + + uri := params.TextDocument.URI + text, err := s.changedText(ctx, uri, params.ContentChanges) + if err != nil { + return err + } + c := file.Modification{ + URI: uri, + Action: file.Change, + Version: params.TextDocument.Version, + Text: text, + } + if err := s.didModifyFiles(ctx, []file.Modification{c}, FromDidChange); err != nil { + return err + } + return s.warnAboutModifyingGeneratedFiles(ctx, uri) +} + +// warnAboutModifyingGeneratedFiles shows a warning if a user tries to edit a +// generated file for the first time. +func (s *server) warnAboutModifyingGeneratedFiles(ctx context.Context, uri protocol.DocumentURI) error { + s.changedFilesMu.Lock() + _, ok := s.changedFiles[uri] + if !ok { + s.changedFiles[uri] = struct{}{} + } + s.changedFilesMu.Unlock() + + // This file has already been edited before. + if ok { + return nil + } + + // Ideally, we should be able to specify that a generated file should + // be opened as read-only. Tell the user that they should not be + // editing a generated file. + snapshot, release, err := s.session.SnapshotOf(ctx, uri) + if err != nil { + return err + } + isGenerated := golang.IsGenerated(ctx, snapshot, uri) + release() + + if isGenerated { + msg := fmt.Sprintf("Do not edit this file! %s is a generated file.", uri.Path()) + showMessage(ctx, s.client, protocol.Warning, msg) + } + return nil +} + +func (s *server) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error { + ctx, done := event.Start(ctx, "lsp.Server.didChangeWatchedFiles") + defer done() + + var modifications []file.Modification + for _, change := range params.Changes { + action := changeTypeToFileAction(change.Type) + modifications = append(modifications, file.Modification{ + URI: change.URI, + Action: action, + OnDisk: true, + }) + } + return s.didModifyFiles(ctx, modifications, FromDidChangeWatchedFiles) +} + +func (s *server) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error { + ctx, done := event.Start(ctx, "lsp.Server.didSave", label.URI.Of(params.TextDocument.URI)) + defer done() + + c := file.Modification{ + URI: params.TextDocument.URI, + Action: file.Save, + } + if params.Text != nil { + c.Text = []byte(*params.Text) + } + return s.didModifyFiles(ctx, []file.Modification{c}, FromDidSave) +} + +func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error { + ctx, done := event.Start(ctx, "lsp.Server.didClose", label.URI.Of(params.TextDocument.URI)) + defer done() + + return s.didModifyFiles(ctx, []file.Modification{ + { + URI: params.TextDocument.URI, + Action: file.Close, + Version: -1, + Text: nil, + }, + }, FromDidClose) +} + +func (s *server) didModifyFiles(ctx context.Context, modifications []file.Modification, cause ModificationSource) error { + // wg guards two conditions: + // 1. didModifyFiles is complete + // 2. the goroutine diagnosing changes on behalf of didModifyFiles is + // complete, if it was started + // + // Both conditions must be satisfied for the purpose of testing: we don't + // want to observe the completion of change processing until we have received + // all diagnostics as well as all server->client notifications done on behalf + // of this function. + var wg sync.WaitGroup + wg.Add(1) + defer wg.Done() + + if s.Options().VerboseWorkDoneProgress { + work := s.progress.Start(ctx, DiagnosticWorkTitle(cause), "Calculating file diagnostics...", nil, nil) + go func() { + wg.Wait() + work.End(ctx, "Done.") + }() + } + + s.stateMu.Lock() + if s.state >= serverShutDown { + // This state check does not prevent races below, and exists only to + // produce a better error message. The actual race to the cache should be + // guarded by Session.viewMu. + s.stateMu.Unlock() + return errors.New("server is shut down") + } + s.stateMu.Unlock() + + // If the set of changes included directories, expand those directories + // to their files. + modifications = s.session.ExpandModificationsToDirectories(ctx, modifications) + + viewsToDiagnose, err := s.session.DidModifyFiles(ctx, modifications) + if err != nil { + return err + } + + // golang/go#50267: diagnostics should be re-sent after each change. + for _, mod := range modifications { + s.mustPublishDiagnostics(mod.URI) + } + + modCtx, modID := s.needsDiagnosis(ctx, viewsToDiagnose) + + wg.Add(1) + go func() { + s.diagnoseChangedViews(modCtx, modID, viewsToDiagnose, cause) + wg.Done() + }() + + // After any file modifications, we need to update our watched files, + // in case something changed. Compute the new set of directories to watch, + // and if it differs from the current set, send updated registrations. + return s.updateWatchedDirectories(ctx) +} + +// needsDiagnosis records the given views as needing diagnosis, returning the +// context and modification id to use for said diagnosis. +// +// Only the keys of viewsToDiagnose are used; the changed files are irrelevant. +func (s *server) needsDiagnosis(ctx context.Context, viewsToDiagnose map[*cache.View][]protocol.DocumentURI) (context.Context, uint64) { + s.modificationMu.Lock() + defer s.modificationMu.Unlock() + if s.cancelPrevDiagnostics != nil { + s.cancelPrevDiagnostics() + } + modCtx := xcontext.Detach(ctx) + modCtx, s.cancelPrevDiagnostics = context.WithCancel(modCtx) + s.lastModificationID++ + modID := s.lastModificationID + + for v := range viewsToDiagnose { + if needs, ok := s.viewsToDiagnose[v]; !ok || needs < modID { + s.viewsToDiagnose[v] = modID + } + } + return modCtx, modID +} + +// DiagnosticWorkTitle returns the title of the diagnostic work resulting from a +// file change originating from the given cause. +func DiagnosticWorkTitle(cause ModificationSource) string { + return fmt.Sprintf("diagnosing %v", cause) +} + +func (s *server) changedText(ctx context.Context, uri protocol.DocumentURI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) { + if len(changes) == 0 { + return nil, fmt.Errorf("%w: no content changes provided", jsonrpc2.ErrInternal) + } + + // Check if the client sent the full content of the file. + // We accept a full content change even if the server expected incremental changes. + if len(changes) == 1 && changes[0].Range == nil && changes[0].RangeLength == 0 { + changeFull.Inc() + return []byte(changes[0].Text), nil + } + return s.applyIncrementalChanges(ctx, uri, changes) +} + +func (s *server) applyIncrementalChanges(ctx context.Context, uri protocol.DocumentURI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) { + fh, err := s.session.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + content, err := fh.Content() + if err != nil { + return nil, fmt.Errorf("%w: file not found (%v)", jsonrpc2.ErrInternal, err) + } + for i, change := range changes { + // TODO(adonovan): refactor to use diff.Apply, which is robust w.r.t. + // out-of-order or overlapping changes---and much more efficient. + + // Make sure to update mapper along with the content. + m := protocol.NewMapper(uri, content) + if change.Range == nil { + return nil, fmt.Errorf("%w: unexpected nil range for change", jsonrpc2.ErrInternal) + } + start, end, err := m.RangeOffsets(*change.Range) + if err != nil { + return nil, err + } + if end < start { + return nil, fmt.Errorf("%w: invalid range for content change", jsonrpc2.ErrInternal) + } + var buf bytes.Buffer + buf.Write(content[:start]) + buf.WriteString(change.Text) + buf.Write(content[end:]) + content = buf.Bytes() + if i == 0 { // only look at the first change if there are seversl + // TODO(pjw): understand multi-change) + s.checkEfficacy(fh.URI(), fh.Version(), change) + } + } + return content, nil +} + +// increment counters if any of the completions look like there were used +func (s *server) checkEfficacy(uri protocol.DocumentURI, version int32, change protocol.TextDocumentContentChangePartial) { + s.efficacyMu.Lock() + defer s.efficacyMu.Unlock() + if s.efficacyURI != uri { + return + } + // gopls increments the version, the test client does not + if version != s.efficacyVersion && version != s.efficacyVersion+1 { + return + } + // does any change at pos match a proposed completion item? + for _, item := range s.efficacyItems { + if item.TextEdit == nil { + continue + } + // CompletionTextEdit may have both insert/replace mode ranges. + // According to the LSP spec, if an `InsertReplaceEdit` is returned + // the edit's insert range must be a prefix of the edit's replace range, + // that means it must be contained and starting at the same position. + // The efficacy computation uses only the start range, so it is not + // affected by whether the client applied the suggestion in insert + // or replace mode. Let's just use the replace mode that was the default + // in gopls for a while. + edit, err := protocol.SelectCompletionTextEdit(item, false) + if err != nil { + continue + } + if edit.Range.Start == change.Range.Start { + // the change and the proposed completion start at the same + if change.RangeLength == 0 && len(change.Text) == 1 { + // a single character added it does not count as a completion + continue + } + ix := strings.Index(edit.NewText, "$") + if ix < 0 && strings.HasPrefix(change.Text, edit.NewText) { + // not a snippet, suggested completion is a prefix of the change + complUsed.Inc() + return + } + if ix > 1 && strings.HasPrefix(change.Text, edit.NewText[:ix]) { + // a snippet, suggested completion up to $ marker is a prefix of the change + complUsed.Inc() + return + } + } + } + complUnused.Inc() +} + +func changeTypeToFileAction(ct protocol.FileChangeType) file.Action { + switch ct { + case protocol.Changed: + return file.Change + case protocol.Created: + return file.Create + case protocol.Deleted: + return file.Delete + } + return file.UnknownAction +} diff --git a/contribs/gnopls/internal/server/unimplemented.go b/contribs/gnopls/internal/server/unimplemented.go new file mode 100644 index 00000000000..c293ee167a7 --- /dev/null +++ b/contribs/gnopls/internal/server/unimplemented.go @@ -0,0 +1,159 @@ +// 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 server + +// This file defines the LSP server methods that gopls does not currently implement. + +import ( + "context" + "fmt" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/jsonrpc2" +) + +func (s *server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) { + return nil, notImplemented("ColorPresentation") +} + +func (s *server) Declaration(context.Context, *protocol.DeclarationParams) (*protocol.Or_textDocument_declaration, error) { + return nil, notImplemented("Declaration") +} + +func (s *server) Diagnostic(context.Context, *string) (*string, error) { + return nil, notImplemented("Diagnostic") +} + +func (s *server) DiagnosticWorkspace(context.Context, *protocol.WorkspaceDiagnosticParams) (*protocol.WorkspaceDiagnosticReport, error) { + return nil, notImplemented("DiagnosticWorkspace") +} + +func (s *server) DidChangeNotebookDocument(context.Context, *protocol.DidChangeNotebookDocumentParams) error { + return notImplemented("DidChangeNotebookDocument") +} + +func (s *server) DidCloseNotebookDocument(context.Context, *protocol.DidCloseNotebookDocumentParams) error { + return notImplemented("DidCloseNotebookDocument") +} + +func (s *server) DidCreateFiles(context.Context, *protocol.CreateFilesParams) error { + return notImplemented("DidCreateFiles") +} + +func (s *server) DidDeleteFiles(context.Context, *protocol.DeleteFilesParams) error { + return notImplemented("DidDeleteFiles") +} + +func (s *server) DidOpenNotebookDocument(context.Context, *protocol.DidOpenNotebookDocumentParams) error { + return notImplemented("DidOpenNotebookDocument") +} + +func (s *server) DidRenameFiles(context.Context, *protocol.RenameFilesParams) error { + return notImplemented("DidRenameFiles") +} + +func (s *server) DidSaveNotebookDocument(context.Context, *protocol.DidSaveNotebookDocumentParams) error { + return notImplemented("DidSaveNotebookDocument") +} + +func (s *server) DocumentColor(context.Context, *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) { + return nil, notImplemented("DocumentColor") +} + +func (s *server) InlineCompletion(context.Context, *protocol.InlineCompletionParams) (*protocol.Or_Result_textDocument_inlineCompletion, error) { + return nil, notImplemented("InlineCompletion") +} + +func (s *server) InlineValue(context.Context, *protocol.InlineValueParams) ([]protocol.InlineValue, error) { + return nil, notImplemented("InlineValue") +} + +func (s *server) LinkedEditingRange(context.Context, *protocol.LinkedEditingRangeParams) (*protocol.LinkedEditingRanges, error) { + return nil, notImplemented("LinkedEditingRange") +} + +func (s *server) Moniker(context.Context, *protocol.MonikerParams) ([]protocol.Moniker, error) { + return nil, notImplemented("Moniker") +} + +func (s *server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) { + return nil, notImplemented("OnTypeFormatting") +} + +func (s *server) PrepareTypeHierarchy(context.Context, *protocol.TypeHierarchyPrepareParams) ([]protocol.TypeHierarchyItem, error) { + return nil, notImplemented("PrepareTypeHierarchy") +} + +func (s *server) Progress(context.Context, *protocol.ProgressParams) error { + return notImplemented("Progress") +} + +func (s *server) RangeFormatting(context.Context, *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) { + return nil, notImplemented("RangeFormatting") +} + +func (s *server) RangesFormatting(context.Context, *protocol.DocumentRangesFormattingParams) ([]protocol.TextEdit, error) { + return nil, notImplemented("RangesFormatting") +} + +func (s *server) Resolve(context.Context, *protocol.InlayHint) (*protocol.InlayHint, error) { + return nil, notImplemented("Resolve") +} + +func (s *server) ResolveCodeLens(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) { + return nil, notImplemented("ResolveCodeLens") +} + +func (s *server) ResolveCompletionItem(context.Context, *protocol.CompletionItem) (*protocol.CompletionItem, error) { + return nil, notImplemented("ResolveCompletionItem") +} + +func (s *server) ResolveDocumentLink(context.Context, *protocol.DocumentLink) (*protocol.DocumentLink, error) { + return nil, notImplemented("ResolveDocumentLink") +} + +func (s *server) ResolveWorkspaceSymbol(context.Context, *protocol.WorkspaceSymbol) (*protocol.WorkspaceSymbol, error) { + return nil, notImplemented("ResolveWorkspaceSymbol") +} + +func (s *server) SemanticTokensFullDelta(context.Context, *protocol.SemanticTokensDeltaParams) (interface{}, error) { + return nil, notImplemented("SemanticTokensFullDelta") +} + +func (s *server) SetTrace(context.Context, *protocol.SetTraceParams) error { + return notImplemented("SetTrace") +} + +func (s *server) Subtypes(context.Context, *protocol.TypeHierarchySubtypesParams) ([]protocol.TypeHierarchyItem, error) { + return nil, notImplemented("Subtypes") +} + +func (s *server) Supertypes(context.Context, *protocol.TypeHierarchySupertypesParams) ([]protocol.TypeHierarchyItem, error) { + return nil, notImplemented("Supertypes") +} + +func (s *server) WillCreateFiles(context.Context, *protocol.CreateFilesParams) (*protocol.WorkspaceEdit, error) { + return nil, notImplemented("WillCreateFiles") +} + +func (s *server) WillDeleteFiles(context.Context, *protocol.DeleteFilesParams) (*protocol.WorkspaceEdit, error) { + return nil, notImplemented("WillDeleteFiles") +} + +func (s *server) WillRenameFiles(context.Context, *protocol.RenameFilesParams) (*protocol.WorkspaceEdit, error) { + return nil, notImplemented("WillRenameFiles") +} + +func (s *server) WillSave(context.Context, *protocol.WillSaveTextDocumentParams) error { + return notImplemented("WillSave") +} + +func (s *server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) { + return nil, notImplemented("WillSaveWaitUntil") +} + +func notImplemented(method string) error { + return fmt.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method) +} diff --git a/contribs/gnopls/internal/server/workspace.go b/contribs/gnopls/internal/server/workspace.go new file mode 100644 index 00000000000..84e663c1049 --- /dev/null +++ b/contribs/gnopls/internal/server/workspace.go @@ -0,0 +1,141 @@ +// 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. + +package server + +import ( + "context" + "fmt" + "reflect" + "strings" + "sync" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/internal/event" +) + +func (s *server) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { + for _, folder := range params.Event.Removed { + if !strings.HasPrefix(folder.URI, "file://") { + // Some clients that support virtual file systems may send workspace change messages + // about workspace folders in the virtual file systems. addFolders must not add + // those folders, so they don't need to be removed either. + continue + } + dir, err := protocol.ParseDocumentURI(folder.URI) + if err != nil { + return fmt.Errorf("invalid folder %q: %v", folder.URI, err) + } + if !s.session.RemoveView(ctx, dir) { + return fmt.Errorf("view %q for %v not found", folder.Name, folder.URI) + } + } + s.addFolders(ctx, params.Event.Added) + return nil +} + +// addView returns a Snapshot and a release function that must be +// called when it is no longer needed. +func (s *server) addView(ctx context.Context, name string, dir protocol.DocumentURI) (*cache.Snapshot, func(), error) { + s.stateMu.Lock() + state := s.state + s.stateMu.Unlock() + if state < serverInitialized { + return nil, nil, fmt.Errorf("addView called before server initialized") + } + opts, err := s.fetchFolderOptions(ctx, dir) + if err != nil { + return nil, nil, err + } + folder, err := s.newFolder(ctx, dir, name, opts) + if err != nil { + return nil, nil, err + } + _, snapshot, release, err := s.session.NewView(ctx, folder) + return snapshot, release, err +} + +func (s *server) DidChangeConfiguration(ctx context.Context, _ *protocol.DidChangeConfigurationParams) error { + ctx, done := event.Start(ctx, "lsp.Server.didChangeConfiguration") + defer done() + + var wg sync.WaitGroup + wg.Add(1) + defer wg.Done() + if s.Options().VerboseWorkDoneProgress { + work := s.progress.Start(ctx, DiagnosticWorkTitle(FromDidChangeConfiguration), "Calculating diagnostics...", nil, nil) + go func() { + wg.Wait() + work.End(ctx, "Done.") + }() + } + + // Apply any changes to the session-level settings. + options, err := s.fetchFolderOptions(ctx, "") + if err != nil { + return err + } + s.SetOptions(options) + + // Collect options for all workspace folders. + // If none have changed, this is a no op. + folderOpts := make(map[protocol.DocumentURI]*settings.Options) + changed := false + // The set of views is implicitly guarded by the fact that gopls processes + // didChange notifications synchronously. + // + // TODO(rfindley): investigate this assumption: perhaps we should hold viewMu + // here. + views := s.session.Views() + for _, view := range views { + folder := view.Folder() + if folderOpts[folder.Dir] != nil { + continue + } + opts, err := s.fetchFolderOptions(ctx, folder.Dir) + if err != nil { + return err + } + + if !reflect.DeepEqual(folder.Options, opts) { + changed = true + } + folderOpts[folder.Dir] = opts + } + if !changed { + return nil + } + + var newFolders []*cache.Folder + for _, view := range views { + folder := view.Folder() + opts := folderOpts[folder.Dir] + newFolder, err := s.newFolder(ctx, folder.Dir, folder.Name, opts) + if err != nil { + return err + } + newFolders = append(newFolders, newFolder) + } + s.session.UpdateFolders(ctx, newFolders) + + // The view set may have been updated above. + viewsToDiagnose := make(map[*cache.View][]protocol.DocumentURI) + for _, view := range s.session.Views() { + viewsToDiagnose[view] = nil + } + + modCtx, modID := s.needsDiagnosis(ctx, viewsToDiagnose) + wg.Add(1) + go func() { + s.diagnoseChangedViews(modCtx, modID, viewsToDiagnose, FromDidChangeConfiguration) + wg.Done() + }() + + // An options change may have affected the detected Go version. + s.checkViewGoVersions() + + return nil +} diff --git a/contribs/gnopls/internal/server/workspace_symbol.go b/contribs/gnopls/internal/server/workspace_symbol.go new file mode 100644 index 00000000000..9eafeb015ad --- /dev/null +++ b/contribs/gnopls/internal/server/workspace_symbol.go @@ -0,0 +1,41 @@ +// 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 server + +import ( + "context" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/golang" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/telemetry" + "golang.org/x/tools/internal/event" +) + +func (s *server) Symbol(ctx context.Context, params *protocol.WorkspaceSymbolParams) (_ []protocol.SymbolInformation, rerr error) { + recordLatency := telemetry.StartLatencyTimer("symbol") + defer func() { + recordLatency(ctx, rerr) + }() + + ctx, done := event.Start(ctx, "lsp.Server.symbol") + defer done() + + views := s.session.Views() + matcher := s.Options().SymbolMatcher + style := s.Options().SymbolStyle + + var snapshots []*cache.Snapshot + for _, v := range views { + snapshot, release, err := v.Snapshot() + if err != nil { + continue // snapshot is shutting down + } + // If err is non-nil, the snapshot is shutting down. Skip it. + defer release() + snapshots = append(snapshots, snapshot) + } + return golang.WorkspaceSymbols(ctx, matcher, style, snapshots, params.Query) +} diff --git a/contribs/gnopls/internal/settings/analysis.go b/contribs/gnopls/internal/settings/analysis.go new file mode 100644 index 00000000000..65ecb215c02 --- /dev/null +++ b/contribs/gnopls/internal/settings/analysis.go @@ -0,0 +1,218 @@ +// 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 settings + +import ( + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/appends" + "golang.org/x/tools/go/analysis/passes/asmdecl" + "golang.org/x/tools/go/analysis/passes/assign" + "golang.org/x/tools/go/analysis/passes/atomic" + "golang.org/x/tools/go/analysis/passes/atomicalign" + "golang.org/x/tools/go/analysis/passes/bools" + "golang.org/x/tools/go/analysis/passes/buildtag" + "golang.org/x/tools/go/analysis/passes/cgocall" + "golang.org/x/tools/go/analysis/passes/composite" + "golang.org/x/tools/go/analysis/passes/copylock" + "golang.org/x/tools/go/analysis/passes/deepequalerrors" + "golang.org/x/tools/go/analysis/passes/defers" + "golang.org/x/tools/go/analysis/passes/directive" + "golang.org/x/tools/go/analysis/passes/errorsas" + "golang.org/x/tools/go/analysis/passes/framepointer" + "golang.org/x/tools/go/analysis/passes/httpresponse" + "golang.org/x/tools/go/analysis/passes/ifaceassert" + "golang.org/x/tools/go/analysis/passes/loopclosure" + "golang.org/x/tools/go/analysis/passes/lostcancel" + "golang.org/x/tools/go/analysis/passes/nilfunc" + "golang.org/x/tools/go/analysis/passes/nilness" + "golang.org/x/tools/go/analysis/passes/printf" + "golang.org/x/tools/go/analysis/passes/shadow" + "golang.org/x/tools/go/analysis/passes/shift" + "golang.org/x/tools/go/analysis/passes/sigchanyzer" + "golang.org/x/tools/go/analysis/passes/slog" + "golang.org/x/tools/go/analysis/passes/sortslice" + "golang.org/x/tools/go/analysis/passes/stdmethods" + "golang.org/x/tools/go/analysis/passes/stdversion" + "golang.org/x/tools/go/analysis/passes/stringintconv" + "golang.org/x/tools/go/analysis/passes/structtag" + "golang.org/x/tools/go/analysis/passes/testinggoroutine" + "golang.org/x/tools/go/analysis/passes/tests" + "golang.org/x/tools/go/analysis/passes/timeformat" + "golang.org/x/tools/go/analysis/passes/unmarshal" + "golang.org/x/tools/go/analysis/passes/unreachable" + "golang.org/x/tools/go/analysis/passes/unsafeptr" + "golang.org/x/tools/go/analysis/passes/unusedresult" + "golang.org/x/tools/go/analysis/passes/unusedwrite" + "golang.org/x/tools/gopls/internal/analysis/deprecated" + "golang.org/x/tools/gopls/internal/analysis/embeddirective" + "golang.org/x/tools/gopls/internal/analysis/fillreturns" + "golang.org/x/tools/gopls/internal/analysis/infertypeargs" + "golang.org/x/tools/gopls/internal/analysis/nonewvars" + "golang.org/x/tools/gopls/internal/analysis/norangeoverfunc" + "golang.org/x/tools/gopls/internal/analysis/noresultvalues" + "golang.org/x/tools/gopls/internal/analysis/simplifycompositelit" + "golang.org/x/tools/gopls/internal/analysis/simplifyrange" + "golang.org/x/tools/gopls/internal/analysis/simplifyslice" + "golang.org/x/tools/gopls/internal/analysis/stubmethods" + "golang.org/x/tools/gopls/internal/analysis/undeclaredname" + "golang.org/x/tools/gopls/internal/analysis/unusedparams" + "golang.org/x/tools/gopls/internal/analysis/unusedvariable" + "golang.org/x/tools/gopls/internal/analysis/useany" + "golang.org/x/tools/gopls/internal/protocol" + "honnef.co/go/tools/staticcheck" +) + +// Analyzer augments a [analysis.Analyzer] with additional LSP configuration. +// +// Analyzers are immutable, since they are shared across multiple LSP sessions. +type Analyzer struct { + analyzer *analysis.Analyzer + enabled bool + actionKinds []protocol.CodeActionKind + severity protocol.DiagnosticSeverity + tags []protocol.DiagnosticTag +} + +// Analyzer returns the [analysis.Analyzer] that this Analyzer wraps. +func (a *Analyzer) Analyzer() *analysis.Analyzer { return a.analyzer } + +// EnabledByDefault reports whether the analyzer is enabled by default for all sessions. +// This value can be configured per-analysis in user settings. +func (a *Analyzer) EnabledByDefault() bool { return a.enabled } + +// ActionKinds is the set of kinds of code action this analyzer produces. +// +// If left unset, it defaults to QuickFix. +// TODO(rfindley): revisit. +func (a *Analyzer) ActionKinds() []protocol.CodeActionKind { return a.actionKinds } + +// Severity is the severity set for diagnostics reported by this +// analyzer. If left unset it defaults to Warning. +// +// Note: diagnostics with severity protocol.SeverityHint do not show up in +// the VS Code "problems" tab. +func (a *Analyzer) Severity() protocol.DiagnosticSeverity { return a.severity } + +// Tags is extra tags (unnecessary, deprecated, etc) for diagnostics +// reported by this analyzer. +func (a *Analyzer) Tags() []protocol.DiagnosticTag { return a.tags } + +// String returns the name of this analyzer. +func (a *Analyzer) String() string { return a.analyzer.String() } + +// DefaultAnalyzers holds the set of Analyzers available to all gopls sessions, +// independent of build version, keyed by analyzer name. +// +// It is the source from which gopls/doc/analyzers.md is generated. +var DefaultAnalyzers = make(map[string]*Analyzer) // initialized below + +func init() { + // Emergency workaround for #67237 to allow standard library + // to use range over func: disable SSA-based analyses of + // go1.23 packages that use range-over-func. + suppressOnRangeOverFunc := func(a *analysis.Analyzer) { + a.Requires = append(a.Requires, norangeoverfunc.Analyzer) + } + // buildir is non-exported so we have to scan the Analysis.Requires graph to find it. + var buildir *analysis.Analyzer + for _, a := range staticcheck.Analyzers { + for _, req := range a.Analyzer.Requires { + if req.Name == "buildir" { + buildir = req + } + } + + // Temporarily disable SA4004 CheckIneffectiveLoop as + // it crashes when encountering go1.23 range-over-func + // (#67237, dominikh/go-tools#1494). + if a.Analyzer.Name == "SA4004" { + suppressOnRangeOverFunc(a.Analyzer) + } + } + if buildir != nil { + suppressOnRangeOverFunc(buildir) + } + + analyzers := []*Analyzer{ + // The traditional vet suite: + {analyzer: appends.Analyzer, enabled: true}, + {analyzer: asmdecl.Analyzer, enabled: true}, + {analyzer: assign.Analyzer, enabled: true}, + {analyzer: atomic.Analyzer, enabled: true}, + {analyzer: bools.Analyzer, enabled: true}, + {analyzer: buildtag.Analyzer, enabled: true}, + {analyzer: cgocall.Analyzer, enabled: true}, + {analyzer: composite.Analyzer, enabled: true}, + {analyzer: copylock.Analyzer, enabled: true}, + {analyzer: defers.Analyzer, enabled: true}, + {analyzer: deprecated.Analyzer, enabled: true, severity: protocol.SeverityHint, tags: []protocol.DiagnosticTag{protocol.Deprecated}}, + {analyzer: directive.Analyzer, enabled: true}, + {analyzer: errorsas.Analyzer, enabled: true}, + {analyzer: framepointer.Analyzer, enabled: true}, + {analyzer: httpresponse.Analyzer, enabled: true}, + {analyzer: ifaceassert.Analyzer, enabled: true}, + {analyzer: loopclosure.Analyzer, enabled: true}, + {analyzer: lostcancel.Analyzer, enabled: true}, + {analyzer: nilfunc.Analyzer, enabled: true}, + {analyzer: printf.Analyzer, enabled: true}, + {analyzer: shift.Analyzer, enabled: true}, + {analyzer: sigchanyzer.Analyzer, enabled: true}, + {analyzer: slog.Analyzer, enabled: true}, + {analyzer: stdmethods.Analyzer, enabled: true}, + {analyzer: stdversion.Analyzer, enabled: true}, + {analyzer: stringintconv.Analyzer, enabled: true}, + {analyzer: structtag.Analyzer, enabled: true}, + {analyzer: testinggoroutine.Analyzer, enabled: true}, + {analyzer: tests.Analyzer, enabled: true}, + {analyzer: timeformat.Analyzer, enabled: true}, + {analyzer: unmarshal.Analyzer, enabled: true}, + {analyzer: unreachable.Analyzer, enabled: true}, + {analyzer: unsafeptr.Analyzer, enabled: true}, + {analyzer: unusedresult.Analyzer, enabled: true}, + + // not suitable for vet: + // - some (nilness) use go/ssa; see #59714. + // - others don't meet the "frequency" criterion; + // see GOROOT/src/cmd/vet/README. + {analyzer: atomicalign.Analyzer, enabled: true}, + {analyzer: deepequalerrors.Analyzer, enabled: true}, + {analyzer: nilness.Analyzer, enabled: true}, // uses go/ssa + {analyzer: sortslice.Analyzer, enabled: true}, + {analyzer: embeddirective.Analyzer, enabled: true}, + + // disabled due to high false positives + {analyzer: shadow.Analyzer, enabled: false}, // very noisy + {analyzer: useany.Analyzer, enabled: false}, // never a bug + // fieldalignment is not even off-by-default; see #67762. + + // "simplifiers": analyzers that offer mere style fixes + // gofmt -s suite: + {analyzer: simplifycompositelit.Analyzer, enabled: true, actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix}}, + {analyzer: simplifyrange.Analyzer, enabled: true, actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix}}, + {analyzer: simplifyslice.Analyzer, enabled: true, actionKinds: []protocol.CodeActionKind{protocol.SourceFixAll, protocol.QuickFix}}, + // other simplifiers: + {analyzer: infertypeargs.Analyzer, enabled: true, severity: protocol.SeverityHint}, + {analyzer: unusedparams.Analyzer, enabled: true}, + {analyzer: unusedwrite.Analyzer, enabled: true}, // uses go/ssa + + // type-error analyzers + // These analyzers enrich go/types errors with suggested fixes. + {analyzer: fillreturns.Analyzer, enabled: true}, + {analyzer: nonewvars.Analyzer, enabled: true}, + {analyzer: noresultvalues.Analyzer, enabled: true}, + {analyzer: stubmethods.Analyzer, enabled: true}, + {analyzer: undeclaredname.Analyzer, enabled: true}, + // TODO(rfindley): why isn't the 'unusedvariable' analyzer enabled, if it + // is only enhancing type errors with suggested fixes? + // + // In particular, enabling this analyzer could cause unused variables to be + // greyed out, (due to the 'deletions only' fix). That seems like a nice UI + // feature. + {analyzer: unusedvariable.Analyzer, enabled: false}, + } + for _, analyzer := range analyzers { + DefaultAnalyzers[analyzer.analyzer.Name] = analyzer + } +} diff --git a/contribs/gnopls/internal/settings/codeactionkind.go b/contribs/gnopls/internal/settings/codeactionkind.go new file mode 100644 index 00000000000..e8e29d8ddb8 --- /dev/null +++ b/contribs/gnopls/internal/settings/codeactionkind.go @@ -0,0 +1,98 @@ +// 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 settings + +import "golang.org/x/tools/gopls/internal/protocol" + +// This file defines constants for non-standard CodeActions. + +// CodeAction kinds specific to gopls +// +// See ../protocol/tsprotocol.go for LSP standard kinds, including +// +// quickfix +// refactor +// refactor.extract +// refactor.inline +// refactor.move +// refactor.rewrite +// source +// source.organizeImports +// source.fixAll +// notebook +// +// Kinds are hierarchical: "refactor" subsumes "refactor.inline", +// which subsumes "refactor.inline.call". The "Only" field in a +// CodeAction request may specify a category such as "refactor"; any +// matching code action will be returned. +// +// All CodeActions returned by gopls use a specific leaf kind such as +// "refactor.inline.call", except for quick fixes, which all use +// "quickfix". TODO(adonovan): perhaps quick fixes should also be +// hierarchical (e.g. quickfix.govulncheck.{reset,upgrade}). +// +// # VS Code +// +// The effects of CodeActionKind on the behavior of VS Code are +// baffling and undocumented. Here's what we have observed. +// +// Clicking on the "Refactor..." menu item shows a submenu of actions +// with kind="refactor.*", and clicking on "Source action..." shows +// actions with kind="source.*". A lightbulb appears in both cases. +// A third menu, "Quick fix...", not found on the usual context +// menu but accessible through the command palette or "⌘.", +// displays code actions of kind "quickfix.*" and "refactor.*", +// and ad hoc ones ("More actions...") such as "gopls.*". +// All of these CodeAction requests have triggerkind=Invoked. +// +// Cursor motion also performs a CodeAction request, but with +// triggerkind=Automatic. Even if this returns a mix of action kinds, +// only the "refactor" and "quickfix" actions seem to matter. +// A lightbulb appears if that subset of actions is non-empty, and the +// menu displays them. (This was noisy--see #65167--so gopls now only +// reports diagnostic-associated code actions if kind is Invoked or +// missing.) +// +// None of these CodeAction requests specifies a "kind" restriction; +// the filtering is done on the response, by the client. +// +// In all these menus, VS Code organizes the actions' menu items +// into groups based on their kind, with hardwired captions such as +// "Extract", "Inline", "More actions", and "Quick fix". +// +// The special category "source.fixAll" is intended for actions that +// are unambiguously safe to apply so that clients may automatically +// apply all actions matching this category on save. (That said, this +// is not VS Code's default behavior; see editor.codeActionsOnSave.) +const ( + // source + GoAssembly protocol.CodeActionKind = "source.assembly" + GoDoc protocol.CodeActionKind = "source.doc" + GoFreeSymbols protocol.CodeActionKind = "source.freesymbols" + GoTest protocol.CodeActionKind = "source.test" + + // gopls + GoplsDocFeatures protocol.CodeActionKind = "gopls.doc.features" + + // refactor.rewrite + RefactorRewriteChangeQuote protocol.CodeActionKind = "refactor.rewrite.changeQuote" + RefactorRewriteFillStruct protocol.CodeActionKind = "refactor.rewrite.fillStruct" + RefactorRewriteFillSwitch protocol.CodeActionKind = "refactor.rewrite.fillSwitch" + RefactorRewriteInvertIf protocol.CodeActionKind = "refactor.rewrite.invertIf" + RefactorRewriteJoinLines protocol.CodeActionKind = "refactor.rewrite.joinLines" + RefactorRewriteRemoveUnusedParam protocol.CodeActionKind = "refactor.rewrite.removeUnusedParam" + RefactorRewriteSplitLines protocol.CodeActionKind = "refactor.rewrite.splitLines" + + // refactor.inline + RefactorInlineCall protocol.CodeActionKind = "refactor.inline.call" + + // refactor.extract + RefactorExtractFunction protocol.CodeActionKind = "refactor.extract.function" + RefactorExtractMethod protocol.CodeActionKind = "refactor.extract.method" + RefactorExtractVariable protocol.CodeActionKind = "refactor.extract.variable" + RefactorExtractToNewFile protocol.CodeActionKind = "refactor.extract.toNewFile" + + // Note: add new kinds to the SupportedCodeActions map in defaults.go too. +) diff --git a/contribs/gnopls/internal/settings/default.go b/contribs/gnopls/internal/settings/default.go new file mode 100644 index 00000000000..25f3eae80f5 --- /dev/null +++ b/contribs/gnopls/internal/settings/default.go @@ -0,0 +1,149 @@ +// 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 settings + +import ( + "sync" + "time" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" +) + +var ( + optionsOnce sync.Once + defaultOptions *Options +) + +// DefaultOptions is the options that are used for Gopls execution independent +// of any externally provided configuration (LSP initialization, command +// invocation, etc.). +// +// It is the source from which gopls/doc/settings.md is generated. +func DefaultOptions(overrides ...func(*Options)) *Options { + optionsOnce.Do(func() { + var commands []string + for _, c := range command.Commands { + commands = append(commands, c.String()) + } + defaultOptions = &Options{ + ClientOptions: ClientOptions{ + InsertTextFormat: protocol.PlainTextTextFormat, + PreferredContentFormat: protocol.Markdown, + ConfigurationSupported: true, + DynamicConfigurationSupported: true, + DynamicRegistrationSemanticTokensSupported: true, + DynamicWatchedFilesSupported: true, + LineFoldingOnly: false, + HierarchicalDocumentSymbolSupport: true, + }, + ServerOptions: ServerOptions{ + SupportedCodeActions: map[file.Kind]map[protocol.CodeActionKind]bool{ + file.Go: { + // This should include specific leaves in the tree, + // (e.g. refactor.inline.call) not generic branches + // (e.g. refactor.inline or refactor). + protocol.SourceFixAll: true, + protocol.SourceOrganizeImports: true, + protocol.QuickFix: true, + GoAssembly: true, + GoDoc: true, + GoFreeSymbols: true, + GoplsDocFeatures: true, + RefactorRewriteChangeQuote: true, + RefactorRewriteFillStruct: true, + RefactorRewriteFillSwitch: true, + RefactorRewriteInvertIf: true, + RefactorRewriteJoinLines: true, + RefactorRewriteRemoveUnusedParam: true, + RefactorRewriteSplitLines: true, + RefactorInlineCall: true, + RefactorExtractFunction: true, + RefactorExtractMethod: true, + RefactorExtractVariable: true, + RefactorExtractToNewFile: true, + // Not GoTest: it must be explicit in CodeActionParams.Context.Only + }, + file.Mod: { + protocol.SourceOrganizeImports: true, + protocol.QuickFix: true, + }, + file.Work: {}, + file.Sum: {}, + file.Tmpl: {}, + }, + SupportedCommands: commands, + }, + UserOptions: UserOptions{ + BuildOptions: BuildOptions{ + ExpandWorkspaceToModule: true, + DirectoryFilters: []string{"-**/node_modules"}, + TemplateExtensions: []string{}, + StandaloneTags: []string{"ignore"}, + }, + UIOptions: UIOptions{ + DiagnosticOptions: DiagnosticOptions{ + Annotations: map[Annotation]bool{ + Bounds: true, + Escape: true, + Inline: true, + Nil: true, + }, + Vulncheck: ModeVulncheckOff, + DiagnosticsDelay: 1 * time.Second, + DiagnosticsTrigger: DiagnosticsOnEdit, + AnalysisProgressReporting: true, + }, + InlayHintOptions: InlayHintOptions{}, + DocumentationOptions: DocumentationOptions{ + HoverKind: FullDocumentation, + LinkTarget: "pkg.go.dev", + LinksInHover: LinksInHover_LinkTarget, + }, + NavigationOptions: NavigationOptions{ + ImportShortcut: BothShortcuts, + SymbolMatcher: SymbolFastFuzzy, + SymbolStyle: DynamicSymbols, + SymbolScope: AllSymbolScope, + }, + CompletionOptions: CompletionOptions{ + Matcher: Fuzzy, + CompletionBudget: 100 * time.Millisecond, + ExperimentalPostfixCompletions: true, + CompleteFunctionCalls: true, + }, + Codelenses: map[CodeLensSource]bool{ + CodeLensGenerate: true, + CodeLensRegenerateCgo: true, + CodeLensTidy: true, + CodeLensGCDetails: false, + CodeLensUpgradeDependency: true, + CodeLensVendor: true, + CodeLensRunGovulncheck: false, // TODO(hyangah): enable + }, + }, + }, + InternalOptions: InternalOptions{ + CompleteUnimported: true, + CompletionDocumentation: true, + DeepCompletion: true, + SubdirWatchPatterns: SubdirWatchPatternsAuto, + ReportAnalysisProgressAfter: 5 * time.Second, + TelemetryPrompt: false, + LinkifyShowMessage: false, + IncludeReplaceInWorkspace: false, + ZeroConfig: true, + }, + } + }) + options := defaultOptions.Clone() + for _, override := range overrides { + if override != nil { + override(options) + } + } + return options +} diff --git a/contribs/gnopls/internal/settings/settings.go b/contribs/gnopls/internal/settings/settings.go new file mode 100644 index 00000000000..719d0690b5a --- /dev/null +++ b/contribs/gnopls/internal/settings/settings.go @@ -0,0 +1,1435 @@ +// 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. + +package settings + +import ( + "fmt" + "maps" + "path/filepath" + "strings" + "time" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/util/frob" +) + +type Annotation string + +const ( + // Nil controls nil checks. + Nil Annotation = "nil" + + // Escape controls diagnostics about escape choices. + Escape Annotation = "escape" + + // Inline controls diagnostics about inlining choices. + Inline Annotation = "inline" + + // Bounds controls bounds checking diagnostics. + Bounds Annotation = "bounds" +) + +// Options holds various configuration that affects Gopls execution, organized +// by the nature or origin of the settings. +// +// Options must be comparable with reflect.DeepEqual, and serializable with +// [frob.Codec]. +// +// This type defines both the logic of LSP-supplied option parsing +// (see [SetOptions]), and the public documentation of options in +// ../../doc/settings.md (generated by gopls/doc/generate). +// +// Each exported field of each embedded type such as "ClientOptions" +// contributes a user-visible option setting. The option name is the +// field name rendered in camelCase. Unlike most Go doc comments, +// these fields should be documented using GitHub markdown. +type Options struct { + ClientOptions + ServerOptions + UserOptions + InternalOptions +} + +// ClientOptions holds LSP-specific configuration that is provided by the +// client. +// +// ClientOptions must be comparable with reflect.DeepEqual. +type ClientOptions struct { + ClientInfo protocol.ClientInfo + InsertTextFormat protocol.InsertTextFormat + InsertReplaceSupported bool + ConfigurationSupported bool + DynamicConfigurationSupported bool + DynamicRegistrationSemanticTokensSupported bool + DynamicWatchedFilesSupported bool + RelativePatternsSupported bool + PreferredContentFormat protocol.MarkupKind + LineFoldingOnly bool + HierarchicalDocumentSymbolSupport bool + SemanticTypes []string + SemanticMods []string + RelatedInformationSupported bool + CompletionTags bool + CompletionDeprecated bool + SupportedResourceOperations []protocol.ResourceOperationKind + CodeActionResolveOptions []string +} + +// ServerOptions holds LSP-specific configuration that is provided by the +// server. +// +// ServerOptions must be comparable with reflect.DeepEqual. +type ServerOptions struct { + SupportedCodeActions map[file.Kind]map[protocol.CodeActionKind]bool + SupportedCommands []string +} + +// Note: BuildOptions must be comparable with reflect.DeepEqual. +type BuildOptions struct { + // 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`. + BuildFlags []string + + // Env adds environment variables to external commands run by `gopls`, most notably `go list`. + Env map[string]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` + DirectoryFilters []string + + // TemplateExtensions gives the extensions of file names that are treated + // as template files. (The extension + // is the part of the file name after the final dot.) + TemplateExtensions []string + + // obsolete, no effect + MemoryMode string `status:"experimental"` + + // 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. + ExpandWorkspaceToModule bool `status:"experimental"` + + // AllowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module + // downloads rather than requiring user action. This option will eventually + // be removed. + AllowImplicitNetworkAccess bool `status:"experimental"` + + // 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. + StandaloneTags []string +} + +// Note: UIOptions must be comparable with reflect.DeepEqual. +type UIOptions struct { + DocumentationOptions + CompletionOptions + NavigationOptions + DiagnosticOptions + InlayHintOptions + + // 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. + // } + // ... + // } + // ``` + Codelenses map[CodeLensSource]bool + + // SemanticTokens controls whether the LSP server will send + // semantic tokens to the client. + SemanticTokens bool `status:"experimental"` + + // NoSemanticString turns off the sending of the semantic token 'string' + NoSemanticString bool `status:"experimental"` + + // NoSemanticNumber turns off the sending of the semantic token 'number' + NoSemanticNumber bool `status:"experimental"` +} + +// A CodeLensSource identifies an (algorithmic) source of code lenses. +type CodeLensSource string + +// CodeLens sources +// +// These identifiers appear in the "codelenses" configuration setting, +// and in the user documentation thereof, which is generated by +// gopls/doc/generate/generate.go parsing this file. +// +// Doc comments should use GitHub Markdown. +// The first line becomes the title. +// +// (For historical reasons, each code lens source identifier typically +// matches the name of one of the command.Commands returned by it, +// but that isn't essential.) +const ( + // 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..."). + CodeLensGCDetails CodeLensSource = "gc_details" + + // 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. + CodeLensGenerate CodeLensSource = "generate" + + // 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. + CodeLensRegenerateCgo CodeLensSource = "regenerate_cgo" + + // 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. + CodeLensRunGovulncheck CodeLensSource = "run_govulncheck" + + // 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. + CodeLensTest CodeLensSource = "test" + + // 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. + CodeLensTidy CodeLensSource = "tidy" + + // 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. + CodeLensUpgradeDependency CodeLensSource = "upgrade_dependency" + + // 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. + CodeLensVendor CodeLensSource = "vendor" +) + +// Note: CompletionOptions must be comparable with reflect.DeepEqual. +type CompletionOptions struct { + // Placeholders enables placeholders for function parameters or struct + // fields in completion responses. + UsePlaceholders bool + + // 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. + CompletionBudget time.Duration `status:"debug"` + + // Matcher sets the algorithm that is used when calculating completion + // candidates. + Matcher Matcher `status:"advanced"` + + // ExperimentalPostfixCompletions enables artificial method snippets + // such as "someSlice.sort!". + ExperimentalPostfixCompletions bool `status:"experimental"` + + // 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). + CompleteFunctionCalls bool +} + +// Note: DocumentationOptions must be comparable with reflect.DeepEqual. +type DocumentationOptions struct { + // HoverKind controls the information that appears in the hover text. + // SingleLine and Structured are intended for use only by authors of editor plugins. + HoverKind HoverKind + + // LinkTarget is the base URL for links to Go package + // documentation returned by LSP operations such as Hover and + // DocumentLinks and in the CodeDescription field of each + // Diagnostic. + // + // 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. + LinkTarget string + + // LinksInHover controls the presence of documentation links in hover markdown. + LinksInHover LinksInHoverEnum +} + +// LinksInHoverEnum has legal values: +// +// - `false`, for no links; +// - `true`, for links to the `linkTarget` domain; or +// - `"gopls"`, for links to gopls' internal documentation viewer. +// +// Note: this type has special logic in loadEnums in generate.go. +// Be sure to reflect enum and doc changes there! +type LinksInHoverEnum int + +const ( + LinksInHover_None LinksInHoverEnum = iota + LinksInHover_LinkTarget + LinksInHover_Gopls +) + +// MarshalJSON implements the json.Marshaler interface, so that the default +// values are formatted correctly in documentation. (See [Options.setOne] for +// the flexible custom unmarshalling behavior). +func (l LinksInHoverEnum) MarshalJSON() ([]byte, error) { + switch l { + case LinksInHover_None: + return []byte("false"), nil + case LinksInHover_LinkTarget: + return []byte("true"), nil + case LinksInHover_Gopls: + return []byte(`"gopls"`), nil + default: + return nil, fmt.Errorf("invalid LinksInHover value %d", l) + } +} + +// Note: FormattingOptions must be comparable with reflect.DeepEqual. +type FormattingOptions struct { + // 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. + // + // It is used when tidying imports (during an LSP Organize + // Imports request) or when inserting new ones (for example, + // during completion); an LSP Formatting request merely sorts the + // existing imports. + Local string + + // Gofumpt indicates if we should run gofumpt formatting. + Gofumpt bool +} + +// Note: DiagnosticOptions must be comparable with reflect.DeepEqual. +type DiagnosticOptions struct { + // 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. + // } + // ... + // ``` + Analyses map[string]bool + + // Staticcheck enables additional analyses from staticcheck.io. + // These analyses are documented on + // [Staticcheck's website](https://staticcheck.io/docs/checks/). + Staticcheck bool `status:"experimental"` + + // Annotations specifies the various kinds of optimization diagnostics + // that should be reported by the gc_details command. + Annotations map[Annotation]bool `status:"experimental"` + + // Vulncheck enables vulnerability scanning. + Vulncheck VulncheckMode `status:"experimental"` + + // 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"`. + DiagnosticsDelay time.Duration `status:"advanced"` + + // DiagnosticsTrigger controls when to run diagnostics. + DiagnosticsTrigger DiagnosticsTrigger `status:"experimental"` + + // 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. + AnalysisProgressReporting bool +} + +type InlayHintOptions struct { + // 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). + Hints map[InlayHint]bool `status:"experimental"` +} + +// An InlayHint identifies a category of hint that may be +// independently requested through the "hints" setting. +type InlayHint string + +// This is the source from which gopls/doc/inlayHints.md is generated. +const ( + // ParameterNames controls inlay hints for parameter names: + // ```go + // parseInt(/* str: */ "123", /* radix: */ 8) + // ``` + ParameterNames InlayHint = "parameterNames" + + // AssignVariableTypes controls inlay hints for variable types in assign statements: + // ```go + // i/* int*/, j/* int*/ := 0, len(r)-1 + // ``` + AssignVariableTypes InlayHint = "assignVariableTypes" + + // ConstantValues controls inlay hints for constant values: + // ```go + // const ( + // KindNone Kind = iota/* = 0*/ + // KindPrint/* = 1*/ + // KindPrintf/* = 2*/ + // KindErrorf/* = 3*/ + // ) + // ``` + ConstantValues InlayHint = "constantValues" + + // RangeVariableTypes controls inlay hints for variable types in range statements: + // ```go + // for k/* int*/, v/* string*/ := range []string{} { + // fmt.Println(k, v) + // } + // ``` + RangeVariableTypes InlayHint = "rangeVariableTypes" + + // CompositeLiteralTypes controls inlay hints for composite literal types: + // ```go + // for _, c := range []struct { + // in, want string + // }{ + // /*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"}, + // } + // ``` + CompositeLiteralTypes InlayHint = "compositeLiteralTypes" + + // CompositeLiteralFieldNames inlay hints for composite literal field names: + // ```go + // {/*in: */"Hello, world", /*want: */"dlrow ,olleH"} + // ``` + CompositeLiteralFieldNames InlayHint = "compositeLiteralFields" + + // FunctionTypeParameters inlay hints for implicit type parameters on generic functions: + // ```go + // myFoo/*[int, string]*/(1, "hello") + // ``` + FunctionTypeParameters InlayHint = "functionTypeParameters" +) + +type NavigationOptions struct { + // ImportShortcut specifies whether import statements should link to + // documentation or go to definitions. + ImportShortcut ImportShortcut + + // SymbolMatcher sets the algorithm that is used when finding workspace symbols. + SymbolMatcher SymbolMatcher `status:"advanced"` + + // SymbolStyle controls how symbols are qualified in symbol responses. + // + // Example Usage: + // + // ```json5 + // "gopls": { + // ... + // "symbolStyle": "Dynamic", + // ... + // } + // ``` + SymbolStyle SymbolStyle `status:"advanced"` + + // 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. + SymbolScope SymbolScope +} + +// UserOptions holds custom Gopls configuration (not part of the LSP) that is +// modified by the client. +// +// UserOptions must be comparable with reflect.DeepEqual. +type UserOptions struct { + BuildOptions + UIOptions + FormattingOptions + + // VerboseOutput enables additional debug logging. + VerboseOutput bool `status:"debug"` +} + +// EnvSlice returns Env as a slice of k=v strings. +func (u *UserOptions) EnvSlice() []string { + var result []string + for k, v := range u.Env { + result = append(result, fmt.Sprintf("%v=%v", k, v)) + } + return result +} + +// SetEnvSlice sets Env from a slice of k=v strings. +func (u *UserOptions) SetEnvSlice(env []string) { + u.Env = map[string]string{} + for _, kv := range env { + split := strings.SplitN(kv, "=", 2) + if len(split) != 2 { + continue + } + u.Env[split[0]] = split[1] + } +} + +// InternalOptions contains settings that are not intended for use by the +// average user. These may be settings used by tests or outdated settings that +// will soon be deprecated. Some of these settings may not even be configurable +// by the user. +// +// TODO(rfindley): even though these settings are not intended for +// modification, some of them should be surfaced in our documentation. +type InternalOptions struct { + // VerboseWorkDoneProgress controls whether the LSP server should send + // progress reports for all work done outside the scope of an RPC. + // Used by the regression tests. + VerboseWorkDoneProgress bool + + // The following options were previously available to users, but they + // really shouldn't be configured by anyone other than "power users". + + // CompletionDocumentation enables documentation with completion results. + CompletionDocumentation bool + + // CompleteUnimported enables completion for packages that you do not + // currently import. + CompleteUnimported bool + + // DeepCompletion enables the ability to return completions from deep + // inside relevant entities, rather than just the locally accessible ones. + // + // Consider this example: + // + // ```go + // package main + // + // import "fmt" + // + // type wrapString struct { + // str string + // } + // + // func main() { + // x := wrapString{"hello world"} + // fmt.Printf(<>) + // } + // ``` + // + // At the location of the `<>` in this program, deep completion would suggest + // the result `x.str`. + DeepCompletion bool + + // ShowBugReports causes a message to be shown when the first bug is reported + // on the server. + // This option applies only during initialization. + ShowBugReports bool + + // SubdirWatchPatterns configures the file watching glob patterns registered + // by gopls. + // + // Some clients (namely VS Code) do not send workspace/didChangeWatchedFile + // notifications for files contained in a directory when that directory is + // deleted: + // https://github.com/microsoft/vscode/issues/109754 + // + // In this case, gopls would miss important notifications about deleted + // packages. To work around this, gopls registers a watch pattern for each + // directory containing Go files. + // + // Unfortunately, other clients experience performance problems with this + // many watch patterns, so there is no single behavior that works well for + // all clients. + // + // The "subdirWatchPatterns" setting allows configuring this behavior. Its + // default value of "auto" attempts to guess the correct behavior based on + // the client name. We'd love to avoid this specialization, but as described + // above there is no single value that works for all clients. + // + // If any LSP client does not behave well with the default value (for + // example, if like VS Code it drops file notifications), please file an + // issue. + SubdirWatchPatterns SubdirWatchPatterns + + // ReportAnalysisProgressAfter sets the duration for gopls to wait before starting + // progress reporting for ongoing go/analysis passes. + // + // It is intended to be used for testing only. + ReportAnalysisProgressAfter time.Duration + + // TelemetryPrompt controls whether gopls prompts about enabling Go telemetry. + // + // Once the prompt is answered, gopls doesn't ask again, but TelemetryPrompt + // can prevent the question from ever being asked in the first place. + TelemetryPrompt bool + + // LinkifyShowMessage controls whether the client wants gopls + // to linkify links in showMessage. e.g. [go.dev](https://go.dev). + LinkifyShowMessage bool + + // IncludeReplaceInWorkspace controls whether locally replaced modules in a + // go.mod file are treated like workspace modules. + // Or in other words, if a go.mod file with local replaces behaves like a + // go.work file. + IncludeReplaceInWorkspace bool + + // ZeroConfig enables the zero-config algorithm for workspace layout, + // dynamically creating build configurations for different modules, + // directories, and GOOS/GOARCH combinations to cover open files. + ZeroConfig bool +} + +type SubdirWatchPatterns string + +const ( + SubdirWatchPatternsOn SubdirWatchPatterns = "on" + SubdirWatchPatternsOff SubdirWatchPatterns = "off" + SubdirWatchPatternsAuto SubdirWatchPatterns = "auto" +) + +type ImportShortcut string + +const ( + BothShortcuts ImportShortcut = "Both" + LinkShortcut ImportShortcut = "Link" + DefinitionShortcut ImportShortcut = "Definition" +) + +func (s ImportShortcut) ShowLinks() bool { + return s == BothShortcuts || s == LinkShortcut +} + +func (s ImportShortcut) ShowDefinition() bool { + return s == BothShortcuts || s == DefinitionShortcut +} + +type Matcher string + +const ( + Fuzzy Matcher = "Fuzzy" + CaseInsensitive Matcher = "CaseInsensitive" + CaseSensitive Matcher = "CaseSensitive" +) + +// A SymbolMatcher controls the matching of symbols for workspace/symbol +// requests. +type SymbolMatcher string + +const ( + SymbolFuzzy SymbolMatcher = "Fuzzy" + SymbolFastFuzzy SymbolMatcher = "FastFuzzy" + SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive" + SymbolCaseSensitive SymbolMatcher = "CaseSensitive" +) + +// A SymbolStyle controls the formatting of symbols in workspace/symbol results. +type SymbolStyle string + +const ( + // PackageQualifiedSymbols is package qualified symbols i.e. + // "pkg.Foo.Field". + PackageQualifiedSymbols SymbolStyle = "Package" + // FullyQualifiedSymbols is fully qualified symbols, i.e. + // "path/to/pkg.Foo.Field". + FullyQualifiedSymbols SymbolStyle = "Full" + // DynamicSymbols 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". + DynamicSymbols SymbolStyle = "Dynamic" +) + +// A SymbolScope controls the search scope for workspace/symbol requests. +type SymbolScope string + +const ( + // WorkspaceSymbolScope matches symbols in workspace packages only. + WorkspaceSymbolScope SymbolScope = "workspace" + // AllSymbolScope matches symbols in any loaded package, including + // dependencies. + AllSymbolScope SymbolScope = "all" +) + +type HoverKind string + +const ( + SingleLine HoverKind = "SingleLine" + NoDocumentation HoverKind = "NoDocumentation" + SynopsisDocumentation HoverKind = "SynopsisDocumentation" + FullDocumentation HoverKind = "FullDocumentation" + + // 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. + Structured HoverKind = "Structured" +) + +type VulncheckMode string + +const ( + // Disable vulnerability analysis. + ModeVulncheckOff VulncheckMode = "Off" + // In Imports mode, `gopls` will report vulnerabilities that affect packages + // directly and indirectly used by the analyzed main module. + ModeVulncheckImports VulncheckMode = "Imports" + + // TODO: VulncheckRequire, VulncheckCallgraph +) + +type DiagnosticsTrigger string + +const ( + // Trigger diagnostics on file edit and save. (default) + DiagnosticsOnEdit DiagnosticsTrigger = "Edit" + // Trigger diagnostics only on file save. Events like initial workspace load + // or configuration change will still trigger diagnostics. + DiagnosticsOnSave DiagnosticsTrigger = "Save" + // TODO: support "Manual"? +) + +// Set updates *options based on the provided JSON value: +// null, bool, string, number, array, or object. +// On failure, it returns one or more non-nil errors. +func (o *Options) Set(value any) (errors []error) { + switch value := value.(type) { + case nil: + case map[string]any: + seen := make(map[string]struct{}) + for name, value := range value { + // Use only the last segment of a dotted name such as + // ui.navigation.symbolMatcher. The other segments + // are discarded, even without validation (!). + // (They are supported to enable hierarchical names + // in the VS Code graphical configuration UI.) + split := strings.Split(name, ".") + name = split[len(split)-1] + + if _, ok := seen[name]; ok { + errors = append(errors, fmt.Errorf("duplicate value for %s", name)) + } + seen[name] = struct{}{} + + if err := o.setOne(name, value); err != nil { + err := fmt.Errorf("setting option %q: %w", name, err) + errors = append(errors, err) + } + } + default: + errors = append(errors, fmt.Errorf("invalid options type %T (want JSON null or object)", value)) + } + return errors +} + +func (o *Options) ForClientCapabilities(clientInfo *protocol.ClientInfo, caps protocol.ClientCapabilities) { + if clientInfo != nil { + o.ClientInfo = *clientInfo + } + if caps.Workspace.WorkspaceEdit != nil { + o.SupportedResourceOperations = caps.Workspace.WorkspaceEdit.ResourceOperations + } + // Check if the client supports snippets in completion items. + if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport { + o.InsertTextFormat = protocol.SnippetTextFormat + } + o.InsertReplaceSupported = caps.TextDocument.Completion.CompletionItem.InsertReplaceSupport + // Check if the client supports configuration messages. + o.ConfigurationSupported = caps.Workspace.Configuration + o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration + o.DynamicRegistrationSemanticTokensSupported = caps.TextDocument.SemanticTokens.DynamicRegistration + o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration + o.RelativePatternsSupported = caps.Workspace.DidChangeWatchedFiles.RelativePatternSupport + + // Check which types of content format are supported by this client. + if hover := caps.TextDocument.Hover; hover != nil && len(hover.ContentFormat) > 0 { + o.PreferredContentFormat = hover.ContentFormat[0] + } + // Check if the client supports only line folding. + + if fr := caps.TextDocument.FoldingRange; fr != nil { + o.LineFoldingOnly = fr.LineFoldingOnly + } + // Check if the client supports hierarchical document symbols. + o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport + + // Client's semantic tokens + o.SemanticTypes = caps.TextDocument.SemanticTokens.TokenTypes + o.SemanticMods = caps.TextDocument.SemanticTokens.TokenModifiers + // we don't need Requests, as we support full functionality + // we don't need Formats, as there is only one, for now + + // Check if the client supports diagnostic related information. + o.RelatedInformationSupported = caps.TextDocument.PublishDiagnostics.RelatedInformation + // Check if the client completion support includes tags (preferred) or deprecation + if caps.TextDocument.Completion.CompletionItem.TagSupport != nil && + caps.TextDocument.Completion.CompletionItem.TagSupport.ValueSet != nil { + o.CompletionTags = true + } else if caps.TextDocument.Completion.CompletionItem.DeprecatedSupport { + o.CompletionDeprecated = true + } + + // Check if the client supports code actions resolving. + if caps.TextDocument.CodeAction.DataSupport && caps.TextDocument.CodeAction.ResolveSupport != nil { + o.CodeActionResolveOptions = caps.TextDocument.CodeAction.ResolveSupport.Properties + } +} + +var codec = frob.CodecFor[*Options]() + +func (o *Options) Clone() *Options { + data := codec.Encode(o) + var clone *Options + codec.Decode(data, &clone) + return clone +} + +// validateDirectoryFilter validates if the filter string +// - is not empty +// - start with either + or - +// - doesn't contain currently unsupported glob operators: *, ? +func validateDirectoryFilter(ifilter string) (string, error) { + filter := fmt.Sprint(ifilter) + if filter == "" || (filter[0] != '+' && filter[0] != '-') { + return "", fmt.Errorf("invalid filter %v, must start with + or -", filter) + } + segs := strings.Split(filter[1:], "/") + unsupportedOps := [...]string{"?", "*"} + for _, seg := range segs { + if seg != "**" { + for _, op := range unsupportedOps { + if strings.Contains(seg, op) { + return "", fmt.Errorf("invalid filter %v, operator %v not supported. If you want to have this operator supported, consider filing an issue.", filter, op) + } + } + } + } + + return strings.TrimRight(filepath.FromSlash(filter), "/"), nil +} + +// setOne updates a field of o based on the name and value. +// It returns an error if the value was invalid or duplicate. +// It is the caller's responsibility to augment the error with 'name'. +func (o *Options) setOne(name string, value any) error { + switch name { + case "env": + env, ok := value.(map[string]any) + if !ok { + return fmt.Errorf("invalid type %T (want JSON object)", value) + } + if o.Env == nil { + o.Env = make(map[string]string) + } + for k, v := range env { + // For historic compatibility, we accept int too (e.g. CGO_ENABLED=1). + switch v.(type) { + case string, int: + o.Env[k] = fmt.Sprint(v) + default: + return fmt.Errorf("invalid map value %T (want string)", v) + } + } + + case "buildFlags": + return setStringSlice(&o.BuildFlags, value) + + case "directoryFilters": + filterStrings, err := asStringSlice(value) + if err != nil { + return err + } + var filters []string + for _, filterStr := range filterStrings { + filter, err := validateDirectoryFilter(filterStr) + if err != nil { + return err + } + filters = append(filters, strings.TrimRight(filepath.FromSlash(filter), "/")) + } + o.DirectoryFilters = filters + + case "completionDocumentation": + return setBool(&o.CompletionDocumentation, value) + case "usePlaceholders": + return setBool(&o.UsePlaceholders, value) + case "deepCompletion": + return setBool(&o.DeepCompletion, value) + case "completeUnimported": + return setBool(&o.CompleteUnimported, value) + case "completionBudget": + return setDuration(&o.CompletionBudget, value) + case "matcher": + return setEnum(&o.Matcher, value, + Fuzzy, + CaseSensitive, + CaseInsensitive) + + case "symbolMatcher": + return setEnum(&o.SymbolMatcher, value, + SymbolFuzzy, + SymbolFastFuzzy, + SymbolCaseInsensitive, + SymbolCaseSensitive) + + case "symbolStyle": + return setEnum(&o.SymbolStyle, value, + FullyQualifiedSymbols, + PackageQualifiedSymbols, + DynamicSymbols) + + case "symbolScope": + return setEnum(&o.SymbolScope, value, + WorkspaceSymbolScope, + AllSymbolScope) + + case "hoverKind": + return setEnum(&o.HoverKind, value, + NoDocumentation, + SingleLine, + SynopsisDocumentation, + FullDocumentation, + Structured) + + case "linkTarget": + return setString(&o.LinkTarget, value) + + case "linksInHover": + switch value { + case false: + o.LinksInHover = LinksInHover_None + case true: + o.LinksInHover = LinksInHover_LinkTarget + case "gopls": + o.LinksInHover = LinksInHover_Gopls + default: + return fmt.Errorf(`invalid value %s; expect false, true, or "gopls"`, + value) + } + + case "importShortcut": + return setEnum(&o.ImportShortcut, value, + BothShortcuts, + LinkShortcut, + DefinitionShortcut) + + case "analyses": + if err := setBoolMap(&o.Analyses, value); err != nil { + return err + } + if o.Analyses["fieldalignment"] { + return deprecatedError("the 'fieldalignment' analyzer was removed in gopls/v0.17.0; instead, hover over struct fields to see size/offset information (https://go.dev/issue/66861)") + } + + case "hints": + return setBoolMap(&o.Hints, value) + + case "annotations": + return setAnnotationMap(&o.Annotations, value) + + case "vulncheck": + return setEnum(&o.Vulncheck, value, + ModeVulncheckOff, + ModeVulncheckImports) + + case "codelenses", "codelens": + lensOverrides, err := asBoolMap[CodeLensSource](value) + if err != nil { + return err + } + if o.Codelenses == nil { + o.Codelenses = make(map[CodeLensSource]bool) + } + o.Codelenses = maps.Clone(o.Codelenses) + for source, enabled := range lensOverrides { + o.Codelenses[source] = enabled + } + + if name == "codelens" { + return deprecatedError("codelenses") + } + + case "staticcheck": + return setBool(&o.Staticcheck, value) + + case "local": + return setString(&o.Local, value) + + case "verboseOutput": + return setBool(&o.VerboseOutput, value) + + case "verboseWorkDoneProgress": + return setBool(&o.VerboseWorkDoneProgress, value) + + case "showBugReports": + return setBool(&o.ShowBugReports, value) + + case "gofumpt": + return setBool(&o.Gofumpt, value) + + case "completeFunctionCalls": + return setBool(&o.CompleteFunctionCalls, value) + + case "semanticTokens": + return setBool(&o.SemanticTokens, value) + + case "noSemanticString": + return setBool(&o.NoSemanticString, value) + + case "noSemanticNumber": + return setBool(&o.NoSemanticNumber, value) + + case "expandWorkspaceToModule": + // See golang/go#63536: we can consider deprecating + // expandWorkspaceToModule, but probably need to change the default + // behavior in that case to *not* expand to the module. + return setBool(&o.ExpandWorkspaceToModule, value) + + case "experimentalPostfixCompletions": + return setBool(&o.ExperimentalPostfixCompletions, value) + + case "templateExtensions": + switch value := value.(type) { + case []any: + return setStringSlice(&o.TemplateExtensions, value) + case nil: + o.TemplateExtensions = nil + default: + return fmt.Errorf("unexpected type %T (want JSON array of string)", value) + } + + case "diagnosticsDelay": + return setDuration(&o.DiagnosticsDelay, value) + + case "diagnosticsTrigger": + return setEnum(&o.DiagnosticsTrigger, value, + DiagnosticsOnEdit, + DiagnosticsOnSave) + + case "analysisProgressReporting": + return setBool(&o.AnalysisProgressReporting, value) + + case "allowImplicitNetworkAccess": + if err := setBool(&o.AllowImplicitNetworkAccess, value); err != nil { + return err + } + return softErrorf("gopls setting \"allowImplicitNetworkAccess\" is deprecated.\nPlease comment on https://go.dev/issue/66861 if this impacts your workflow.") + + case "standaloneTags": + return setStringSlice(&o.StandaloneTags, value) + + case "subdirWatchPatterns": + return setEnum(&o.SubdirWatchPatterns, value, + SubdirWatchPatternsOn, + SubdirWatchPatternsOff, + SubdirWatchPatternsAuto) + + case "reportAnalysisProgressAfter": + return setDuration(&o.ReportAnalysisProgressAfter, value) + + case "telemetryPrompt": + return setBool(&o.TelemetryPrompt, value) + + case "linkifyShowMessage": + return setBool(&o.LinkifyShowMessage, value) + + case "includeReplaceInWorkspace": + return setBool(&o.IncludeReplaceInWorkspace, value) + + case "zeroConfig": + return setBool(&o.ZeroConfig, value) + + // deprecated and renamed settings + // + // These should never be deleted: there is essentially no cost + // to providing a better error message indefinitely; it's not + // as if we would ever want to recycle the name of a setting. + + // renamed + case "experimentalDisabledAnalyses": + return deprecatedError("analyses") + + case "disableDeepCompletion": + return deprecatedError("deepCompletion") + + case "disableFuzzyMatching": + return deprecatedError("fuzzyMatching") + + case "wantCompletionDocumentation": + return deprecatedError("completionDocumentation") + + case "wantUnimportedCompletions": + return deprecatedError("completeUnimported") + + case "fuzzyMatching": + return deprecatedError("matcher") + + case "caseSensitiveCompletion": + return deprecatedError("matcher") + + case "experimentalDiagnosticsDelay": + return deprecatedError("diagnosticsDelay") + + // deprecated + case "memoryMode": + return deprecatedError("") + + case "tempModFile": + return deprecatedError("") + + case "experimentalWorkspaceModule": + return deprecatedError("") + + case "experimentalTemplateSupport": + return deprecatedError("") + + case "experimentalWatchedFileDelay": + return deprecatedError("") + + case "experimentalPackageCacheKey": + return deprecatedError("") + + case "allowModfileModifications": + return deprecatedError("") + + case "allExperiments": + // golang/go#65548: this setting is a no-op, but we fail don't report it as + // deprecated, since the nightly VS Code injects it. + // + // If, in the future, VS Code stops injecting this, we could theoretically + // report an error here, but it also seems harmless to keep ignoring this + // setting forever. + + case "experimentalUseInvalidMetadata": + return deprecatedError("") + + case "newDiff": + return deprecatedError("") + + case "wantSuggestedFixes": + return deprecatedError("") + + case "noIncrementalSync": + return deprecatedError("") + + case "watchFileChanges": + return deprecatedError("") + + case "go-diff": + return deprecatedError("") + + default: + return fmt.Errorf("unexpected setting") + } + return nil +} + +// A SoftError is an error that does not affect the functionality of gopls. +type SoftError struct { + msg string +} + +func (e *SoftError) Error() string { + return e.msg +} + +// softErrorf reports a soft error related to the current option. +func softErrorf(format string, args ...any) error { + return &SoftError{fmt.Sprintf(format, args...)} +} + +// deprecatedError reports the current setting as deprecated. +// The optional replacement is suggested to the user. +func deprecatedError(replacement string) error { + msg := fmt.Sprintf("this setting is deprecated") + if replacement != "" { + msg = fmt.Sprintf("%s, use %q instead", msg, replacement) + } + return &SoftError{msg} +} + +// setT() and asT() helpers: the setT forms write to the 'dest *T' +// variable only on success, to reduce boilerplate in Option.set. + +func setBool(dest *bool, value any) error { + b, err := asBool(value) + if err != nil { + return err + } + *dest = b + return nil +} + +func asBool(value any) (bool, error) { + b, ok := value.(bool) + if !ok { + return false, fmt.Errorf("invalid type %T (want bool)", value) + } + return b, nil +} + +func setDuration(dest *time.Duration, value any) error { + str, err := asString(value) + if err != nil { + return err + } + parsed, err := time.ParseDuration(str) + if err != nil { + return err + } + *dest = parsed + return nil +} + +func setAnnotationMap(dest *map[Annotation]bool, value any) error { + all, err := asBoolMap[string](value) + if err != nil { + return err + } + if all == nil { + return nil + } + // Default to everything enabled by default. + m := make(map[Annotation]bool) + for k, enabled := range all { + var a Annotation + if err := setEnum(&a, k, + Nil, + Escape, + Inline, + Bounds); err != nil { + // In case of an error, process any legacy values. + switch k { + case "noEscape": + m[Escape] = false + return fmt.Errorf(`"noEscape" is deprecated, set "Escape: false" instead`) + case "noNilcheck": + m[Nil] = false + return fmt.Errorf(`"noNilcheck" is deprecated, set "Nil: false" instead`) + + case "noInline": + m[Inline] = false + return fmt.Errorf(`"noInline" is deprecated, set "Inline: false" instead`) + case "noBounds": + m[Bounds] = false + return fmt.Errorf(`"noBounds" is deprecated, set "Bounds: false" instead`) + default: + return err + } + } + m[a] = enabled + } + *dest = m + return nil +} + +func setBoolMap[K ~string](dest *map[K]bool, value any) error { + m, err := asBoolMap[K](value) + if err != nil { + return err + } + *dest = m + return nil +} + +func asBoolMap[K ~string](value any) (map[K]bool, error) { + all, ok := value.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid type %T (want JSON object)", value) + } + m := make(map[K]bool) + for a, enabled := range all { + b, ok := enabled.(bool) + if !ok { + return nil, fmt.Errorf("invalid type %T for object field %q", enabled, a) + } + m[K(a)] = b + } + return m, nil +} + +func setString(dest *string, value any) error { + str, err := asString(value) + if err != nil { + return err + } + *dest = str + return nil +} + +func asString(value any) (string, error) { + str, ok := value.(string) + if !ok { + return "", fmt.Errorf("invalid type %T (want string)", value) + } + return str, nil +} + +func setStringSlice(dest *[]string, value any) error { + slice, err := asStringSlice(value) + if err != nil { + return err + } + *dest = slice + return nil +} + +func asStringSlice(value any) ([]string, error) { + array, ok := value.([]any) + if !ok { + return nil, fmt.Errorf("invalid type %T (want JSON array of string)", value) + } + var slice []string + for _, elem := range array { + str, ok := elem.(string) + if !ok { + return nil, fmt.Errorf("invalid array element type %T (want string)", elem) + } + slice = append(slice, str) + } + return slice, nil +} + +func setEnum[S ~string](dest *S, value any, options ...S) error { + enum, err := asEnum(value, options...) + if err != nil { + return err + } + *dest = enum + return nil +} + +func asEnum[S ~string](value any, options ...S) (S, error) { + str, err := asString(value) + if err != nil { + return "", err + } + for _, opt := range options { + if strings.EqualFold(str, string(opt)) { + return opt, nil + } + } + return "", fmt.Errorf("invalid option %q for enum", str) +} diff --git a/contribs/gnopls/internal/settings/settings_test.go b/contribs/gnopls/internal/settings/settings_test.go new file mode 100644 index 00000000000..6f865083a9d --- /dev/null +++ b/contribs/gnopls/internal/settings/settings_test.go @@ -0,0 +1,242 @@ +// 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 settings_test + +import ( + "reflect" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/clonetest" + . "golang.org/x/tools/gopls/internal/settings" +) + +func TestDefaultsEquivalence(t *testing.T) { + opts1 := DefaultOptions() + opts2 := DefaultOptions() + if !reflect.DeepEqual(opts1, opts2) { + t.Fatal("default options are not equivalent using reflect.DeepEqual") + } +} + +func TestOptions_Set(t *testing.T) { + type testCase struct { + name string + value any + wantError bool + check func(Options) bool + } + tests := []testCase{ + { + name: "symbolStyle", + value: "Dynamic", + check: func(o Options) bool { return o.SymbolStyle == DynamicSymbols }, + }, + { + name: "symbolStyle", + value: "", + wantError: true, + check: func(o Options) bool { return o.SymbolStyle == "" }, + }, + { + name: "symbolStyle", + value: false, + wantError: true, + check: func(o Options) bool { return o.SymbolStyle == "" }, + }, + { + name: "symbolMatcher", + value: "caseInsensitive", + check: func(o Options) bool { return o.SymbolMatcher == SymbolCaseInsensitive }, + }, + { + name: "completionBudget", + value: "2s", + check: func(o Options) bool { return o.CompletionBudget == 2*time.Second }, + }, + { + name: "codelenses", + value: map[string]any{"generate": true}, + check: func(o Options) bool { return o.Codelenses["generate"] }, + }, + { + name: "allExperiments", + value: true, + check: func(o Options) bool { + return true // just confirm that we handle this setting + }, + }, + { + name: "hoverKind", + value: "FullDocumentation", + check: func(o Options) bool { + return o.HoverKind == FullDocumentation + }, + }, + { + name: "hoverKind", + value: "NoDocumentation", + check: func(o Options) bool { + return o.HoverKind == NoDocumentation + }, + }, + { + name: "hoverKind", + value: "SingleLine", + check: func(o Options) bool { + return o.HoverKind == SingleLine + }, + }, + { + name: "hoverKind", + value: "Structured", + check: func(o Options) bool { + return o.HoverKind == Structured + }, + }, + { + name: "ui.documentation.hoverKind", + value: "Structured", + check: func(o Options) bool { + return o.HoverKind == Structured + }, + }, + { + name: "matcher", + value: "Fuzzy", + check: func(o Options) bool { + return o.Matcher == Fuzzy + }, + }, + { + name: "matcher", + value: "CaseSensitive", + check: func(o Options) bool { + return o.Matcher == CaseSensitive + }, + }, + { + name: "matcher", + value: "CaseInsensitive", + check: func(o Options) bool { + return o.Matcher == CaseInsensitive + }, + }, + { + name: "env", + value: map[string]any{"testing": "true"}, + check: func(o Options) bool { + v, found := o.Env["testing"] + return found && v == "true" + }, + }, + { + name: "env", + value: []string{"invalid", "input"}, + wantError: true, + check: func(o Options) bool { + return o.Env == nil + }, + }, + { + name: "directoryFilters", + value: []any{"-node_modules", "+project_a"}, + check: func(o Options) bool { + return len(o.DirectoryFilters) == 2 + }, + }, + { + name: "directoryFilters", + value: []any{"invalid"}, + wantError: true, + check: func(o Options) bool { + return len(o.DirectoryFilters) == 0 + }, + }, + { + name: "directoryFilters", + value: []string{"-invalid", "+type"}, + wantError: true, + check: func(o Options) bool { + return len(o.DirectoryFilters) == 0 + }, + }, + { + name: "annotations", + value: map[string]any{ + "Nil": false, + "noBounds": true, + }, + wantError: true, + check: func(o Options) bool { + return !o.Annotations[Nil] && !o.Annotations[Bounds] + }, + }, + { + name: "vulncheck", + value: []any{"invalid"}, + wantError: true, + check: func(o Options) bool { + return o.Vulncheck == "" // For invalid value, default to 'off'. + }, + }, + { + name: "vulncheck", + value: "Imports", + check: func(o Options) bool { + return o.Vulncheck == ModeVulncheckImports // For invalid value, default to 'off'. + }, + }, + { + name: "vulncheck", + value: "imports", + check: func(o Options) bool { + return o.Vulncheck == ModeVulncheckImports + }, + }, + } + + for _, test := range tests { + var opts Options + err := opts.Set(map[string]any{test.name: test.value}) + if err != nil { + if !test.wantError { + t.Errorf("Options.set(%q, %v) failed: %v", + test.name, test.value, err) + } + continue + } else if test.wantError { + t.Fatalf("Options.set(%q, %v) succeeded unexpectedly", + test.name, test.value) + } + + // TODO: this could be made much better using cmp.Diff, if that becomes + // available in this module. + if !test.check(opts) { + t.Errorf("Options.set(%q, %v): unexpected result %+v", test.name, test.value, opts) + } + } +} + +func TestOptions_Clone(t *testing.T) { + // Test that the Options.Clone actually performs a deep clone of the Options + // struct. + + golden := clonetest.NonZero[*Options]() + opts := clonetest.NonZero[*Options]() + opts2 := opts.Clone() + + // The clone should be equivalent to the original. + if diff := cmp.Diff(golden, opts2); diff != "" { + t.Errorf("Clone() does not match original (-want +got):\n%s", diff) + } + + // Mutating the clone should not mutate the original. + clonetest.ZeroOut(opts2) + if diff := cmp.Diff(golden, opts); diff != "" { + t.Errorf("Mutating clone mutated the original (-want +got):\n%s", diff) + } +} diff --git a/contribs/gnopls/internal/settings/staticcheck.go b/contribs/gnopls/internal/settings/staticcheck.go new file mode 100644 index 00000000000..fca3e55f17e --- /dev/null +++ b/contribs/gnopls/internal/settings/staticcheck.go @@ -0,0 +1,63 @@ +// 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. + +package settings + +import ( + "golang.org/x/tools/gopls/internal/protocol" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/quickfix" + "honnef.co/go/tools/simple" + "honnef.co/go/tools/staticcheck" + "honnef.co/go/tools/stylecheck" +) + +// StaticcheckAnalzyers describes available Staticcheck analyzers, keyed by +// analyzer name. +var StaticcheckAnalyzers = make(map[string]*Analyzer) // written by analysis_.go + +func init() { + mapSeverity := func(severity lint.Severity) protocol.DiagnosticSeverity { + switch severity { + case lint.SeverityError: + return protocol.SeverityError + case lint.SeverityDeprecated: + // TODO(dh): in LSP, deprecated is a tag, not a severity. + // We'll want to support this once we enable SA5011. + return protocol.SeverityWarning + case lint.SeverityWarning: + return protocol.SeverityWarning + case lint.SeverityInfo: + return protocol.SeverityInformation + case lint.SeverityHint: + return protocol.SeverityHint + default: + return protocol.SeverityWarning + } + } + add := func(analyzers []*lint.Analyzer, skip map[string]struct{}) { + for _, a := range analyzers { + if _, ok := skip[a.Analyzer.Name]; ok { + continue + } + + StaticcheckAnalyzers[a.Analyzer.Name] = &Analyzer{ + analyzer: a.Analyzer, + enabled: !a.Doc.NonDefault, + severity: mapSeverity(a.Doc.Severity), + } + } + } + + add(simple.Analyzers, nil) + add(staticcheck.Analyzers, map[string]struct{}{ + // This check conflicts with the vet printf check (golang/go#34494). + "SA5009": {}, + // This check relies on facts from dependencies, which + // we don't currently compute. + "SA5011": {}, + }) + add(stylecheck.Analyzers, nil) + add(quickfix.Analyzers, nil) +} diff --git a/contribs/gnopls/internal/settings/vet_test.go b/contribs/gnopls/internal/settings/vet_test.go new file mode 100644 index 00000000000..56daf678c43 --- /dev/null +++ b/contribs/gnopls/internal/settings/vet_test.go @@ -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 settings_test + +import ( + "encoding/json" + "fmt" + "os/exec" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/doc" + "golang.org/x/tools/internal/testenv" +) + +// TestVetSuite ensures that gopls's analyser suite is a superset of vet's. +// +// This test may fail spuriously if gopls/doc/generate.TestGenerated +// fails. In that case retry after re-running the JSON generator. +func TestVetSuite(t *testing.T) { + testenv.NeedsTool(t, "go") + + // Read gopls' suite from the API JSON. + goplsAnalyzers := make(map[string]bool) + var api doc.API + if err := json.Unmarshal([]byte(doc.JSON), &api); err != nil { + t.Fatal(err) + } + for _, a := range api.Analyzers { + goplsAnalyzers[a.Name] = true + } + + // Read vet's suite by parsing its help message. + cmd := exec.Command("go", "tool", "vet", "help") + cmd.Stdout = new(strings.Builder) + if err := cmd.Run(); err != nil { + t.Fatalf("failed to run vet: %v", err) + } + out := fmt.Sprint(cmd.Stdout) + _, out, _ = strings.Cut(out, "Registered analyzers:\n\n") + out, _, _ = strings.Cut(out, "\n\n") + for _, line := range strings.Split(out, "\n") { + name := strings.Fields(line)[0] + if !goplsAnalyzers[name] { + t.Errorf("gopls lacks vet analyzer %q", name) + } + } +} diff --git a/contribs/gnopls/internal/telemetry/cmd/stacks/stacks.go b/contribs/gnopls/internal/telemetry/cmd/stacks/stacks.go new file mode 100644 index 00000000000..746b0ff68ef --- /dev/null +++ b/contribs/gnopls/internal/telemetry/cmd/stacks/stacks.go @@ -0,0 +1,970 @@ +// 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. + +//go:build linux || darwin + +// The stacks command finds all gopls stack traces reported by +// telemetry in the past 7 days, and reports their associated GitHub +// issue, creating new issues as needed. +// +// The association of stacks with GitHub issues (labelled +// gopls/telemetry-wins) is represented in two different ways by the +// body (first comment) of the issue: +// +// 1. Each distinct stack is identified by an ID, 6-digit base64 +// string such as "TwtkSg". If a stack's ID appears anywhere +// within the issue body, the stack is associated with the issue. +// +// Some problems are highly deterministic, resulting in many +// field reports of the exact same stack. For such problems, a +// single ID in the issue body suffices to record the +// association. But most problems are exhibited in a variety of +// ways, leading to multiple field reports of similar but +// distinct stacks. +// +// 2. Each GitHub issue body may start with a code block of this form: +// +// ``` +// #!stacks +// "runtime.sigpanic" && "golang.hover:+170" +// ``` +// +// The first line indicates the purpose of the block; the +// remainder is a predicate that matches stacks. +// It is an expression defined by this grammar: +// +// > expr = "string literal" +// > | ( expr ) +// > | ! expr +// > | expr && expr +// > | expr || expr +// +// Each string literal implies a substring match on the stack; +// the other productions are boolean operations. +// +// The stacks command gathers all such predicates out of the +// labelled issues and evaluates each one against each new stack. +// If the predicate for an issue matches, the issue is considered +// to have "claimed" the stack: the stack command appends a +// comment containing the new (variant) stack to the issue, and +// appends the stack's ID to the last line of the issue body. +// +// It is an error if two issues' predicates attempt to claim the +// same stack. +package main + +// TODO(adonovan): create a proper package with tests. Much of this +// machinery might find wider use in other x/telemetry clients. + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "hash/fnv" + "io" + "log" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "time" + "unicode" + + "golang.org/x/sys/unix" + "golang.org/x/telemetry" + "golang.org/x/tools/gopls/internal/util/browser" + "golang.org/x/tools/gopls/internal/util/moremaps" +) + +// flags +var ( + daysFlag = flag.Int("days", 7, "number of previous days of telemetry data to read") + + authToken string // mandatory GitHub authentication token (for R/W issues access) +) + +func main() { + log.SetFlags(0) + log.SetPrefix("stacks: ") + flag.Parse() + + // Read GitHub authentication token from $HOME/.stacks.token. + // + // You can create one using the flow at: GitHub > You > Settings > + // Developer Settings > Personal Access Tokens > Fine-grained tokens > + // Generate New Token. Generate the token on behalf of golang/go + // with R/W access to "Issues". + // The token is typically of the form "github_pat_XXX", with 82 hex digits. + // Save it in the file, with mode 0400. + // + // For security, secret tokens should be read from files, not + // command-line flags or environment variables. + { + home, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } + tokenFile := filepath.Join(home, ".stacks.token") + content, err := os.ReadFile(tokenFile) + if err != nil { + if !os.IsNotExist(err) { + log.Fatalf("cannot read GitHub authentication token: %v", err) + } + log.Fatalf("no file %s containing GitHub authentication token.", tokenFile) + } + authToken = string(bytes.TrimSpace(content)) + } + + // Maps stack text to Info to count. + stacks := make(map[string]map[Info]int64) + var distinctStacks int + + // Maps stack to a telemetry URL. + stackToURL := make(map[string]string) + + // Read all recent telemetry reports. + t := time.Now() + for i := 0; i < *daysFlag; i++ { + date := t.Add(-time.Duration(i+1) * 24 * time.Hour).Format(time.DateOnly) + + url := fmt.Sprintf("https://storage.googleapis.com/prod-telemetry-merged/%s.json", date) + resp, err := http.Get(url) + if err != nil { + log.Fatalf("can't GET %s: %v", url, err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + log.Fatalf("GET %s returned %d %s", url, resp.StatusCode, resp.Status) + } + + dec := json.NewDecoder(resp.Body) + for { + var report telemetry.Report + if err := dec.Decode(&report); err != nil { + if err == io.EOF { + break + } + log.Fatal(err) + } + for _, prog := range report.Programs { + if prog.Program == "golang.org/x/tools/gopls" && len(prog.Stacks) > 0 { + // Include applicable client names (e.g. vscode, eglot). + var clients []string + var clientSuffix string + for key := range prog.Counters { + client := strings.TrimPrefix(key, "gopls/client:") + if client != key { + clients = append(clients, client) + } + } + sort.Strings(clients) + if len(clients) > 0 { + clientSuffix = strings.Join(clients, ",") + } + + // Ignore @devel versions as they correspond to + // ephemeral (and often numerous) variations of + // the program as we work on a fix to a bug. + if prog.Version == "devel" { + continue + } + + distinctStacks++ + + info := Info{ + Program: prog.Program, + Version: prog.Version, + GoVersion: prog.GoVersion, + GOOS: prog.GOOS, + GOARCH: prog.GOARCH, + Client: clientSuffix, + } + for stack, count := range prog.Stacks { + counts := stacks[stack] + if counts == nil { + counts = make(map[Info]int64) + stacks[stack] = counts + } + counts[info] += count + stackToURL[stack] = url + } + } + } + } + } + + // Query GitHub for all existing GitHub issues with label:gopls/telemetry-wins. + // + // TODO(adonovan): by default GitHub returns at most 30 + // issues; we have lifted this to 100 using per_page=%d, but + // that won't work forever; use paging. + const query = "is:issue label:gopls/telemetry-wins" + res, err := searchIssues(query) + if err != nil { + log.Fatalf("GitHub issues query %q failed: %v", query, err) + } + + // Extract and validate predicate expressions in ```#!stacks...``` code blocks. + // See the package doc comment for the grammar. + for _, issue := range res.Items { + block := findPredicateBlock(issue.Body) + if block != "" { + expr, err := parser.ParseExpr(block) + if err != nil { + log.Printf("invalid predicate in issue #%d: %v\n<<%s>>", + issue.Number, err, block) + continue + } + var validate func(ast.Expr) error + validate = func(e ast.Expr) error { + switch e := e.(type) { + case *ast.UnaryExpr: + if e.Op != token.NOT { + return fmt.Errorf("invalid op: %s", e.Op) + } + return validate(e.X) + + case *ast.BinaryExpr: + if e.Op != token.LAND && e.Op != token.LOR { + return fmt.Errorf("invalid op: %s", e.Op) + } + if err := validate(e.X); err != nil { + return err + } + return validate(e.Y) + + case *ast.ParenExpr: + return validate(e.X) + + case *ast.BasicLit: + if e.Kind != token.STRING { + return fmt.Errorf("invalid literal (%s)", e.Kind) + } + if _, err := strconv.Unquote(e.Value); err != nil { + return err + } + + default: + return fmt.Errorf("syntax error (%T)", e) + } + return nil + } + if err := validate(expr); err != nil { + log.Printf("invalid predicate in issue #%d: %v\n<<%s>>", + issue.Number, err, block) + continue + } + issue.predicateText = block + issue.predicate = func(stack string) bool { + var eval func(ast.Expr) bool + eval = func(e ast.Expr) bool { + switch e := e.(type) { + case *ast.UnaryExpr: + return !eval(e.X) + + case *ast.BinaryExpr: + if e.Op == token.LAND { + return eval(e.X) && eval(e.Y) + } else { + return eval(e.X) || eval(e.Y) + } + + case *ast.ParenExpr: + return eval(e.X) + + case *ast.BasicLit: + substr, _ := strconv.Unquote(e.Value) + return strings.Contains(stack, substr) + } + panic("unreachable") + } + return eval(expr) + } + } + } + + // Map each stack ID to its issue. + // + // An issue can claim a stack two ways: + // + // 1. if the issue body contains the ID of the stack. Matching + // is a little loose but base64 will rarely produce words + // that appear in the body by chance. + // + // 2. if the issue body contains a ```#!stacks``` predicate + // that matches the stack. + // + // We report an error if two different issues attempt to claim + // the same stack. + // + // This is O(new stacks x existing issues). + claimedBy := make(map[string]*Issue) + for stack := range stacks { + id := stackID(stack) + for _, issue := range res.Items { + byPredicate := false + if strings.Contains(issue.Body, id) { + // nop + } else if issue.predicate != nil && issue.predicate(stack) { + byPredicate = true + } else { + continue + } + + if prev := claimedBy[id]; prev != nil && prev != issue { + log.Printf("stack %s is claimed by issues #%d and #%d", + id, prev.Number, issue.Number) + continue + } + if false { + log.Printf("stack %s claimed by issue #%d", + id, issue.Number) + } + claimedBy[id] = issue + if byPredicate { + // The stack ID matched the predicate but was not + // found in the issue body, so this is a new stack. + issue.newStacks = append(issue.newStacks, stack) + } + } + } + + // For each stack, show existing issue or create a new one. + // Aggregate stack IDs by issue summary. + var ( + // Both vars map the summary line to the stack count. + existingIssues = make(map[string]int64) + newIssues = make(map[string]int64) + ) + for stack, counts := range stacks { + id := stackID(stack) + + var total int64 + for _, count := range counts { + total += count + } + + if issue, ok := claimedBy[id]; ok { + // existing issue + summary := fmt.Sprintf("#%d: %s [%s]", + issue.Number, issue.Title, issue.State) + existingIssues[summary] += total + } else { + // new issue + title := newIssue(stack, id, stackToURL[stack], counts) + summary := fmt.Sprintf("%s: %s [%s]", id, title, "new") + newIssues[summary] += total + } + } + + // Update existing issues that claimed new stacks by predicate. + for _, issue := range res.Items { + if len(issue.newStacks) == 0 { + continue + } + + // Add a comment to the existing issue listing all its new stacks. + // (Save the ID of each stack for the second step.) + comment := new(bytes.Buffer) + var newStackIDs []string + for _, stack := range issue.newStacks { + id := stackID(stack) + newStackIDs = append(newStackIDs, id) + writeStackComment(comment, stack, id, stackToURL[stack], stacks[stack]) + } + if err := addIssueComment(issue.Number, comment.String()); err != nil { + log.Println(err) + continue + } + log.Printf("added comment to issue #%d", issue.Number) + + // Append to the "Dups: ID ..." list on last line of issue body. + body := strings.TrimSpace(issue.Body) + lastLineStart := strings.LastIndexByte(body, '\n') + 1 + lastLine := body[lastLineStart:] + if !strings.HasPrefix(lastLine, "Dups:") { + body += "\nDups:" + } + body += " " + strings.Join(newStackIDs, " ") + if err := updateIssueBody(issue.Number, body); err != nil { + log.Println(err) + } + log.Printf("updated body of issue #%d", issue.Number) + } + + fmt.Printf("Found %d distinct stacks in last %v days:\n", distinctStacks, *daysFlag) + print := func(caption string, issues map[string]int64) { + // Print items in descending frequency. + keys := moremaps.KeySlice(issues) + sort.Slice(keys, func(i, j int) bool { + return issues[keys[i]] > issues[keys[j]] + }) + fmt.Printf("%s issues:\n", caption) + for _, summary := range keys { + count := issues[summary] + // Show closed issues in "white". + if isTerminal(os.Stdout) && strings.Contains(summary, "[closed]") { + // ESC + "[" + n + "m" => change color to n + // (37 = white, 0 = default) + summary = "\x1B[37m" + summary + "\x1B[0m" + } + fmt.Printf("%s (n=%d)\n", summary, count) + } + } + print("Existing", existingIssues) + print("New", newIssues) +} + +// Info is used as a key for de-duping and aggregating. +// Do not add detail about particular records (e.g. data, telemetry URL). +type Info struct { + Program string // "golang.org/x/tools/gopls" + Version, GoVersion string // e.g. "gopls/v0.16.1", "go1.23" + GOOS, GOARCH string + Client string // e.g. "vscode" +} + +func (info Info) String() string { + return fmt.Sprintf("%s@%s %s %s/%s %s", + info.Program, info.Version, + info.GoVersion, info.GOOS, info.GOARCH, + info.Client) +} + +// stackID returns a 32-bit identifier for a stack +// suitable for use in GitHub issue titles. +func stackID(stack string) string { + // Encode it using base64 (6 bytes) for brevity, + // as a single issue's body might contain multiple IDs + // if separate issues with same cause were manually de-duped, + // e.g. "AAAAAA, BBBBBB" + // + // https://hbfs.wordpress.com/2012/03/30/finding-collisions: + // the chance of a collision is 1 - exp(-n(n-1)/2d) where n + // is the number of items and d is the number of distinct values. + // So, even with n=10^4 telemetry-reported stacks each identified + // by a uint32 (d=2^32), we have a 1% chance of a collision, + // which is plenty good enough. + h := fnv.New32() + io.WriteString(h, stack) + return base64.URLEncoding.EncodeToString(h.Sum(nil))[:6] +} + +// newIssue creates a browser tab with a populated GitHub "New issue" +// form for the specified stack. (The triage person is expected to +// manually de-dup the issue before deciding whether to submit the form.) +// +// It returns the title. +func newIssue(stack, id string, jsonURL string, counts map[Info]int64) string { + // Use a heuristic to find a suitable symbol to blame + // in the title: the first public function or method + // of a public type, in gopls, to appear in the stack + // trace. We can always refine it later. + // + // TODO(adonovan): include in the issue a source snippet ±5 + // lines around the PC in this symbol. + var symbol string + for _, line := range strings.Split(stack, "\n") { + // Look for: + // gopls/.../pkg.Func + // gopls/.../pkg.Type.method + // gopls/.../pkg.(*Type).method + if strings.Contains(line, "internal/util/bug.") { + continue // not interesting + } + if _, rest, ok := strings.Cut(line, "golang.org/x/tools/gopls/"); ok { + if i := strings.IndexByte(rest, '.'); i >= 0 { + rest = rest[i+1:] + rest = strings.TrimPrefix(rest, "(*") + if rest != "" && 'A' <= rest[0] && rest[0] <= 'Z' { + rest, _, _ = strings.Cut(rest, ":") + symbol = " " + rest + break + } + } + } + } + + // Populate the form (title, body, label) + title := fmt.Sprintf("x/tools/gopls: bug in %s", symbol) + + body := new(bytes.Buffer) + + // Add a placeholder ```#!stacks``` block since this is a new issue. + body.WriteString("```" + ` +#!stacks +"" +` + "```\n") + fmt.Fprintf(body, "Issue created by [stacks](https://pkg.go.dev/golang.org/x/tools/gopls/internal/telemetry/cmd/stacks).\n\n") + + writeStackComment(body, stack, id, jsonURL, counts) + + const labels = "gopls,Tools,gopls/telemetry-wins,NeedsInvestigation" + + // Report it. The user will interactively finish the task, + // since they will typically de-dup it without even creating a new issue + // by expanding the #!stacks predicate of an existing issue. + if !browser.Open("https://github.com/golang/go/issues/new?labels=" + labels + "&title=" + url.QueryEscape(title) + "&body=" + url.QueryEscape(body.String())) { + log.Print("Please file a new issue at golang.org/issue/new using this template:\n\n") + log.Printf("Title: %s\n", title) + log.Printf("Labels: %s\n", labels) + log.Printf("Body: %s\n", body) + } + + return title +} + +// writeStackComment writes a stack in Markdown form, for a new GitHub +// issue or new comment on an existing one. +func writeStackComment(body *bytes.Buffer, stack, id string, jsonURL string, counts map[Info]int64) { + if len(counts) == 0 { + panic("no counts") + } + var info Info // pick an arbitrary key + for info = range counts { + break + } + + fmt.Fprintf(body, "This stack `%s` was [reported by telemetry](%s):\n\n", + id, jsonURL) + + // Read the mapping from symbols to file/line. + pclntab, err := readPCLineTable(info) + if err != nil { + log.Fatal(err) + } + + // Parse the stack and get the symbol names out. + for _, frame := range strings.Split(stack, "\n") { + if url := frameURL(pclntab, info, frame); url != "" { + fmt.Fprintf(body, "- [`%s`](%s)\n", frame, url) + } else { + fmt.Fprintf(body, "- `%s`\n", frame) + } + } + + // Add counts, gopls version, and platform info. + // This isn't very precise but should provide clues. + fmt.Fprintf(body, "```\n") + for info, count := range counts { + fmt.Fprintf(body, "%s (%d)\n", info, count) + } + fmt.Fprintf(body, "```\n\n") +} + +// frameURL returns the CodeSearch URL for the stack frame, if known. +func frameURL(pclntab map[string]FileLine, info Info, frame string) string { + // e.g. "golang.org/x/tools/gopls/foo.(*Type).Method.inlined.func3:+5" + symbol, offset, ok := strings.Cut(frame, ":") + if !ok { + // Not a symbol (perhaps stack counter title: "gopls/bug"?) + return "" + } + + fileline, ok := pclntab[symbol] + if !ok { + // objdump reports ELF symbol names, which in + // rare cases may be the Go symbols of + // runtime.CallersFrames mangled by (e.g.) the + // addition of .abi0 suffix; see + // https://github.com/golang/go/issues/69390#issuecomment-2343795920 + // So this should not be a hard error. + if symbol != "runtime.goexit" { + log.Printf("no pclntab info for symbol: %s", symbol) + } + return "" + } + + if offset == "" { + log.Fatalf("missing line offset: %s", frame) + } + if unicode.IsDigit(rune(offset[0])) { + // Fix gopls/v0.14.2 legacy syntax ":%d" -> ":+%d". + offset = "+" + offset + } + offsetNum, err := strconv.Atoi(offset[1:]) + if err != nil { + log.Fatalf("invalid line offset: %s", frame) + } + linenum := fileline.line + switch offset[0] { + case '-': + linenum -= offsetNum + case '+': + linenum += offsetNum + case '=': + linenum = offsetNum + } + + // Construct CodeSearch URL. + + // std module? + firstSegment, _, _ := strings.Cut(fileline.file, "/") + if !strings.Contains(firstSegment, ".") { + // (First segment is a dir beneath GOROOT/src, not a module domain name.) + return fmt.Sprintf("https://cs.opensource.google/go/go/+/%s:src/%s;l=%d", + info.GoVersion, fileline.file, linenum) + } + + // x/tools repo (tools or gopls module)? + if rest, ok := strings.CutPrefix(fileline.file, "golang.org/x/tools"); ok { + if rest[0] == '/' { + // "golang.org/x/tools/gopls" -> "gopls" + rest = rest[1:] + } else if rest[0] == '@' { + // "golang.org/x/tools@version/dir/file.go" -> "dir/file.go" + rest = rest[strings.Index(rest, "/")+1:] + } + + return fmt.Sprintf("https://cs.opensource.google/go/x/tools/+/%s:%s;l=%d", + "gopls/"+info.Version, rest, linenum) + } + + // other x/ module dependency? + // e.g. golang.org/x/sync@v0.8.0/errgroup/errgroup.go + if rest, ok := strings.CutPrefix(fileline.file, "golang.org/x/"); ok { + if modVer, filename, ok := strings.Cut(rest, "/"); ok { + if mod, version, ok := strings.Cut(modVer, "@"); ok { + return fmt.Sprintf("https://cs.opensource.google/go/x/%s/+/%s:%s;l=%d", + mod, version, filename, linenum) + } + } + } + + log.Printf("no CodeSearch URL for %q (%s:%d)", + symbol, fileline.file, linenum) + return "" +} + +// -- GitHub search -- + +// searchIssues queries the GitHub issue tracker. +func searchIssues(query string) (*IssuesSearchResult, error) { + q := url.QueryEscape(query) + + req, err := http.NewRequest("GET", "https://api.github.com/search/issues?q="+q+"&per_page=100", nil) + if err != nil { + return nil, err + } + if authToken != "" { + req.Header.Add("Authorization", "Bearer "+authToken) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("search query failed: %s (body: %s)", resp.Status, body) + } + var result IssuesSearchResult + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + return &result, nil +} + +// updateIssueBody updates the body of the numbered issue. +func updateIssueBody(number int, body string) error { + // https://docs.github.com/en/rest/issues/comments#update-an-issue + var payload struct { + Body string `json:"body"` + } + payload.Body = body + data, err := json.Marshal(payload) + if err != nil { + return err + } + + url := fmt.Sprintf("https://api.github.com/repos/golang/go/issues/%d", number) + req, err := http.NewRequest("PATCH", url, bytes.NewReader(data)) + if err != nil { + return err + } + if authToken != "" { + req.Header.Add("Authorization", "Bearer "+authToken) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("issue update failed: %s (body: %s)", resp.Status, body) + } + return nil +} + +// addIssueComment adds a markdown comment to the numbered issue. +func addIssueComment(number int, comment string) error { + // https://docs.github.com/en/rest/issues/comments#create-an-issue-comment + var payload struct { + Body string `json:"body"` + } + payload.Body = comment + data, err := json.Marshal(payload) + if err != nil { + return err + } + + url := fmt.Sprintf("https://api.github.com/repos/golang/go/issues/%d/comments", number) + req, err := http.NewRequest("POST", url, bytes.NewReader(data)) + if err != nil { + return err + } + if authToken != "" { + req.Header.Add("Authorization", "Bearer "+authToken) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("failed to create issue comment: %s (body: %s)", resp.Status, body) + } + return nil +} + +// See https://developer.github.com/v3/search/#search-issues. + +type IssuesSearchResult struct { + TotalCount int `json:"total_count"` + Items []*Issue +} + +type Issue struct { + Number int + HTMLURL string `json:"html_url"` + Title string + State string + User *User + CreatedAt time.Time `json:"created_at"` + Body string // in Markdown format + + predicateText string // text of ```#!stacks...``` predicate block + predicate func(string) bool // matching predicate over stack text + newStacks []string // new stacks to add to existing issue (comments and IDs) +} + +type User struct { + Login string + HTMLURL string `json:"html_url"` +} + +// -- helpers -- + +func min(x, y int) int { + if x < y { + return x + } else { + return y + } +} + +// -- pclntab -- + +type FileLine struct { + file string // "module@version/dir/file.go" or path relative to $GOROOT/src + line int +} + +// readPCLineTable builds the gopls executable specified by info, +// reads its PC-to-line-number table, and returns the file/line of +// each TEXT symbol. +func readPCLineTable(info Info) (map[string]FileLine, error) { + // The stacks dir will be a semi-durable temp directory + // (i.e. lasts for at least hours) holding source trees + // and executables we have build recently. + // + // Each subdir will hold a specific revision. + stacksDir := "/tmp/gopls-stacks" + if err := os.MkdirAll(stacksDir, 0777); err != nil { + return nil, fmt.Errorf("can't create stacks dir: %v", err) + } + + // Fetch the source for the tools repo, + // shallow-cloning just the desired revision. + // (Skip if it's already cloned.) + revDir := filepath.Join(stacksDir, info.Version) + if !fileExists(revDir) { + log.Printf("cloning tools@gopls/%s", info.Version) + if err := shallowClone(revDir, "https://go.googlesource.com/tools", "gopls/"+info.Version); err != nil { + _ = os.RemoveAll(revDir) // ignore errors + return nil, fmt.Errorf("clone: %v", err) + } + } + + // Build the executable with the correct GOTOOLCHAIN, GOOS, GOARCH. + // Use -trimpath for normalized file names. + // (Skip if it's already built.) + exe := fmt.Sprintf("exe-%s.%s-%s", info.GoVersion, info.GOOS, info.GOARCH) + cmd := exec.Command("go", "build", "-trimpath", "-o", "../"+exe) + cmd.Dir = filepath.Join(revDir, "gopls") + cmd.Env = append(os.Environ(), + "GOTOOLCHAIN="+info.GoVersion, + "GOOS="+info.GOOS, + "GOARCH="+info.GOARCH, + ) + if !fileExists(filepath.Join(revDir, exe)) { + log.Printf("building %s@%s with %s on %s/%s", + info.Program, info.Version, info.GoVersion, info.GOOS, info.GOARCH) + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("building: %v", err) + } + } + + // Read pclntab of executable. + cmd = exec.Command("go", "tool", "objdump", exe) + cmd.Stdout = new(strings.Builder) + cmd.Stderr = os.Stderr + cmd.Dir = revDir + cmd.Env = append(os.Environ(), + "GOTOOLCHAIN="+info.GoVersion, + "GOOS="+info.GOOS, + "GOARCH="+info.GOARCH, + ) + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("reading pclntab %v", err) + } + pclntab := make(map[string]FileLine) + lines := strings.Split(fmt.Sprint(cmd.Stdout), "\n") + for i, line := range lines { + // Each function is of this form: + // + // TEXT symbol(SB) filename + // basename.go:line instruction + // ... + if !strings.HasPrefix(line, "TEXT ") { + continue + } + fields := strings.Fields(line) + if len(fields) != 3 { + continue // symbol without file (e.g. go:buildid) + } + + symbol := strings.TrimSuffix(fields[1], "(SB)") + + filename := fields[2] + + _, line, ok := strings.Cut(strings.Fields(lines[i+1])[0], ":") + if !ok { + return nil, fmt.Errorf("can't parse 'basename.go:line' from first instruction of %s:\n%s", + symbol, line) + } + linenum, err := strconv.Atoi(line) + if err != nil { + return nil, fmt.Errorf("can't parse line number of %s: %s", symbol, line) + } + pclntab[symbol] = FileLine{filename, linenum} + } + + return pclntab, nil +} + +// shallowClone performs a shallow clone of repo into dir at the given +// 'commitish' ref (any commit reference understood by git). +// +// The directory dir must not already exist. +func shallowClone(dir, repo, commitish string) error { + if err := os.Mkdir(dir, 0750); err != nil { + return fmt.Errorf("creating dir for %s: %v", repo, err) + } + + // Set a timeout for git fetch. If this proves flaky, it can be removed. + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + // Use a shallow fetch to download just the relevant commit. + shInit := fmt.Sprintf("git init && git fetch --depth=1 %q %q && git checkout FETCH_HEAD", repo, commitish) + initCmd := exec.CommandContext(ctx, "/bin/sh", "-c", shInit) + initCmd.Dir = dir + if output, err := initCmd.CombinedOutput(); err != nil { + return fmt.Errorf("checking out %s: %v\n%s", repo, err, output) + } + return nil +} + +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +// findPredicateBlock returns the content (sans "#!stacks") of the +// code block at the start of the issue body. +// Logic plundered from x/build/cmd/watchflakes/github.go. +func findPredicateBlock(body string) string { + // Extract ```-fenced or indented code block at start of issue description (body). + body = strings.ReplaceAll(body, "\r\n", "\n") + lines := strings.SplitAfter(body, "\n") + for len(lines) > 0 && strings.TrimSpace(lines[0]) == "" { + lines = lines[1:] + } + text := "" + // A code quotation is bracketed by sequence of 3+ backticks. + // (More than 3 are permitted so that one can quote 3 backticks.) + if len(lines) > 0 && strings.HasPrefix(lines[0], "```") { + marker := lines[0] + n := 0 + for n < len(marker) && marker[n] == '`' { + n++ + } + marker = marker[:n] + i := 1 + for i := 1; i < len(lines); i++ { + if strings.HasPrefix(lines[i], marker) && strings.TrimSpace(strings.TrimLeft(lines[i], "`")) == "" { + text = strings.Join(lines[1:i], "") + break + } + } + if i < len(lines) { + } + } else if strings.HasPrefix(lines[0], "\t") || strings.HasPrefix(lines[0], " ") { + i := 1 + for i < len(lines) && (strings.HasPrefix(lines[i], "\t") || strings.HasPrefix(lines[i], " ")) { + i++ + } + text = strings.Join(lines[:i], "") + } + + // Must start with #!stacks so we're sure it is for us. + hdr, rest, _ := strings.Cut(text, "\n") + hdr = strings.TrimSpace(hdr) + if hdr != "#!stacks" { + return "" + } + return rest +} + +// isTerminal reports whether file is a terminal, +// avoiding a dependency on golang.org/x/term. +func isTerminal(file *os.File) bool { + // Hardwire the constants to avoid the need for build tags. + // The values here are good for our dev machines. + switch runtime.GOOS { + case "darwin": + const TIOCGETA = 0x40487413 // from unix.TIOCGETA + _, err := unix.IoctlGetTermios(int(file.Fd()), TIOCGETA) + return err == nil + case "linux": + const TCGETS = 0x5401 // from unix.TCGETS + _, err := unix.IoctlGetTermios(int(file.Fd()), TCGETS) + return err == nil + } + panic("unreachable") +} diff --git a/contribs/gnopls/internal/telemetry/latency.go b/contribs/gnopls/internal/telemetry/latency.go new file mode 100644 index 00000000000..3147ecb9f7f --- /dev/null +++ b/contribs/gnopls/internal/telemetry/latency.go @@ -0,0 +1,102 @@ +// 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 telemetry + +import ( + "context" + "errors" + "fmt" + "sort" + "sync" + "time" + + "golang.org/x/telemetry/counter" +) + +// latencyKey is used for looking up latency counters. +type latencyKey struct { + operation, bucket string + isError bool +} + +var ( + latencyBuckets = []struct { + end time.Duration + name string + }{ + {10 * time.Millisecond, "<10ms"}, + {50 * time.Millisecond, "<50ms"}, + {100 * time.Millisecond, "<100ms"}, + {200 * time.Millisecond, "<200ms"}, + {500 * time.Millisecond, "<500ms"}, + {1 * time.Second, "<1s"}, + {5 * time.Second, "<5s"}, + {24 * time.Hour, "<24h"}, + } + + latencyCounterMu sync.Mutex + latencyCounters = make(map[latencyKey]*counter.Counter) // lazily populated +) + +// ForEachLatencyCounter runs the provided function for each current latency +// counter measuring the given operation. +// +// Exported for testing. +func ForEachLatencyCounter(operation string, isError bool, f func(*counter.Counter)) { + latencyCounterMu.Lock() + defer latencyCounterMu.Unlock() + + for k, v := range latencyCounters { + if k.operation == operation && k.isError == isError { + f(v) + } + } +} + +// getLatencyCounter returns the counter used to record latency of the given +// operation in the given bucket. +func getLatencyCounter(operation, bucket string, isError bool) *counter.Counter { + latencyCounterMu.Lock() + defer latencyCounterMu.Unlock() + + key := latencyKey{operation, bucket, isError} + c, ok := latencyCounters[key] + if !ok { + var name string + if isError { + name = fmt.Sprintf("gopls/%s/error-latency:%s", operation, bucket) + } else { + name = fmt.Sprintf("gopls/%s/latency:%s", operation, bucket) + } + c = counter.New(name) + latencyCounters[key] = c + } + return c +} + +// StartLatencyTimer starts a timer for the gopls operation with the given +// name, and returns a func to stop the timer and record the latency sample. +// +// If the context provided to the resulting func is done, no observation is +// recorded. +func StartLatencyTimer(operation string) func(context.Context, error) { + start := time.Now() + return func(ctx context.Context, err error) { + if errors.Is(ctx.Err(), context.Canceled) { + // Ignore timing where the operation is cancelled, it may be influenced + // by client behavior. + return + } + latency := time.Since(start) + bucketIdx := sort.Search(len(latencyBuckets), func(i int) bool { + bucket := latencyBuckets[i] + return latency < bucket.end + }) + if bucketIdx < len(latencyBuckets) { // ignore latency longer than a day :) + bucketName := latencyBuckets[bucketIdx].name + getLatencyCounter(operation, bucketName, err != nil).Inc() + } + } +} diff --git a/contribs/gnopls/internal/telemetry/telemetry_test.go b/contribs/gnopls/internal/telemetry/telemetry_test.go new file mode 100644 index 00000000000..7aaca41ab55 --- /dev/null +++ b/contribs/gnopls/internal/telemetry/telemetry_test.go @@ -0,0 +1,232 @@ +// 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. + +//go:build go1.21 && !openbsd && !js && !wasip1 && !solaris && !android && !386 +// +build go1.21,!openbsd,!js,!wasip1,!solaris,!android,!386 + +package telemetry_test + +import ( + "context" + "errors" + "os" + "strconv" + "strings" + "testing" + "time" + + "golang.org/x/telemetry/counter" + "golang.org/x/telemetry/counter/countertest" // requires go1.21+ + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/telemetry" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/util/bug" +) + +func TestMain(m *testing.M) { + tmp, err := os.MkdirTemp("", "gopls-telemetry-test-counters") + if err != nil { + panic(err) + } + countertest.Open(tmp) + code := Main(m) + os.RemoveAll(tmp) // golang/go#68243: ignore error; cleanup fails on Windows + os.Exit(code) +} + +func TestTelemetry(t *testing.T) { + var ( + goversion = "" + editor = "vscode" // We set ClientName("Visual Studio Code") below. + ) + + // Run gopls once to determine the Go version. + WithOptions( + Modes(Default), + ).Run(t, "", func(_ *testing.T, env *Env) { + goversion = strconv.Itoa(env.GoVersion()) + }) + + // counters that should be incremented once per session + sessionCounters := []*counter.Counter{ + counter.New("gopls/client:" + editor), + counter.New("gopls/goversion:1." + goversion), + counter.New("fwd/vscode/linter:a"), + counter.New("gopls/gotoolchain:local"), + } + initialCounts := make([]uint64, len(sessionCounters)) + for i, c := range sessionCounters { + count, err := countertest.ReadCounter(c) + if err != nil { + continue // counter db not open, or counter not found + } + initialCounts[i] = count + } + + // Verify that a properly configured session gets notified of a bug on the + // server. + WithOptions( + Modes(Default), // must be in-process to receive the bug report below + Settings{"showBugReports": true}, + ClientName("Visual Studio Code"), + EnvVars{ + "GOTOOLCHAIN": "local", // so that the local counter is incremented + }, + ).Run(t, "", func(_ *testing.T, env *Env) { + goversion = strconv.Itoa(env.GoVersion()) + addForwardedCounters(env, []string{"vscode/linter:a"}, []int64{1}) + const desc = "got a bug" + + // This will increment a counter named something like: + // + // `gopls/bug + // golang.org/x/tools/gopls/internal/util/bug.report:+35 + // golang.org/x/tools/gopls/internal/util/bug.Report:=68 + // golang.org/x/tools/gopls/internal/telemetry_test.TestTelemetry.func2:+4 + // golang.org/x/tools/gopls/internal/test/integration.(*Runner).Run.func1:+87 + // testing.tRunner:+150 + // runtime.goexit:+0` + // + bug.Report(desc) // want a stack counter with the trace starting from here. + + env.Await(ShownMessage(desc)) + }) + + // gopls/editor:client + // gopls/goversion:1.x + // fwd/vscode/linter:a + // gopls/gotoolchain:local + for i, c := range sessionCounters { + want := initialCounts[i] + 1 + got, err := countertest.ReadCounter(c) + if err != nil || got != want { + t.Errorf("ReadCounter(%q) = (%v, %v), want (%v, nil)", c.Name(), got, err, want) + t.Logf("Current timestamp = %v", time.Now().UTC()) + } + } + + // gopls/bug + bugcount := bug.BugReportCount + counts, err := countertest.ReadStackCounter(bugcount) + if err != nil { + t.Fatalf("ReadStackCounter(bugreportcount) failed - %v", err) + } + if len(counts) != 1 || !hasEntry(counts, t.Name(), 1) { + t.Errorf("read stackcounter(%q) = (%#v, %v), want one entry", "gopls/bug", counts, err) + t.Logf("Current timestamp = %v", time.Now().UTC()) + } +} + +func addForwardedCounters(env *Env, names []string, values []int64) { + args, err := command.MarshalArgs(command.AddTelemetryCountersArgs{ + Names: names, Values: values, + }) + if err != nil { + env.T.Fatal(err) + } + var res error + env.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: command.AddTelemetryCounters.String(), + Arguments: args, + }, &res) + if res != nil { + env.T.Errorf("%v failed - %v", command.AddTelemetryCounters, res) + } +} + +func hasEntry(counts map[string]uint64, pattern string, want uint64) bool { + for k, v := range counts { + if strings.Contains(k, pattern) && v == want { + return true + } + } + return false +} + +func TestLatencyCounter(t *testing.T) { + const operation = "TestLatencyCounter" // a unique operation name + + stop := telemetry.StartLatencyTimer(operation) + stop(context.Background(), nil) + + for isError, want := range map[bool]uint64{false: 1, true: 0} { + if got := totalLatencySamples(t, operation, isError); got != want { + t.Errorf("totalLatencySamples(operation=%v, isError=%v) = %d, want %d", operation, isError, got, want) + } + } +} + +func TestLatencyCounter_Error(t *testing.T) { + const operation = "TestLatencyCounter_Error" // a unique operation name + + stop := telemetry.StartLatencyTimer(operation) + stop(context.Background(), errors.New("bad")) + + for isError, want := range map[bool]uint64{false: 0, true: 1} { + if got := totalLatencySamples(t, operation, isError); got != want { + t.Errorf("totalLatencySamples(operation=%v, isError=%v) = %d, want %d", operation, isError, got, want) + } + } +} + +func TestLatencyCounter_Cancellation(t *testing.T) { + const operation = "TestLatencyCounter_Cancellation" + + stop := telemetry.StartLatencyTimer(operation) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + stop(ctx, nil) + + for isError, want := range map[bool]uint64{false: 0, true: 0} { + if got := totalLatencySamples(t, operation, isError); got != want { + t.Errorf("totalLatencySamples(operation=%v, isError=%v) = %d, want %d", operation, isError, got, want) + } + } +} + +func totalLatencySamples(t *testing.T, operation string, isError bool) uint64 { + var total uint64 + telemetry.ForEachLatencyCounter(operation, isError, func(c *counter.Counter) { + count, err := countertest.ReadCounter(c) + if err != nil { + t.Errorf("ReadCounter(%s) failed: %v", c.Name(), err) + } else { + total += count + } + }) + return total +} + +func TestLatencyInstrumentation(t *testing.T) { + const files = ` +-- go.mod -- +module mod.test/a +go 1.18 +-- a.go -- +package a + +func _() { + x := 0 + _ = x +} +` + + // Verify that a properly configured session gets notified of a bug on the + // server. + WithOptions( + Modes(Default), // must be in-process to receive the bug report below + ).Run(t, files, func(_ *testing.T, env *Env) { + env.OpenFile("a.go") + before := totalLatencySamples(t, "completion", false) + loc := env.RegexpSearch("a.go", "x") + for i := 0; i < 10; i++ { + env.Completion(loc) + } + after := totalLatencySamples(t, "completion", false) + if after-before < 10 { + t.Errorf("after 10 completions, completion counter went from %d to %d", before, after) + } + }) +} diff --git a/contribs/gnopls/internal/template/completion.go b/contribs/gnopls/internal/template/completion.go new file mode 100644 index 00000000000..dfacefc938e --- /dev/null +++ b/contribs/gnopls/internal/template/completion.go @@ -0,0 +1,253 @@ +// 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 template + +import ( + "bytes" + "context" + "fmt" + "go/scanner" + "go/token" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" +) + +// information needed for completion +type completer struct { + p *Parsed + pos protocol.Position + offset int // offset of the start of the Token + ctx protocol.CompletionContext + syms map[string]symbol +} + +func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pos protocol.Position, context protocol.CompletionContext) (*protocol.CompletionList, error) { + all := New(snapshot.Templates()) + var start int // the beginning of the Token (completed or not) + syms := make(map[string]symbol) + var p *Parsed + for fn, fc := range all.files { + // collect symbols from all template files + filterSyms(syms, fc.symbols) + if fn.Path() != fh.URI().Path() { + continue + } + if start = inTemplate(fc, pos); start == -1 { + return nil, nil + } + p = fc + } + if p == nil { + // this cannot happen unless the search missed a template file + return nil, fmt.Errorf("%s not found", fh.Identity().URI.Path()) + } + c := completer{ + p: p, + pos: pos, + offset: start + len(Left), + ctx: context, + syms: syms, + } + return c.complete() +} + +func filterSyms(syms map[string]symbol, ns []symbol) { + for _, xsym := range ns { + switch xsym.kind { + case protocol.Method, protocol.Package, protocol.Boolean, protocol.Namespace, + protocol.Function: + syms[xsym.name] = xsym // we don't care which symbol we get + case protocol.Variable: + if xsym.name != "dot" { + syms[xsym.name] = xsym + } + case protocol.Constant: + if xsym.name == "nil" { + syms[xsym.name] = xsym + } + } + } +} + +// return the starting position of the enclosing token, or -1 if none +func inTemplate(fc *Parsed, pos protocol.Position) int { + // pos is the pos-th character. if the cursor is at the beginning + // of the file, pos is 0. That is, we've only seen characters before pos + // 1. pos might be in a Token, return tk.Start + // 2. pos might be after an elided but before a Token, return elided + // 3. return -1 for false + offset := fc.FromPosition(pos) + // this could be a binary search, as the tokens are ordered + for _, tk := range fc.tokens { + if tk.Start < offset && offset <= tk.End { + return tk.Start + } + } + for _, x := range fc.elided { + if x > offset { + // fc.elided is sorted + break + } + // If the interval [x,offset] does not contain Left or Right + // then provide completions. (do we need the test for Right?) + if !bytes.Contains(fc.buf[x:offset], Left) && !bytes.Contains(fc.buf[x:offset], Right) { + return x + } + } + return -1 +} + +var ( + keywords = []string{"if", "with", "else", "block", "range", "template", "end}}", "end"} + globals = []string{"and", "call", "html", "index", "slice", "js", "len", "not", "or", + "urlquery", "printf", "println", "print", "eq", "ne", "le", "lt", "ge", "gt"} +) + +// find the completions. start is the offset of either the Token enclosing pos, or where +// the incomplete token starts. +// The error return is always nil. +func (c *completer) complete() (*protocol.CompletionList, error) { + ans := &protocol.CompletionList{IsIncomplete: true, Items: []protocol.CompletionItem{}} + start := c.p.FromPosition(c.pos) + sofar := c.p.buf[c.offset:start] + if len(sofar) == 0 || sofar[len(sofar)-1] == ' ' || sofar[len(sofar)-1] == '\t' { + return ans, nil + } + // sofar could be parsed by either c.analyzer() or scan(). The latter is precise + // and slower, but fast enough + words := scan(sofar) + // 1. if pattern starts $, show variables + // 2. if pattern starts ., show methods (and . by itself?) + // 3. if len(words) == 1, show firstWords (but if it were a |, show functions and globals) + // 4. ...? (parenthetical expressions, arguments, ...) (packages, namespaces, nil?) + if len(words) == 0 { + return nil, nil // if this happens, why were we called? + } + pattern := words[len(words)-1] + if pattern[0] == '$' { + // should we also return a raw "$"? + for _, s := range c.syms { + if s.kind == protocol.Variable && weakMatch(s.name, pattern) > 0 { + ans.Items = append(ans.Items, protocol.CompletionItem{ + Label: s.name, + Kind: protocol.VariableCompletion, + Detail: "Variable", + }) + } + } + return ans, nil + } + if pattern[0] == '.' { + for _, s := range c.syms { + if s.kind == protocol.Method && weakMatch("."+s.name, pattern) > 0 { + ans.Items = append(ans.Items, protocol.CompletionItem{ + Label: s.name, + Kind: protocol.MethodCompletion, + Detail: "Method/member", + }) + } + } + return ans, nil + } + // could we get completion attempts in strings or numbers, and if so, do we care? + // globals + for _, kw := range globals { + if weakMatch(kw, pattern) != 0 { + ans.Items = append(ans.Items, protocol.CompletionItem{ + Label: kw, + Kind: protocol.KeywordCompletion, + Detail: "Function", + }) + } + } + // and functions + for _, s := range c.syms { + if s.kind == protocol.Function && weakMatch(s.name, pattern) != 0 { + ans.Items = append(ans.Items, protocol.CompletionItem{ + Label: s.name, + Kind: protocol.FunctionCompletion, + Detail: "Function", + }) + } + } + // keywords if we're at the beginning + if len(words) <= 1 || len(words[len(words)-2]) == 1 && words[len(words)-2][0] == '|' { + for _, kw := range keywords { + if weakMatch(kw, pattern) != 0 { + ans.Items = append(ans.Items, protocol.CompletionItem{ + Label: kw, + Kind: protocol.KeywordCompletion, + Detail: "keyword", + }) + } + } + } + return ans, nil +} + +// version of c.analyze that uses go/scanner. +func scan(buf []byte) []string { + fset := token.NewFileSet() + fp := fset.AddFile("", -1, len(buf)) + var sc scanner.Scanner + sc.Init(fp, buf, func(pos token.Position, msg string) {}, scanner.ScanComments) + ans := make([]string, 0, 10) // preallocating gives a measurable savings + for { + _, tok, lit := sc.Scan() // tok is an int + if tok == token.EOF { + break // done + } else if tok == token.SEMICOLON && lit == "\n" { + continue // don't care, but probably can't happen + } else if tok == token.PERIOD { + ans = append(ans, ".") // lit is empty + } else if tok == token.IDENT && len(ans) > 0 && ans[len(ans)-1] == "." { + ans[len(ans)-1] = "." + lit + } else if tok == token.IDENT && len(ans) > 0 && ans[len(ans)-1] == "$" { + ans[len(ans)-1] = "$" + lit + } else if lit != "" { + ans = append(ans, lit) + } + } + return ans +} + +// pattern is what the user has typed +func weakMatch(choice, pattern string) float64 { + lower := strings.ToLower(choice) + // for now, use only lower-case everywhere + pattern = strings.ToLower(pattern) + // The first char has to match + if pattern[0] != lower[0] { + return 0 + } + // If they start with ., then the second char has to match + from := 1 + if pattern[0] == '.' { + if len(pattern) < 2 { + return 1 // pattern just a ., so it matches + } + if pattern[1] != lower[1] { + return 0 + } + from = 2 + } + // check that all the characters of pattern occur as a subsequence of choice + i, j := from, from + for ; i < len(lower) && j < len(pattern); j++ { + if pattern[j] == lower[i] { + i++ + if i >= len(lower) { + return 0 + } + } + } + if j < len(pattern) { + return 0 + } + return 1 +} diff --git a/contribs/gnopls/internal/template/completion_test.go b/contribs/gnopls/internal/template/completion_test.go new file mode 100644 index 00000000000..8e1bdbf0535 --- /dev/null +++ b/contribs/gnopls/internal/template/completion_test.go @@ -0,0 +1,102 @@ +// 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 template + +import ( + "log" + "sort" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +func init() { + log.SetFlags(log.Lshortfile) +} + +type tparse struct { + marked string // ^ shows where to ask for completions. (The user just typed the following character.) + wanted []string // expected completions +} + +// Test completions in templates that parse enough (if completion needs symbols) +// Seen characters up to the ^ +func TestParsed(t *testing.T) { + var tests = []tparse{ + {"{{x}}{{12. xx^", nil}, // https://github.com/golang/go/issues/50430 + {``, nil}, + {"{{i^f}}", []string{"index", "if"}}, + {"{{if .}}{{e^ {{end}}", []string{"eq", "end}}", "else", "end"}}, + {"{{foo}}{{f^", []string{"foo"}}, + {"{{$^}}", []string{"$"}}, + {"{{$x:=4}}{{$^", []string{"$x"}}, + {"{{$x:=4}}{{$ ^ ", []string{}}, + {"{{len .Modified}}{{.^Mo", []string{"Modified"}}, + {"{{len .Modified}}{{.mf^", []string{"Modified"}}, + {"{{$^ }}", []string{"$"}}, + {"{{$a =3}}{{$^", []string{"$a"}}, + // .two is not good here: fix someday + {`{{.Modified}}{{.^{{if $.one.two}}xxx{{end}}`, []string{"Modified", "one", "two"}}, + {`{{.Modified}}{{.o^{{if $.one.two}}xxx{{end}}`, []string{"one"}}, + {"{{.Modiifed}}{{.one.t^{{if $.one.two}}xxx{{end}}", []string{"two"}}, + {`{{block "foo" .}}{{i^`, []string{"index", "if"}}, + {"{{in^{{Internal}}", []string{"index", "Internal", "if"}}, + // simple number has no completions + {"{{4^e", []string{}}, + // simple string has no completions + {"{{`e^", []string{}}, + {"{{`No i^", []string{}}, // example of why go/scanner is used + {"{{xavier}}{{12. x^", []string{"xavier"}}, + } + for _, tx := range tests { + c := testCompleter(t, tx) + var v []string + if c != nil { + ans, _ := c.complete() + for _, a := range ans.Items { + v = append(v, a.Label) + } + } + if len(v) != len(tx.wanted) { + t.Errorf("%q: got %q, wanted %q %d,%d", tx.marked, v, tx.wanted, len(v), len(tx.wanted)) + continue + } + sort.Strings(tx.wanted) + sort.Strings(v) + for i := 0; i < len(v); i++ { + if tx.wanted[i] != v[i] { + t.Errorf("%q at %d: got %v, wanted %v", tx.marked, i, v, tx.wanted) + break + } + } + } +} + +func testCompleter(t *testing.T, tx tparse) *completer { + t.Helper() + // seen chars up to ^ + col := strings.Index(tx.marked, "^") + buf := strings.Replace(tx.marked, "^", "", 1) + p := parseBuffer([]byte(buf)) + pos := protocol.Position{Line: 0, Character: uint32(col)} + if p.ParseErr != nil { + log.Printf("%q: %v", tx.marked, p.ParseErr) + } + offset := inTemplate(p, pos) + if offset == -1 { + return nil + } + syms := make(map[string]symbol) + filterSyms(syms, p.symbols) + c := &completer{ + p: p, + pos: protocol.Position{Line: 0, Character: uint32(col)}, + offset: offset + len(Left), + ctx: protocol.CompletionContext{TriggerKind: protocol.Invoked}, + syms: syms, + } + return c +} diff --git a/contribs/gnopls/internal/template/highlight.go b/contribs/gnopls/internal/template/highlight.go new file mode 100644 index 00000000000..39812cfd0ba --- /dev/null +++ b/contribs/gnopls/internal/template/highlight.go @@ -0,0 +1,97 @@ +// 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 template + +import ( + "context" + "fmt" + "regexp" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" +) + +func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, loc protocol.Position) ([]protocol.DocumentHighlight, error) { + buf, err := fh.Content() + if err != nil { + return nil, err + } + p := parseBuffer(buf) + pos := p.FromPosition(loc) + var ans []protocol.DocumentHighlight + if p.ParseErr == nil { + for _, s := range p.symbols { + if s.start <= pos && pos < s.start+s.length { + return markSymbols(p, s) + } + } + } + // these tokens exist whether or not there was a parse error + // (symbols require a successful parse) + for _, tok := range p.tokens { + if tok.Start <= pos && pos < tok.End { + wordAt := findWordAt(p, pos) + if len(wordAt) > 0 { + return markWordInToken(p, wordAt) + } + } + } + // find the 'word' at pos, etc: someday + // until then we get the default action, which doesn't respect word boundaries + return ans, nil +} + +func markSymbols(p *Parsed, sym symbol) ([]protocol.DocumentHighlight, error) { + var ans []protocol.DocumentHighlight + for _, s := range p.symbols { + if s.name == sym.name { + kind := protocol.Read + if s.vardef { + kind = protocol.Write + } + ans = append(ans, protocol.DocumentHighlight{ + Range: p.Range(s.start, s.length), + Kind: kind, + }) + } + } + return ans, nil +} + +// A token is {{...}}, and this marks words in the token that equal the give word +func markWordInToken(p *Parsed, wordAt string) ([]protocol.DocumentHighlight, error) { + var ans []protocol.DocumentHighlight + pat, err := regexp.Compile(fmt.Sprintf(`\b%s\b`, wordAt)) + if err != nil { + return nil, fmt.Errorf("%q: unmatchable word (%v)", wordAt, err) + } + for _, tok := range p.tokens { + got := pat.FindAllIndex(p.buf[tok.Start:tok.End], -1) + for i := 0; i < len(got); i++ { + ans = append(ans, protocol.DocumentHighlight{ + Range: p.Range(got[i][0], got[i][1]-got[i][0]), + Kind: protocol.Text, + }) + } + } + return ans, nil +} + +var wordRe = regexp.MustCompile(`[$]?\w+$`) +var moreRe = regexp.MustCompile(`^[$]?\w+`) + +// findWordAt finds the word the cursor is in (meaning in or just before) +func findWordAt(p *Parsed, pos int) string { + if pos >= len(p.buf) { + return "" // can't happen, as we are called with pos < tok.End + } + after := moreRe.Find(p.buf[pos:]) + if len(after) == 0 { + return "" // end of the word + } + got := wordRe.Find(p.buf[:pos+len(after)]) + return string(got) +} diff --git a/contribs/gnopls/internal/template/implementations.go b/contribs/gnopls/internal/template/implementations.go new file mode 100644 index 00000000000..19a27620b57 --- /dev/null +++ b/contribs/gnopls/internal/template/implementations.go @@ -0,0 +1,218 @@ +// 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 template + +import ( + "context" + "fmt" + "regexp" + "strconv" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/semtok" +) + +// line number (1-based) and message +var errRe = regexp.MustCompile(`template.*:(\d+): (.*)`) + +// Diagnostics returns parse errors. There is only one per file. +// The errors are not always helpful. For instance { {end}} +// will likely point to the end of the file. +func Diagnostics(snapshot *cache.Snapshot) map[protocol.DocumentURI][]*cache.Diagnostic { + diags := make(map[protocol.DocumentURI][]*cache.Diagnostic) + for uri, fh := range snapshot.Templates() { + diags[uri] = diagnoseOne(fh) + } + return diags +} + +func diagnoseOne(fh file.Handle) []*cache.Diagnostic { + // no need for skipTemplate check, as Diagnose is called on the + // snapshot's template files + buf, err := fh.Content() + if err != nil { + // Is a Diagnostic with no Range useful? event.Error also? + msg := fmt.Sprintf("failed to read %s (%v)", fh.URI().Path(), err) + d := cache.Diagnostic{Message: msg, Severity: protocol.SeverityError, URI: fh.URI(), + Source: cache.TemplateError} + return []*cache.Diagnostic{&d} + } + p := parseBuffer(buf) + if p.ParseErr == nil { + return nil + } + unknownError := func(msg string) []*cache.Diagnostic { + s := fmt.Sprintf("malformed template error %q: %s", p.ParseErr.Error(), msg) + d := cache.Diagnostic{ + Message: s, Severity: protocol.SeverityError, Range: p.Range(p.nls[0], 1), + URI: fh.URI(), Source: cache.TemplateError} + return []*cache.Diagnostic{&d} + } + // errors look like `template: :40: unexpected "}" in operand` + // so the string needs to be parsed + matches := errRe.FindStringSubmatch(p.ParseErr.Error()) + if len(matches) != 3 { + msg := fmt.Sprintf("expected 3 matches, got %d (%v)", len(matches), matches) + return unknownError(msg) + } + lineno, err := strconv.Atoi(matches[1]) + if err != nil { + msg := fmt.Sprintf("couldn't convert %q to int, %v", matches[1], err) + return unknownError(msg) + } + msg := matches[2] + d := cache.Diagnostic{Message: msg, Severity: protocol.SeverityError, + Source: cache.TemplateError} + start := p.nls[lineno-1] + if lineno < len(p.nls) { + size := p.nls[lineno] - start + d.Range = p.Range(start, size) + } else { + d.Range = p.Range(start, 1) + } + return []*cache.Diagnostic{&d} +} + +// Definition finds the definitions of the symbol at loc. It +// does not understand scoping (if any) in templates. This code is +// for definitions, type definitions, and implementations. +// Results only for variables and templates. +func Definition(snapshot *cache.Snapshot, fh file.Handle, loc protocol.Position) ([]protocol.Location, error) { + x, _, err := symAtPosition(fh, loc) + if err != nil { + return nil, err + } + sym := x.name + ans := []protocol.Location{} + // PJW: this is probably a pattern to abstract + a := New(snapshot.Templates()) + for k, p := range a.files { + for _, s := range p.symbols { + if !s.vardef || s.name != sym { + continue + } + ans = append(ans, protocol.Location{URI: k, Range: p.Range(s.start, s.length)}) + } + } + return ans, nil +} + +func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) { + sym, p, err := symAtPosition(fh, position) + if sym == nil || err != nil { + return nil, err + } + ans := protocol.Hover{Range: p.Range(sym.start, sym.length), Contents: protocol.MarkupContent{Kind: protocol.Markdown}} + switch sym.kind { + case protocol.Function: + ans.Contents.Value = fmt.Sprintf("function: %s", sym.name) + case protocol.Variable: + ans.Contents.Value = fmt.Sprintf("variable: %s", sym.name) + case protocol.Constant: + ans.Contents.Value = fmt.Sprintf("constant %s", sym.name) + case protocol.Method: // field or method + ans.Contents.Value = fmt.Sprintf("%s: field or method", sym.name) + case protocol.Package: // template use, template def (PJW: do we want two?) + ans.Contents.Value = fmt.Sprintf("template %s\n(add definition)", sym.name) + case protocol.Namespace: + ans.Contents.Value = fmt.Sprintf("template %s defined", sym.name) + case protocol.Number: + ans.Contents.Value = "number" + case protocol.String: + ans.Contents.Value = "string" + case protocol.Boolean: + ans.Contents.Value = "boolean" + default: + ans.Contents.Value = fmt.Sprintf("oops, sym=%#v", sym) + } + return &ans, nil +} + +func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, params *protocol.ReferenceParams) ([]protocol.Location, error) { + sym, _, err := symAtPosition(fh, params.Position) + if sym == nil || err != nil || sym.name == "" { + return nil, err + } + ans := []protocol.Location{} + + a := New(snapshot.Templates()) + for k, p := range a.files { + for _, s := range p.symbols { + if s.name != sym.name { + continue + } + if s.vardef && !params.Context.IncludeDeclaration { + continue + } + ans = append(ans, protocol.Location{URI: k, Range: p.Range(s.start, s.length)}) + } + } + // do these need to be sorted? (a.files is a map) + return ans, nil +} + +func SemanticTokens(ctx context.Context, snapshot *cache.Snapshot, spn protocol.DocumentURI) (*protocol.SemanticTokens, error) { + fh, err := snapshot.ReadFile(ctx, spn) + if err != nil { + return nil, err + } + buf, err := fh.Content() + if err != nil { + return nil, err + } + p := parseBuffer(buf) + + var items []semtok.Token + add := func(line, start, len uint32) { + if len == 0 { + return // vscode doesn't like 0-length Tokens + } + // TODO(adonovan): don't ignore the rng restriction, if any. + items = append(items, semtok.Token{ + Line: line, + Start: start, + Len: len, + Type: semtok.TokMacro, + }) + } + + for _, t := range p.Tokens() { + if t.Multiline { + la, ca := p.LineCol(t.Start) + lb, cb := p.LineCol(t.End) + add(la, ca, p.RuneCount(la, ca, 0)) + for l := la + 1; l < lb; l++ { + add(l, 0, p.RuneCount(l, 0, 0)) + } + add(lb, 0, p.RuneCount(lb, 0, cb)) + continue + } + sz, err := p.TokenSize(t) + if err != nil { + return nil, err + } + line, col := p.LineCol(t.Start) + add(line, col, uint32(sz)) + } + const noStrings = false + const noNumbers = false + ans := &protocol.SemanticTokens{ + Data: semtok.Encode( + items, + noStrings, + noNumbers, + snapshot.Options().SemanticTypes, + snapshot.Options().SemanticMods), + // for small cache, some day. for now, the LSP client ignores this + // (that is, when the LSP client starts returning these, we can cache) + ResultID: fmt.Sprintf("%v", time.Now()), + } + return ans, nil +} + +// still need to do rename, etc diff --git a/contribs/gnopls/internal/template/parse.go b/contribs/gnopls/internal/template/parse.go new file mode 100644 index 00000000000..448a5ab51e8 --- /dev/null +++ b/contribs/gnopls/internal/template/parse.go @@ -0,0 +1,504 @@ +// 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 template contains code for dealing with templates +package template + +// template files are small enough that the code reprocesses them each time +// this may be a bad choice for projects with lots of template files. + +import ( + "bytes" + "context" + "fmt" + "io" + "log" + "regexp" + "runtime" + "sort" + "text/template" + "text/template/parse" + "unicode/utf8" + + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +var ( + Left = []byte("{{") + Right = []byte("}}") +) + +type Parsed struct { + buf []byte //contents + lines [][]byte // needed?, other than for debugging? + elided []int // offsets where Left was replaced by blanks + + // tokens are matched Left-Right pairs, computed before trying to parse + tokens []Token + + // result of parsing + named []*template.Template // the template and embedded templates + ParseErr error + symbols []symbol + stack []parse.Node // used while computing symbols + + // for mapping from offsets in buf to LSP coordinates + // See FromPosition() and LineCol() + nls []int // offset of newlines before each line (nls[0]==-1) + lastnl int // last line seen + check int // used to decide whether to use lastnl or search through nls + nonASCII bool // are there any non-ascii runes in buf? +} + +// Token is a single {{...}}. More precisely, Left...Right +type Token struct { + Start, End int // offset from start of template + Multiline bool +} + +// All contains the Parse of all the template files +type All struct { + files map[protocol.DocumentURI]*Parsed +} + +// New returns the Parses of the snapshot's tmpl files +// (maybe cache these, but then avoiding import cycles needs code rearrangements) +func New(tmpls map[protocol.DocumentURI]file.Handle) *All { + all := make(map[protocol.DocumentURI]*Parsed) + for k, v := range tmpls { + buf, err := v.Content() + if err != nil { // PJW: decide what to do with these errors + log.Printf("failed to read %s (%v)", v.URI().Path(), err) + continue + } + all[k] = parseBuffer(buf) + } + return &All{files: all} +} + +func parseBuffer(buf []byte) *Parsed { + ans := &Parsed{ + buf: buf, + check: -1, + nls: []int{-1}, + } + if len(buf) == 0 { + return ans + } + // how to compute allAscii... + for _, b := range buf { + if b >= utf8.RuneSelf { + ans.nonASCII = true + break + } + } + if buf[len(buf)-1] != '\n' { + ans.buf = append(buf, '\n') + } + for i, p := range ans.buf { + if p == '\n' { + ans.nls = append(ans.nls, i) + } + } + ans.setTokens() // ans.buf may be a new []byte + ans.lines = bytes.Split(ans.buf, []byte{'\n'}) + t, err := template.New("").Parse(string(ans.buf)) + if err != nil { + funcs := make(template.FuncMap) + for t == nil && ans.ParseErr == nil { + // in 1.17 it may be possible to avoid getting this error + // template: :2: function "foo" not defined + matches := parseErrR.FindStringSubmatch(err.Error()) + if len(matches) == 2 { + // suppress the error by giving it a function with the right name + funcs[matches[1]] = func() interface{} { return nil } + t, err = template.New("").Funcs(funcs).Parse(string(ans.buf)) + continue + } + ans.ParseErr = err // unfixed error + return ans + } + } + ans.named = t.Templates() + // set the symbols + for _, t := range ans.named { + ans.stack = append(ans.stack, t.Root) + ans.findSymbols() + if t.Name() != "" { + // defining a template. The pos is just after {{define...}} (or {{block...}}?) + at, sz := ans.FindLiteralBefore(int(t.Root.Pos)) + s := symbol{start: at, length: sz, name: t.Name(), kind: protocol.Namespace, vardef: true} + ans.symbols = append(ans.symbols, s) + } + } + + sort.Slice(ans.symbols, func(i, j int) bool { + left, right := ans.symbols[i], ans.symbols[j] + if left.start != right.start { + return left.start < right.start + } + if left.vardef != right.vardef { + return left.vardef + } + return left.kind < right.kind + }) + return ans +} + +// FindLiteralBefore locates the first preceding string literal +// returning its position and length in buf +// or returns -1 if there is none. +// Assume double-quoted string rather than backquoted string for now. +func (p *Parsed) FindLiteralBefore(pos int) (int, int) { + left, right := -1, -1 + for i := pos - 1; i >= 0; i-- { + if p.buf[i] != '"' { + continue + } + if right == -1 { + right = i + continue + } + left = i + break + } + if left == -1 { + return -1, 0 + } + return left + 1, right - left - 1 +} + +var ( + parseErrR = regexp.MustCompile(`template:.*function "([^"]+)" not defined`) +) + +func (p *Parsed) setTokens() { + const ( + // InRaw and InString only occur inside an action (SeenLeft) + Start = iota + InRaw + InString + SeenLeft + ) + state := Start + var left, oldState int + for n := 0; n < len(p.buf); n++ { + c := p.buf[n] + switch state { + case InRaw: + if c == '`' { + state = oldState + } + case InString: + if c == '"' && !isEscaped(p.buf[:n]) { + state = oldState + } + case SeenLeft: + if c == '`' { + oldState = state // it's SeenLeft, but a little clearer this way + state = InRaw + continue + } + if c == '"' { + oldState = state + state = InString + continue + } + if bytes.HasPrefix(p.buf[n:], Right) { + right := n + len(Right) + tok := Token{Start: left, + End: right, + Multiline: bytes.Contains(p.buf[left:right], []byte{'\n'}), + } + p.tokens = append(p.tokens, tok) + state = Start + } + // If we see (unquoted) Left then the original left is probably the user + // typing. Suppress the original left + if bytes.HasPrefix(p.buf[n:], Left) { + p.elideAt(left) + left = n + n += len(Left) - 1 // skip the rest + } + case Start: + if bytes.HasPrefix(p.buf[n:], Left) { + left = n + state = SeenLeft + n += len(Left) - 1 // skip the rest (avoids {{{ bug) + } + } + } + // this error occurs after typing {{ at the end of the file + if state != Start { + // Unclosed Left. remove the Left at left + p.elideAt(left) + } +} + +func (p *Parsed) elideAt(left int) { + if p.elided == nil { + // p.buf is the same buffer that v.Read() returns, so copy it. + // (otherwise the next time it's parsed, elided information is lost) + b := make([]byte, len(p.buf)) + copy(b, p.buf) + p.buf = b + } + for i := 0; i < len(Left); i++ { + p.buf[left+i] = ' ' + } + p.elided = append(p.elided, left) +} + +// isEscaped reports whether the byte after buf is escaped +func isEscaped(buf []byte) bool { + backSlashes := 0 + for j := len(buf) - 1; j >= 0 && buf[j] == '\\'; j-- { + backSlashes++ + } + return backSlashes%2 == 1 +} + +func (p *Parsed) Tokens() []Token { + return p.tokens +} + +// TODO(adonovan): the next 100 lines could perhaps replaced by use of protocol.Mapper. + +func (p *Parsed) utf16len(buf []byte) int { + cnt := 0 + if !p.nonASCII { + return len(buf) + } + // we need a utf16len(rune), but we don't have it + for _, r := range string(buf) { + cnt++ + if r >= 1<<16 { + cnt++ + } + } + return cnt +} + +func (p *Parsed) TokenSize(t Token) (int, error) { + if t.Multiline { + return -1, fmt.Errorf("TokenSize called with Multiline token %#v", t) + } + ans := p.utf16len(p.buf[t.Start:t.End]) + return ans, nil +} + +// RuneCount counts runes in line l, from col s to e +// (e==0 for end of line. called only for multiline tokens) +func (p *Parsed) RuneCount(l, s, e uint32) uint32 { + start := p.nls[l] + 1 + int(s) + end := p.nls[l] + 1 + int(e) + if e == 0 || end > p.nls[l+1] { + end = p.nls[l+1] + } + return uint32(utf8.RuneCount(p.buf[start:end])) +} + +// LineCol converts from a 0-based byte offset to 0-based line, col. col in runes +func (p *Parsed) LineCol(x int) (uint32, uint32) { + if x < p.check { + p.lastnl = 0 + } + p.check = x + for i := p.lastnl; i < len(p.nls); i++ { + if p.nls[i] <= x { + continue + } + p.lastnl = i + var count int + if i > 0 && x == p.nls[i-1] { // \n + count = 0 + } else { + count = p.utf16len(p.buf[p.nls[i-1]+1 : x]) + } + return uint32(i - 1), uint32(count) + } + if x == len(p.buf)-1 { // trailing \n + return uint32(len(p.nls) - 1), 0 + } + // shouldn't happen + for i := 1; i < 4; i++ { + _, f, l, ok := runtime.Caller(i) + if !ok { + break + } + log.Printf("%d: %s:%d", i, f, l) + } + + msg := fmt.Errorf("LineCol off the end, %d of %d, nls=%v, %q", x, len(p.buf), p.nls, p.buf[x:]) + event.Error(context.Background(), "internal error", msg) + return 0, 0 +} + +// Position produces a protocol.Position from an offset in the template +func (p *Parsed) Position(pos int) protocol.Position { + line, col := p.LineCol(pos) + return protocol.Position{Line: line, Character: col} +} + +func (p *Parsed) Range(x, length int) protocol.Range { + line, col := p.LineCol(x) + ans := protocol.Range{ + Start: protocol.Position{Line: line, Character: col}, + End: protocol.Position{Line: line, Character: col + uint32(length)}, + } + return ans +} + +// FromPosition translates a protocol.Position into an offset into the template +func (p *Parsed) FromPosition(x protocol.Position) int { + l, c := int(x.Line), int(x.Character) + if l >= len(p.nls) || p.nls[l]+1 >= len(p.buf) { + // paranoia to avoid panic. return the largest offset + return len(p.buf) + } + line := p.buf[p.nls[l]+1:] + cnt := 0 + for w := range string(line) { + if cnt >= c { + return w + p.nls[l] + 1 + } + cnt++ + } + // do we get here? NO + pos := int(x.Character) + p.nls[int(x.Line)] + 1 + event.Error(context.Background(), "internal error", fmt.Errorf("surprise %#v", x)) + return pos +} + +func symAtPosition(fh file.Handle, loc protocol.Position) (*symbol, *Parsed, error) { + buf, err := fh.Content() + if err != nil { + return nil, nil, err + } + p := parseBuffer(buf) + pos := p.FromPosition(loc) + syms := p.SymsAtPos(pos) + if len(syms) == 0 { + return nil, p, fmt.Errorf("no symbol found") + } + if len(syms) > 1 { + log.Printf("Hover: %d syms, not 1 %v", len(syms), syms) + } + sym := syms[0] + return &sym, p, nil +} + +func (p *Parsed) SymsAtPos(pos int) []symbol { + ans := []symbol{} + for _, s := range p.symbols { + if s.start <= pos && pos < s.start+s.length { + ans = append(ans, s) + } + } + return ans +} + +type wrNode struct { + p *Parsed + w io.Writer +} + +// WriteNode is for debugging +func (p *Parsed) WriteNode(w io.Writer, n parse.Node) { + wr := wrNode{p: p, w: w} + wr.writeNode(n, "") +} + +func (wr wrNode) writeNode(n parse.Node, indent string) { + if n == nil { + return + } + at := func(pos parse.Pos) string { + line, col := wr.p.LineCol(int(pos)) + return fmt.Sprintf("(%d)%v:%v", pos, line, col) + } + switch x := n.(type) { + case *parse.ActionNode: + fmt.Fprintf(wr.w, "%sActionNode at %s\n", indent, at(x.Pos)) + wr.writeNode(x.Pipe, indent+". ") + case *parse.BoolNode: + fmt.Fprintf(wr.w, "%sBoolNode at %s, %v\n", indent, at(x.Pos), x.True) + case *parse.BranchNode: + fmt.Fprintf(wr.w, "%sBranchNode at %s\n", indent, at(x.Pos)) + wr.writeNode(x.Pipe, indent+"Pipe. ") + wr.writeNode(x.List, indent+"List. ") + wr.writeNode(x.ElseList, indent+"Else. ") + case *parse.ChainNode: + fmt.Fprintf(wr.w, "%sChainNode at %s, %v\n", indent, at(x.Pos), x.Field) + case *parse.CommandNode: + fmt.Fprintf(wr.w, "%sCommandNode at %s, %d children\n", indent, at(x.Pos), len(x.Args)) + for _, a := range x.Args { + wr.writeNode(a, indent+". ") + } + //case *parse.CommentNode: // 1.16 + case *parse.DotNode: + fmt.Fprintf(wr.w, "%sDotNode at %s\n", indent, at(x.Pos)) + case *parse.FieldNode: + fmt.Fprintf(wr.w, "%sFieldNode at %s, %v\n", indent, at(x.Pos), x.Ident) + case *parse.IdentifierNode: + fmt.Fprintf(wr.w, "%sIdentifierNode at %s, %v\n", indent, at(x.Pos), x.Ident) + case *parse.IfNode: + fmt.Fprintf(wr.w, "%sIfNode at %s\n", indent, at(x.Pos)) + wr.writeNode(&x.BranchNode, indent+". ") + case *parse.ListNode: + if x == nil { + return // nil BranchNode.ElseList + } + fmt.Fprintf(wr.w, "%sListNode at %s, %d children\n", indent, at(x.Pos), len(x.Nodes)) + for _, n := range x.Nodes { + wr.writeNode(n, indent+". ") + } + case *parse.NilNode: + fmt.Fprintf(wr.w, "%sNilNode at %s\n", indent, at(x.Pos)) + case *parse.NumberNode: + fmt.Fprintf(wr.w, "%sNumberNode at %s, %s\n", indent, at(x.Pos), x.Text) + case *parse.PipeNode: + if x == nil { + return // {{template "xxx"}} + } + fmt.Fprintf(wr.w, "%sPipeNode at %s, %d vars, %d cmds, IsAssign:%v\n", + indent, at(x.Pos), len(x.Decl), len(x.Cmds), x.IsAssign) + for _, d := range x.Decl { + wr.writeNode(d, indent+"Decl. ") + } + for _, c := range x.Cmds { + wr.writeNode(c, indent+"Cmd. ") + } + case *parse.RangeNode: + fmt.Fprintf(wr.w, "%sRangeNode at %s\n", indent, at(x.Pos)) + wr.writeNode(&x.BranchNode, indent+". ") + case *parse.StringNode: + fmt.Fprintf(wr.w, "%sStringNode at %s, %s\n", indent, at(x.Pos), x.Quoted) + case *parse.TemplateNode: + fmt.Fprintf(wr.w, "%sTemplateNode at %s, %s\n", indent, at(x.Pos), x.Name) + wr.writeNode(x.Pipe, indent+". ") + case *parse.TextNode: + fmt.Fprintf(wr.w, "%sTextNode at %s, len %d\n", indent, at(x.Pos), len(x.Text)) + case *parse.VariableNode: + fmt.Fprintf(wr.w, "%sVariableNode at %s, %v\n", indent, at(x.Pos), x.Ident) + case *parse.WithNode: + fmt.Fprintf(wr.w, "%sWithNode at %s\n", indent, at(x.Pos)) + wr.writeNode(&x.BranchNode, indent+". ") + } +} + +var kindNames = []string{"", "File", "Module", "Namespace", "Package", "Class", "Method", "Property", + "Field", "Constructor", "Enum", "Interface", "Function", "Variable", "Constant", "String", + "Number", "Boolean", "Array", "Object", "Key", "Null", "EnumMember", "Struct", "Event", + "Operator", "TypeParameter"} + +func kindStr(k protocol.SymbolKind) string { + n := int(k) + if n < 1 || n >= len(kindNames) { + return fmt.Sprintf("?SymbolKind %d?", n) + } + return kindNames[n] +} diff --git a/contribs/gnopls/internal/template/parse_test.go b/contribs/gnopls/internal/template/parse_test.go new file mode 100644 index 00000000000..345f52347fa --- /dev/null +++ b/contribs/gnopls/internal/template/parse_test.go @@ -0,0 +1,238 @@ +// 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 template + +import ( + "strings" + "testing" +) + +type datum struct { + buf string + cnt int + syms []string // the symbols in the parse of buf +} + +var tmpl = []datum{{` +{{if (foo .X.Y)}}{{$A := "hi"}}{{.Z $A}}{{else}} +{{$A.X 12}} +{{foo (.X.Y) 23 ($A.Zü)}} +{{end}}`, 1, []string{"{7,3,foo,Function,false}", "{12,1,X,Method,false}", + "{14,1,Y,Method,false}", "{21,2,$A,Variable,true}", "{26,2,,String,false}", + "{35,1,Z,Method,false}", "{38,2,$A,Variable,false}", + "{53,2,$A,Variable,false}", "{56,1,X,Method,false}", "{57,2,,Number,false}", + "{64,3,foo,Function,false}", "{70,1,X,Method,false}", + "{72,1,Y,Method,false}", "{75,2,,Number,false}", "{80,2,$A,Variable,false}", + "{83,2,Zü,Method,false}", "{94,3,,Constant,false}"}}, + + {`{{define "zzz"}}{{.}}{{end}} +{{template "zzz"}}`, 2, []string{"{10,3,zzz,Namespace,true}", "{18,1,dot,Variable,false}", + "{41,3,zzz,Package,false}"}}, + + {`{{block "aaa" foo}}b{{end}}`, 2, []string{"{9,3,aaa,Namespace,true}", + "{9,3,aaa,Package,false}", "{14,3,foo,Function,false}", "{19,1,,Constant,false}"}}, + {"", 0, nil}, +} + +func TestSymbols(t *testing.T) { + for i, x := range tmpl { + got := parseBuffer([]byte(x.buf)) + if got.ParseErr != nil { + t.Errorf("error:%v", got.ParseErr) + continue + } + if len(got.named) != x.cnt { + t.Errorf("%d: got %d, expected %d", i, len(got.named), x.cnt) + } + for n, s := range got.symbols { + if s.String() != x.syms[n] { + t.Errorf("%d: got %s, expected %s", i, s.String(), x.syms[n]) + } + } + } +} + +func TestWordAt(t *testing.T) { + want := []string{"", "", "$A", "$A", "", "", "", "", "", "", + "", "", "", "if", "if", "", "$A", "$A", "", "", + "B", "", "", "end", "end", "end", "", "", ""} + p := parseBuffer([]byte("{{$A := .}}{{if $A}}B{{end}}")) + for i := 0; i < len(p.buf); i++ { + got := findWordAt(p, i) + if got != want[i] { + t.Errorf("for %d, got %q, wanted %q", i, got, want[i]) + } + } +} + +func TestNLS(t *testing.T) { + buf := `{{if (foÜx .X.Y)}}{{$A := "hi"}}{{.Z $A}}{{else}} + {{$A.X 12}} + {{foo (.X.Y) 23 ($A.Z)}} + {{end}} + ` + p := parseBuffer([]byte(buf)) + if p.ParseErr != nil { + t.Fatal(p.ParseErr) + } + // line 0 doesn't have a \n in front of it + for i := 1; i < len(p.nls)-1; i++ { + if buf[p.nls[i]] != '\n' { + t.Errorf("line %d got %c", i, buf[p.nls[i]]) + } + } + // fake line at end of file + if p.nls[len(p.nls)-1] != len(buf) { + t.Errorf("got %d expected %d", p.nls[len(p.nls)-1], len(buf)) + } +} + +func TestLineCol(t *testing.T) { + buf := `{{if (foÜx .X.Y)}}{{$A := "hi"}}{{.Z $A}}{{else}} + {{$A.X 12}} + {{foo (.X.Y) 23 ($A.Z)}} + {{end}}` + if false { + t.Error(buf) + } + for n, cx := range tmpl { + buf := cx.buf + p := parseBuffer([]byte(buf)) + if p.ParseErr != nil { + t.Fatal(p.ParseErr) + } + type loc struct { + offset int + l, c uint32 + } + saved := []loc{} + // forwards + var lastl, lastc uint32 + for offset := range buf { + l, c := p.LineCol(offset) + saved = append(saved, loc{offset, l, c}) + if l > lastl { + lastl = l + if c != 0 { + t.Errorf("line %d, got %d instead of 0", l, c) + } + } + if c > lastc { + lastc = c + } + } + lines := strings.Split(buf, "\n") + mxlen := -1 + for _, l := range lines { + if len(l) > mxlen { + mxlen = len(l) + } + } + if int(lastl) != len(lines)-1 && int(lastc) != mxlen { + // lastl is 0 if there is only 1 line(?) + t.Errorf("expected %d, %d, got %d, %d for case %d", len(lines)-1, mxlen, lastl, lastc, n) + } + // backwards + for j := len(saved) - 1; j >= 0; j-- { + s := saved[j] + xl, xc := p.LineCol(s.offset) + if xl != s.l || xc != s.c { + t.Errorf("at offset %d(%d), got (%d,%d), expected (%d,%d)", s.offset, j, xl, xc, s.l, s.c) + } + } + } +} + +func TestLineColNL(t *testing.T) { + buf := "\n\n\n\n\n" + p := parseBuffer([]byte(buf)) + if p.ParseErr != nil { + t.Fatal(p.ParseErr) + } + for i := 0; i < len(buf); i++ { + l, c := p.LineCol(i) + if c != 0 || int(l) != i+1 { + t.Errorf("got (%d,%d), expected (%d,0)", l, c, i) + } + } +} + +func TestPos(t *testing.T) { + buf := ` + {{if (foÜx .X.Y)}}{{$A := "hi"}}{{.Z $A}}{{else}} + {{$A.X 12}} + {{foo (.X.Y) 23 ($A.Z)}} + {{end}}` + p := parseBuffer([]byte(buf)) + if p.ParseErr != nil { + t.Fatal(p.ParseErr) + } + for pos, r := range buf { + if r == '\n' { + continue + } + x := p.Position(pos) + n := p.FromPosition(x) + if n != pos { + // once it's wrong, it will be wrong forever + t.Fatalf("at pos %d (rune %c) got %d {%#v]", pos, r, n, x) + } + + } +} +func TestLen(t *testing.T) { + data := []struct { + cnt int + v string + }{{1, "a"}, {1, "膈"}, {4, "😆🥸"}, {7, "3😀4567"}} + p := &Parsed{nonASCII: true} + for _, d := range data { + got := p.utf16len([]byte(d.v)) + if got != d.cnt { + t.Errorf("%v, got %d wanted %d", d, got, d.cnt) + } + } +} + +func TestUtf16(t *testing.T) { + buf := ` + {{if (foÜx .X.Y)}}😀{{$A := "hi"}}{{.Z $A}}{{else}} + {{$A.X 12}} + {{foo (.X.Y) 23 ($A.Z)}} + {{end}}` + p := parseBuffer([]byte(buf)) + if p.nonASCII == false { + t.Error("expected nonASCII to be true") + } +} + +type ttest struct { + tmpl string + tokCnt int + elidedCnt int8 +} + +func TestQuotes(t *testing.T) { + tsts := []ttest{ + {"{{- /*comment*/ -}}", 1, 0}, + {"{{/*`\ncomment\n`*/}}", 1, 0}, + //{"{{foo\nbar}}\n", 1, 0}, // this action spanning lines parses in 1.16 + {"{{\"{{foo}}{{\"}}", 1, 0}, + {"{{\n{{- when}}", 1, 1}, // corrected + {"{{{{if .}}xx{{\n{{end}}", 2, 2}, // corrected + } + for _, s := range tsts { + p := parseBuffer([]byte(s.tmpl)) + if len(p.tokens) != s.tokCnt { + t.Errorf("%q: got %d tokens, expected %d", s, len(p.tokens), s.tokCnt) + } + if p.ParseErr != nil { + t.Errorf("%q: %v", string(p.buf), p.ParseErr) + } + if len(p.elided) != int(s.elidedCnt) { + t.Errorf("%q: elided %d, expected %d", s, len(p.elided), s.elidedCnt) + } + } +} diff --git a/contribs/gnopls/internal/template/symbols.go b/contribs/gnopls/internal/template/symbols.go new file mode 100644 index 00000000000..fcbaec43c54 --- /dev/null +++ b/contribs/gnopls/internal/template/symbols.go @@ -0,0 +1,231 @@ +// 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 template + +import ( + "bytes" + "context" + "fmt" + "text/template/parse" + "unicode/utf8" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +// in local coordinates, to be translated to protocol.DocumentSymbol +type symbol struct { + start int // for sorting + length int // in runes (unicode code points) + name string + kind protocol.SymbolKind + vardef bool // is this a variable definition? + // do we care about selection range, or children? + // no children yet, and selection range is the same as range +} + +func (s symbol) String() string { + return fmt.Sprintf("{%d,%d,%s,%s,%v}", s.start, s.length, s.name, s.kind, s.vardef) +} + +// for FieldNode or VariableNode (or ChainNode?) +func (p *Parsed) fields(flds []string, x parse.Node) []symbol { + ans := []symbol{} + // guessing that there are no embedded blanks allowed. The doc is unclear + lookfor := "" + switch x.(type) { + case *parse.FieldNode: + for _, f := range flds { + lookfor += "." + f // quadratic, but probably ok + } + case *parse.VariableNode: + lookfor = flds[0] + for i := 1; i < len(flds); i++ { + lookfor += "." + flds[i] + } + case *parse.ChainNode: // PJW, what are these? + for _, f := range flds { + lookfor += "." + f // quadratic, but probably ok + } + default: + // If these happen they will happen even if gopls is restarted + // and the users does the same thing, so it is better not to panic. + // context.Background() is used because we don't have access + // to any other context. [we could, but it would be complicated] + event.Log(context.Background(), fmt.Sprintf("%T unexpected in fields()", x)) + return nil + } + if len(lookfor) == 0 { + event.Log(context.Background(), fmt.Sprintf("no strings in fields() %#v", x)) + return nil + } + startsAt := int(x.Position()) + ix := bytes.Index(p.buf[startsAt:], []byte(lookfor)) // HasPrefix? PJW? + if ix < 0 || ix > len(lookfor) { // lookfor expected to be at start (or so) + // probably golang.go/#43388, so back up + startsAt -= len(flds[0]) + 1 + ix = bytes.Index(p.buf[startsAt:], []byte(lookfor)) // ix might be 1? PJW + if ix < 0 { + return ans + } + } + at := ix + startsAt + for _, f := range flds { + at += 1 // . + kind := protocol.Method + if f[0] == '$' { + kind = protocol.Variable + } + sym := symbol{name: f, kind: kind, start: at, length: utf8.RuneCount([]byte(f))} + if kind == protocol.Variable && len(p.stack) > 1 { + if pipe, ok := p.stack[len(p.stack)-2].(*parse.PipeNode); ok { + for _, y := range pipe.Decl { + if x == y { + sym.vardef = true + } + } + } + } + ans = append(ans, sym) + at += len(f) + } + return ans +} + +func (p *Parsed) findSymbols() { + if len(p.stack) == 0 { + return + } + n := p.stack[len(p.stack)-1] + pop := func() { + p.stack = p.stack[:len(p.stack)-1] + } + if n == nil { // allowing nil simplifies the code + pop() + return + } + nxt := func(nd parse.Node) { + p.stack = append(p.stack, nd) + p.findSymbols() + } + switch x := n.(type) { + case *parse.ActionNode: + nxt(x.Pipe) + case *parse.BoolNode: + // need to compute the length from the value + msg := fmt.Sprintf("%v", x.True) + p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: len(msg), kind: protocol.Boolean}) + case *parse.BranchNode: + nxt(x.Pipe) + nxt(x.List) + nxt(x.ElseList) + case *parse.ChainNode: + p.symbols = append(p.symbols, p.fields(x.Field, x)...) + nxt(x.Node) + case *parse.CommandNode: + for _, a := range x.Args { + nxt(a) + } + //case *parse.CommentNode: // go 1.16 + // log.Printf("implement %d", x.Type()) + case *parse.DotNode: + sym := symbol{name: "dot", kind: protocol.Variable, start: int(x.Pos), length: 1} + p.symbols = append(p.symbols, sym) + case *parse.FieldNode: + p.symbols = append(p.symbols, p.fields(x.Ident, x)...) + case *parse.IdentifierNode: + sym := symbol{name: x.Ident, kind: protocol.Function, start: int(x.Pos), + length: utf8.RuneCount([]byte(x.Ident))} + p.symbols = append(p.symbols, sym) + case *parse.IfNode: + nxt(&x.BranchNode) + case *parse.ListNode: + if x != nil { // wretched typed nils. Node should have an IfNil + for _, nd := range x.Nodes { + nxt(nd) + } + } + case *parse.NilNode: + sym := symbol{name: "nil", kind: protocol.Constant, start: int(x.Pos), length: 3} + p.symbols = append(p.symbols, sym) + case *parse.NumberNode: + // no name; ascii + p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: len(x.Text), kind: protocol.Number}) + case *parse.PipeNode: + if x == nil { // {{template "foo"}} + return + } + for _, d := range x.Decl { + nxt(d) + } + for _, c := range x.Cmds { + nxt(c) + } + case *parse.RangeNode: + nxt(&x.BranchNode) + case *parse.StringNode: + // no name + sz := utf8.RuneCount([]byte(x.Text)) + p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: sz, kind: protocol.String}) + case *parse.TemplateNode: // invoking a template + // x.Pos points to the quote before the name + p.symbols = append(p.symbols, symbol{name: x.Name, kind: protocol.Package, start: int(x.Pos) + 1, + length: utf8.RuneCount([]byte(x.Name))}) + nxt(x.Pipe) + case *parse.TextNode: + if len(x.Text) == 1 && x.Text[0] == '\n' { + break + } + // nothing to report, but build one for hover + sz := utf8.RuneCount(x.Text) + p.symbols = append(p.symbols, symbol{start: int(x.Pos), length: sz, kind: protocol.Constant}) + case *parse.VariableNode: + p.symbols = append(p.symbols, p.fields(x.Ident, x)...) + case *parse.WithNode: + nxt(&x.BranchNode) + + } + pop() +} + +// DocumentSymbols returns a hierarchy of the symbols defined in a template file. +// (The hierarchy is flat. SymbolInformation might be better.) +func DocumentSymbols(snapshot *cache.Snapshot, fh file.Handle) ([]protocol.DocumentSymbol, error) { + buf, err := fh.Content() + if err != nil { + return nil, err + } + p := parseBuffer(buf) + if p.ParseErr != nil { + return nil, p.ParseErr + } + var ans []protocol.DocumentSymbol + for _, s := range p.symbols { + if s.kind == protocol.Constant { + continue + } + d := kindStr(s.kind) + if d == "Namespace" { + d = "Template" + } + if s.vardef { + d += "(def)" + } else { + d += "(use)" + } + r := p.Range(s.start, s.length) + y := protocol.DocumentSymbol{ + Name: s.name, + Detail: d, + Kind: s.kind, + Range: r, + SelectionRange: r, // or should this be the entire {{...}}? + } + ans = append(ans, y) + } + return ans, nil +} diff --git a/contribs/gnopls/internal/test/compare/text.go b/contribs/gnopls/internal/test/compare/text.go new file mode 100644 index 00000000000..4ce2f8c6b28 --- /dev/null +++ b/contribs/gnopls/internal/test/compare/text.go @@ -0,0 +1,49 @@ +// 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 compare + +import ( + "bytes" + + "golang.org/x/tools/internal/diff" +) + +// Text returns a formatted unified diff of the edits to go from want to +// got, returning "" if and only if want == got. +// +// This function is intended for use in testing, and panics if any error occurs +// while computing the diff. It is not sufficiently tested for production use. +func Text(want, got string) string { + return NamedText("want", "got", want, got) +} + +// NamedText is like text, but allows passing custom names of the 'want' and +// 'got' content. +func NamedText(wantName, gotName, want, got string) string { + if want == got { + return "" + } + + // Add newlines to avoid verbose newline messages ("No newline at end of file"). + unified := diff.Unified(wantName, gotName, want+"\n", got+"\n") + + // Defensively assert that we get an actual diff, so that we guarantee the + // invariant that we return "" if and only if want == got. + // + // This is probably unnecessary, but convenient. + if unified == "" { + panic("empty diff for non-identical input") + } + + return unified +} + +// Bytes is like Text but using byte slices. +func Bytes(want, got []byte) string { + if bytes.Equal(want, got) { + return "" // common case + } + return Text(string(want), string(got)) +} diff --git a/contribs/gnopls/internal/test/compare/text_test.go b/contribs/gnopls/internal/test/compare/text_test.go new file mode 100644 index 00000000000..66bdf0996e2 --- /dev/null +++ b/contribs/gnopls/internal/test/compare/text_test.go @@ -0,0 +1,28 @@ +// 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 compare_test + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/test/compare" +) + +func TestText(t *testing.T) { + tests := []struct { + got, want, wantDiff string + }{ + {"", "", ""}, + {"equal", "equal", ""}, + {"a", "b", "--- want\n+++ got\n@@ -1 +1 @@\n-b\n+a\n"}, + {"a\nd\nc\n", "a\nb\nc\n", "--- want\n+++ got\n@@ -1,4 +1,4 @@\n a\n-b\n+d\n c\n \n"}, + } + + for _, test := range tests { + if gotDiff := compare.Text(test.want, test.got); gotDiff != test.wantDiff { + t.Errorf("compare.Text(%q, %q) =\n%q, want\n%q", test.want, test.got, gotDiff, test.wantDiff) + } + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/bench_test.go b/contribs/gnopls/internal/test/integration/bench/bench_test.go new file mode 100644 index 00000000000..5de6804c03b --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/bench_test.go @@ -0,0 +1,349 @@ +// 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 bench + +import ( + "bytes" + "compress/gzip" + "context" + "flag" + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/cmd" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/fakenet" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/jsonrpc2/servertest" + "golang.org/x/tools/internal/pprof" + "golang.org/x/tools/internal/tool" +) + +var ( + goplsPath = flag.String("gopls_path", "", "if set, use this gopls for testing; incompatible with -gopls_commit") + + installGoplsOnce sync.Once // guards installing gopls at -gopls_commit + goplsCommit = flag.String("gopls_commit", "", "if set, install and use gopls at this commit for testing; incompatible with -gopls_path") + + cpuProfile = flag.String("gopls_cpuprofile", "", "if set, the cpu profile file suffix; see \"Profiling\" in the package doc") + memProfile = flag.String("gopls_memprofile", "", "if set, the mem profile file suffix; see \"Profiling\" in the package doc") + allocProfile = flag.String("gopls_allocprofile", "", "if set, the alloc profile file suffix; see \"Profiling\" in the package doc") + trace = flag.String("gopls_trace", "", "if set, the trace file suffix; see \"Profiling\" in the package doc") + + // If non-empty, tempDir is a temporary working dir that was created by this + // test suite. + makeTempDirOnce sync.Once // guards creation of the temp dir + tempDir string +) + +// if runAsGopls is "true", run the gopls command instead of the testing.M. +const runAsGopls = "_GOPLS_BENCH_RUN_AS_GOPLS" + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + if os.Getenv(runAsGopls) == "true" { + tool.Main(context.Background(), cmd.New(), os.Args[1:]) + os.Exit(0) + } + event.SetExporter(nil) // don't log to stderr + code := m.Run() + if err := cleanup(); err != nil { + fmt.Fprintf(os.Stderr, "cleaning up after benchmarks: %v\n", err) + if code == 0 { + code = 1 + } + } + os.Exit(code) +} + +// getTempDir returns the temporary directory to use for benchmark files, +// creating it if necessary. +func getTempDir() string { + makeTempDirOnce.Do(func() { + var err error + tempDir, err = os.MkdirTemp("", "gopls-bench") + if err != nil { + log.Fatal(err) + } + }) + return tempDir +} + +// shallowClone performs a shallow clone of repo into dir at the given +// 'commitish' ref (any commit reference understood by git). +// +// The directory dir must not already exist. +func shallowClone(dir, repo, commitish string) error { + if err := os.Mkdir(dir, 0750); err != nil { + return fmt.Errorf("creating dir for %s: %v", repo, err) + } + + // Set a timeout for git fetch. If this proves flaky, it can be removed. + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + // Use a shallow fetch to download just the relevant commit. + shInit := fmt.Sprintf("git init && git fetch --depth=1 %q %q && git checkout FETCH_HEAD", repo, commitish) + initCmd := exec.CommandContext(ctx, "/bin/sh", "-c", shInit) + initCmd.Dir = dir + if output, err := initCmd.CombinedOutput(); err != nil { + return fmt.Errorf("checking out %s: %v\n%s", repo, err, output) + } + return nil +} + +// connectEditor connects a fake editor session in the given dir, using the +// given editor config. +func connectEditor(dir string, config fake.EditorConfig, ts servertest.Connector) (*fake.Sandbox, *fake.Editor, *integration.Awaiter, error) { + s, err := fake.NewSandbox(&fake.SandboxConfig{ + Workdir: dir, + GOPROXY: "https://proxy.golang.org", + }) + if err != nil { + return nil, nil, nil, err + } + + a := integration.NewAwaiter(s.Workdir) + editor, err := fake.NewEditor(s, config).Connect(context.Background(), ts, a.Hooks()) + if err != nil { + return nil, nil, nil, err + } + + return s, editor, a, nil +} + +// newGoplsConnector returns a connector that connects to a new gopls process, +// executed with the provided arguments. +func newGoplsConnector(args []string) (servertest.Connector, error) { + if *goplsPath != "" && *goplsCommit != "" { + panic("can't set both -gopls_path and -gopls_commit") + } + var ( + goplsPath = *goplsPath + env []string + ) + if *goplsCommit != "" { + goplsPath = getInstalledGopls() + } + if goplsPath == "" { + var err error + goplsPath, err = os.Executable() + if err != nil { + return nil, err + } + env = []string{fmt.Sprintf("%s=true", runAsGopls)} + } + return &SidecarServer{ + goplsPath: goplsPath, + env: env, + args: args, + }, nil +} + +// profileArgs returns additional command-line arguments to use when invoking +// gopls, to enable the user-requested profiles. +// +// If wantCPU is set, CPU profiling is enabled as well. Some tests may want to +// instrument profiling around specific critical sections of the benchmark, +// rather than the entire process. +// +// TODO(rfindley): like CPU, all of these would be better served by a custom +// command. Very rarely do we care about memory usage as the process exits: we +// care about specific points in time during the benchmark. mem and alloc +// should be snapshotted, and tracing should be bracketed around critical +// sections. +func profileArgs(name string, wantCPU bool) []string { + var args []string + if wantCPU && *cpuProfile != "" { + args = append(args, fmt.Sprintf("-profile.cpu=%s", qualifiedName(name, *cpuProfile))) + } + if *memProfile != "" { + args = append(args, fmt.Sprintf("-profile.mem=%s", qualifiedName(name, *memProfile))) + } + if *allocProfile != "" { + args = append(args, fmt.Sprintf("-profile.alloc=%s", qualifiedName(name, *allocProfile))) + } + if *trace != "" { + args = append(args, fmt.Sprintf("-profile.trace=%s", qualifiedName(name, *trace))) + } + return args +} + +func qualifiedName(args ...string) string { + return strings.Join(args, ".") +} + +// getInstalledGopls builds gopls at the given -gopls_commit, returning the +// path to the gopls binary. +func getInstalledGopls() string { + if *goplsCommit == "" { + panic("must provide -gopls_commit") + } + toolsDir := filepath.Join(getTempDir(), "gopls_build") + goplsPath := filepath.Join(toolsDir, "gopls", "gopls") + + installGoplsOnce.Do(func() { + log.Printf("installing gopls: checking out x/tools@%s into %s\n", *goplsCommit, toolsDir) + if err := shallowClone(toolsDir, "https://go.googlesource.com/tools", *goplsCommit); err != nil { + log.Fatal(err) + } + + log.Println("installing gopls: building...") + bld := exec.Command("go", "build", ".") + bld.Dir = filepath.Join(toolsDir, "gopls") + if output, err := bld.CombinedOutput(); err != nil { + log.Fatalf("building gopls: %v\n%s", err, output) + } + + // Confirm that the resulting path now exists. + if _, err := os.Stat(goplsPath); err != nil { + log.Fatalf("os.Stat(%s): %v", goplsPath, err) + } + }) + return goplsPath +} + +// A SidecarServer starts (and connects to) a separate gopls process at the +// given path. +type SidecarServer struct { + goplsPath string + env []string // additional environment bindings + args []string // command-line arguments +} + +// Connect creates new io.Pipes and binds them to the underlying StreamServer. +// +// It implements the servertest.Connector interface. +func (s *SidecarServer) Connect(ctx context.Context) jsonrpc2.Conn { + // Note: don't use CommandContext here, as we want gopls to exit gracefully + // in order to write out profile data. + // + // We close the connection on context cancelation below. + cmd := exec.Command(s.goplsPath, s.args...) + + stdin, err := cmd.StdinPipe() + if err != nil { + log.Fatal(err) + } + stdout, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = os.Stderr + cmd.Env = append(os.Environ(), s.env...) + if err := cmd.Start(); err != nil { + log.Fatalf("starting gopls: %v", err) + } + + go func() { + // If we don't log.Fatal here, benchmarks may hang indefinitely if gopls + // exits abnormally. + // + // TODO(rfindley): ideally we would shut down the connection gracefully, + // but that doesn't currently work. + if err := cmd.Wait(); err != nil { + log.Fatalf("gopls invocation failed with error: %v", err) + } + }() + + clientStream := jsonrpc2.NewHeaderStream(fakenet.NewConn("stdio", stdout, stdin)) + clientConn := jsonrpc2.NewConn(clientStream) + + go func() { + select { + case <-ctx.Done(): + clientConn.Close() + clientStream.Close() + case <-clientConn.Done(): + } + }() + + return clientConn +} + +// startProfileIfSupported checks to see if the remote gopls instance supports +// the start/stop profiling commands. If so, it starts profiling and returns a +// function that stops profiling and records the total CPU seconds sampled in the +// cpu_seconds benchmark metric. +// +// If the remote gopls instance does not support profiling commands, this +// function returns nil. +// +// If the supplied userSuffix is non-empty, the profile is written to +// ., and not deleted when the benchmark exits. Otherwise, +// the profile is written to a temp file that is deleted after the cpu_seconds +// metric has been computed. +func startProfileIfSupported(b *testing.B, env *integration.Env, name string) func() { + if !env.Editor.HasCommand(command.StartProfile) { + return nil + } + b.StopTimer() + stopProfile := env.StartProfile() + b.StartTimer() + return func() { + b.StopTimer() + profFile := stopProfile() + totalCPU, err := totalCPUForProfile(profFile) + if err != nil { + b.Fatalf("reading profile: %v", err) + } + b.ReportMetric(totalCPU.Seconds()/float64(b.N), "cpu_seconds/op") + if *cpuProfile == "" { + // The user didn't request profiles, so delete it to clean up. + if err := os.Remove(profFile); err != nil { + b.Errorf("removing profile file: %v", err) + } + } else { + // NOTE: if this proves unreliable (due to e.g. EXDEV), we can fall back + // on Read+Write+Remove. + name := qualifiedName(name, *cpuProfile) + if err := os.Rename(profFile, name); err != nil { + b.Fatalf("renaming profile file: %v", err) + } + } + } +} + +// totalCPUForProfile reads the pprof profile with the given file name, parses, +// and aggregates the total CPU sampled during the profile. +func totalCPUForProfile(filename string) (time.Duration, error) { + protoGz, err := os.ReadFile(filename) + if err != nil { + return 0, err + } + rd, err := gzip.NewReader(bytes.NewReader(protoGz)) + if err != nil { + return 0, fmt.Errorf("creating gzip reader for %s: %v", filename, err) + } + data, err := io.ReadAll(rd) + if err != nil { + return 0, fmt.Errorf("reading %s: %v", filename, err) + } + return pprof.TotalTime(data) +} + +// closeBuffer stops the benchmark timer and closes the buffer with the given +// name. +// +// It may be used to clean up files opened in the shared environment during +// benchmarking. +func closeBuffer(b *testing.B, env *integration.Env, name string) { + b.StopTimer() + env.CloseBuffer(name) + env.AfterChange() + b.StartTimer() +} diff --git a/contribs/gnopls/internal/test/integration/bench/codeaction_test.go b/contribs/gnopls/internal/test/integration/bench/codeaction_test.go new file mode 100644 index 00000000000..679f2d4cf3d --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/codeaction_test.go @@ -0,0 +1,69 @@ +// 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 bench + +import ( + "fmt" + "sync/atomic" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +func BenchmarkCodeAction(b *testing.B) { + for _, test := range didChangeTests { + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + defer closeBuffer(b, env, test.file) + env.AfterChange() + + env.CodeActionForFile(test.file, nil) // pre-warm + + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "hover")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + env.CodeActionForFile(test.file, nil) + } + }) + } +} + +func BenchmarkCodeActionFollowingEdit(b *testing.B) { + for _, test := range didChangeTests { + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + defer closeBuffer(b, env, test.file) + env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"}) + env.AfterChange() + + env.CodeActionForFile(test.file, nil) // pre-warm + + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "hover")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + edits := atomic.AddInt64(&editID, 1) + env.EditBuffer(test.file, protocol.TextEdit{ + Range: protocol.Range{ + Start: protocol.Position{Line: 0, Character: 0}, + End: protocol.Position{Line: 1, Character: 0}, + }, + // Increment the placeholder text, to ensure cache misses. + NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits), + }) + env.CodeActionForFile(test.file, nil) + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/completion_test.go b/contribs/gnopls/internal/test/integration/bench/completion_test.go new file mode 100644 index 00000000000..bbbba0e3fd1 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/completion_test.go @@ -0,0 +1,330 @@ +// 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 bench + +import ( + "flag" + "fmt" + "sync/atomic" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" +) + +var completionGOPATH = flag.String("completion_gopath", "", "if set, use this GOPATH for BenchmarkCompletion") + +type completionBenchOptions struct { + file, locationRegexp string + + // Hooks to run edits before initial completion + setup func(*Env) // run before the benchmark starts + beforeCompletion func(*Env) // run before each completion +} + +// Deprecated: new tests should be expressed in BenchmarkCompletion. +func benchmarkCompletion(options completionBenchOptions, b *testing.B) { + repo := getRepo(b, "tools") + _ = repo.sharedEnv(b) // ensure cache is warm + env := repo.newEnv(b, fake.EditorConfig{}, "completion", false) + defer env.Close() + + // Run edits required for this completion. + if options.setup != nil { + options.setup(env) + } + + // Run a completion to make sure the system is warm. + loc := env.RegexpSearch(options.file, options.locationRegexp) + completions := env.Completion(loc) + + if testing.Verbose() { + fmt.Println("Results:") + for i := 0; i < len(completions.Items); i++ { + fmt.Printf("\t%d. %v\n", i, completions.Items[i]) + } + } + + b.Run("tools", func(b *testing.B) { + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName("tools", "completion")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + if options.beforeCompletion != nil { + options.beforeCompletion(env) + } + env.Completion(loc) + } + }) +} + +// endRangeInBuffer returns the position for last character in the buffer for +// the given file. +func endRangeInBuffer(env *Env, name string) protocol.Range { + buffer := env.BufferText(name) + m := protocol.NewMapper("", []byte(buffer)) + rng, err := m.OffsetRange(len(buffer), len(buffer)) + if err != nil { + env.T.Fatal(err) + } + return rng +} + +// Benchmark struct completion in tools codebase. +func BenchmarkStructCompletion(b *testing.B) { + file := "internal/lsp/cache/session.go" + + setup := func(env *Env) { + env.OpenFile(file) + env.EditBuffer(file, protocol.TextEdit{ + Range: endRangeInBuffer(env, file), + NewText: "\nvar testVariable map[string]bool = Session{}.\n", + }) + } + + benchmarkCompletion(completionBenchOptions{ + file: file, + locationRegexp: `var testVariable map\[string\]bool = Session{}(\.)`, + setup: setup, + }, b) +} + +// Benchmark import completion in tools codebase. +func BenchmarkImportCompletion(b *testing.B) { + const file = "internal/lsp/source/completion/completion.go" + benchmarkCompletion(completionBenchOptions{ + file: file, + locationRegexp: `go\/()`, + setup: func(env *Env) { env.OpenFile(file) }, + }, b) +} + +// Benchmark slice completion in tools codebase. +func BenchmarkSliceCompletion(b *testing.B) { + file := "internal/lsp/cache/session.go" + + setup := func(env *Env) { + env.OpenFile(file) + env.EditBuffer(file, protocol.TextEdit{ + Range: endRangeInBuffer(env, file), + NewText: "\nvar testVariable []byte = \n", + }) + } + + benchmarkCompletion(completionBenchOptions{ + file: file, + locationRegexp: `var testVariable \[\]byte (=)`, + setup: setup, + }, b) +} + +// Benchmark deep completion in function call in tools codebase. +func BenchmarkFuncDeepCompletion(b *testing.B) { + file := "internal/lsp/source/completion/completion.go" + fileContent := ` +func (c *completer) _() { + c.inference.kindMatches(c.) +} +` + setup := func(env *Env) { + env.OpenFile(file) + originalBuffer := env.BufferText(file) + env.EditBuffer(file, protocol.TextEdit{ + Range: endRangeInBuffer(env, file), + // TODO(rfindley): this is a bug: it should just be fileContent. + NewText: originalBuffer + fileContent, + }) + } + + benchmarkCompletion(completionBenchOptions{ + file: file, + locationRegexp: `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`, + setup: setup, + }, b) +} + +type completionTest struct { + repo string + name string + file string // repo-relative file to create + content string // file content + locationRegexp string // regexp for completion +} + +var completionTests = []completionTest{ + { + "tools", + "selector", + "internal/lsp/source/completion/completion2.go", + ` +package completion + +func (c *completer) _() { + c.inference.kindMatches(c.) +} +`, + `func \(c \*completer\) _\(\) {\n\tc\.inference\.kindMatches\((c)`, + }, + { + "tools", + "unimportedident", + "internal/lsp/source/completion/completion2.go", + ` +package completion + +func (c *completer) _() { + lo +} +`, + `lo()`, + }, + { + "tools", + "unimportedselector", + "internal/lsp/source/completion/completion2.go", + ` +package completion + +func (c *completer) _() { + log. +} +`, + `log\.()`, + }, + { + "kubernetes", + "selector", + "pkg/kubelet/kubelet2.go", + ` +package kubelet + +func (kl *Kubelet) _() { + kl. +} +`, + `kl\.()`, + }, + { + "kubernetes", + "identifier", + "pkg/kubelet/kubelet2.go", + ` +package kubelet + +func (kl *Kubelet) _() { + k // here +} +`, + `k() // here`, + }, + { + "oracle", + "selector", + "dataintegration/pivot2.go", + ` +package dataintegration + +func (p *Pivot) _() { + p. +} +`, + `p\.()`, + }, +} + +// Benchmark completion following an arbitrary edit. +// +// Edits force type-checked packages to be invalidated, so we want to measure +// how long it takes before completion results are available. +func BenchmarkCompletion(b *testing.B) { + for _, test := range completionTests { + b.Run(fmt.Sprintf("%s_%s", test.repo, test.name), func(b *testing.B) { + for _, followingEdit := range []bool{true, false} { + b.Run(fmt.Sprintf("edit=%v", followingEdit), func(b *testing.B) { + for _, completeUnimported := range []bool{true, false} { + b.Run(fmt.Sprintf("unimported=%v", completeUnimported), func(b *testing.B) { + for _, budget := range []string{"0s", "100ms"} { + b.Run(fmt.Sprintf("budget=%s", budget), func(b *testing.B) { + runCompletion(b, test, followingEdit, completeUnimported, budget) + }) + } + }) + } + }) + } + }) + } +} + +// For optimizing unimported completion, it can be useful to benchmark with a +// huge GOMODCACHE. +var gomodcache = flag.String("gomodcache", "", "optional GOMODCACHE for unimported completion benchmarks") + +func runCompletion(b *testing.B, test completionTest, followingEdit, completeUnimported bool, budget string) { + repo := getRepo(b, test.repo) + gopath := *completionGOPATH + if gopath == "" { + // use a warm GOPATH + sharedEnv := repo.sharedEnv(b) + gopath = sharedEnv.Sandbox.GOPATH() + } + envvars := map[string]string{ + "GOPATH": gopath, + } + + if *gomodcache != "" { + envvars["GOMODCACHE"] = *gomodcache + } + + env := repo.newEnv(b, fake.EditorConfig{ + Env: envvars, + Settings: map[string]interface{}{ + "completeUnimported": completeUnimported, + "completionBudget": budget, + }, + }, "completion", false) + defer env.Close() + + env.CreateBuffer(test.file, "// __TEST_PLACEHOLDER_0__\n"+test.content) + editPlaceholder := func() { + edits := atomic.AddInt64(&editID, 1) + env.EditBuffer(test.file, protocol.TextEdit{ + Range: protocol.Range{ + Start: protocol.Position{Line: 0, Character: 0}, + End: protocol.Position{Line: 1, Character: 0}, + }, + // Increment the placeholder text, to ensure cache misses. + NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits), + }) + } + env.AfterChange() + + // Run a completion to make sure the system is warm. + loc := env.RegexpSearch(test.file, test.locationRegexp) + completions := env.Completion(loc) + + if testing.Verbose() { + fmt.Println("Results:") + for i, item := range completions.Items { + fmt.Printf("\t%d. %v\n", i, item) + } + } + + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "completion")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + if followingEdit { + editPlaceholder() + } + loc := env.RegexpSearch(test.file, test.locationRegexp) + env.Completion(loc) + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/definition_test.go b/contribs/gnopls/internal/test/integration/bench/definition_test.go new file mode 100644 index 00000000000..b703378a27b --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/definition_test.go @@ -0,0 +1,46 @@ +// 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 bench + +import ( + "testing" +) + +func BenchmarkDefinition(b *testing.B) { + tests := []struct { + repo string + file string + regexp string + }{ + {"istio", "pkg/config/model.go", `gogotypes\.(MarshalAny)`}, + {"google-cloud-go", "httpreplay/httpreplay.go", `proxy\.(ForRecording)`}, + {"kubernetes", "pkg/controller/lookup_cache.go", `hashutil\.(DeepHashObject)`}, + {"kuma", "api/generic/insights.go", `proto\.(Message)`}, + {"pkgsite", "internal/log/log.go", `derrors\.(Wrap)`}, + {"starlark", "starlark/eval.go", "prog.compiled.(Encode)"}, + {"tools", "internal/lsp/cache/check.go", `(snapshot)\) buildKey`}, + } + + for _, test := range tests { + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + defer closeBuffer(b, env, test.file) + + loc := env.RegexpSearch(test.file, test.regexp) + env.Await(env.DoneWithOpen()) + env.GoToDefinition(loc) // pre-warm the query, and open the target file + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "definition")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + env.GoToDefinition(loc) // pre-warm the query + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/didchange_test.go b/contribs/gnopls/internal/test/integration/bench/didchange_test.go new file mode 100644 index 00000000000..22e7ca2a11b --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/didchange_test.go @@ -0,0 +1,142 @@ +// 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 bench + +import ( + "fmt" + "sync/atomic" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/integration/fake" +) + +// Use a global edit counter as bench function may execute multiple times, and +// we want to avoid cache hits. Use time.Now to also avoid cache hits from the +// shared file cache. +var editID int64 = time.Now().UnixNano() + +type changeTest struct { + repo string + file string + canSave bool +} + +var didChangeTests = []changeTest{ + {"google-cloud-go", "internal/annotate.go", true}, + {"istio", "pkg/fuzz/util.go", true}, + {"kubernetes", "pkg/controller/lookup_cache.go", true}, + {"kuma", "api/generic/insights.go", true}, + {"oracle", "dataintegration/data_type.go", false}, // diagnoseSave fails because this package is generated + {"pkgsite", "internal/frontend/server.go", true}, + {"starlark", "starlark/eval.go", true}, + {"tools", "internal/lsp/cache/snapshot.go", true}, +} + +// BenchmarkDidChange benchmarks modifications of a single file by making +// synthetic modifications in a comment. It controls pacing by waiting for the +// server to actually start processing the didChange notification before +// proceeding. Notably it does not wait for diagnostics to complete. +func BenchmarkDidChange(b *testing.B) { + for _, test := range didChangeTests { + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + defer closeBuffer(b, env, test.file) + + // Insert the text we'll be modifying at the top of the file. + env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"}) + env.AfterChange() + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "didchange")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + edits := atomic.AddInt64(&editID, 1) + env.EditBuffer(test.file, protocol.TextEdit{ + Range: protocol.Range{ + Start: protocol.Position{Line: 0, Character: 0}, + End: protocol.Position{Line: 1, Character: 0}, + }, + // Increment the placeholder text, to ensure cache misses. + NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits), + }) + env.Await(env.StartedChange()) + } + }) + } +} + +func BenchmarkDiagnoseChange(b *testing.B) { + for _, test := range didChangeTests { + runChangeDiagnosticsBenchmark(b, test, false, "diagnoseChange") + } +} + +// TODO(rfindley): add a benchmark for with a metadata-affecting change, when +// this matters. +func BenchmarkDiagnoseSave(b *testing.B) { + for _, test := range didChangeTests { + runChangeDiagnosticsBenchmark(b, test, true, "diagnoseSave") + } +} + +// runChangeDiagnosticsBenchmark runs a benchmark to edit the test file and +// await the resulting diagnostics pass. If save is set, the file is also saved. +func runChangeDiagnosticsBenchmark(b *testing.B, test changeTest, save bool, operation string) { + b.Run(test.repo, func(b *testing.B) { + if !test.canSave { + b.Skipf("skipping as %s cannot be saved", test.file) + } + sharedEnv := getRepo(b, test.repo).sharedEnv(b) + config := fake.EditorConfig{ + Env: map[string]string{ + "GOPATH": sharedEnv.Sandbox.GOPATH(), + }, + Settings: map[string]interface{}{ + "diagnosticsDelay": "0s", + }, + } + // Use a new env to avoid the diagnostic delay: we want to measure how + // long it takes to produce the diagnostics. + env := getRepo(b, test.repo).newEnv(b, config, operation, false) + defer env.Close() + env.OpenFile(test.file) + // Insert the text we'll be modifying at the top of the file. + env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"}) + if save { + env.SaveBuffer(test.file) + } + env.AfterChange() + b.ResetTimer() + + // We must use an extra subtest layer here, so that we only set up the + // shared env once (otherwise we pay additional overhead and the profiling + // flags don't work). + b.Run("diagnose", func(b *testing.B) { + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, operation)); stopAndRecord != nil { + defer stopAndRecord() + } + for i := 0; i < b.N; i++ { + edits := atomic.AddInt64(&editID, 1) + env.EditBuffer(test.file, protocol.TextEdit{ + Range: protocol.Range{ + Start: protocol.Position{Line: 0, Character: 0}, + End: protocol.Position{Line: 1, Character: 0}, + }, + // Increment the placeholder text, to ensure cache misses. + NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits), + }) + if save { + env.SaveBuffer(test.file) + } + env.AfterChange() + } + }) + }) +} diff --git a/contribs/gnopls/internal/test/integration/bench/doc.go b/contribs/gnopls/internal/test/integration/bench/doc.go new file mode 100644 index 00000000000..fff7bac1785 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/doc.go @@ -0,0 +1,40 @@ +// 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. + +// The bench package implements benchmarks for various LSP operations. +// +// Benchmarks check out specific commits of popular and/or exemplary +// repositories, and script an external gopls process via a fake text editor. +// By default, benchmarks run the test executable as gopls (using a special +// "gopls mode" environment variable). A different gopls binary may be used by +// setting the -gopls_path or -gopls_commit flags. +// +// This package is a work in progress. +// +// # Profiling +// +// Benchmark functions run gopls in a separate process, which means the normal +// test flags for profiling aren't useful. Instead the -gopls_cpuprofile, +// -gopls_memprofile, -gopls_allocprofile, and -gopls_trace flags may be used +// to pass through profiling to the gopls subproces. +// +// Each of these flags sets a suffix for the respective gopls profile, which is +// named according to the schema ... For example, +// setting -gopls_cpuprofile=cpu will result in profiles named tools.iwl.cpu, +// tools.rename.cpu, etc. In some cases, these profiles are for the entire +// gopls subprocess (as in the initial workspace load), whereas in others they +// span only the critical section of the benchmark. It is up to each benchmark +// to implement profiling as appropriate. +// +// # Integration with perf.golang.org +// +// Benchmarks that run with -short are automatically tracked by +// perf.golang.org, at +// https://perf.golang.org/dashboard/?benchmark=all&repository=tools&branch=release-branch.go1.20 +// +// # TODO +// - add more benchmarks, and more repositories +// - fix the perf dashboard to not require the branch= parameter +// - improve this documentation +package bench diff --git a/contribs/gnopls/internal/test/integration/bench/hover_test.go b/contribs/gnopls/internal/test/integration/bench/hover_test.go new file mode 100644 index 00000000000..c3b0c6bc0cb --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/hover_test.go @@ -0,0 +1,47 @@ +// 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 bench + +import ( + "testing" +) + +func BenchmarkHover(b *testing.B) { + tests := []struct { + repo string + file string + regexp string + }{ + {"google-cloud-go", "httpreplay/httpreplay.go", `proxy\.(ForRecording)`}, + {"istio", "pkg/config/model.go", `gogotypes\.(MarshalAny)`}, + {"kubernetes", "pkg/apis/core/types.go", "type (Pod)"}, + {"kuma", "api/generic/insights.go", `proto\.(Message)`}, + {"pkgsite", "internal/log/log.go", `derrors\.(Wrap)`}, + {"starlark", "starlark/eval.go", "prog.compiled.(Encode)"}, + {"tools", "internal/lsp/cache/check.go", `(snapshot)\) buildKey`}, + } + + for _, test := range tests { + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + defer closeBuffer(b, env, test.file) + + loc := env.RegexpSearch(test.file, test.regexp) + env.AfterChange() + + env.Hover(loc) // pre-warm the query + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "hover")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + env.Hover(loc) // pre-warm the query + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/implementations_test.go b/contribs/gnopls/internal/test/integration/bench/implementations_test.go new file mode 100644 index 00000000000..b7e08aa3141 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/implementations_test.go @@ -0,0 +1,44 @@ +// 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 bench + +import "testing" + +func BenchmarkImplementations(b *testing.B) { + tests := []struct { + repo string + file string + regexp string + }{ + {"google-cloud-go", "httpreplay/httpreplay.go", `type (Recorder)`}, + {"istio", "pkg/config/mesh/watcher.go", `type (Watcher)`}, + {"kubernetes", "pkg/controller/lookup_cache.go", `objectWithMeta`}, + {"kuma", "api/generic/insights.go", `type (Insight)`}, + {"pkgsite", "internal/datasource.go", `type (DataSource)`}, + {"starlark", "syntax/syntax.go", `type (Expr)`}, + {"tools", "internal/lsp/source/view.go", `type (Snapshot)`}, + } + + for _, test := range tests { + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + defer closeBuffer(b, env, test.file) + + loc := env.RegexpSearch(test.file, test.regexp) + env.AfterChange() + env.Implementations(loc) // pre-warm the query + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "implementations")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + env.Implementations(loc) + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/imports_test.go b/contribs/gnopls/internal/test/integration/bench/imports_test.go new file mode 100644 index 00000000000..97419cb10c5 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/imports_test.go @@ -0,0 +1,89 @@ +// 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 bench + +import ( + "context" + "flag" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" +) + +var gopath = flag.String("gopath", "", "if set, run goimports scan with this GOPATH value") + +func BenchmarkInitialGoimportsScan(b *testing.B) { + if *gopath == "" { + // This test doesn't make much sense with a tiny module cache. + // For now, don't bother trying to construct a huge cache, since it likely + // wouldn't work well on the perf builder. Instead, this benchmark only + // runs with a pre-existing GOPATH. + b.Skip("imports scan requires an explicit GOPATH to be set with -gopath") + } + + repo := getRepo(b, "tools") // since this a test of module cache scanning, any repo will do + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + func() { + // Unfortunately we (intentionally) don't support resetting the module + // cache scan state, so in order to have an accurate benchmark we must + // effectively restart gopls on every iteration. + // + // Warning: this can cause this benchmark to run quite slowly if the + // observed time (when the timer is running) is a tiny fraction of the + // actual time. + b.StopTimer() + config := fake.EditorConfig{ + Env: map[string]string{"GOPATH": *gopath}, + } + env := repo.newEnv(b, config, "imports", false) + defer env.Close() + env.Await(InitialWorkspaceLoad) + + // Create a buffer with a dangling selctor where the receiver is a single + // character ('a') that matches a large fraction of the module cache. + env.CreateBuffer("internal/lsp/cache/temp.go", ` +// This is a temp file to exercise goimports scan of the module cache. +package cache + +func _() { + _ = a.B // a dangling selector causes goimports to scan many packages +} +`) + env.AfterChange() + + // Force a scan of the imports cache, so that the goimports algorithm + // observes all directories. + env.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: command.ScanImports.String(), + }, nil) + + if stopAndRecord := startProfileIfSupported(b, env, "importsscan"); stopAndRecord != nil { + defer stopAndRecord() + } + + b.StartTimer() + if false { + // golang/go#67923: testing resuming imports scanning after a + // cancellation. + // + // Cancelling and then resuming the scan should take around the same + // amount of time. + ctx, cancel := context.WithTimeout(env.Ctx, 50*time.Millisecond) + defer cancel() + if err := env.Editor.OrganizeImports(ctx, "internal/lsp/cache/temp.go"); err != nil { + b.Logf("organize imports failed: %v", err) + } + } + env.OrganizeImports("internal/lsp/cache/temp.go") + }() + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/iwl_test.go b/contribs/gnopls/internal/test/integration/bench/iwl_test.go new file mode 100644 index 00000000000..ecf26f95463 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/iwl_test.go @@ -0,0 +1,72 @@ +// 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 bench + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" +) + +// BenchmarkInitialWorkspaceLoad benchmarks the initial workspace load time for +// a new editing session. +func BenchmarkInitialWorkspaceLoad(b *testing.B) { + repoNames := []string{ + "google-cloud-go", + "istio", + "kubernetes", + "kuma", + "oracle", + "pkgsite", + "starlark", + "tools", + "hashiform", + } + for _, repoName := range repoNames { + b.Run(repoName, func(b *testing.B) { + repo := getRepo(b, repoName) + // get the (initialized) shared env to ensure the cache is warm. + // Reuse its GOPATH so that we get cache hits for things in the module + // cache. + sharedEnv := repo.sharedEnv(b) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + doIWL(b, sharedEnv.Sandbox.GOPATH(), repo) + } + }) + } +} + +func doIWL(b *testing.B, gopath string, repo *repo) { + // Exclude the time to set up the env from the benchmark time, as this may + // involve installing gopls and/or checking out the repo dir. + b.StopTimer() + config := fake.EditorConfig{Env: map[string]string{"GOPATH": gopath}} + env := repo.newEnv(b, config, "iwl", true) + defer env.Close() + b.StartTimer() + + // Note: in the future, we may need to open a file in order to cause gopls to + // start loading the workspace. + + env.Await(InitialWorkspaceLoad) + + if env.Editor.HasCommand(command.MemStats) { + b.StopTimer() + params := &protocol.ExecuteCommandParams{ + Command: command.MemStats.String(), + } + var memstats command.MemStatsResult + env.ExecuteCommand(params, &memstats) + b.ReportMetric(float64(memstats.HeapAlloc), "alloc_bytes") + b.ReportMetric(float64(memstats.HeapInUse), "in_use_bytes") + b.ReportMetric(float64(memstats.TotalAlloc), "total_alloc_bytes") + b.StartTimer() + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/references_test.go b/contribs/gnopls/internal/test/integration/bench/references_test.go new file mode 100644 index 00000000000..aeaba6f5683 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/references_test.go @@ -0,0 +1,44 @@ +// 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 bench + +import "testing" + +func BenchmarkReferences(b *testing.B) { + tests := []struct { + repo string + file string + regexp string + }{ + {"google-cloud-go", "httpreplay/httpreplay.go", `func (NewRecorder)`}, + {"istio", "pkg/config/model.go", "type (Meta)"}, + {"kubernetes", "pkg/controller/lookup_cache.go", "type (objectWithMeta)"}, // TODO: choose an exported identifier + {"kuma", "pkg/events/interfaces.go", "type (Event)"}, + {"pkgsite", "internal/log/log.go", "func (Infof)"}, + {"starlark", "syntax/syntax.go", "type (Ident)"}, + {"tools", "internal/lsp/source/view.go", "type (Snapshot)"}, + } + + for _, test := range tests { + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + defer closeBuffer(b, env, test.file) + + loc := env.RegexpSearch(test.file, test.regexp) + env.AfterChange() + env.References(loc) // pre-warm the query + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "references")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + env.References(loc) + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/reload_test.go b/contribs/gnopls/internal/test/integration/bench/reload_test.go new file mode 100644 index 00000000000..332809ee1eb --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/reload_test.go @@ -0,0 +1,52 @@ +// 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 bench + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// BenchmarkReload benchmarks reloading a file metadata after a change to an import. +// +// This ensures we are able to diagnose a changed file without reloading all +// invalidated packages. See also golang/go#61344 +func BenchmarkReload(b *testing.B) { + // TODO(rfindley): add more tests, make this test table-driven + const ( + repo = "kubernetes" + // pkg/util/hash is transitively imported by a large number of packages. + // We should not need to reload those packages to get a diagnostic. + file = "pkg/util/hash/hash.go" + ) + b.Run(repo, func(b *testing.B) { + env := getRepo(b, repo).sharedEnv(b) + + env.OpenFile(file) + defer closeBuffer(b, env, file) + + env.AfterChange() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(repo, "reload")); stopAndRecord != nil { + defer stopAndRecord() + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Change the "hash" import. This may result in cache hits, but that's + // OK: the goal is to ensure that we don't reload more than just the + // current package. + env.RegexpReplace(file, `"hash"`, `"hashx"`) + // Note: don't use env.AfterChange() here: we only want to await the + // first diagnostic. + // + // Awaiting a full diagnosis would await diagnosing everything, which + // would require reloading everything. + env.Await(Diagnostics(ForFile(file))) + env.RegexpReplace(file, `"hashx"`, `"hash"`) + env.Await(NoDiagnostics(ForFile(file))) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/bench/rename_test.go b/contribs/gnopls/internal/test/integration/bench/rename_test.go new file mode 100644 index 00000000000..ca5ed5f4397 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/rename_test.go @@ -0,0 +1,49 @@ +// 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 bench + +import ( + "fmt" + "testing" +) + +func BenchmarkRename(b *testing.B) { + tests := []struct { + repo string + file string + regexp string + baseName string + }{ + {"google-cloud-go", "httpreplay/httpreplay.go", `func (NewRecorder)`, "NewRecorder"}, + {"istio", "pkg/config/model.go", `(Namespace) string`, "Namespace"}, + {"kubernetes", "pkg/controller/lookup_cache.go", `hashutil\.(DeepHashObject)`, "DeepHashObject"}, + {"kuma", "pkg/events/interfaces.go", `Delete`, "Delete"}, + {"pkgsite", "internal/log/log.go", `func (Infof)`, "Infof"}, + {"starlark", "starlark/eval.go", `Program\) (Filename)`, "Filename"}, + {"tools", "internal/lsp/cache/snapshot.go", `meta \*(metadataGraph)`, "metadataGraph"}, + } + + for _, test := range tests { + names := 0 // bench function may execute multiple times + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + loc := env.RegexpSearch(test.file, test.regexp) + env.Await(env.DoneWithOpen()) + env.Rename(loc, test.baseName+"X") // pre-warm the query + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "rename")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + names++ + newName := fmt.Sprintf("%s%d", test.baseName, names) + env.Rename(loc, newName) + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/repo_test.go b/contribs/gnopls/internal/test/integration/bench/repo_test.go new file mode 100644 index 00000000000..0e86f3e1da7 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/repo_test.go @@ -0,0 +1,290 @@ +// 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 bench + +import ( + "bytes" + "context" + "errors" + "flag" + "fmt" + "log" + "os" + "path/filepath" + "sync" + "testing" + "time" + + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" +) + +// repos holds shared repositories for use in benchmarks. +// +// These repos were selected to represent a variety of different types of +// codebases. +var repos = map[string]*repo{ + // google-cloud-go has 145 workspace modules (!), and is quite large. + "google-cloud-go": { + name: "google-cloud-go", + url: "https://github.com/googleapis/google-cloud-go.git", + commit: "07da765765218debf83148cc7ed8a36d6e8921d5", + inDir: flag.String("cloud_go_dir", "", "if set, reuse this directory as google-cloud-go@07da7657"), + }, + + // Used by x/benchmarks; large. + "istio": { + name: "istio", + url: "https://github.com/istio/istio", + commit: "1.17.0", + inDir: flag.String("istio_dir", "", "if set, reuse this directory as istio@v1.17.0"), + }, + + // Kubernetes is a large repo with many dependencies, and in the past has + // been about as large a repo as gopls could handle. + "kubernetes": { + name: "kubernetes", + url: "https://github.com/kubernetes/kubernetes", + commit: "v1.24.0", + short: true, + inDir: flag.String("kubernetes_dir", "", "if set, reuse this directory as kubernetes@v1.24.0"), + }, + + // A large, industrial application. + "kuma": { + name: "kuma", + url: "https://github.com/kumahq/kuma", + commit: "2.1.1", + inDir: flag.String("kuma_dir", "", "if set, reuse this directory as kuma@v2.1.1"), + }, + + // A repo containing a very large package (./dataintegration). + "oracle": { + name: "oracle", + url: "https://github.com/oracle/oci-go-sdk.git", + commit: "v65.43.0", + short: true, + inDir: flag.String("oracle_dir", "", "if set, reuse this directory as oracle/oci-go-sdk@v65.43.0"), + }, + + // x/pkgsite is familiar and represents a common use case (a webserver). It + // also has a number of static non-go files and template files. + "pkgsite": { + name: "pkgsite", + url: "https://go.googlesource.com/pkgsite", + commit: "81f6f8d4175ad0bf6feaa03543cc433f8b04b19b", + short: true, + inDir: flag.String("pkgsite_dir", "", "if set, reuse this directory as pkgsite@81f6f8d4"), + }, + + // A tiny self-contained project. + "starlark": { + name: "starlark", + url: "https://github.com/google/starlark-go", + commit: "3f75dec8e4039385901a30981e3703470d77e027", + short: true, + inDir: flag.String("starlark_dir", "", "if set, reuse this directory as starlark@3f75dec8"), + }, + + // The current repository, which is medium-small and has very few dependencies. + "tools": { + name: "tools", + url: "https://go.googlesource.com/tools", + commit: "gopls/v0.9.0", + short: true, + inDir: flag.String("tools_dir", "", "if set, reuse this directory as x/tools@v0.9.0"), + }, + + // A repo of similar size to kubernetes, but with substantially more + // complex types that led to a serious performance regression (issue #60621). + "hashiform": { + name: "hashiform", + url: "https://github.com/hashicorp/terraform-provider-aws", + commit: "ac55de2b1950972d93feaa250d7505d9ed829c7c", + inDir: flag.String("hashiform_dir", "", "if set, reuse this directory as hashiform@ac55de2"), + }, +} + +// getRepo gets the requested repo, and skips the test if -short is set and +// repo is not configured as a short repo. +func getRepo(tb testing.TB, name string) *repo { + tb.Helper() + repo := repos[name] + if repo == nil { + tb.Fatalf("repo %s does not exist", name) + } + if !repo.short && testing.Short() { + tb.Skipf("large repo %s does not run with -short", repo.name) + } + return repo +} + +// A repo represents a working directory for a repository checked out at a +// specific commit. +// +// Repos are used for sharing state across benchmarks that operate on the same +// codebase. +type repo struct { + // static configuration + name string // must be unique, used for subdirectory + url string // repo url + commit string // full commit hash or tag + short bool // whether this repo runs with -short + inDir *string // if set, use this dir as url@commit, and don't delete + + dirOnce sync.Once + dir string // directory containing source code checked out to url@commit + + // shared editor state + editorOnce sync.Once + editor *fake.Editor + sandbox *fake.Sandbox + awaiter *Awaiter +} + +// reusableDir return a reusable directory for benchmarking, or "". +// +// If the user specifies a directory, the test will create and populate it +// on the first run an re-use it on subsequent runs. Otherwise it will +// create, populate, and delete a temporary directory. +func (r *repo) reusableDir() string { + if r.inDir == nil { + return "" + } + return *r.inDir +} + +// getDir returns directory containing repo source code, creating it if +// necessary. It is safe for concurrent use. +func (r *repo) getDir() string { + r.dirOnce.Do(func() { + if r.dir = r.reusableDir(); r.dir == "" { + r.dir = filepath.Join(getTempDir(), r.name) + } + + _, err := os.Stat(r.dir) + switch { + case os.IsNotExist(err): + log.Printf("cloning %s@%s into %s", r.url, r.commit, r.dir) + if err := shallowClone(r.dir, r.url, r.commit); err != nil { + log.Fatal(err) + } + case err != nil: + log.Fatal(err) + default: + log.Printf("reusing %s as %s@%s", r.dir, r.url, r.commit) + } + }) + return r.dir +} + +// sharedEnv returns a shared benchmark environment. It is safe for concurrent +// use. +// +// Every call to sharedEnv uses the same editor and sandbox, as a means to +// avoid reinitializing the editor for large repos. Calling repo.Close cleans +// up the shared environment. +// +// Repos in the package-local Repos var are closed at the end of the test main +// function. +func (r *repo) sharedEnv(tb testing.TB) *Env { + r.editorOnce.Do(func() { + dir := r.getDir() + + start := time.Now() + log.Printf("starting initial workspace load for %s", r.name) + ts, err := newGoplsConnector(profileArgs(r.name, false)) + if err != nil { + log.Fatal(err) + } + r.sandbox, r.editor, r.awaiter, err = connectEditor(dir, fake.EditorConfig{}, ts) + if err != nil { + log.Fatalf("connecting editor: %v", err) + } + + if err := r.awaiter.Await(context.Background(), InitialWorkspaceLoad); err != nil { + log.Fatal(err) + } + log.Printf("initial workspace load (cold) for %s took %v", r.name, time.Since(start)) + }) + + return &Env{ + T: tb, + Ctx: context.Background(), + Editor: r.editor, + Sandbox: r.sandbox, + Awaiter: r.awaiter, + } +} + +// newEnv returns a new Env connected to a new gopls process communicating +// over stdin/stdout. It is safe for concurrent use. +// +// It is the caller's responsibility to call Close on the resulting Env when it +// is no longer needed. +func (r *repo) newEnv(tb testing.TB, config fake.EditorConfig, forOperation string, cpuProfile bool) *Env { + dir := r.getDir() + + args := profileArgs(qualifiedName(r.name, forOperation), cpuProfile) + ts, err := newGoplsConnector(args) + if err != nil { + tb.Fatal(err) + } + sandbox, editor, awaiter, err := connectEditor(dir, config, ts) + if err != nil { + log.Fatalf("connecting editor: %v", err) + } + + return &Env{ + T: tb, + Ctx: context.Background(), + Editor: editor, + Sandbox: sandbox, + Awaiter: awaiter, + } +} + +// Close cleans up shared state referenced by the repo. +func (r *repo) Close() error { + var errBuf bytes.Buffer + if r.editor != nil { + if err := r.editor.Close(context.Background()); err != nil { + fmt.Fprintf(&errBuf, "closing editor: %v", err) + } + } + if r.sandbox != nil { + if err := r.sandbox.Close(); err != nil { + fmt.Fprintf(&errBuf, "closing sandbox: %v", err) + } + } + if r.dir != "" && r.reusableDir() == "" { + if err := os.RemoveAll(r.dir); err != nil { + fmt.Fprintf(&errBuf, "cleaning dir: %v", err) + } + } + if errBuf.Len() > 0 { + return errors.New(errBuf.String()) + } + return nil +} + +// cleanup cleans up state that is shared across benchmark functions. +func cleanup() error { + var errBuf bytes.Buffer + for _, repo := range repos { + if err := repo.Close(); err != nil { + fmt.Fprintf(&errBuf, "closing %q: %v", repo.name, err) + } + } + if tempDir != "" { + if err := os.RemoveAll(tempDir); err != nil { + fmt.Fprintf(&errBuf, "cleaning tempDir: %v", err) + } + } + if errBuf.Len() > 0 { + return errors.New(errBuf.String()) + } + return nil +} diff --git a/contribs/gnopls/internal/test/integration/bench/stress_test.go b/contribs/gnopls/internal/test/integration/bench/stress_test.go new file mode 100644 index 00000000000..1b63e3aff9e --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/stress_test.go @@ -0,0 +1,92 @@ +// 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 bench + +import ( + "context" + "flag" + "fmt" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/lsprpc" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/jsonrpc2/servertest" +) + +// github.com/pilosa/pilosa is a repository that has historically caused +// significant memory problems for Gopls. We use it for a simple stress test +// that types arbitrarily in a file with lots of dependents. + +var pilosaPath = flag.String("pilosa_path", "", "Path to a directory containing "+ + "github.com/pilosa/pilosa, for stress testing. Do not set this unless you "+ + "know what you're doing!") + +func TestPilosaStress(t *testing.T) { + // TODO(rfindley): revisit this test and make it is hermetic: it should check + // out pilosa into a directory. + // + // Note: This stress test has not been run recently, and may no longer + // function properly. + if *pilosaPath == "" { + t.Skip("-pilosa_path not configured") + } + + sandbox, err := fake.NewSandbox(&fake.SandboxConfig{ + Workdir: *pilosaPath, + GOPROXY: "https://proxy.golang.org", + }) + if err != nil { + t.Fatal(err) + } + + server := lsprpc.NewStreamServer(cache.New(nil), false, nil) + ts := servertest.NewPipeServer(server, jsonrpc2.NewRawStream) + ctx := context.Background() + + editor, err := fake.NewEditor(sandbox, fake.EditorConfig{}).Connect(ctx, ts, fake.ClientHooks{}) + if err != nil { + t.Fatal(err) + } + + files := []string{ + "cmd.go", + "internal/private.pb.go", + "roaring/roaring.go", + "roaring/roaring_internal_test.go", + "server/handler_test.go", + } + for _, file := range files { + if err := editor.OpenFile(ctx, file); err != nil { + t.Fatal(err) + } + } + ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) + defer cancel() + + i := 1 + // MagicNumber is an identifier that occurs in roaring.go. Just change it + // arbitrarily. + if err := editor.RegexpReplace(ctx, "roaring/roaring.go", "MagicNumber", fmt.Sprintf("MagicNumber%d", 1)); err != nil { + t.Fatal(err) + } + for { + select { + case <-ctx.Done(): + return + default: + } + if err := editor.RegexpReplace(ctx, "roaring/roaring.go", fmt.Sprintf("MagicNumber%d", i), fmt.Sprintf("MagicNumber%d", i+1)); err != nil { + t.Fatal(err) + } + // Simulate (very fast) typing. + // + // Typing 80 wpm ~150ms per keystroke. + time.Sleep(150 * time.Millisecond) + i++ + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/tests_test.go b/contribs/gnopls/internal/test/integration/bench/tests_test.go new file mode 100644 index 00000000000..3bc69ef95e1 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/tests_test.go @@ -0,0 +1,96 @@ +// 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 bench + +import ( + "encoding/json" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/test/integration" +) + +func BenchmarkPackagesCommand(b *testing.B) { + // By convention, x/benchmarks runs the gopls benchmarks with -short, so that + // we can use this flag to filter out benchmarks that should not be run by + // the perf builder. + // + // In this case, the benchmark must be skipped because the current baseline + // (gopls@v0.11.0) lacks the gopls.package command. + if testing.Short() { + b.Skip("not supported by the benchmark dashboard baseline") + } + + tests := []struct { + repo string + files []string + recurse bool + }{ + {"tools", []string{"internal/lsp/debounce_test.go"}, false}, + } + for _, test := range tests { + b.Run(test.repo, func(b *testing.B) { + args := command.PackagesArgs{ + Mode: command.NeedTests, + } + + env := getRepo(b, test.repo).sharedEnv(b) + for _, file := range test.files { + env.OpenFile(file) + defer closeBuffer(b, env, file) + args.Files = append(args.Files, env.Editor.DocumentURI(file)) + } + env.AfterChange() + + result := executePackagesCmd(b, env, args) // pre-warm + + // sanity check JSON {en,de}coding + var pkgs command.PackagesResult + data, err := json.Marshal(result) + if err != nil { + b.Fatal(err) + } + err = json.Unmarshal(data, &pkgs) + if err != nil { + b.Fatal(err) + } + var haveTest bool + for _, pkg := range pkgs.Packages { + for _, file := range pkg.TestFiles { + if len(file.Tests) > 0 { + haveTest = true + break + } + } + } + if !haveTest { + b.Fatalf("Expected tests") + } + + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "packages")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + executePackagesCmd(b, env, args) + } + }) + } +} + +func executePackagesCmd(t testing.TB, env *integration.Env, args command.PackagesArgs) any { + t.Helper() + cmd := command.NewPackagesCommand("Packages", args) + result, err := env.Editor.Server.ExecuteCommand(env.Ctx, &protocol.ExecuteCommandParams{ + Command: command.Packages.String(), + Arguments: cmd.Arguments, + }) + if err != nil { + t.Fatal(err) + } + return result +} diff --git a/contribs/gnopls/internal/test/integration/bench/typing_test.go b/contribs/gnopls/internal/test/integration/bench/typing_test.go new file mode 100644 index 00000000000..78bd16cef5b --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/typing_test.go @@ -0,0 +1,63 @@ +// 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 bench + +import ( + "fmt" + "sync/atomic" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// BenchmarkTyping simulates typing steadily in a single file at different +// paces. +// +// The key metric for this benchmark is not latency, but cpu_seconds per +// operation. +func BenchmarkTyping(b *testing.B) { + for _, test := range didChangeTests { + b.Run(test.repo, func(b *testing.B) { + env := getRepo(b, test.repo).sharedEnv(b) + env.OpenFile(test.file) + defer closeBuffer(b, env, test.file) + + // Insert the text we'll be modifying at the top of the file. + env.EditBuffer(test.file, protocol.TextEdit{NewText: "// __TEST_PLACEHOLDER_0__\n"}) + env.AfterChange() + + delays := []time.Duration{ + 10 * time.Millisecond, // automated changes + 50 * time.Millisecond, // very fast mashing, or fast key sequences + 150 * time.Millisecond, // avg interval for 80wpm typing. + } + + for _, delay := range delays { + b.Run(delay.String(), func(b *testing.B) { + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(test.repo, "typing")); stopAndRecord != nil { + defer stopAndRecord() + } + ticker := time.NewTicker(delay) + for i := 0; i < b.N; i++ { + edits := atomic.AddInt64(&editID, 1) + env.EditBuffer(test.file, protocol.TextEdit{ + Range: protocol.Range{ + Start: protocol.Position{Line: 0, Character: 0}, + End: protocol.Position{Line: 1, Character: 0}, + }, + // Increment the placeholder text, to ensure cache misses. + NewText: fmt.Sprintf("// __TEST_PLACEHOLDER_%d__\n", edits), + }) + <-ticker.C + } + b.StopTimer() + ticker.Stop() + env.AfterChange() // wait for all change processing to complete + }) + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/bench/workspace_symbols_test.go b/contribs/gnopls/internal/test/integration/bench/workspace_symbols_test.go new file mode 100644 index 00000000000..94dd9e08cf3 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/bench/workspace_symbols_test.go @@ -0,0 +1,41 @@ +// 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 bench + +import ( + "flag" + "fmt" + "testing" +) + +var symbolQuery = flag.String("symbol_query", "test", "symbol query to use in benchmark") + +// BenchmarkWorkspaceSymbols benchmarks the time to execute a workspace symbols +// request (controlled by the -symbol_query flag). +func BenchmarkWorkspaceSymbols(b *testing.B) { + for name := range repos { + b.Run(name, func(b *testing.B) { + env := getRepo(b, name).sharedEnv(b) + symbols := env.Symbol(*symbolQuery) // warm the cache + + if testing.Verbose() { + fmt.Println("Results:") + for i, symbol := range symbols { + fmt.Printf("\t%d. %s (%s)\n", i, symbol.Name, symbol.ContainerName) + } + } + + b.ResetTimer() + + if stopAndRecord := startProfileIfSupported(b, env, qualifiedName(name, "workspaceSymbols")); stopAndRecord != nil { + defer stopAndRecord() + } + + for i := 0; i < b.N; i++ { + env.Symbol(*symbolQuery) + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/codelens/codelens_test.go b/contribs/gnopls/internal/test/integration/codelens/codelens_test.go new file mode 100644 index 00000000000..75b9fda1fbf --- /dev/null +++ b/contribs/gnopls/internal/test/integration/codelens/codelens_test.go @@ -0,0 +1,411 @@ +// 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 codelens + +import ( + "fmt" + "os" + "testing" + + "golang.org/x/tools/gopls/internal/server" + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/util/bug" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/internal/testenv" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(Main(m)) +} + +func TestDisablingCodeLens(t *testing.T) { + const workspace = ` +-- go.mod -- +module codelens.test + +go 1.12 +-- lib.go -- +package lib + +type Number int + +const ( + Zero Number = iota + One + Two +) + +//` + `go:generate stringer -type=Number +` + tests := []struct { + label string + enabled map[string]bool + wantCodeLens bool + }{ + { + label: "default", + wantCodeLens: true, + }, + { + label: "generate disabled", + enabled: map[string]bool{string(settings.CodeLensGenerate): false}, + wantCodeLens: false, + }, + } + for _, test := range tests { + t.Run(test.label, func(t *testing.T) { + WithOptions( + Settings{"codelenses": test.enabled}, + ).Run(t, workspace, func(t *testing.T, env *Env) { + env.OpenFile("lib.go") + lens := env.CodeLens("lib.go") + if gotCodeLens := len(lens) > 0; gotCodeLens != test.wantCodeLens { + t.Errorf("got codeLens: %t, want %t", gotCodeLens, test.wantCodeLens) + } + }) + }) + } +} + +const proxyWithLatest = ` +-- golang.org/x/hello@v1.3.3/go.mod -- +module golang.org/x/hello + +go 1.12 +-- golang.org/x/hello@v1.3.3/hi/hi.go -- +package hi + +var Goodbye error +-- golang.org/x/hello@v1.2.3/go.mod -- +module golang.org/x/hello + +go 1.12 +-- golang.org/x/hello@v1.2.3/hi/hi.go -- +package hi + +var Goodbye error +` + +// This test confirms the full functionality of the code lenses for updating +// dependencies in a go.mod file, when using a go.work file. It checks for the +// code lens that suggests an update and then executes the command associated +// with that code lens. A regression test for golang/go#39446. It also checks +// that these code lenses only affect the diagnostics and contents of the +// containing go.mod file. +func TestUpgradeCodelens_Workspace(t *testing.T) { + const shouldUpdateDep = ` +-- go.work -- +go 1.18 + +use ( + ./a + ./b +) +-- a/go.mod -- +module mod.com/a + +go 1.14 + +require golang.org/x/hello v1.2.3 +-- a/go.sum -- +golang.org/x/hello v1.2.3 h1:7Wesfkx/uBd+eFgPrq0irYj/1XfmbvLV8jZ/W7C2Dwg= +golang.org/x/hello v1.2.3/go.mod h1:OgtlzsxVMUUdsdQCIDYgaauCTH47B8T8vofouNJfzgY= +-- a/main.go -- +package main + +import "golang.org/x/hello/hi" + +func main() { + _ = hi.Goodbye +} +-- b/go.mod -- +module mod.com/b + +go 1.14 + +require golang.org/x/hello v1.2.3 +-- b/go.sum -- +golang.org/x/hello v1.2.3 h1:7Wesfkx/uBd+eFgPrq0irYj/1XfmbvLV8jZ/W7C2Dwg= +golang.org/x/hello v1.2.3/go.mod h1:OgtlzsxVMUUdsdQCIDYgaauCTH47B8T8vofouNJfzgY= +-- b/main.go -- +package main + +import ( + "golang.org/x/hello/hi" +) + +func main() { + _ = hi.Goodbye +} +` + + const wantGoModA = `module mod.com/a + +go 1.14 + +require golang.org/x/hello v1.3.3 +` + // Applying the diagnostics or running the codelenses for a/go.mod + // should not change the contents of b/go.mod + const wantGoModB = `module mod.com/b + +go 1.14 + +require golang.org/x/hello v1.2.3 +` + + for _, commandTitle := range []string{ + "Upgrade transitive dependencies", + "Upgrade direct dependencies", + } { + t.Run(commandTitle, func(t *testing.T) { + WithOptions( + ProxyFiles(proxyWithLatest), + ).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) { + env.OpenFile("a/go.mod") + env.OpenFile("b/go.mod") + var lens protocol.CodeLens + var found bool + for _, l := range env.CodeLens("a/go.mod") { + if l.Command.Title == commandTitle { + lens = l + found = true + } + } + if !found { + t.Fatalf("found no command with the title %s", commandTitle) + } + if _, err := env.Editor.ExecuteCommand(env.Ctx, &protocol.ExecuteCommandParams{ + Command: lens.Command.Command, + Arguments: lens.Command.Arguments, + }); err != nil { + t.Fatal(err) + } + env.AfterChange() + if got := env.BufferText("a/go.mod"); got != wantGoModA { + t.Fatalf("a/go.mod upgrade failed:\n%s", compare.Text(wantGoModA, got)) + } + if got := env.BufferText("b/go.mod"); got != wantGoModB { + t.Fatalf("b/go.mod changed unexpectedly:\n%s", compare.Text(wantGoModB, got)) + } + }) + }) + } + for _, vendoring := range []bool{false, true} { + t.Run(fmt.Sprintf("Upgrade individual dependency vendoring=%v", vendoring), func(t *testing.T) { + WithOptions( + ProxyFiles(proxyWithLatest), + ).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) { + if vendoring { + env.RunGoCommandInDirWithEnv("a", []string{"GOWORK=off"}, "mod", "vendor") + } + env.AfterChange() + env.OpenFile("a/go.mod") + env.OpenFile("b/go.mod") + + env.ExecuteCodeLensCommand("a/go.mod", command.CheckUpgrades, nil) + d := &protocol.PublishDiagnosticsParams{} + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromCheckUpgrades), 1, true), + Diagnostics(env.AtRegexp("a/go.mod", `require`), WithMessage("can be upgraded")), + ReadDiagnostics("a/go.mod", d), + // We do not want there to be a diagnostic for b/go.mod, + // but there may be some subtlety in timing here, where this + // should always succeed, but may not actually test the correct + // behavior. + NoDiagnostics(env.AtRegexp("b/go.mod", `require`)), + ) + // Check for upgrades in b/go.mod and then clear them. + env.ExecuteCodeLensCommand("b/go.mod", command.CheckUpgrades, nil) + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromCheckUpgrades), 2, true), + Diagnostics(env.AtRegexp("b/go.mod", `require`), WithMessage("can be upgraded")), + ) + env.ExecuteCodeLensCommand("b/go.mod", command.ResetGoModDiagnostics, nil) + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromResetGoModDiagnostics), 1, true), + NoDiagnostics(ForFile("b/go.mod")), + ) + + // Apply the diagnostics to a/go.mod. + env.ApplyQuickFixes("a/go.mod", d.Diagnostics) + env.AfterChange() + if got := env.BufferText("a/go.mod"); got != wantGoModA { + t.Fatalf("a/go.mod upgrade failed:\n%s", compare.Text(wantGoModA, got)) + } + if got := env.BufferText("b/go.mod"); got != wantGoModB { + t.Fatalf("b/go.mod changed unexpectedly:\n%s", compare.Text(wantGoModB, got)) + } + }) + }) + } +} + +func TestUpgradeCodelens_ModVendor(t *testing.T) { + // This test checks the regression of golang/go#66055. The upgrade codelens + // should work in a mod vendor context (the test above using a go.work file + // was not broken). + testenv.NeedsGo1Point(t, 22) + const shouldUpdateDep = ` +-- go.mod -- +module mod.com/a + +go 1.22 + +require golang.org/x/hello v1.2.3 +-- go.sum -- +golang.org/x/hello v1.2.3 h1:7Wesfkx/uBd+eFgPrq0irYj/1XfmbvLV8jZ/W7C2Dwg= +golang.org/x/hello v1.2.3/go.mod h1:OgtlzsxVMUUdsdQCIDYgaauCTH47B8T8vofouNJfzgY= +-- main.go -- +package main + +import "golang.org/x/hello/hi" + +func main() { + _ = hi.Goodbye +} +` + + const wantGoModA = `module mod.com/a + +go 1.22 + +require golang.org/x/hello v1.3.3 +` + + WithOptions( + ProxyFiles(proxyWithLatest), + ).Run(t, shouldUpdateDep, func(t *testing.T, env *Env) { + env.RunGoCommand("mod", "vendor") + env.AfterChange() + env.OpenFile("go.mod") + + env.ExecuteCodeLensCommand("go.mod", command.CheckUpgrades, nil) + d := &protocol.PublishDiagnosticsParams{} + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromCheckUpgrades), 1, true), + Diagnostics(env.AtRegexp("go.mod", `require`), WithMessage("can be upgraded")), + ReadDiagnostics("go.mod", d), + ) + + // Apply the diagnostics to a/go.mod. + env.ApplyQuickFixes("go.mod", d.Diagnostics) + env.AfterChange() + if got := env.BufferText("go.mod"); got != wantGoModA { + t.Fatalf("go.mod upgrade failed:\n%s", compare.Text(wantGoModA, got)) + } + }) +} + +func TestUnusedDependenciesCodelens(t *testing.T) { + const proxy = ` +-- golang.org/x/hello@v1.0.0/go.mod -- +module golang.org/x/hello + +go 1.14 +-- golang.org/x/hello@v1.0.0/hi/hi.go -- +package hi + +var Goodbye error +-- golang.org/x/unused@v1.0.0/go.mod -- +module golang.org/x/unused + +go 1.14 +-- golang.org/x/unused@v1.0.0/nouse/nouse.go -- +package nouse + +var NotUsed error +` + + const shouldRemoveDep = ` +-- go.mod -- +module mod.com + +go 1.14 + +require golang.org/x/hello v1.0.0 +require golang.org/x/unused v1.0.0 + +// EOF +-- go.sum -- +golang.org/x/hello v1.0.0 h1:qbzE1/qT0/zojAMd/JcPsO2Vb9K4Bkeyq0vB2JGMmsw= +golang.org/x/hello v1.0.0/go.mod h1:WW7ER2MRNXWA6c8/4bDIek4Hc/+DofTrMaQQitGXcco= +golang.org/x/unused v1.0.0 h1:LecSbCn5P3vTcxubungSt1Pn4D/WocCaiWOPDC0y0rw= +golang.org/x/unused v1.0.0/go.mod h1:ihoW8SgWzugwwj0N2SfLfPZCxTB1QOVfhMfB5PWTQ8U= +-- main.go -- +package main + +import "golang.org/x/hello/hi" + +func main() { + _ = hi.Goodbye +} +` + WithOptions(ProxyFiles(proxy)).Run(t, shouldRemoveDep, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.RegexpReplace("go.mod", "// EOF", "// EOF unsaved edit") // unsaved edits ok + env.ExecuteCodeLensCommand("go.mod", command.Tidy, nil) + env.AfterChange() + got := env.BufferText("go.mod") + const wantGoMod = `module mod.com + +go 1.14 + +require golang.org/x/hello v1.0.0 + +// EOF unsaved edit +` + if got != wantGoMod { + t.Fatalf("go.mod tidy failed:\n%s", compare.Text(wantGoMod, got)) + } + }) +} + +func TestRegenerateCgo(t *testing.T) { + testenv.NeedsTool(t, "cgo") + const workspace = ` +-- go.mod -- +module example.com + +go 1.12 +-- cgo.go -- +package x + +/* +int fortythree() { return 42; } +*/ +import "C" + +func Foo() { + print(C.fortytwo()) +} +` + Run(t, workspace, func(t *testing.T, env *Env) { + // Open the file. We have a nonexistant symbol that will break cgo processing. + env.OpenFile("cgo.go") + env.AfterChange( + Diagnostics(env.AtRegexp("cgo.go", ``), WithMessage("go list failed to return CompiledGoFiles")), + ) + + // Fix the C function name. We haven't regenerated cgo, so nothing should be fixed. + env.RegexpReplace("cgo.go", `int fortythree`, "int fortytwo") + env.SaveBuffer("cgo.go") + env.AfterChange( + Diagnostics(env.AtRegexp("cgo.go", ``), WithMessage("go list failed to return CompiledGoFiles")), + ) + + // Regenerate cgo, fixing the diagnostic. + env.ExecuteCodeLensCommand("cgo.go", command.RegenerateCgo, nil) + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromRegenerateCgo), 1, true), + NoDiagnostics(ForFile("cgo.go")), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/codelens/gcdetails_test.go b/contribs/gnopls/internal/test/integration/codelens/gcdetails_test.go new file mode 100644 index 00000000000..359a7804ec4 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/codelens/gcdetails_test.go @@ -0,0 +1,125 @@ +// 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 codelens + +import ( + "runtime" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/server" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/testenv" +) + +func TestGCDetails_Toggle(t *testing.T) { + if runtime.GOOS == "android" { + t.Skipf("the gc details code lens doesn't work on Android") + } + // The overlay portion of the test fails with go1.19. + // I'm not sure why and not inclined to investigate. + testenv.NeedsGo1Point(t, 20) + + const mod = ` +-- go.mod -- +module mod.com + +go 1.15 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println(42) +} +` + WithOptions( + Settings{ + "codelenses": map[string]bool{ + "gc_details": true, + }, + }, + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.ExecuteCodeLensCommand("main.go", command.GCDetails, nil) + + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromToggleGCDetails), 1, true), + Diagnostics( + ForFile("main.go"), + WithMessage("42 escapes"), + WithSeverityTags("optimizer details", protocol.SeverityInformation, nil), + ), + ) + + // GCDetails diagnostics should be reported even on unsaved + // edited buffers, thanks to the magic of overlays. + env.SetBufferContent("main.go", ` +package main +func main() {} +func f(x int) *int { return &x }`) + env.AfterChange(Diagnostics( + ForFile("main.go"), + WithMessage("x escapes"), + WithSeverityTags("optimizer details", protocol.SeverityInformation, nil), + )) + + // Toggle the GC details code lens again so now it should be off. + env.ExecuteCodeLensCommand("main.go", command.GCDetails, nil) + env.OnceMet( + CompletedWork(server.DiagnosticWorkTitle(server.FromToggleGCDetails), 2, true), + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +// Test for the crasher in golang/go#54199 +func TestGCDetails_NewFile(t *testing.T) { + bug.PanicOnBugs = false + const src = ` +-- go.mod -- +module mod.test + +go 1.12 +` + + WithOptions( + Settings{ + "codelenses": map[string]bool{ + "gc_details": true, + }, + }, + ).Run(t, src, func(t *testing.T, env *Env) { + env.CreateBuffer("p_test.go", "") + + hasGCDetails := func() bool { + lenses := env.CodeLens("p_test.go") // should not crash + for _, lens := range lenses { + if lens.Command.Command == command.GCDetails.String() { + return true + } + } + return false + } + + // With an empty file, we shouldn't get the gc_details codelens because + // there is nowhere to position it (it needs a package name). + if hasGCDetails() { + t.Errorf("got the gc_details codelens for an empty file") + } + + // Edit to provide a package name. + env.EditBuffer("p_test.go", fake.NewEdit(0, 0, 0, 0, "package p")) + + // Now we should get the gc_details codelens. + if !hasGCDetails() { + t.Errorf("didn't get the gc_details codelens for a valid non-empty Go file") + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/completion/completion18_test.go b/contribs/gnopls/internal/test/integration/completion/completion18_test.go new file mode 100644 index 00000000000..a35061d693b --- /dev/null +++ b/contribs/gnopls/internal/test/integration/completion/completion18_test.go @@ -0,0 +1,121 @@ +// 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 completion + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// test generic receivers +func TestGenericReceiver(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- main.go -- +package main +type SyncMap[K any, V comparable] struct {} +func (s *SyncMap[K,V]) f() {} +type XX[T any] struct {} +type UU[T any] struct {} +func (s SyncMap[XX,string]) g(v UU) {} +` + + tests := []struct { + pat string + want []string + }{ + {"s .Syn", []string{"SyncMap[K, V]"}}, + {"Map.X", []string{}}, // This is probably wrong, Maybe "XX"? + {"v U", []string{"UU", "uint", "uint16", "uint32", "uint64", "uint8", "uintptr"}}, // not U[T] + } + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.Await(env.DoneWithOpen()) + for _, tst := range tests { + loc := env.RegexpSearch("main.go", tst.pat) + loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte(tst.pat))) + completions := env.Completion(loc) + result := compareCompletionLabels(tst.want, completions.Items) + if result != "" { + t.Errorf("%s: wanted %v", result, tst.want) + for i, g := range completions.Items { + t.Errorf("got %d %s %s", i, g.Label, g.Detail) + } + } + } + }) +} +func TestFuzzFunc(t *testing.T) { + // use the example from the package documentation + modfile := ` +-- go.mod -- +module mod.com + +go 1.18 +` + part0 := `package foo +import "testing" +func FuzzNone(f *testing.F) { + f.Add(12) // better not find this f.Add +} +func FuzzHex(f *testing.F) { + for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { + f.Ad` + part1 := `d(seed) + } + f.F` + part2 := `uzz(func(t *testing.T, in []byte) { + enc := hex.EncodeToString(in) + out, err := hex.DecodeString(enc) + if err != nil { + f.Failed() + } + if !bytes.Equal(in, out) { + t.Fatalf("%v: round trip: %v, %s", in, out, f.Name()) + } + }) +} +` + data := modfile + `-- a_test.go -- +` + part0 + ` +-- b_test.go -- +` + part0 + part1 + ` +-- c_test.go -- +` + part0 + part1 + part2 + + tests := []struct { + file string + pat string + offset uint32 // UTF16 length from the beginning of pat to what the user just typed + want []string + }{ + {"a_test.go", "f.Ad", 3, []string{"Add"}}, + {"c_test.go", " f.F", 4, []string{"Failed"}}, + {"c_test.go", "f.N", 3, []string{"Name"}}, + {"b_test.go", "f.F", 3, []string{"Fuzz(func(t *testing.T, a []byte)", "Fail", "FailNow", + "Failed", "Fatal", "Fatalf"}}, + } + Run(t, data, func(t *testing.T, env *Env) { + for _, test := range tests { + env.OpenFile(test.file) + env.Await(env.DoneWithOpen()) + loc := env.RegexpSearch(test.file, test.pat) + loc.Range.Start.Character += test.offset // character user just typed? will type? + completions := env.Completion(loc) + result := compareCompletionLabels(test.want, completions.Items) + if result != "" { + t.Errorf("pat %q %q", test.pat, result) + for i, it := range completions.Items { + t.Errorf("%d got %q %q", i, it.Label, it.Detail) + } + } + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/completion/completion_test.go b/contribs/gnopls/internal/test/integration/completion/completion_test.go new file mode 100644 index 00000000000..c96e569f1ad --- /dev/null +++ b/contribs/gnopls/internal/test/integration/completion/completion_test.go @@ -0,0 +1,1166 @@ +// 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 completion + +import ( + "fmt" + "os" + "sort" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "golang.org/x/telemetry/counter" + "golang.org/x/telemetry/counter/countertest" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/server" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/testenv" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(Main(m)) +} + +const proxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +const Name = "Blah" +-- random.org@v1.2.3/go.mod -- +module random.org + +go 1.12 +-- random.org@v1.2.3/blah/blah.go -- +package hello + +const Name = "Hello" +` + +func TestPackageCompletion(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- fruits/apple.go -- +package apple + +fun apple() int { + return 0 +} + +-- fruits/testfile.go -- +// this is a comment + +/* + this is a multiline comment +*/ + +import "fmt" + +func test() {} + +-- fruits/testfile2.go -- +package + +-- fruits/testfile3.go -- +pac +-- 123f_r.u~its-123/testfile.go -- +package + +-- .invalid-dir@-name/testfile.go -- +package +` + var ( + testfile4 = "" + testfile5 = "/*a comment*/ " + testfile6 = "/*a comment*/\n" + ) + for _, tc := range []struct { + name string + filename string + content *string + triggerRegexp string + want []string + editRegexp string + }{ + { + name: "package completion at valid position", + filename: "fruits/testfile.go", + triggerRegexp: "\n()", + want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, + editRegexp: "\n()", + }, + { + name: "package completion in a comment", + filename: "fruits/testfile.go", + triggerRegexp: "th(i)s", + want: nil, + }, + { + name: "package completion in a multiline comment", + filename: "fruits/testfile.go", + triggerRegexp: `\/\*\n()`, + want: nil, + }, + { + name: "package completion at invalid position", + filename: "fruits/testfile.go", + triggerRegexp: "import \"fmt\"\n()", + want: nil, + }, + { + name: "package completion after package keyword", + filename: "fruits/testfile2.go", + triggerRegexp: "package()", + want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, + editRegexp: "package", + }, + { + name: "package completion with 'pac' prefix", + filename: "fruits/testfile3.go", + triggerRegexp: "pac()", + want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, + editRegexp: "pac", + }, + { + name: "package completion for empty file", + filename: "fruits/testfile4.go", + triggerRegexp: "^$", + content: &testfile4, + want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, + editRegexp: "^$", + }, + { + name: "package completion without terminal newline", + filename: "fruits/testfile5.go", + triggerRegexp: `\*\/ ()`, + content: &testfile5, + want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, + editRegexp: `\*\/ ()`, + }, + { + name: "package completion on terminal newline", + filename: "fruits/testfile6.go", + triggerRegexp: `\*\/\n()`, + content: &testfile6, + want: []string{"package apple", "package apple_test", "package fruits", "package fruits_test", "package main"}, + editRegexp: `\*\/\n()`, + }, + // Issue golang/go#44680 + { + name: "package completion for dir name with punctuation", + filename: "123f_r.u~its-123/testfile.go", + triggerRegexp: "package()", + want: []string{"package fruits123", "package fruits123_test", "package main"}, + editRegexp: "package", + }, + { + name: "package completion for invalid dir name", + filename: ".invalid-dir@-name/testfile.go", + triggerRegexp: "package()", + want: []string{"package main"}, + editRegexp: "package", + }, + } { + t.Run(tc.name, func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + if tc.content != nil { + env.WriteWorkspaceFile(tc.filename, *tc.content) + env.Await(env.DoneWithChangeWatchedFiles()) + } + env.OpenFile(tc.filename) + completions := env.Completion(env.RegexpSearch(tc.filename, tc.triggerRegexp)) + + // Check that the completion item suggestions are in the range + // of the file. {Start,End}.Line are zero-based. + lineCount := len(strings.Split(env.BufferText(tc.filename), "\n")) + for _, item := range completions.Items { + for _, mode := range []string{"replace", "insert"} { + edit, err := protocol.SelectCompletionTextEdit(item, mode == "replace") + if err != nil { + t.Fatalf("unexpected text edit in completion item (%v): %v", mode, err) + } + if start := int(edit.Range.Start.Line); start > lineCount { + t.Fatalf("unexpected text edit range (%v) start line number: got %d, want <= %d", mode, start, lineCount) + } + if end := int(edit.Range.End.Line); end > lineCount { + t.Fatalf("unexpected text edit range (%v) end line number: got %d, want <= %d", mode, end, lineCount) + } + } + } + + if tc.want != nil { + expectedLoc := env.RegexpSearch(tc.filename, tc.editRegexp) + for _, item := range completions.Items { + for _, mode := range []string{"replace", "insert"} { + edit, _ := protocol.SelectCompletionTextEdit(item, mode == "replace") + gotRng := edit.Range + if expectedLoc.Range != gotRng { + t.Errorf("unexpected completion range (%v) for completion item %s: got %v, want %v", + mode, item.Label, gotRng, expectedLoc.Range) + } + } + } + } + + diff := compareCompletionLabels(tc.want, completions.Items) + if diff != "" { + t.Error(diff) + } + }) + }) + } +} + +func TestPackageNameCompletion(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- math/add.go -- +package ma +` + + want := []string{"ma", "ma_test", "main", "math", "math_test"} + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("math/add.go") + completions := env.Completion(env.RegexpSearch("math/add.go", "package ma()")) + + diff := compareCompletionLabels(want, completions.Items) + if diff != "" { + t.Fatal(diff) + } + }) +} + +// TODO(rfindley): audit/clean up call sites for this helper, to ensure +// consistent test errors. +func compareCompletionLabels(want []string, gotItems []protocol.CompletionItem) string { + var got []string + for _, item := range gotItems { + got = append(got, item.Label) + if item.Label != item.InsertText && item.TextEdit == nil { + // Label should be the same as InsertText, if InsertText is to be used + return fmt.Sprintf("label not the same as InsertText %#v", item) + } + } + + if len(got) == 0 && len(want) == 0 { + return "" // treat nil and the empty slice as equivalent + } + + if diff := cmp.Diff(want, got); diff != "" { + return fmt.Sprintf("completion item mismatch (-want +got):\n%s", diff) + } + return "" +} + +func TestUnimportedCompletion(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.14 + +require example.com v1.2.3 +-- go.sum -- +example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- main.go -- +package main + +func main() { + _ = blah +} +-- main2.go -- +package main + +import "example.com/blah" + +func _() { + _ = blah.Hello +} +` + WithOptions( + ProxyFiles(proxy), + ).Run(t, mod, func(t *testing.T, env *Env) { + // Make sure the dependency is in the module cache and accessible for + // unimported completions, and then remove it before proceeding. + env.RemoveWorkspaceFile("main2.go") + env.RunGoCommand("mod", "tidy") + env.Await(env.DoneWithChangeWatchedFiles()) + + // Trigger unimported completions for the example.com/blah package. + env.OpenFile("main.go") + env.Await(env.DoneWithOpen()) + loc := env.RegexpSearch("main.go", "ah") + completions := env.Completion(loc) + if len(completions.Items) == 0 { + t.Fatalf("no completion items") + } + env.AcceptCompletion(loc, completions.Items[0]) // adds blah import to main.go + env.Await(env.DoneWithChange()) + + // Trigger completions once again for the blah.<> selector. + env.RegexpReplace("main.go", "_ = blah", "_ = blah.") + env.Await(env.DoneWithChange()) + loc = env.RegexpSearch("main.go", "\n}") + completions = env.Completion(loc) + if len(completions.Items) != 1 { + t.Fatalf("expected 1 completion item, got %v", len(completions.Items)) + } + item := completions.Items[0] + if item.Label != "Name" { + t.Fatalf("expected completion item blah.Name, got %v", item.Label) + } + env.AcceptCompletion(loc, item) + + // Await the diagnostics to add example.com/blah to the go.mod file. + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", `"example.com/blah"`)), + ) + }) +} + +// Test that completions still work with an undownloaded module, golang/go#43333. +func TestUndownloadedModule(t *testing.T) { + // mod.com depends on example.com, but only in a file that's hidden by a + // build tag, so the IWL won't download example.com. That will cause errors + // in the go list -m call performed by the imports package. + const files = ` +-- go.mod -- +module mod.com + +go 1.14 + +require example.com v1.2.3 +-- go.sum -- +example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- useblah.go -- +// +build hidden + +package pkg +import "example.com/blah" +var _ = blah.Name +-- mainmod/mainmod.go -- +package mainmod + +const Name = "mainmod" +` + WithOptions(ProxyFiles(proxy)).Run(t, files, func(t *testing.T, env *Env) { + env.CreateBuffer("import.go", "package pkg\nvar _ = mainmod.Name\n") + env.SaveBuffer("import.go") + content := env.ReadWorkspaceFile("import.go") + if !strings.Contains(content, `import "mod.com/mainmod`) { + t.Errorf("expected import of mod.com/mainmod in %q", content) + } + }) +} + +// Test that we can doctor the source code enough so the file is +// parseable and completion works as expected. +func TestSourceFixup(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- foo.go -- +package foo + +func _() { + var s S + if s. +} + +type S struct { + i int +} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("foo.go") + completions := env.Completion(env.RegexpSearch("foo.go", `if s\.()`)) + diff := compareCompletionLabels([]string{"i"}, completions.Items) + if diff != "" { + t.Fatal(diff) + } + }) +} + +func TestCompletion_Issue45510(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func _() { + type a *a + var aaaa1, aaaa2 a + var _ a = aaaa + + type b a + var bbbb1, bbbb2 b + var _ b = bbbb +} + +type ( + c *d + d *e + e **c +) + +func _() { + var ( + xxxxc c + xxxxd d + xxxxe e + ) + + var _ c = xxxx + var _ d = xxxx + var _ e = xxxx +} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + + tests := []struct { + re string + want []string + }{ + {`var _ a = aaaa()`, []string{"aaaa1", "aaaa2"}}, + {`var _ b = bbbb()`, []string{"bbbb1", "bbbb2"}}, + {`var _ c = xxxx()`, []string{"xxxxc", "xxxxd", "xxxxe"}}, + {`var _ d = xxxx()`, []string{"xxxxc", "xxxxd", "xxxxe"}}, + {`var _ e = xxxx()`, []string{"xxxxc", "xxxxd", "xxxxe"}}, + } + for _, tt := range tests { + completions := env.Completion(env.RegexpSearch("main.go", tt.re)) + diff := compareCompletionLabels(tt.want, completions.Items) + if diff != "" { + t.Errorf("%s: %s", tt.re, diff) + } + } + }) +} + +func TestCompletionDeprecation(t *testing.T) { + const files = ` +-- go.mod -- +module test.com + +go 1.16 +-- prog.go -- +package waste +// Deprecated, use newFoof +func fooFunc() bool { + return false +} + +// Deprecated +const badPi = 3.14 + +func doit() { + if fooF + panic() + x := badP +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("prog.go") + loc := env.RegexpSearch("prog.go", "if fooF") + loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte("if fooF"))) + completions := env.Completion(loc) + diff := compareCompletionLabels([]string{"fooFunc"}, completions.Items) + if diff != "" { + t.Error(diff) + } + if completions.Items[0].Tags == nil { + t.Errorf("expected Tags to show deprecation %#v", completions.Items[0].Tags) + } + loc = env.RegexpSearch("prog.go", "= badP") + loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte("= badP"))) + completions = env.Completion(loc) + diff = compareCompletionLabels([]string{"badPi"}, completions.Items) + if diff != "" { + t.Error(diff) + } + if completions.Items[0].Tags == nil { + t.Errorf("expected Tags to show deprecation %#v", completions.Items[0].Tags) + } + }) +} + +func TestUnimportedCompletion_VSCodeIssue1489(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.14 + +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println("a") + math.Sqr +} +` + WithOptions( + WindowsLineEndings(), + Settings{"ui.completion.usePlaceholders": true}, + ).Run(t, src, func(t *testing.T, env *Env) { + // Trigger unimported completions for the mod.com package. + env.OpenFile("main.go") + env.Await(env.DoneWithOpen()) + loc := env.RegexpSearch("main.go", "Sqr()") + completions := env.Completion(loc) + if len(completions.Items) == 0 { + t.Fatalf("no completion items") + } + env.AcceptCompletion(loc, completions.Items[0]) + env.Await(env.DoneWithChange()) + got := env.BufferText("main.go") + want := "package main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"math\"\r\n)\r\n\r\nfunc main() {\r\n\tfmt.Println(\"a\")\r\n\tmath.Sqrt(${1:x float64})\r\n}\r\n" + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("unimported completion (-want +got):\n%s", diff) + } + }) +} + +func TestUnimportedCompletion_VSCodeIssue3365(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.19 + +-- main.go -- +package main + +func main() { + println(strings.TLower) +} + +var Lower = "" +` + find := func(t *testing.T, completions *protocol.CompletionList, name string) protocol.CompletionItem { + t.Helper() + if completions == nil || len(completions.Items) == 0 { + t.Fatalf("no completion items") + } + for _, i := range completions.Items { + if i.Label == name { + return i + } + } + t.Fatalf("no item with label %q", name) + return protocol.CompletionItem{} + } + + for _, supportInsertReplace := range []bool{true, false} { + t.Run(fmt.Sprintf("insertReplaceSupport=%v", supportInsertReplace), func(t *testing.T) { + capabilities := fmt.Sprintf(`{ "textDocument": { "completion": { "completionItem": {"insertReplaceSupport":%t, "snippetSupport": false } } } }`, supportInsertReplace) + runner := WithOptions(CapabilitiesJSON([]byte(capabilities))) + runner.Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.Await(env.DoneWithOpen()) + orig := env.BufferText("main.go") + + // We try to trigger completion at "println(strings.T<>Lower)" + // and accept the completion candidate that matches the 'accept' label. + insertModeWant := "println(strings.ToUpperLower)" + if !supportInsertReplace { + insertModeWant = "println(strings.ToUpper)" + } + testcases := []struct { + mode string + accept string + want string + }{ + { + mode: "insert", + accept: "ToUpper", + want: insertModeWant, + }, + { + mode: "insert", + accept: "ToLower", + want: "println(strings.ToLower)", // The suffix 'Lower' is included in the text edit. + }, + { + mode: "replace", + accept: "ToUpper", + want: "println(strings.ToUpper)", + }, + { + mode: "replace", + accept: "ToLower", + want: "println(strings.ToLower)", + }, + } + + for _, tc := range testcases { + t.Run(fmt.Sprintf("%v/%v", tc.mode, tc.accept), func(t *testing.T) { + + env.SetSuggestionInsertReplaceMode(tc.mode == "replace") + env.SetBufferContent("main.go", orig) + loc := env.RegexpSearch("main.go", `Lower\)`) + completions := env.Completion(loc) + item := find(t, completions, tc.accept) + env.AcceptCompletion(loc, item) + env.Await(env.DoneWithChange()) + got := env.BufferText("main.go") + if !strings.Contains(got, tc.want) { + t.Errorf("unexpected state after completion:\n%v\nwanted %v", got, tc.want) + } + }) + } + }) + }) + } +} +func TestUnimportedCompletionHasPlaceholders60269(t *testing.T) { + // We can't express this as a marker test because it doesn't support AcceptCompletion. + const src = ` +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +var _ = b.F + +-- b/b.go -- +package b + +func F0(a, b int, c float64) {} +func F1(int, chan *string) {} +func F2[K, V any](map[K]V, chan V) {} // missing type parameters was issue #60959 +func F3[K comparable, V any](map[K]V, chan V) {} +` + WithOptions( + WindowsLineEndings(), + Settings{"ui.completion.usePlaceholders": true}, + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.Await(env.DoneWithOpen()) + + // The table lists the expected completions of b.F as they appear in Items. + const common = "package a\r\n\r\nimport \"example.com/b\"\r\n\r\nvar _ = " + for i, want := range []string{ + common + "b.F0(${1:a int}, ${2:b int}, ${3:c float64})\r\n", + common + "b.F1(${1:_ int}, ${2:_ chan *string})\r\n", + common + "b.F2[${1:K any}, ${2:V any}](${3:_ map[K]V}, ${4:_ chan V})\r\n", + common + "b.F3[${1:K comparable}, ${2:V any}](${3:_ map[K]V}, ${4:_ chan V})\r\n", + } { + loc := env.RegexpSearch("a/a.go", "b.F()") + completions := env.Completion(loc) + if len(completions.Items) == 0 { + t.Fatalf("no completion items") + } + saved := env.BufferText("a/a.go") + env.AcceptCompletion(loc, completions.Items[i]) + env.Await(env.DoneWithChange()) + got := env.BufferText("a/a.go") + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("%d: unimported completion (-want +got):\n%s", i, diff) + } + env.SetBufferContent("a/a.go", saved) // restore + } + }) +} + +func TestPackageMemberCompletionAfterSyntaxError(t *testing.T) { + // This test documents the current broken behavior due to golang/go#58833. + const src = ` +-- go.mod -- +module mod.com + +go 1.14 + +-- main.go -- +package main + +import "math" + +func main() { + math.Sqrt(,0) + math.Ldex +} +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.Await(env.DoneWithOpen()) + loc := env.RegexpSearch("main.go", "Ldex()") + completions := env.Completion(loc) + if len(completions.Items) == 0 { + t.Fatalf("no completion items") + } + env.AcceptCompletion(loc, completions.Items[0]) + env.Await(env.DoneWithChange()) + got := env.BufferText("main.go") + // The completion of math.Ldex after the syntax error on the + // previous line is not "math.Ldexp" but "math.Ldexmath.Abs". + // (In VSCode, "Abs" wrongly appears in the completion menu.) + // This is a consequence of poor error recovery in the parser + // causing "math.Ldex" to become a BadExpr. + want := "package main\n\nimport \"math\"\n\nfunc main() {\n\tmath.Sqrt(,0)\n\tmath.Ldexmath.Abs(${1:})\n}\n" + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("unimported completion (-want +got):\n%s", diff) + } + }) +} + +func TestCompleteAllFields(t *testing.T) { + // This test verifies that completion results always include all struct fields. + // See golang/go#53992. + + const src = ` +-- go.mod -- +module mod.com + +go 1.18 + +-- p/p.go -- +package p + +import ( + "fmt" + + . "net/http" + . "runtime" + . "go/types" + . "go/parser" + . "go/ast" +) + +type S struct { + a, b, c, d, e, f, g, h, i, j, k, l, m int + n, o, p, q, r, s, t, u, v, w, x, y, z int +} + +func _() { + var s S + fmt.Println(s.) +} +` + + WithOptions(Settings{ + "completionBudget": "1ns", // must be non-zero as 0 => infinity + }).Run(t, src, func(t *testing.T, env *Env) { + wantFields := make(map[string]bool) + for c := 'a'; c <= 'z'; c++ { + wantFields[string(c)] = true + } + + env.OpenFile("p/p.go") + // Make an arbitrary edit to ensure we're not hitting the cache. + env.EditBuffer("p/p.go", fake.NewEdit(0, 0, 0, 0, fmt.Sprintf("// current time: %v\n", time.Now()))) + loc := env.RegexpSearch("p/p.go", `s\.()`) + completions := env.Completion(loc) + gotFields := make(map[string]bool) + for _, item := range completions.Items { + if item.Kind == protocol.FieldCompletion { + gotFields[item.Label] = true + } + } + + if diff := cmp.Diff(wantFields, gotFields); diff != "" { + t.Errorf("Completion(...) returned mismatching fields (-want +got):\n%s", diff) + } + }) +} + +func TestDefinition(t *testing.T) { + files := ` +-- go.mod -- +module mod.com + +go 1.18 +-- a_test.go -- +package foo +` + tests := []struct { + line string // the sole line in the buffer after the package statement + pat string // the pattern to search for + want []string // expected completions + }{ + {"func T", "T", []string{"TestXxx(t *testing.T)", "TestMain(m *testing.M)"}}, + {"func T()", "T", []string{"TestMain", "Test"}}, + {"func TestM", "TestM", []string{"TestMain(m *testing.M)", "TestM(t *testing.T)"}}, + {"func TestM()", "TestM", []string{"TestMain"}}, + {"func TestMi", "TestMi", []string{"TestMi(t *testing.T)"}}, + {"func TestMi()", "TestMi", nil}, + {"func TestG", "TestG", []string{"TestG(t *testing.T)"}}, + {"func TestG(", "TestG", nil}, + {"func Ben", "B", []string{"BenchmarkXxx(b *testing.B)"}}, + {"func Ben(", "Ben", []string{"Benchmark"}}, + {"func BenchmarkFoo", "BenchmarkFoo", []string{"BenchmarkFoo(b *testing.B)"}}, + {"func BenchmarkFoo(", "BenchmarkFoo", nil}, + {"func Fuz", "F", []string{"FuzzXxx(f *testing.F)"}}, + {"func Fuz(", "Fuz", []string{"Fuzz"}}, + {"func Testx", "Testx", nil}, + {"func TestMe(t *testing.T)", "TestMe", nil}, + {"func Te(t *testing.T)", "Te", []string{"TestMain", "Test"}}, + } + fname := "a_test.go" + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile(fname) + env.Await(env.DoneWithOpen()) + for _, test := range tests { + env.SetBufferContent(fname, "package foo\n"+test.line) + loc := env.RegexpSearch(fname, test.pat) + loc.Range.Start.Character += uint32(protocol.UTF16Len([]byte(test.pat))) + completions := env.Completion(loc) + if diff := compareCompletionLabels(test.want, completions.Items); diff != "" { + t.Error(diff) + } + } + }) +} + +// Test that completing a definition replaces source text when applied, golang/go#56852. +func TestDefinitionReplaceRange(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.17 +` + + tests := []struct { + name string + before, after string + }{ + { + name: "func TestMa", + before: ` +package foo_test + +func TestMa +`, + after: ` +package foo_test + +func TestMain(m *testing.M) +`, + }, + { + name: "func TestSome", + before: ` +package foo_test + +func TestSome +`, + after: ` +package foo_test + +func TestSome(t *testing.T) +`, + }, + { + name: "func Bench", + before: ` +package foo_test + +func Bench +`, + // Note: Snippet with escaped }. + after: ` +package foo_test + +func Benchmark${1:Xxx}(b *testing.B) { + $0 +\} +`, + }, + } + + Run(t, mod, func(t *testing.T, env *Env) { + env.CreateBuffer("foo_test.go", "") + + for _, tst := range tests { + tst.before = strings.Trim(tst.before, "\n") + tst.after = strings.Trim(tst.after, "\n") + env.SetBufferContent("foo_test.go", tst.before) + + loc := env.RegexpSearch("foo_test.go", tst.name) + loc.Range.Start.Character = uint32(protocol.UTF16Len([]byte(tst.name))) + completions := env.Completion(loc) + if len(completions.Items) == 0 { + t.Fatalf("no completion items") + } + + env.AcceptCompletion(loc, completions.Items[0]) + env.Await(env.DoneWithChange()) + if buf := env.BufferText("foo_test.go"); buf != tst.after { + t.Errorf("%s:incorrect completion: got %q, want %q", tst.name, buf, tst.after) + } + } + }) +} + +func TestGoWorkCompletion(t *testing.T) { + const files = ` +-- go.work -- +go 1.18 + +use ./a +use ./a/ba +use ./a/b/ +use ./dir/foo +use ./dir/foobar/ +use ./missing/ +-- a/go.mod -- +-- go.mod -- +-- a/bar/go.mod -- +-- a/b/c/d/e/f/go.mod -- +-- dir/bar -- +-- dir/foobar/go.mod -- +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + + tests := []struct { + re string + want []string + }{ + {`use ()\.`, []string{".", "./a", "./a/bar", "./dir/foobar"}}, + {`use \.()`, []string{"", "/a", "/a/bar", "/dir/foobar"}}, + {`use \./()`, []string{"a", "a/bar", "dir/foobar"}}, + {`use ./a()`, []string{"", "/b/c/d/e/f", "/bar"}}, + {`use ./a/b()`, []string{"/c/d/e/f", "ar"}}, + {`use ./a/b/()`, []string{`c/d/e/f`}}, + {`use ./a/ba()`, []string{"r"}}, + {`use ./dir/foo()`, []string{"bar"}}, + {`use ./dir/foobar/()`, []string{}}, + {`use ./missing/()`, []string{}}, + } + for _, tt := range tests { + completions := env.Completion(env.RegexpSearch("go.work", tt.re)) + diff := compareCompletionLabels(tt.want, completions.Items) + if diff != "" { + t.Errorf("%s: %s", tt.re, diff) + } + } + }) +} + +func TestBuiltinCompletion(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- a.go -- +package a + +func _() { + // here +} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + result := env.Completion(env.RegexpSearch("a.go", `// here`)) + builtins := []string{ + "any", "append", "bool", "byte", "cap", "close", + "comparable", "complex", "complex128", "complex64", "copy", "delete", + "error", "false", "float32", "float64", "imag", "int", "int16", "int32", + "int64", "int8", "len", "make", "new", "panic", "print", "println", "real", + "recover", "rune", "string", "true", "uint", "uint16", "uint32", "uint64", + "uint8", "uintptr", "nil", + } + if testenv.Go1Point() >= 21 { + builtins = append(builtins, "clear", "max", "min") + } + sort.Strings(builtins) + var got []string + + for _, item := range result.Items { + // TODO(rfindley): for flexibility, ignore zero while it is being + // implemented. Remove this if/when zero lands. + if item.Label != "zero" { + got = append(got, item.Label) + } + } + sort.Strings(got) + + if diff := cmp.Diff(builtins, got); diff != "" { + t.Errorf("Completion: unexpected mismatch (-want +got):\n%s", diff) + } + }) +} + +func TestOverlayCompletion(t *testing.T) { + const files = ` +-- go.mod -- +module foo.test + +go 1.18 + +-- foo/foo.go -- +package foo + +type Foo struct{} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.CreateBuffer("nodisk/nodisk.go", ` +package nodisk + +import ( + "foo.test/foo" +) + +func _() { + foo.Foo() +} +`) + list := env.Completion(env.RegexpSearch("nodisk/nodisk.go", "foo.(Foo)")) + want := []string{"Foo"} + var got []string + for _, item := range list.Items { + got = append(got, item.Label) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("Completion: unexpected mismatch (-want +got):\n%s", diff) + } + }) +} + +// Fix for golang/go#60062: unimported completion included "golang.org/toolchain" results. +func TestToolchainCompletions(t *testing.T) { + const files = ` +-- go.mod -- +module foo.test/foo + +go 1.21 + +-- foo.go -- +package foo + +func _() { + os.Open +} + +func _() { + strings +} +` + + const proxy = ` +-- golang.org/toolchain@v0.0.1-go1.21.1.linux-amd64/go.mod -- +module golang.org/toolchain +-- golang.org/toolchain@v0.0.1-go1.21.1.linux-amd64/src/os/os.go -- +package os + +func Open() {} +-- golang.org/toolchain@v0.0.1-go1.21.1.linux-amd64/src/strings/strings.go -- +package strings + +func Join() {} +` + + WithOptions( + ProxyFiles(proxy), + ).Run(t, files, func(t *testing.T, env *Env) { + env.RunGoCommand("mod", "download", "golang.org/toolchain@v0.0.1-go1.21.1.linux-amd64") + env.OpenFile("foo.go") + + for _, pattern := range []string{"os.Open()", "string()"} { + loc := env.RegexpSearch("foo.go", pattern) + res := env.Completion(loc) + for _, item := range res.Items { + if strings.Contains(item.Detail, "golang.org/toolchain") { + t.Errorf("Completion(...) returned toolchain item %#v", item) + } + } + } + }) +} + +// show that the efficacy counters get exercised. Fortuntely a small program +// exercises them all +func TestCounters(t *testing.T) { + const files = ` +-- go.mod -- +module foo +go 1.21 +-- x.go -- +package foo + +func main() { +} + +` + WithOptions( + Modes(Default), + ).Run(t, files, func(t *testing.T, env *Env) { + cts := func() map[*counter.Counter]uint64 { + ans := make(map[*counter.Counter]uint64) + for _, c := range server.CompletionCounters { + ans[c], _ = countertest.ReadCounter(c) + } + return ans + } + before := cts() + env.OpenFile("x.go") + env.Await(env.DoneWithOpen()) + saved := env.BufferText("x.go") + lines := strings.Split(saved, "\n") + // make sure the unused counter is exercised + loc := env.RegexpSearch("x.go", "main") + loc.Range.End = loc.Range.Start + env.Completion(loc) // ignore the proposed completions + env.RegexpReplace("x.go", "main", "Main") // completions are unused + env.SetBufferContent("x.go", saved) // restore x.go + // used:no + + // all the action is after 4 characters on line 2 (counting from 0) + for i := 2; i < len(lines); i++ { + l := lines[i] + loc.Range.Start.Line = uint32(i) + for j := 4; j < len(l); j++ { + loc.Range.Start.Character = uint32(j) + loc.Range.End = loc.Range.Start + res := env.Completion(loc) + if len(res.Items) > 0 { + r := res.Items[0] + env.AcceptCompletion(loc, r) + env.SetBufferContent("x.go", saved) + } + } + } + after := cts() + for c := range after { + if after[c] <= before[c] { + t.Errorf("%s did not increase", c.Name()) + } + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/completion/fixedbugs_test.go b/contribs/gnopls/internal/test/integration/completion/fixedbugs_test.go new file mode 100644 index 00000000000..faa5324e138 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/completion/fixedbugs_test.go @@ -0,0 +1,40 @@ +// 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 completion + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestPackageCompletionCrash_Issue68169(t *testing.T) { + // This test reproduces the scenario of golang/go#68169, a crash in + // completion.Selection.Suffix. + // + // The file content here is extracted from the issue. + const files = ` +-- go.mod -- +module example.com + +go 1.18 +-- playdos/play.go -- +package +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("playdos/play.go") + // Previously, this call would crash gopls as it was incorrectly computing + // the surrounding completion suffix. + completions := env.Completion(env.RegexpSearch("playdos/play.go", "package ()")) + if len(completions.Items) == 0 { + t.Fatal("Completion() returned empty results") + } + // Sanity check: we should get package clause completion. + if got, want := completions.Items[0].Label, "package playdos"; got != want { + t.Errorf("Completion()[0].Label == %s, want %s", got, want) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/completion/postfix_snippet_test.go b/contribs/gnopls/internal/test/integration/completion/postfix_snippet_test.go new file mode 100644 index 00000000000..884be420835 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/completion/postfix_snippet_test.go @@ -0,0 +1,762 @@ +// 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 completion + +import ( + "strings" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestPostfixSnippetCompletion(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +` + + cases := []struct { + name string + before, after string + allowMultipleItem bool + }{ + { + name: "sort", + before: ` +package foo + +func _() { + var foo []int + foo.sort +} +`, + after: ` +package foo + +import "sort" + +func _() { + var foo []int + sort.Slice(foo, func(i, j int) bool { + $0 +}) +} +`, + }, + { + name: "sort_renamed_sort_package", + before: ` +package foo + +import blahsort "sort" + +var j int + +func _() { + var foo []int + foo.sort +} +`, + after: ` +package foo + +import blahsort "sort" + +var j int + +func _() { + var foo []int + blahsort.Slice(foo, func(i, j2 int) bool { + $0 +}) +} +`, + }, + { + name: "last", + before: ` +package foo + +func _() { + var s struct { i []int } + s.i.last +} +`, + after: ` +package foo + +func _() { + var s struct { i []int } + s.i[len(s.i)-1] +} +`, + }, + { + name: "reverse", + before: ` +package foo + +func _() { + var foo []int + foo.reverse +} +`, + after: ` +package foo + +import "slices" + +func _() { + var foo []int + slices.Reverse(foo) +} +`, + }, + { + name: "slice_range", + before: ` +package foo + +func _() { + type myThing struct{} + var foo []myThing + foo.range +} +`, + after: ` +package foo + +func _() { + type myThing struct{} + var foo []myThing + for ${1:}, ${2:} := range foo { + $0 +} +} +`, + }, + { + name: "append_stmt", + before: ` +package foo + +func _() { + var foo []int + foo.append +} +`, + after: ` +package foo + +func _() { + var foo []int + foo = append(foo, $0) +} +`, + }, + { + name: "append_expr", + before: ` +package foo + +func _() { + var foo []int + var _ []int = foo.append +} +`, + after: ` +package foo + +func _() { + var foo []int + var _ []int = append(foo, $0) +} +`, + }, + { + name: "slice_copy", + before: ` +package foo + +func _() { + var foo []int + foo.copy +} +`, + after: ` +package foo + +func _() { + var foo []int + fooCopy := make([]int, len(foo)) +copy(fooCopy, foo) + +} +`, + }, + { + name: "map_range", + before: ` +package foo + +func _() { + var foo map[string]int + foo.range +} +`, + after: ` +package foo + +func _() { + var foo map[string]int + for ${1:}, ${2:} := range foo { + $0 +} +} +`, + }, + { + name: "map_clear", + before: ` +package foo + +func _() { + var foo map[string]int + foo.clear +} +`, + after: ` +package foo + +func _() { + var foo map[string]int + for k := range foo { + delete(foo, k) +} + +} +`, + }, + { + name: "map_keys", + before: ` +package foo + +func _() { + var foo map[string]int + foo.keys +} +`, + after: ` +package foo + +func _() { + var foo map[string]int + keys := make([]string, 0, len(foo)) +for k := range foo { + keys = append(keys, k) +} + +} +`, + }, + { + name: "channel_range", + before: ` +package foo + +func _() { + foo := make(chan int) + foo.range +} +`, + after: ` +package foo + +func _() { + foo := make(chan int) + for ${1:} := range foo { + $0 +} +} +`, + }, + { + name: "var", + before: ` +package foo + +func foo() (int, error) { return 0, nil } + +func _() { + foo().var +} +`, + after: ` +package foo + +func foo() (int, error) { return 0, nil } + +func _() { + ${1:}, ${2:} := foo() +} +`, + allowMultipleItem: true, + }, + { + name: "var_single_value", + before: ` +package foo + +func foo() error { return nil } + +func _() { + foo().var +} +`, + allowMultipleItem: true, + after: ` +package foo + +func foo() error { return nil } + +func _() { + ${1:} := foo() +} +`, + }, + { + name: "var_same_type", + before: ` +package foo + +func foo() (int, int) { return 0, 0 } + +func _() { + foo().var +} +`, + after: ` +package foo + +func foo() (int, int) { return 0, 0 } + +func _() { + ${1:}, ${2:} := foo() +} +`, + }, + { + name: "print_scalar", + before: ` +package foo + +func _() { + var foo int + foo.print +} +`, + after: ` +package foo + +import "fmt" + +func _() { + var foo int + fmt.Printf("foo: %v\n", foo) +} +`, + }, + { + name: "print_multi", + before: ` +package foo + +func foo() (int, error) { return 0, nil } + +func _() { + foo().print +} +`, + after: ` +package foo + +import "fmt" + +func foo() (int, error) { return 0, nil } + +func _() { + fmt.Println(foo()) +} +`, + }, + { + name: "string split", + before: ` +package foo + +func foo() []string { + x := "test" + return x.split +}`, + after: ` +package foo + +import "strings" + +func foo() []string { + x := "test" + return strings.Split(x, "$0") +}`, + }, + { + name: "string slice join", + before: ` +package foo + +func foo() string { + x := []string{"a", "test"} + return x.join +}`, + after: ` +package foo + +import "strings" + +func foo() string { + x := []string{"a", "test"} + return strings.Join(x, "$0") +}`, + }, + { + name: "if not nil interface", + before: ` +package foo + +func _() { + var foo error + foo.ifnotnil +} +`, + after: ` +package foo + +func _() { + var foo error + if foo != nil { + $0 +} +} +`, + }, + { + name: "if not nil pointer", + before: ` +package foo + +func _() { + var foo *int + foo.ifnotnil +} +`, + after: ` +package foo + +func _() { + var foo *int + if foo != nil { + $0 +} +} +`, + }, + { + name: "if not nil slice", + before: ` +package foo + +func _() { + var foo []int + foo.ifnotnil +} +`, + after: ` +package foo + +func _() { + var foo []int + if foo != nil { + $0 +} +} +`, + }, + { + name: "if not nil map", + before: ` +package foo + +func _() { + var foo map[string]any + foo.ifnotnil +} +`, + after: ` +package foo + +func _() { + var foo map[string]any + if foo != nil { + $0 +} +} +`, + }, + { + name: "if not nil channel", + before: ` +package foo + +func _() { + var foo chan int + foo.ifnotnil +} +`, + after: ` +package foo + +func _() { + var foo chan int + if foo != nil { + $0 +} +} +`, + }, + { + name: "if not nil function", + before: ` +package foo + +func _() { + var foo func() + foo.ifnotnil +} +`, + after: ` +package foo + +func _() { + var foo func() + if foo != nil { + $0 +} +} +`, + }, + { + name: "slice_len", + before: ` +package foo + +func _() { + var foo []int + foo.len +} +`, + after: ` +package foo + +func _() { + var foo []int + len(foo) +} +`, + }, + { + name: "map_len", + before: ` +package foo + +func _() { + var foo map[string]int + foo.len +} +`, + after: ` +package foo + +func _() { + var foo map[string]int + len(foo) +} +`, + }, + { + name: "slice_for", + allowMultipleItem: true, + before: ` +package foo + +func _() { + var foo []int + foo.for +} +`, + after: ` +package foo + +func _() { + var foo []int + for ${1:} := range foo { + $0 +} +} +`, + }, + { + name: "map_for", + allowMultipleItem: true, + before: ` +package foo + +func _() { + var foo map[string]int + foo.for +} +`, + after: ` +package foo + +func _() { + var foo map[string]int + for ${1:} := range foo { + $0 +} +} +`, + }, + { + name: "chan_for", + allowMultipleItem: true, + before: ` +package foo + +func _() { + var foo chan int + foo.for +} +`, + after: ` +package foo + +func _() { + var foo chan int + for ${1:} := range foo { + $0 +} +} +`, + }, + { + name: "slice_forr", + before: ` +package foo + +func _() { + var foo []int + foo.forr +} +`, + after: ` +package foo + +func _() { + var foo []int + for ${1:}, ${2:} := range foo { + $0 +} +} +`, + }, + { + name: "slice_forr", + before: ` +package foo + +func _() { + var foo []int + foo.forr +} +`, + after: ` +package foo + +func _() { + var foo []int + for ${1:}, ${2:} := range foo { + $0 +} +} +`, + }, + { + name: "map_forr", + before: ` +package foo + +func _() { + var foo map[string]int + foo.forr +} +`, + after: ` +package foo + +func _() { + var foo map[string]int + for ${1:}, ${2:} := range foo { + $0 +} +} +`, + }, + } + + r := WithOptions( + Settings{ + "experimentalPostfixCompletions": true, + }, + ) + r.Run(t, mod, func(t *testing.T, env *Env) { + env.CreateBuffer("foo.go", "") + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + c.before = strings.Trim(c.before, "\n") + c.after = strings.Trim(c.after, "\n") + + env.SetBufferContent("foo.go", c.before) + + loc := env.RegexpSearch("foo.go", "\n}") + completions := env.Completion(loc) + if len(completions.Items) < 1 { + t.Fatalf("expected at least one completion, got %v", completions.Items) + } + if !c.allowMultipleItem && len(completions.Items) > 1 { + t.Fatalf("expected one completion, got %v", completions.Items) + } + + env.AcceptCompletion(loc, completions.Items[0]) + + if buf := env.BufferText("foo.go"); buf != c.after { + t.Errorf("\nGOT:\n%s\nEXPECTED:\n%s", buf, c.after) + } + }) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/debug/debug_test.go b/contribs/gnopls/internal/test/integration/debug/debug_test.go new file mode 100644 index 00000000000..1dccea43062 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/debug/debug_test.go @@ -0,0 +1,101 @@ +// 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 debug + +import ( + "context" + "encoding/json" + "io" + "net/http" + "os" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/util/bug" +) + +func TestMain(m *testing.M) { + os.Exit(Main(m)) +} + +func TestBugNotification(t *testing.T) { + // Verify that a properly configured session gets notified of a bug on the + // server. + WithOptions( + Modes(Default), // must be in-process to receive the bug report below + Settings{"showBugReports": true}, + ).Run(t, "", func(t *testing.T, env *Env) { + const desc = "got a bug" + bug.Report(desc) + env.Await(ShownMessage(desc)) + }) +} + +// TestStartDebugging executes a gopls.start_debugging command to +// start the internal web server. +func TestStartDebugging(t *testing.T) { + WithOptions( + Modes(Default), // doesn't work in Forwarded mode + ).Run(t, "", func(t *testing.T, env *Env) { + // Start a debugging server. + res, err := startDebugging(env.Ctx, env.Editor.Server, &command.DebuggingArgs{ + Addr: "", // any free port + }) + if err != nil { + t.Fatalf("startDebugging: %v", err) + } + + // Assert that the server requested that the + // client show the debug page in a browser. + debugURL := res.URLs[0] + env.Await(ShownDocument(debugURL)) + + // Send a request to the debug server and ensure it responds. + resp, err := http.Get(debugURL) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("reading HTTP response body: %v", err) + } + const want = "Gopls" + if !strings.Contains(string(data), want) { + t.Errorf("GET %s response does not contain %q: <<%s>>", debugURL, want, data) + } + }) +} + +// startDebugging starts a debugging server. +// TODO(adonovan): move into command package? +func startDebugging(ctx context.Context, server protocol.Server, args *command.DebuggingArgs) (*command.DebuggingResult, error) { + rawArgs, err := command.MarshalArgs(args) + if err != nil { + return nil, err + } + res0, err := server.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{ + Command: command.StartDebugging.String(), + Arguments: rawArgs, + }) + if err != nil { + return nil, err + } + // res0 is the result of a schemaless (map[string]any) JSON decoding. + // Re-encode and decode into the correct Go struct type. + // TODO(adonovan): fix (*serverDispatcher).ExecuteCommand. + data, err := json.Marshal(res0) + if err != nil { + return nil, err + } + var res *command.DebuggingResult + if err := json.Unmarshal(data, &res); err != nil { + return nil, err + } + return res, nil +} diff --git a/contribs/gnopls/internal/test/integration/diagnostics/analysis_test.go b/contribs/gnopls/internal/test/integration/diagnostics/analysis_test.go new file mode 100644 index 00000000000..8cb86f8f735 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/diagnostics/analysis_test.go @@ -0,0 +1,127 @@ +// 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 diagnostics + +import ( + "fmt" + "testing" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// Test for the timeformat analyzer, following golang/vscode-go#2406. +// +// This test checks that applying the suggested fix from the analyzer resolves +// the diagnostic warning. +func TestTimeFormatAnalyzer(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- main.go -- +package main + +import ( + "fmt" + "time" +) + +func main() { + now := time.Now() + fmt.Println(now.Format("2006-02-01")) +}` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "2006-02-01")), + ReadDiagnostics("main.go", &d), + ) + + env.ApplyQuickFixes("main.go", d.Diagnostics) + env.AfterChange(NoDiagnostics(ForFile("main.go"))) + }) +} + +func TestAnalysisProgressReporting(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 + +-- main.go -- +package main + +func main() { +}` + + tests := []struct { + setting bool + want Expectation + }{ + {true, CompletedWork(cache.AnalysisProgressTitle, 1, true)}, + {false, Not(CompletedWork(cache.AnalysisProgressTitle, 1, true))}, + } + + for _, test := range tests { + t.Run(fmt.Sprint(test.setting), func(t *testing.T) { + WithOptions( + Settings{ + "reportAnalysisProgressAfter": "0s", + "analysisProgressReporting": test.setting, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange(test.want) + }) + }) + } +} + +// Test the embed directive analyzer. +// +// There is a fix for missing imports, but it should not trigger for other +// kinds of issues reported by the analayzer, here the variable +// declaration following the embed directive is wrong. +func TestNoSuggestedFixesForEmbedDirectiveDeclaration(t *testing.T) { + const generated = ` +-- go.mod -- +module mod.com + +go 1.20 + +-- foo.txt -- +FOO + +-- main.go -- +package main + +import _ "embed" + +//go:embed foo.txt +var foo, bar string + +func main() { + _ = foo +} +` + Run(t, generated, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "//go:embed")), + ReadDiagnostics("main.go", &d), + ) + if fixes := env.GetQuickFixes("main.go", d.Diagnostics); len(fixes) != 0 { + t.Errorf("got quick fixes %v, wanted none", fixes) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/diagnostics/builtin_test.go b/contribs/gnopls/internal/test/integration/diagnostics/builtin_test.go new file mode 100644 index 00000000000..d6828a0df5c --- /dev/null +++ b/contribs/gnopls/internal/test/integration/diagnostics/builtin_test.go @@ -0,0 +1,35 @@ +// 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 diagnostics + +import ( + "strings" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestIssue44866(t *testing.T) { + src := ` +-- go.mod -- +module mod.com + +go 1.12 +-- a.go -- +package a + +const ( + c = iota +) +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + loc := env.GoToDefinition(env.RegexpSearch("a.go", "iota")) + if !strings.HasSuffix(string(loc.URI), "builtin.go") { + t.Fatalf("jumped to %q, want builtin.go", loc.URI) + } + env.AfterChange(NoDiagnostics(ForFile("builtin.go"))) + }) +} diff --git a/contribs/gnopls/internal/test/integration/diagnostics/diagnostics_test.go b/contribs/gnopls/internal/test/integration/diagnostics/diagnostics_test.go new file mode 100644 index 00000000000..195089ffce3 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/diagnostics/diagnostics_test.go @@ -0,0 +1,2209 @@ +// 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 diagnostics + +import ( + "context" + "fmt" + "os" + "os/exec" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/server" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/internal/testenv" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(Main(m)) +} + +// Use mod.com for all go.mod files due to golang/go#35230. +const exampleProgram = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println("Hello World.") +}` + +func TestDiagnosticErrorInEditedFile(t *testing.T) { + // This test is very basic: start with a clean Go program, make an error, and + // get a diagnostic for that error. However, it also demonstrates how to + // combine Expectations to await more complex state in the editor. + RunMultiple{ + {"golist", WithOptions(Modes(Default))}, + {"gopackages", WithOptions( + Modes(Default), + FakeGoPackagesDriver(t), + )}, + }.Run(t, exampleProgram, func(t *testing.T, env *Env) { + // Deleting the 'n' at the end of Println should generate a single error + // diagnostic. + env.OpenFile("main.go") + env.RegexpReplace("main.go", "Printl(n)", "") + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "Printl")), + // Assert that this test has sent no error logs to the client. This is not + // strictly necessary for testing this regression, but is included here + // as an example of using the NoErrorLogs() expectation. Feel free to + // delete. + NoErrorLogs(), + ) + }) +} + +func TestMissingImportDiagsClearOnFirstFile(t *testing.T) { + const onlyMod = ` +-- go.mod -- +module mod.com + +go 1.12 +` + Run(t, onlyMod, func(t *testing.T, env *Env) { + env.CreateBuffer("main.go", `package main + +func m() { + log.Println() +} +`) + env.AfterChange(Diagnostics(env.AtRegexp("main.go", "log"))) + env.SaveBuffer("main.go") + env.AfterChange(NoDiagnostics(ForFile("main.go"))) + }) +} + +func TestDiagnosticErrorInNewFile(t *testing.T) { + const brokenFile = `package main + +const Foo = "abc +` + RunMultiple{ + {"golist", WithOptions(Modes(Default))}, + // Since this test requires loading an overlay, + // it verifies that the fake go/packages driver honors overlays. + {"gopackages", WithOptions( + Modes(Default), + FakeGoPackagesDriver(t), + )}, + }.Run(t, brokenFile, func(t *testing.T, env *Env) { + env.CreateBuffer("broken.go", brokenFile) + env.AfterChange(Diagnostics(env.AtRegexp("broken.go", "\"abc"))) + }) +} + +// badPackage contains a duplicate definition of the 'a' const. +const badPackage = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a.go -- +package consts + +const a = 1 +-- b.go -- +package consts + +const a = 2 +` + +func TestDiagnosticClearingOnEdit(t *testing.T) { + Run(t, badPackage, func(t *testing.T, env *Env) { + env.OpenFile("b.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a.go", "a = 1")), + Diagnostics(env.AtRegexp("b.go", "a = 2")), + ) + + // Fix the error by editing the const name in b.go to `b`. + env.RegexpReplace("b.go", "(a) = 2", "b") + env.AfterChange( + NoDiagnostics(ForFile("a.go")), + NoDiagnostics(ForFile("b.go")), + ) + }) +} + +func TestDiagnosticClearingOnDelete_Issue37049(t *testing.T) { + Run(t, badPackage, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a.go", "a = 1")), + Diagnostics(env.AtRegexp("b.go", "a = 2")), + ) + env.RemoveWorkspaceFile("b.go") + + env.AfterChange( + NoDiagnostics(ForFile("a.go")), + NoDiagnostics(ForFile("b.go")), + ) + }) +} + +func TestDiagnosticClearingOnClose(t *testing.T) { + Run(t, badPackage, func(t *testing.T, env *Env) { + env.CreateBuffer("c.go", `package consts + +const a = 3`) + env.AfterChange( + Diagnostics(env.AtRegexp("a.go", "a = 1")), + Diagnostics(env.AtRegexp("b.go", "a = 2")), + Diagnostics(env.AtRegexp("c.go", "a = 3")), + ) + env.CloseBuffer("c.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a.go", "a = 1")), + Diagnostics(env.AtRegexp("b.go", "a = 2")), + NoDiagnostics(ForFile("c.go")), + ) + }) +} + +// Tests golang/go#37978. +func TestIssue37978(t *testing.T) { + Run(t, exampleProgram, func(t *testing.T, env *Env) { + // Create a new workspace-level directory and empty file. + env.CreateBuffer("c/c.go", "") + + // Write the file contents with a missing import. + env.EditBuffer("c/c.go", protocol.TextEdit{ + NewText: `package c + +const a = http.MethodGet +`, + }) + env.AfterChange( + Diagnostics(env.AtRegexp("c/c.go", "http.MethodGet")), + ) + // Save file, which will organize imports, adding the expected import. + // Expect the diagnostics to clear. + env.SaveBuffer("c/c.go") + env.AfterChange( + NoDiagnostics(ForFile("c/c.go")), + ) + }) +} + +// Tests golang/go#38878: good a.go, bad a_test.go, remove a_test.go but its errors remain +// If the file is open in the editor, this is working as intended +// If the file is not open in the editor, the errors go away +const test38878 = ` +-- go.mod -- +module foo + +go 1.12 +-- a.go -- +package x + +// import "fmt" + +func f() {} + +-- a_test.go -- +package x + +import "testing" + +func TestA(t *testing.T) { + f(3) +} +` + +// Tests golang/go#38878: deleting a test file should clear its errors, and +// not break the workspace. +func TestDeleteTestVariant(t *testing.T) { + Run(t, test38878, func(t *testing.T, env *Env) { + env.AfterChange(Diagnostics(env.AtRegexp("a_test.go", `f\((3)\)`))) + env.RemoveWorkspaceFile("a_test.go") + env.AfterChange(NoDiagnostics(ForFile("a_test.go"))) + + // Make sure the test variant has been removed from the workspace by + // triggering a metadata load. + env.OpenFile("a.go") + env.RegexpReplace("a.go", `// import`, "import") + env.AfterChange(Diagnostics(env.AtRegexp("a.go", `"fmt"`))) + }) +} + +// Tests golang/go#38878: deleting a test file on disk while it's still open +// should not clear its errors. +func TestDeleteTestVariant_DiskOnly(t *testing.T) { + Run(t, test38878, func(t *testing.T, env *Env) { + env.OpenFile("a_test.go") + env.AfterChange(Diagnostics(AtPosition("a_test.go", 5, 3))) + env.Sandbox.Workdir.RemoveFile(context.Background(), "a_test.go") + env.AfterChange(Diagnostics(AtPosition("a_test.go", 5, 3))) + }) +} + +// TestNoMod confirms that gopls continues to work when a user adds a go.mod +// file to their workspace. +func TestNoMod(t *testing.T) { + const noMod = ` +-- main.go -- +package main + +import "mod.com/bob" + +func main() { + bob.Hello() +} +-- bob/bob.go -- +package bob + +func Hello() { + var x int +} +` + + t.Run("manual", func(t *testing.T) { + Run(t, noMod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `"mod.com/bob"`)), + ) + env.CreateBuffer("go.mod", `module mod.com + + go 1.12 +`) + env.SaveBuffer("go.mod") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + Diagnostics(env.AtRegexp("bob/bob.go", "x")), + ReadDiagnostics("bob/bob.go", &d), + ) + if len(d.Diagnostics) != 1 { + t.Fatalf("expected 1 diagnostic, got %v", len(d.Diagnostics)) + } + }) + }) + t.Run("initialized", func(t *testing.T) { + Run(t, noMod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `"mod.com/bob"`)), + ) + env.RunGoCommand("mod", "init", "mod.com") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + Diagnostics(env.AtRegexp("bob/bob.go", "x")), + ) + }) + }) + + t.Run("without workspace module", func(t *testing.T) { + WithOptions( + Modes(Default), + ).Run(t, noMod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `"mod.com/bob"`)), + ) + if err := env.Sandbox.RunGoCommand(env.Ctx, "", "mod", []string{"init", "mod.com"}, nil, true); err != nil { + t.Fatal(err) + } + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + Diagnostics(env.AtRegexp("bob/bob.go", "x")), + ) + }) + }) +} + +// Tests golang/go#38267. +func TestIssue38267(t *testing.T) { + const testPackage = ` +-- go.mod -- +module mod.com + +go 1.12 +-- lib.go -- +package lib + +func Hello(x string) { + _ = x +} +-- lib_test.go -- +package lib + +import "testing" + +type testStruct struct{ + name string +} + +func TestHello(t *testing.T) { + testStructs := []*testStruct{ + &testStruct{"hello"}, + &testStruct{"goodbye"}, + } + for y := range testStructs { + _ = y + } +} +` + + Run(t, testPackage, func(t *testing.T, env *Env) { + env.OpenFile("lib_test.go") + env.AfterChange( + Diagnostics(AtPosition("lib_test.go", 10, 2)), + Diagnostics(AtPosition("lib_test.go", 11, 2)), + ) + env.OpenFile("lib.go") + env.RegexpReplace("lib.go", "_ = x", "var y int") + env.AfterChange( + Diagnostics(env.AtRegexp("lib.go", "y int")), + NoDiagnostics(ForFile("lib_test.go")), + ) + }) +} + +// Tests golang/go#38328. +func TestPackageChange_Issue38328(t *testing.T) { + const packageChange = ` +-- go.mod -- +module fake + +go 1.12 +-- a.go -- +package foo +func main() {} +` + Run(t, packageChange, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.RegexpReplace("a.go", "foo", "foox") + env.AfterChange( + NoDiagnostics(ForFile("a.go")), + ) + }) +} + +const testPackageWithRequire = ` +-- go.mod -- +module mod.com + +go 1.12 + +require foo.test v1.2.3 +-- go.sum -- +foo.test v1.2.3 h1:TMA+lyd1ck0TqjSFpNe4T6cf/K6TYkoHwOOcMBMjaEw= +foo.test v1.2.3/go.mod h1:Ij3kyLIe5lzjycjh13NL8I2gX0quZuTdW0MnmlwGBL4= +-- print.go -- +package lib + +import ( + "fmt" + + "foo.test/bar" +) + +func PrintAnswer() { + fmt.Printf("answer: %s", bar.Answer) +} +` + +const testPackageWithRequireProxy = ` +-- foo.test@v1.2.3/go.mod -- +module foo.test + +go 1.12 +-- foo.test@v1.2.3/bar/const.go -- +package bar + +const Answer = 42 +` + +func TestResolveDiagnosticWithDownload(t *testing.T) { + WithOptions( + ProxyFiles(testPackageWithRequireProxy), + ).Run(t, testPackageWithRequire, func(t *testing.T, env *Env) { + env.OpenFile("print.go") + // Check that gopackages correctly loaded this dependency. We should get a + // diagnostic for the wrong formatting type. + env.AfterChange( + Diagnostics( + env.AtRegexp("print.go", "fmt.Printf"), + WithMessage("wrong type int"), + ), + ) + }) +} + +func TestMissingDependency(t *testing.T) { + Run(t, testPackageWithRequire, func(t *testing.T, env *Env) { + env.OpenFile("print.go") + env.Await( + // Log messages are asynchronous to other events on the LSP stream, so we + // can't use OnceMet or AfterChange here. + LogMatching(protocol.Error, "initial workspace load failed", 1, false), + ) + }) +} + +// Tests golang/go#36951. +func TestAdHocPackages_Issue36951(t *testing.T) { + const adHoc = ` +-- b/b.go -- +package b + +func Hello() { + var x int +} +` + Run(t, adHoc, func(t *testing.T, env *Env) { + env.OpenFile("b/b.go") + env.AfterChange( + Diagnostics(env.AtRegexp("b/b.go", "x")), + ) + }) +} + +// Tests golang/go#37984: GOPATH should be read from the go command. +func TestNoGOPATH_Issue37984(t *testing.T) { + const files = ` +-- main.go -- +package main + +func _() { + fmt.Println("Hello World") +} +` + WithOptions( + EnvVars{ + "GOPATH": "", + "GO111MODULE": "off", + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange(Diagnostics(env.AtRegexp("main.go", "fmt"))) + env.SaveBuffer("main.go") + env.AfterChange(NoDiagnostics(ForFile("main.go"))) + }) +} + +// Tests golang/go#38669. +func TestEqualInEnv_Issue38669(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +var _ = x.X +-- x/x.go -- +package x + +var X = 0 +` + WithOptions( + EnvVars{"GOFLAGS": "-tags=foo"}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.OrganizeImports("main.go") + env.AfterChange(NoDiagnostics(ForFile("main.go"))) + }) +} + +// Tests golang/go#38467. +func TestNoSuggestedFixesForGeneratedFiles_Issue38467(t *testing.T) { + const generated = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +// Code generated by generator.go. DO NOT EDIT. + +func _() { + for i, _ := range []string{} { + _ = i + } +} +` + Run(t, generated, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(AtPosition("main.go", 5, 8)), + ReadDiagnostics("main.go", &d), + ) + if fixes := env.GetQuickFixes("main.go", d.Diagnostics); len(fixes) != 0 { + t.Errorf("got quick fixes %v, wanted none", fixes) + } + }) +} + +// Expect a module/GOPATH error if there is an error in the file at startup. +// Tests golang/go#37279. +func TestBrokenWorkspace_OutsideModule(t *testing.T) { + const noModule = ` +-- a.go -- +package foo + +import "mod.com/hello" + +func f() { + hello.Goodbye() +} +` + Run(t, noModule, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.AfterChange( + // AdHoc views are not critical errors, but their missing import + // diagnostics should specifically mention GOROOT or GOPATH (and not + // modules). + NoOutstandingWork(IgnoreTelemetryPromptWork), + Diagnostics( + env.AtRegexp("a.go", `"mod.com`), + WithMessage("in GOROOT"), + ), + ) + // Deleting the import dismisses the warning. + env.RegexpReplace("a.go", `import "mod.com/hello"`, "") + env.AfterChange( + NoOutstandingWork(IgnoreTelemetryPromptWork), + ) + }) +} + +func TestNonGoFolder(t *testing.T) { + const files = ` +-- hello.txt -- +hi mom +` + for _, go111module := range []string{"on", "off", ""} { + t.Run(fmt.Sprintf("GO111MODULE_%v", go111module), func(t *testing.T) { + WithOptions( + EnvVars{"GO111MODULE": go111module}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + NoOutstandingWork(IgnoreTelemetryPromptWork), + ) + }) + }) + } +} + +// Tests the repro case from golang/go#38602. Diagnostics are now handled properly, +// which blocks type checking. +func TestConflictingMainPackageErrors(t *testing.T) { + const collision = ` +-- x/x.go -- +package x + +import "x/hello" + +func Hello() { + hello.HiThere() +} +-- x/main.go -- +package main + +func main() { + fmt.Println("") +} +` + WithOptions( + InGOPATH(), + EnvVars{"GO111MODULE": "off"}, + ).Run(t, collision, func(t *testing.T, env *Env) { + env.OpenFile("x/x.go") + env.AfterChange( + Diagnostics(env.AtRegexp("x/x.go", `^`), WithMessage("found packages main (main.go) and x (x.go)")), + Diagnostics(env.AtRegexp("x/main.go", `^`), WithMessage("found packages main (main.go) and x (x.go)")), + ) + + // We don't recover cleanly from the errors without good overlay support. + if testenv.Go1Point() >= 16 { + env.RegexpReplace("x/x.go", `package x`, `package main`) + env.AfterChange( + Diagnostics(env.AtRegexp("x/main.go", `fmt`)), + ) + } + }) +} + +const ardanLabsProxy = ` +-- github.com/ardanlabs/conf@v1.2.3/go.mod -- +module github.com/ardanlabs/conf + +go 1.12 +-- github.com/ardanlabs/conf@v1.2.3/conf.go -- +package conf + +var ErrHelpWanted error +` + +// Test for golang/go#38211. +func Test_Issue38211(t *testing.T) { + const ardanLabs = ` +-- go.mod -- +module mod.com + +go 1.14 +-- main.go -- +package main + +import "github.com/ardanlabs/conf" + +func main() { + _ = conf.ErrHelpWanted +} +` + WithOptions( + ProxyFiles(ardanLabsProxy), + ).Run(t, ardanLabs, func(t *testing.T, env *Env) { + // Expect a diagnostic with a suggested fix to add + // "github.com/ardanlabs/conf" to the go.mod file. + env.OpenFile("go.mod") + env.OpenFile("main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", `"github.com/ardanlabs/conf"`)), + ReadDiagnostics("main.go", &d), + ) + env.ApplyQuickFixes("main.go", d.Diagnostics) + env.SaveBuffer("go.mod") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + // Comment out the line that depends on conf and expect a + // diagnostic and a fix to remove the import. + env.RegexpReplace("main.go", "_ = conf.ErrHelpWanted", "//_ = conf.ErrHelpWanted") + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", `"github.com/ardanlabs/conf"`)), + ) + env.SaveBuffer("main.go") + // Expect a diagnostic and fix to remove the dependency in the go.mod. + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + Diagnostics(env.AtRegexp("go.mod", "require github.com/ardanlabs/conf"), WithMessage("not used in this module")), + ReadDiagnostics("go.mod", &d), + ) + env.ApplyQuickFixes("go.mod", d.Diagnostics) + env.SaveBuffer("go.mod") + env.AfterChange( + NoDiagnostics(ForFile("go.mod")), + ) + // Uncomment the lines and expect a new diagnostic for the import. + env.RegexpReplace("main.go", "//_ = conf.ErrHelpWanted", "_ = conf.ErrHelpWanted") + env.SaveBuffer("main.go") + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", `"github.com/ardanlabs/conf"`)), + ) + }) +} + +// Test for golang/go#38207. +func TestNewModule_Issue38207(t *testing.T) { + const emptyFile = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +` + WithOptions( + ProxyFiles(ardanLabsProxy), + ).Run(t, emptyFile, func(t *testing.T, env *Env) { + env.CreateBuffer("main.go", `package main + +import "github.com/ardanlabs/conf" + +func main() { + _ = conf.ErrHelpWanted +} +`) + env.SaveBuffer("main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", `"github.com/ardanlabs/conf"`), WithMessage("no required module")), + ReadDiagnostics("main.go", &d), + ) + env.ApplyQuickFixes("main.go", d.Diagnostics) + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +// Test for golang/go#36960. +func TestNewFileBadImports_Issue36960(t *testing.T) { + const simplePackage = ` +-- go.mod -- +module mod.com + +go 1.14 +-- a/a1.go -- +package a + +import "fmt" + +func _() { + fmt.Println("hi") +} +` + Run(t, simplePackage, func(t *testing.T, env *Env) { + env.OpenFile("a/a1.go") + env.CreateBuffer("a/a2.go", ``) + env.SaveBufferWithoutActions("a/a2.go") + env.AfterChange( + NoDiagnostics(ForFile("a/a1.go")), + ) + env.EditBuffer("a/a2.go", fake.NewEdit(0, 0, 0, 0, `package a`)) + env.AfterChange( + NoDiagnostics(ForFile("a/a1.go")), + ) + }) +} + +// This test tries to replicate the workflow of a user creating a new x test. +// It also tests golang/go#39315. +func TestManuallyCreatingXTest(t *testing.T) { + // Create a package that already has a test variant (in-package test). + const testVariant = ` +-- go.mod -- +module mod.com + +go 1.15 +-- hello/hello.go -- +package hello + +func Hello() { + var x int +} +-- hello/hello_test.go -- +package hello + +import "testing" + +func TestHello(t *testing.T) { + var x int + Hello() +} +` + Run(t, testVariant, func(t *testing.T, env *Env) { + // Open the file, triggering the workspace load. + // There are errors in the code to ensure all is working as expected. + env.OpenFile("hello/hello.go") + env.AfterChange( + Diagnostics(env.AtRegexp("hello/hello.go", "x")), + Diagnostics(env.AtRegexp("hello/hello_test.go", "x")), + ) + + // Create an empty file with the intention of making it an x test. + // This resembles a typical flow in an editor like VS Code, in which + // a user would create an empty file and add content, saving + // intermittently. + // TODO(rstambler): There might be more edge cases here, as file + // content can be added incrementally. + env.CreateBuffer("hello/hello_x_test.go", ``) + + // Save the empty file (no actions since formatting will fail). + env.SaveBufferWithoutActions("hello/hello_x_test.go") + + // Add the content. The missing import is for the package under test. + env.EditBuffer("hello/hello_x_test.go", fake.NewEdit(0, 0, 0, 0, `package hello_test + +import ( + "testing" +) + +func TestHello(t *testing.T) { + hello.Hello() +} +`)) + // Expect a diagnostic for the missing import. Save, which should + // trigger import organization. The diagnostic should clear. + env.AfterChange( + Diagnostics(env.AtRegexp("hello/hello_x_test.go", "hello.Hello")), + ) + env.SaveBuffer("hello/hello_x_test.go") + env.AfterChange( + NoDiagnostics(ForFile("hello/hello_x_test.go")), + ) + }) +} + +// Reproduce golang/go#40690. +func TestCreateOnlyXTest(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- foo/foo.go -- +package foo +-- foo/bar_test.go -- +` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("foo/bar_test.go") + env.EditBuffer("foo/bar_test.go", fake.NewEdit(0, 0, 0, 0, "package foo")) + env.Await(env.DoneWithChange()) + env.RegexpReplace("foo/bar_test.go", "package foo", `package foo_test + +import "testing" + +func TestX(t *testing.T) { + var x int +} +`) + env.AfterChange( + Diagnostics(env.AtRegexp("foo/bar_test.go", "x")), + ) + }) +} + +func TestChangePackageName(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- foo/foo.go -- +package foo +-- foo/bar_test.go -- +package foo_ +` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("foo/bar_test.go") + env.AfterChange() + env.RegexpReplace("foo/bar_test.go", "package foo_", "package foo_test") + env.AfterChange( + NoDiagnostics(ForFile("foo/bar_test.go")), + NoDiagnostics(ForFile("foo/foo.go")), + ) + }) +} + +func TestIgnoredFiles(t *testing.T) { + const ws = ` +-- go.mod -- +module mod.com + +go 1.12 +-- _foo/x.go -- +package x + +var _ = foo.Bar +` + Run(t, ws, func(t *testing.T, env *Env) { + env.OpenFile("_foo/x.go") + env.AfterChange( + NoDiagnostics(ForFile("_foo/x.go")), + ) + }) +} + +// Partially reproduces golang/go#38977, moving a file between packages. +// It also gets hit by some go command bug fixed in 1.15, but we don't +// care about that so much here. +func TestDeletePackage(t *testing.T) { + const ws = ` +-- go.mod -- +module mod.com + +go 1.15 +-- a/a.go -- +package a + +const A = 1 + +-- b/b.go -- +package b + +import "mod.com/a" + +const B = a.A + +-- c/c.go -- +package c + +import "mod.com/a" + +const C = a.A +` + Run(t, ws, func(t *testing.T, env *Env) { + env.OpenFile("b/b.go") + env.Await(env.DoneWithOpen()) + // Delete c/c.go, the only file in package c. + env.RemoveWorkspaceFile("c/c.go") + + // We should still get diagnostics for files that exist. + env.RegexpReplace("b/b.go", `a.A`, "a.Nonexistant") + env.AfterChange( + Diagnostics(env.AtRegexp("b/b.go", `Nonexistant`)), + ) + }) +} + +// This is a copy of the scenario_default/quickfix_empty_files.txt test from +// govim. Reproduces golang/go#39646. +func TestQuickFixEmptyFiles(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +` + // To fully recreate the govim tests, we create files by inserting + // a newline, adding to the file, and then deleting the newline. + // Wait for each event to process to avoid cancellations and force + // package loads. + writeGoVim := func(env *Env, name, content string) { + env.WriteWorkspaceFile(name, "") + env.Await(env.DoneWithChangeWatchedFiles()) + + env.CreateBuffer(name, "\n") + env.Await(env.DoneWithOpen()) + + env.EditBuffer(name, fake.NewEdit(1, 0, 1, 0, content)) + env.Await(env.DoneWithChange()) + + env.EditBuffer(name, fake.NewEdit(0, 0, 1, 0, "")) + env.Await(env.DoneWithChange()) + } + + const p = `package p; func DoIt(s string) {};` + const main = `package main + +import "mod.com/p" + +func main() { + p.DoIt(5) +} +` + // A simple version of the test that reproduces most of the problems it + // exposes. + t.Run("short", func(t *testing.T) { + Run(t, mod, func(t *testing.T, env *Env) { + writeGoVim(env, "p/p.go", p) + writeGoVim(env, "main.go", main) + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "5")), + ) + }) + }) + + // A full version that replicates the whole flow of the test. + t.Run("full", func(t *testing.T) { + Run(t, mod, func(t *testing.T, env *Env) { + writeGoVim(env, "p/p.go", p) + writeGoVim(env, "main.go", main) + writeGoVim(env, "p/p_test.go", `package p + +import "testing" + +func TestDoIt(t *testing.T) { + DoIt(5) +} +`) + writeGoVim(env, "p/x_test.go", `package p_test + +import ( + "testing" + + "mod.com/p" +) + +func TestDoIt(t *testing.T) { + p.DoIt(5) +} +`) + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "5")), + Diagnostics(env.AtRegexp("p/p_test.go", "5")), + Diagnostics(env.AtRegexp("p/x_test.go", "5")), + ) + env.RegexpReplace("p/p.go", "s string", "i int") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + NoDiagnostics(ForFile("p/p_test.go")), + NoDiagnostics(ForFile("p/x_test.go")), + ) + }) + }) +} + +func TestSingleFile(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.13 +-- a/a.go -- +package a + +func _() { + var x int +} +` + WithOptions( + // Empty workspace folders. + WorkspaceFolders(), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "x")), + ) + }) +} + +// Reproduces the case described in +// https://github.com/golang/go/issues/39296#issuecomment-652058883. +func TestPkgm(t *testing.T) { + const basic = ` +-- go.mod -- +module mod.com + +go 1.15 +-- foo/foo.go -- +package foo + +import "fmt" + +func Foo() { + fmt.Println("") +} +` + Run(t, basic, func(t *testing.T, env *Env) { + env.WriteWorkspaceFile("foo/foo_test.go", `package main + +func main() { + +}`) + env.OpenFile("foo/foo_test.go") + env.RegexpReplace("foo/foo_test.go", `package main`, `package foo`) + env.AfterChange(NoDiagnostics(ForFile("foo/foo.go"))) + }) +} + +func TestClosingBuffer(t *testing.T) { + const basic = ` +-- go.mod -- +module mod.com + +go 1.14 +-- main.go -- +package main + +func main() {} +` + Run(t, basic, func(t *testing.T, env *Env) { + env.Editor.CreateBuffer(env.Ctx, "foo.go", `package main`) + env.AfterChange() + env.CloseBuffer("foo.go") + env.AfterChange(NoLogMatching(protocol.Info, "packages=0")) + }) +} + +// Reproduces golang/go#38424. +func TestCutAndPaste(t *testing.T) { + const basic = ` +-- go.mod -- +module mod.com + +go 1.14 +-- main2.go -- +package main +` + Run(t, basic, func(t *testing.T, env *Env) { + env.CreateBuffer("main.go", "") + env.Await(env.DoneWithOpen()) + + env.SaveBufferWithoutActions("main.go") + env.Await(env.DoneWithSave(), env.DoneWithChangeWatchedFiles()) + + env.EditBuffer("main.go", fake.NewEdit(0, 0, 0, 0, `package main + +func main() { +} +`)) + env.Await(env.DoneWithChange()) + + env.SaveBuffer("main.go") + env.Await(env.DoneWithSave(), env.DoneWithChangeWatchedFiles()) + + env.EditBuffer("main.go", fake.NewEdit(0, 0, 4, 0, "")) + env.Await(env.DoneWithChange()) + + env.EditBuffer("main.go", fake.NewEdit(0, 0, 0, 0, `package main + +func main() { + var x int +} +`)) + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "x")), + ) + }) +} + +// Reproduces golang/go#39763. +func TestInvalidPackageName(t *testing.T) { + const pkgDefault = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package default + +func main() {} +` + Run(t, pkgDefault, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange( + Diagnostics( + env.AtRegexp("main.go", "default"), + WithMessage("expected 'IDENT'"), + ), + ) + }) +} + +// This test verifies that the workspace scope is effectively limited to the +// workspace folder, if expandWorkspaceToModule is set. +func TestExpandWorkspaceToModule(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/main.go -- +package main + +func main() {} +-- main.go -- +package main + +func main() { + var x int +} +` + WithOptions( + WorkspaceFolders("a"), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a/main.go") + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "x")), + ) + }) + WithOptions( + WorkspaceFolders("a"), + Settings{"expandWorkspaceToModule": false}, + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a/main.go") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +// This test verifies that the workspace scope is effectively limited to the +// set of active modules. +// +// We should not get diagnostics or file watching patterns for paths outside of +// the active workspace. +func TestWorkspaceModules(t *testing.T) { + const mod = ` +-- go.work -- +go 1.18 + +use a +-- a/go.mod -- +module mod.com/a + +go 1.12 +-- a/a.go -- +package a + +func _() { + var x int +} +-- b/go.mod -- +module mod.com/b + +go 1.18 +` + WithOptions( + Settings{ + "subdirWatchPatterns": "on", + }, + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + // Writing this file may cause the snapshot to 'know' about the file b, but + // that shouldn't cause it to watch the 'b' directory. + env.WriteWorkspaceFile("b/b.go", `package b + +func _() { + var x int +} +`) + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "x")), + NoDiagnostics(ForFile("b/b.go")), + FileWatchMatching("a$"), + NoFileWatchMatching("b$"), + ) + }) +} + +func TestSimplifyCompositeLitDiagnostic(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import "fmt" + +type t struct { + msg string +} + +func main() { + x := []t{t{"msg"}} + fmt.Println(x) +} +` + + WithOptions( + Settings{"staticcheck": true}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", `t{"msg"}`), WithMessage("redundant type")), + ReadDiagnostics("main.go", &d), + ) + if tags := d.Diagnostics[0].Tags; len(tags) == 0 || tags[0] != protocol.Unnecessary { + t.Errorf("wanted Unnecessary tag on diagnostic, got %v", tags) + } + env.ApplyQuickFixes("main.go", d.Diagnostics) + env.AfterChange(NoDiagnostics(ForFile("main.go"))) + }) +} + +// Test some secondary diagnostics +func TestSecondaryDiagnostics(t *testing.T) { + const dir = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main +func main() { + panic("not here") +} +-- other.go -- +package main +func main() {} +` + Run(t, dir, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.OpenFile("other.go") + var mainDiags, otherDiags protocol.PublishDiagnosticsParams + env.AfterChange( + ReadDiagnostics("main.go", &mainDiags), + ReadDiagnostics("other.go", &otherDiags), + ) + if len(mainDiags.Diagnostics) != 1 { + t.Fatalf("main.go, got %d diagnostics, expected 1", len(mainDiags.Diagnostics)) + } + keep := mainDiags.Diagnostics[0] + if len(otherDiags.Diagnostics) != 1 { + t.Fatalf("other.go: got %d diagnostics, expected 1", len(otherDiags.Diagnostics)) + } + if len(otherDiags.Diagnostics[0].RelatedInformation) != 1 { + t.Fatalf("got %d RelatedInformations, expected 1", len(otherDiags.Diagnostics[0].RelatedInformation)) + } + // check that the RelatedInformation matches the error from main.go + c := otherDiags.Diagnostics[0].RelatedInformation[0] + if c.Location.Range != keep.Range { + t.Errorf("locations don't match. Got %v expected %v", c.Location.Range, keep.Range) + } + }) +} + +func TestOrphanedFiles(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/a.go -- +package a + +func main() { + var x int +} +-- a/a_exclude.go -- +// +build exclude + +package a + +func _() { + var x int +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "x")), + ) + env.OpenFile("a/a_exclude.go") + + loadOnce := LogMatching(protocol.Info, "query=.*file=.*a_exclude.go", 1, false) + + // can't use OnceMet or AfterChange as logs are async + env.Await(loadOnce) + // ...but ensure that the change has been fully processed before editing. + // Otherwise, there may be a race where the snapshot is cloned before all + // state changes resulting from the load have been processed + // (golang/go#61521). + env.AfterChange() + + // Check that orphaned files are not reloaded, by making a change in + // a.go file and confirming that the workspace diagnosis did not reload + // a_exclude.go. + // + // This is racy (but fails open) because logs are asynchronous to other LSP + // operations. There's a chance gopls _did_ log, and we just haven't seen + // it yet. + env.RegexpReplace("a/a.go", "package a", "package a // arbitrary comment") + env.AfterChange(loadOnce) + }) +} + +func TestSwig(t *testing.T) { + if _, err := exec.LookPath("swig"); err != nil { + t.Skip("skipping test: swig not available") + } + if _, err := exec.LookPath("g++"); err != nil { + t.Skip("skipping test: g++ not available") + } + + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- pkg/simple/export_swig.go -- +package simple + +func ExportSimple(x, y int) int { + return Gcd(x, y) +} +-- pkg/simple/simple.swigcxx -- +%module simple + +%inline %{ +extern int gcd(int x, int y) +{ + int g; + g = y; + while (x > 0) { + g = x; + x = y % x; + y = g; + } + return g; +} +%} +-- main.go -- +package a + +func main() { + var x int +} +` + Run(t, mod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + NoDiagnostics(WithMessage("illegal character U+0023 '#'")), + ) + }) +} + +// When foo_test.go is opened, gopls will object to the borked package name. +// This test asserts that when the package name is fixed, gopls will soon after +// have no more complaints about it. +// https://github.com/golang/go/issues/41061 +func TestRenamePackage(t *testing.T) { + const proxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +const Name = "Blah" +-- random.org@v1.2.3/go.mod -- +module random.org + +go 1.12 +-- random.org@v1.2.3/blah/blah.go -- +package hello + +const Name = "Hello" +` + + const contents = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import "example.com/blah" + +func main() { + blah.Hello() +} +-- bob.go -- +package main +-- foo/foo.go -- +package foo +-- foo/foo_test.go -- +package foo_ +` + + WithOptions( + ProxyFiles(proxy), + InGOPATH(), + EnvVars{"GO111MODULE": "off"}, + ).Run(t, contents, func(t *testing.T, env *Env) { + // Simulate typing character by character. + env.OpenFile("foo/foo_test.go") + env.Await(env.DoneWithOpen()) + env.RegexpReplace("foo/foo_test.go", "_", "_t") + env.Await(env.DoneWithChange()) + env.RegexpReplace("foo/foo_test.go", "_t", "_test") + env.AfterChange( + NoDiagnostics(ForFile("foo/foo_test.go")), + NoOutstandingWork(IgnoreTelemetryPromptWork), + ) + }) +} + +// TestProgressBarErrors confirms that critical workspace load errors are shown +// and updated via progress reports. +func TestProgressBarErrors(t *testing.T) { + const pkg = ` +-- go.mod -- +modul mod.com + +go 1.12 +-- main.go -- +package main +` + Run(t, pkg, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.AfterChange( + OutstandingWork(server.WorkspaceLoadFailure, "unknown directive"), + ) + env.EditBuffer("go.mod", fake.NewEdit(0, 0, 3, 0, `module mod.com + +go 1.hello +`)) + // As of golang/go#42529, go.mod changes do not reload the workspace until + // they are saved. + env.SaveBufferWithoutActions("go.mod") + env.AfterChange( + OutstandingWork(server.WorkspaceLoadFailure, "invalid go version"), + ) + env.RegexpReplace("go.mod", "go 1.hello", "go 1.12") + env.SaveBufferWithoutActions("go.mod") + env.AfterChange( + NoOutstandingWork(IgnoreTelemetryPromptWork), + ) + }) +} + +func TestDeleteDirectory(t *testing.T) { + const mod = ` +-- bob/bob.go -- +package bob + +func Hello() { + var x int +} +-- go.mod -- +module mod.com +-- cmd/main.go -- +package main + +import "mod.com/bob" + +func main() { + bob.Hello() +} +` + WithOptions( + Settings{ + // Now that we don't watch subdirs by default (except for VS Code), + // we must explicitly ask gopls to requests subdir watch patterns. + "subdirWatchPatterns": "on", + }, + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + FileWatchMatching("bob"), + ) + env.RemoveWorkspaceFile("bob") + env.AfterChange( + Diagnostics(env.AtRegexp("cmd/main.go", `"mod.com/bob"`)), + NoDiagnostics(ForFile("bob/bob.go")), + NoFileWatchMatching("bob"), + ) + }) +} + +// Confirms that circular imports are tested and reported. +func TestCircularImports(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- self/self.go -- +package self + +import _ "mod.com/self" +func Hello() {} +-- double/a/a.go -- +package a + +import _ "mod.com/double/b" +-- double/b/b.go -- +package b + +import _ "mod.com/double/a" +-- triple/a/a.go -- +package a + +import _ "mod.com/triple/b" +-- triple/b/b.go -- +package b + +import _ "mod.com/triple/c" +-- triple/c/c.go -- +package c + +import _ "mod.com/triple/a" +` + Run(t, mod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("self/self.go", `_ "mod.com/self"`), WithMessage("import cycle not allowed")), + Diagnostics(env.AtRegexp("double/a/a.go", `_ "mod.com/double/b"`), WithMessage("import cycle not allowed")), + Diagnostics(env.AtRegexp("triple/a/a.go", `_ "mod.com/triple/b"`), WithMessage("import cycle not allowed")), + ) + }) +} + +// Tests golang/go#46667: deleting a problematic import path should resolve +// import cycle errors. +func TestResolveImportCycle(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.test + +go 1.16 +-- a/a.go -- +package a + +import "mod.test/b" + +const A = b.A +const B = 2 +-- b/b.go -- +package b + +import "mod.test/a" + +const A = 1 +const B = a.B + ` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.OpenFile("b/b.go") + env.AfterChange( + // The Go command sometimes tells us about only one of the import cycle + // errors below. Also, sometimes we get an error during type checking + // instead of during list, due to missing metadata. This is likely due to + // a race. + // For robustness of this test, succeed if we get any reasonable error. + // + // TODO(golang/go#52904): we should get *both* of these errors. + // TODO(golang/go#64899): we should always get an import cycle error + // rather than a missing metadata error. + AnyOf( + Diagnostics(env.AtRegexp("a/a.go", `"mod.test/b"`)), + Diagnostics(env.AtRegexp("b/b.go", `"mod.test/a"`)), + ), + ) + env.RegexpReplace("b/b.go", `const B = a\.B`, "") + env.SaveBuffer("b/b.go") + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + NoDiagnostics(ForFile("b/b.go")), + ) + }) +} + +func TestBadImport(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import ( + _ "nosuchpkg" +) +` + t.Run("module", func(t *testing.T) { + Run(t, mod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `"nosuchpkg"`), WithMessage(`could not import nosuchpkg (no required module provides package "nosuchpkg"`)), + ) + }) + }) + t.Run("GOPATH", func(t *testing.T) { + WithOptions( + InGOPATH(), + EnvVars{"GO111MODULE": "off"}, + Modes(Default), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `"nosuchpkg"`), WithMessage(`cannot find package "nosuchpkg"`)), + ) + }) + }) +} + +func TestNestedModules(t *testing.T) { + const proxy = ` +-- nested.com@v1.0.0/go.mod -- +module nested.com + +go 1.12 +-- nested.com@v1.0.0/hello/hello.go -- +package hello + +func Hello() {} +` + + const nested = ` +-- go.mod -- +module mod.com + +go 1.12 + +require nested.com v1.0.0 +-- go.sum -- +nested.com v1.0.0 h1:I6spLE4CgFqMdBPc+wTV2asDO2QJ3tU0YAT+jkLeN1I= +nested.com v1.0.0/go.mod h1:ly53UzXQgVjSlV7wicdBB4p8BxfytuGT1Xcyv0ReJfI= +-- main.go -- +package main + +import "nested.com/hello" + +func main() { + hello.Hello() +} +-- nested/go.mod -- +module nested.com + +-- nested/hello/hello.go -- +package hello + +func Hello() { + helloHelper() +} +-- nested/hello/hello_helper.go -- +package hello + +func helloHelper() {} +` + WithOptions( + ProxyFiles(proxy), + Modes(Default), + ).Run(t, nested, func(t *testing.T, env *Env) { + // Expect a diagnostic in a nested module. + env.OpenFile("nested/hello/hello.go") + env.AfterChange( + NoDiagnostics(ForFile("nested/hello/hello.go")), + ) + loc := env.GoToDefinition(env.RegexpSearch("nested/hello/hello.go", "helloHelper")) + want := "nested/hello/hello_helper.go" + if got := env.Sandbox.Workdir.URIToPath(loc.URI); got != want { + t.Errorf("Definition() returned %q, want %q", got, want) + } + }) +} + +func TestAdHocPackagesReloading(t *testing.T) { + const nomod = ` +-- main.go -- +package main + +func main() {} +` + Run(t, nomod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.RegexpReplace("main.go", "{}", "{ var x int; }") // simulate typing + env.AfterChange(NoLogMatching(protocol.Info, "packages=1")) + }) +} + +func TestBuildTagChange(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- foo.go -- +// decoy comment +// +build hidden +// decoy comment + +package foo +var Foo = 1 +-- bar.go -- +package foo +var Bar = Foo +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("foo.go") + env.AfterChange(Diagnostics(env.AtRegexp("bar.go", `Foo`))) + env.RegexpReplace("foo.go", `\+build`, "") + env.AfterChange(NoDiagnostics(ForFile("bar.go"))) + }) + +} + +func TestIssue44736(t *testing.T) { + const files = ` + -- go.mod -- +module blah.com + +go 1.16 +-- main.go -- +package main + +import "fmt" + +func main() { + asdf + fmt.Printf("This is a test %v") + fdas +} +-- other.go -- +package main + +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.OpenFile("other.go") + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "asdf")), + Diagnostics(env.AtRegexp("main.go", "fdas")), + ) + env.SetBufferContent("other.go", "package main\n\nasdf") + // The new diagnostic in other.go should not suppress diagnostics in main.go. + env.AfterChange( + Diagnostics(env.AtRegexp("other.go", "asdf"), WithMessage("expected declaration")), + Diagnostics(env.AtRegexp("main.go", "asdf")), + ) + }) +} + +func TestInitialization(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.16 +-- main.go -- +package main +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.Await(env.DoneWithOpen()) + env.RegexpReplace("go.mod", "module", "modul") + env.SaveBufferWithoutActions("go.mod") + env.AfterChange( + NoLogMatching(protocol.Error, "initial workspace load failed"), + ) + }) +} + +// This test confirms that the view does not reinitialize when a go.mod file is +// opened. +func TestNoReinitialize(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() {} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.Await( + // Check that we have only loaded "<dir>/..." once. + // Log messages are asynchronous to other events on the LSP stream, so we + // can't use OnceMet or AfterChange here. + LogMatching(protocol.Info, `.*query=.*\.\.\..*`, 1, false), + ) + }) +} + +func TestLangVersion(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +const C = 0b10 +` + Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `0b10`), WithMessage("go1.13 or later")), + ) + env.WriteWorkspaceFile("go.mod", "module mod.com \n\ngo 1.13\n") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +func TestNoQuickFixForUndeclaredConstraint(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- main.go -- +package main + +func F[T C](_ T) { +} +` + + Run(t, files, func(t *testing.T, env *Env) { + var d protocol.PublishDiagnosticsParams + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `C`)), + ReadDiagnostics("main.go", &d), + ) + if fixes := env.GetQuickFixes("main.go", d.Diagnostics); len(fixes) != 0 { + t.Errorf("got quick fixes %v, wanted none", fixes) + } + }) +} + +func TestEditGoDirective(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.16 +-- main.go -- +package main + +func F[T any](_ T) { +} +` + Run(t, files, func(_ *testing.T, env *Env) { // Create a new workspace-level directory and empty file. + var d protocol.PublishDiagnosticsParams + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `T any`), WithMessage("type parameter")), + ReadDiagnostics("main.go", &d), + ) + + env.ApplyQuickFixes("main.go", d.Diagnostics) + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +func TestEditGoDirectiveWorkspace(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.16 +-- go.work -- +go 1.18 + +use . +-- main.go -- +package main + +func F[T any](_ T) { +} +` + Run(t, files, func(_ *testing.T, env *Env) { // Create a new workspace-level directory and empty file. + var d protocol.PublishDiagnosticsParams + + // We should have a diagnostic because generics are not supported at 1.16. + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `T any`), WithMessage("type parameter")), + ReadDiagnostics("main.go", &d), + ) + + // This diagnostic should have a quick fix to edit the go version. + env.ApplyQuickFixes("main.go", d.Diagnostics) + + // Once the edit is applied, the problematic diagnostics should be + // resolved. + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +// This test demonstrates that analysis facts are correctly propagated +// across packages. +func TestInterpackageAnalysis(t *testing.T) { + const src = ` +-- go.mod -- +module example.com +-- a/a.go -- +package a + +import "example.com/b" + +func _() { + new(b.B).Printf("%d", "s") // printf error +} + +-- b/b.go -- +package b + +import "example.com/c" + +type B struct{} + +func (B) Printf(format string, args ...interface{}) { + c.MyPrintf(format, args...) +} + +-- c/c.go -- +package c + +import "fmt" + +func MyPrintf(format string, args ...interface{}) { + fmt.Printf(format, args...) +} +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange( + Diagnostics( + env.AtRegexp("a/a.go", "new.*Printf"), + WithMessage("format %d has arg \"s\" of wrong type string"), + ), + ) + }) +} + +// This test ensures that only Analyzers with RunDespiteErrors=true +// are invoked on a package that would not compile, even if the errors +// are distant and localized. +func TestErrorsThatPreventAnalysis(t *testing.T) { + const src = ` +-- go.mod -- +module example.com +-- a/a.go -- +package a + +import "fmt" +import "sync" +import _ "example.com/b" + +func _() { + // The copylocks analyzer (RunDespiteErrors, FactTypes={}) does run. + var mu sync.Mutex + mu2 := mu // copylocks error, reported + _ = &mu2 + + // The printf analyzer (!RunDespiteErrors, FactTypes!={}) does not run: + // (c, printf) failed because of type error in c + // (b, printf) and (a, printf) do not run because of failed prerequisites. + fmt.Printf("%d", "s") // printf error, unreported + + // The bools analyzer (!RunDespiteErrors, FactTypes={}) does not run: + var cond bool + _ = cond != true && cond != true // bools error, unreported +} + +-- b/b.go -- +package b + +import _ "example.com/c" + +-- c/c.go -- +package c + +var _ = 1 / "" // type error + +` + Run(t, src, func(t *testing.T, env *Env) { + var diags protocol.PublishDiagnosticsParams + env.OpenFile("a/a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "mu2 := (mu)"), WithMessage("assignment copies lock value")), + ReadDiagnostics("a/a.go", &diags)) + + // Assert that there were no other diagnostics. + // In particular: + // - "fmt.Printf" does not trigger a [printf] finding; + // - "cond != true" does not trigger a [bools] finding. + // + // We use this check in preference to NoDiagnosticAtRegexp + // as it is robust in case of minor mistakes in the position + // regexp, and because it reports unexpected diagnostics. + if got, want := len(diags.Diagnostics), 1; got != want { + t.Errorf("got %d diagnostics in a/a.go, want %d:", got, want) + for i, diag := range diags.Diagnostics { + t.Logf("Diagnostics[%d] = %+v", i, diag) + } + } + }) +} + +// This test demonstrates the deprecated symbol analyzer +// produces deprecation notices with expected severity and tags. +func TestDeprecatedAnalysis(t *testing.T) { + const src = ` +-- go.mod -- +module example.com +-- a/a.go -- +package a + +import "example.com/b" + +func _() { + new(b.B).Obsolete() // deprecated +} + +-- b/b.go -- +package b + +type B struct{} + +// Deprecated: use New instead. +func (B) Obsolete() {} + +func (B) New() {} +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange( + Diagnostics( + env.AtRegexp("a/a.go", "new.*Obsolete"), + WithMessage("use New instead."), + WithSeverityTags("deprecated", protocol.SeverityHint, []protocol.DiagnosticTag{protocol.Deprecated}), + ), + ) + }) +} + +func TestDiagnosticsOnlyOnSaveFile(t *testing.T) { + // This functionality is broken because the new orphaned file diagnostics + // logic wants to publish diagnostics for changed files, independent of any + // snapshot diagnostics pass, and this causes stale diagnostics to be + // invalidated. + // + // We can fix this behavior more correctly by also honoring the + // diagnosticsTrigger in DiagnoseOrphanedFiles, but that would require + // resolving configuration that is independent of the snapshot. In other + // words, we need to figure out which cache.Folder.Options applies to the + // changed file, even if it does not have a snapshot. + t.Skip("temporary skip for golang/go#57979: revisit after zero-config logic is in place") + + const onlyMod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { + Foo() +} +-- foo.go -- +package main + +func Foo() {} +` + WithOptions( + Settings{ + "diagnosticsTrigger": "Save", + }, + ).Run(t, onlyMod, func(t *testing.T, env *Env) { + env.OpenFile("foo.go") + env.RegexpReplace("foo.go", "(Foo)", "Bar") // Makes reference to Foo undefined/undeclared. + env.AfterChange(NoDiagnostics()) // No diagnostics update until file save. + + env.SaveBuffer("foo.go") + // Compiler's error message about undeclared names vary depending on the version, + // but must be explicit about the problematic name. + env.AfterChange(Diagnostics(env.AtRegexp("main.go", "Foo"), WithMessage("Foo"))) + + env.OpenFile("main.go") + env.RegexpReplace("main.go", "(Foo)", "Bar") + // No diagnostics update until file save. That results in outdated diagnostic. + env.AfterChange(Diagnostics(env.AtRegexp("main.go", "Bar"), WithMessage("Foo"))) + + env.SaveBuffer("main.go") + env.AfterChange(NoDiagnostics()) + }) +} diff --git a/contribs/gnopls/internal/test/integration/diagnostics/golist_test.go b/contribs/gnopls/internal/test/integration/diagnostics/golist_test.go new file mode 100644 index 00000000000..8c11246d3e1 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/diagnostics/golist_test.go @@ -0,0 +1,71 @@ +// 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 diagnostics + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/cache" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/internal/testenv" +) + +func TestGoListErrors(t *testing.T) { + testenv.NeedsTool(t, "cgo") + + const src = ` +-- go.mod -- +module a.com + +go 1.18 +-- a/a.go -- +package a + +import +-- c/c.go -- +package c + +/* +int fortythree() { return 42; } +*/ +import "C" + +func Foo() { + print(C.fortytwo()) +} +-- p/p.go -- +package p + +import "a.com/q" + +const P = q.Q + 1 +-- q/q.go -- +package q + +import "a.com/p" + +const Q = p.P + 1 +` + + Run(t, src, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics( + env.AtRegexp("a/a.go", "import\n()"), + FromSource(string(cache.ParseError)), + ), + Diagnostics( + AtPosition("c/c.go", 0, 0), + FromSource(string(cache.ListError)), + WithMessage("may indicate failure to perform cgo processing"), + ), + Diagnostics( + env.AtRegexp("p/p.go", `"a.com/q"`), + FromSource(string(cache.ListError)), + WithMessage("import cycle not allowed"), + ), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/diagnostics/gopackagesdriver_test.go b/contribs/gnopls/internal/test/integration/diagnostics/gopackagesdriver_test.go new file mode 100644 index 00000000000..65700b69795 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/diagnostics/gopackagesdriver_test.go @@ -0,0 +1,85 @@ +// 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 diagnostics + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// Test that the import error does not mention GOPATH when building with +// go/packages driver. +func TestBrokenWorkspace_GOPACKAGESDRIVER(t *testing.T) { + // A go.mod file is actually needed here, because the fake go/packages driver + // uses go list behind the scenes, and we load go/packages driver workspaces + // with ./... + const files = ` +-- go.mod -- +module m +go 1.12 + +-- a.go -- +package foo + +import "mod.com/hello" + +func f() { +} +` + WithOptions( + FakeGoPackagesDriver(t), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.AfterChange( + Diagnostics( + env.AtRegexp("a.go", `"mod.com`), + WithMessage("go/packages driver"), + ), + ) + // Deleting the import removes the error. + env.RegexpReplace("a.go", `import "mod.com/hello"`, "") + env.AfterChange( + NoDiagnostics(ForFile("a.go")), + ) + }) +} + +func TestValidImportCheck_GoPackagesDriver(t *testing.T) { + const files = ` +-- go.work -- +use . + +-- go.mod -- +module example.com +go 1.0 + +-- a/a.go -- +package a +import _ "example.com/b/internal/c" + +-- b/internal/c/c.go -- +package c +` + + // Note that 'go list' produces an error ("use of internal package %q not allowed") + // and gopls produces another ("invalid use of internal package %q") with source=compiler. + // Here we assert that the second one is not reported with a go/packages driver. + // (We don't assert that the first is missing, because the test driver wraps go list!) + + // go list + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange(Diagnostics(WithMessage(`invalid use of internal package "example.com/b/internal/c"`))) + }) + + // test driver + WithOptions( + FakeGoPackagesDriver(t), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange(NoDiagnostics(WithMessage(`invalid use of internal package "example.com/b/internal/c"`))) + }) +} diff --git a/contribs/gnopls/internal/test/integration/diagnostics/invalidation_test.go b/contribs/gnopls/internal/test/integration/diagnostics/invalidation_test.go new file mode 100644 index 00000000000..e8d39c3c38a --- /dev/null +++ b/contribs/gnopls/internal/test/integration/diagnostics/invalidation_test.go @@ -0,0 +1,141 @@ +// 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 diagnostics + +import ( + "fmt" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// Test for golang/go#50267: diagnostics should be re-sent after a file is +// opened. +func TestDiagnosticsAreResentAfterCloseOrOpen(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.16 +-- main.go -- +package main + +func _() { + x := 2 +} +` + Run(t, files, func(t *testing.T, env *Env) { // Create a new workspace-level directory and empty file. + env.OpenFile("main.go") + var afterOpen protocol.PublishDiagnosticsParams + env.AfterChange( + ReadDiagnostics("main.go", &afterOpen), + ) + env.CloseBuffer("main.go") + var afterClose protocol.PublishDiagnosticsParams + env.AfterChange( + ReadDiagnostics("main.go", &afterClose), + ) + if afterOpen.Version == afterClose.Version { + t.Errorf("publishDiagnostics: got the same version after closing (%d) as after opening", afterOpen.Version) + } + env.OpenFile("main.go") + var afterReopen protocol.PublishDiagnosticsParams + env.AfterChange( + ReadDiagnostics("main.go", &afterReopen), + ) + if afterReopen.Version == afterClose.Version { + t.Errorf("pubslishDiagnostics: got the same version after reopening (%d) as after closing", afterClose.Version) + } + }) +} + +// Test for the "chatty" diagnostics: gopls should re-send diagnostics for +// changed files after every file change, even if diagnostics did not change. +func TestChattyDiagnostics(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.16 +-- main.go -- +package main + +func _() { + x := 2 +} + +// Irrelevant comment #0 +` + + Run(t, files, func(t *testing.T, env *Env) { // Create a new workspace-level directory and empty file. + env.OpenFile("main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + ReadDiagnostics("main.go", &d), + ) + + if len(d.Diagnostics) != 1 { + t.Fatalf("len(Diagnostics) = %d, want 1", len(d.Diagnostics)) + } + msg := d.Diagnostics[0].Message + + for i := 0; i < 5; i++ { + before := d.Version + env.RegexpReplace("main.go", "Irrelevant comment #.", fmt.Sprintf("Irrelevant comment #%d", i)) + env.AfterChange( + ReadDiagnostics("main.go", &d), + ) + + if d.Version == before { + t.Errorf("after change, got version %d, want new version", d.Version) + } + + // As a sanity check, make sure we have the same diagnostic. + if len(d.Diagnostics) != 1 { + t.Fatalf("len(Diagnostics) = %d, want 1", len(d.Diagnostics)) + } + newMsg := d.Diagnostics[0].Message + if newMsg != msg { + t.Errorf("after change, got message %q, want %q", newMsg, msg) + } + } + }) +} + +func TestCreatingPackageInvalidatesDiagnostics_Issue66384(t *testing.T) { + const files = ` +-- go.mod -- +module example.com + +go 1.15 +-- main.go -- +package main + +import "example.com/pkg" + +func main() { + var _ pkg.Thing +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `"example.com/pkg"`)), + ) + // In order for this test to reproduce golang/go#66384, we have to create + // the buffer, wait for loads, and *then* "type out" the contents. Doing so + // reproduces the conditions of the bug report, that typing the package + // name itself doesn't invalidate the broken import. + env.CreateBuffer("pkg/pkg.go", "") + env.AfterChange() + env.EditBuffer("pkg/pkg.go", protocol.TextEdit{NewText: "package pkg\ntype Thing struct{}\n"}) + env.AfterChange() + env.SaveBuffer("pkg/pkg.go") + env.AfterChange(NoDiagnostics()) + env.SetBufferContent("pkg/pkg.go", "package pkg") + env.AfterChange(Diagnostics(env.AtRegexp("main.go", "Thing"))) + }) +} diff --git a/contribs/gnopls/internal/test/integration/diagnostics/undeclared_test.go b/contribs/gnopls/internal/test/integration/diagnostics/undeclared_test.go new file mode 100644 index 00000000000..5579c0752d7 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/diagnostics/undeclared_test.go @@ -0,0 +1,73 @@ +// 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 diagnostics + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestUndeclaredDiagnostics(t *testing.T) { + src := ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/a.go -- +package a + +func _() int { + return x +} +-- b/b.go -- +package b + +func _() int { + var y int + y = y + return y +} +` + Run(t, src, func(t *testing.T, env *Env) { + isUnnecessary := func(diag protocol.Diagnostic) bool { + for _, tag := range diag.Tags { + if tag == protocol.Unnecessary { + return true + } + } + return false + } + + // 'x' is undeclared, but still necessary. + env.OpenFile("a/a.go") + var adiags protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "x")), + ReadDiagnostics("a/a.go", &adiags), + ) + if got := len(adiags.Diagnostics); got != 1 { + t.Errorf("len(Diagnostics) = %d, want 1", got) + } + if diag := adiags.Diagnostics[0]; isUnnecessary(diag) { + t.Errorf("%v tagged unnecessary, want necessary", diag) + } + + // 'y = y' is pointless, and should be detected as unnecessary. + env.OpenFile("b/b.go") + var bdiags protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("b/b.go", "y = y")), + ReadDiagnostics("b/b.go", &bdiags), + ) + if got := len(bdiags.Diagnostics); got != 1 { + t.Errorf("len(Diagnostics) = %d, want 1", got) + } + if diag := bdiags.Diagnostics[0]; !isUnnecessary(diag) { + t.Errorf("%v tagged necessary, want unnecessary", diag) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/doc.go b/contribs/gnopls/internal/test/integration/doc.go new file mode 100644 index 00000000000..a1c5856c261 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/doc.go @@ -0,0 +1,156 @@ +// 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 integration provides a framework for writing integration tests of gopls. +// +// The behaviors that matter to users, and the scenarios they +// typically describe in bug report, are usually expressed in terms of +// editor interactions. For example: "When I open my editor in this +// directory, navigate to this file, and change this line, I get a +// diagnostic that doesn't make sense". The integration package +// provides an API for gopls maintainers to express these types of +// user interactions in ordinary Go tests, validate them, and run them +// in a variety of execution modes. +// +// # Test package setup +// +// The integration test package uses a couple of uncommon patterns to reduce +// boilerplate in test bodies. First, it is intended to be imported as "." so +// that helpers do not need to be qualified. Second, it requires some setup +// that is currently implemented in the integration.Main function, which must be +// invoked by TestMain. Therefore, a minimal integration testing package looks +// like this: +// +// package feature +// +// import ( +// "fmt" +// "testing" +// +// "golang.org/x/tools/gopls/internal/hooks" +// . "golang.org/x/tools/gopls/internal/test/integration" +// ) +// +// func TestMain(m *testing.M) { +// os.Exit(Main(m, hooks.Options)) +// } +// +// # Writing a simple integration test +// +// To run an integration test use the integration.Run function, which accepts a +// txtar-encoded archive defining the initial workspace state. This function +// sets up the workspace in a temporary directory, creates a fake text editor, +// starts gopls, and initializes an LSP session. It then invokes the provided +// test function with an *Env encapsulating the newly created +// environment. Because gopls may be run in various modes (as a sidecar or +// daemon process, with different settings), the test runner may perform this +// process multiple times, re-running the test function each time with a new +// environment. +// +// func TestOpenFile(t *testing.T) { +// const files = ` +// -- go.mod -- +// module mod.com +// +// go 1.12 +// -- foo.go -- +// package foo +// ` +// Run(t, files, func(t *testing.T, env *Env) { +// env.OpenFile("foo.go") +// }) +// } +// +// # Configuring integration test execution +// +// The integration package exposes several options that affect the setup process +// described above. To use these options, use the WithOptions function: +// +// WithOptions(opts...).Run(...) +// +// See options.go for a full list of available options. +// +// # Operating on editor state +// +// To operate on editor state within the test body, the Env type provides +// access to the workspace directory (Env.SandBox), text editor (Env.Editor), +// LSP server (Env.Server), and 'awaiter' (Env.Awaiter). +// +// In most cases, operations on these primitive building blocks of the +// integration test environment expect a Context (which should be a child of +// env.Ctx), and return an error. To avoid boilerplate, the Env exposes a set +// of wrappers in wrappers.go for use in scripting: +// +// env.CreateBuffer("c/c.go", "") +// env.EditBuffer("c/c.go", editor.Edit{ +// Text: `package c`, +// }) +// +// These wrappers thread through Env.Ctx, and call t.Fatal on any errors. +// +// # Expressing expectations +// +// The general pattern for an integration test is to script interactions with the +// fake editor and sandbox, and assert that gopls behaves correctly after each +// state change. Unfortunately, this is complicated by the fact that state +// changes are communicated to gopls via unidirectional client->server +// notifications (didOpen, didChange, etc.), and resulting gopls behavior such +// as diagnostics, logs, or messages is communicated back via server->client +// notifications. Therefore, within integration tests we must be able to say "do +// this, and then eventually gopls should do that". To achieve this, the +// integration package provides a framework for expressing conditions that must +// eventually be met, in terms of the Expectation type. +// +// To express the assertion that "eventually gopls must meet these +// expectations", use env.Await(...): +// +// env.RegexpReplace("x/x.go", `package x`, `package main`) +// env.Await(env.DiagnosticAtRegexp("x/main.go", `fmt`)) +// +// Await evaluates the provided expectations atomically, whenever the client +// receives a state-changing notification from gopls. See expectation.go for a +// full list of available expectations. +// +// A problem with this model is that if gopls never meets the provided +// expectations, the test runner will hang until the test timeout +// (which defaults to 10m). There are two ways to work around this +// poor behavior: +// +// 1. Use a precondition to define precisely when we expect conditions to be +// met. Gopls provides the OnceMet(precondition, expectations...) pattern +// to express ("once this precondition is met, the following expectations +// must all hold"). To instrument preconditions, gopls uses verbose +// progress notifications to inform the client about ongoing work (see +// CompletedWork). The most common precondition is to wait for gopls to be +// done processing all change notifications, for which the integration package +// provides the AfterChange helper. For example: +// +// // We expect diagnostics to be cleared after gopls is done processing the +// // didSave notification. +// env.SaveBuffer("a/go.mod") +// env.AfterChange(EmptyDiagnostics("a/go.mod")) +// +// 2. Set a shorter timeout during development, if you expect to be breaking +// tests. By setting the environment variable GOPLS_INTEGRATION_TEST_TIMEOUT=5s, +// integration tests will time out after 5 seconds. +// +// # Tips & Tricks +// +// Here are some tips and tricks for working with integration tests: +// +// 1. Set the environment variable GOPLS_INTEGRRATION_TEST_TIMEOUT=5s during development. +// 2. Run tests with -short. This will only run integration tests in the +// default gopls execution mode. +// 3. Use capture groups to narrow regexp positions. All regular-expression +// based positions (such as DiagnosticAtRegexp) will match the position of +// the first capture group, if any are provided. This can be used to +// identify a specific position in the code for a pattern that may occur in +// multiple places. For example `var (mu) sync.Mutex` matches the position +// of "mu" within the variable declaration. +// 4. Read diagnostics into a variable to implement more complicated +// assertions about diagnostic state in the editor. To do this, use the +// pattern OnceMet(precondition, ReadDiagnostics("file.go", &d)) to capture +// the current diagnostics as soon as the precondition is met. This is +// preferable to accessing the diagnostics directly, as it avoids races. +package integration diff --git a/contribs/gnopls/internal/test/integration/env.go b/contribs/gnopls/internal/test/integration/env.go new file mode 100644 index 00000000000..1a7ea70c89b --- /dev/null +++ b/contribs/gnopls/internal/test/integration/env.go @@ -0,0 +1,358 @@ +// 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 integration + +import ( + "context" + "fmt" + "strings" + "sync" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/internal/jsonrpc2/servertest" +) + +// Env holds the building blocks of an editor testing environment, providing +// wrapper methods that hide the boilerplate of plumbing contexts and checking +// errors. +type Env struct { + T testing.TB // TODO(rfindley): rename to TB + Ctx context.Context + + // Most tests should not need to access the scratch area, editor, server, or + // connection, but they are available if needed. + Sandbox *fake.Sandbox + Server servertest.Connector + + // Editor is owned by the Env, and shut down + Editor *fake.Editor + + Awaiter *Awaiter +} + +// An Awaiter keeps track of relevant LSP state, so that it may be asserted +// upon with Expectations. +// +// Wire it into a fake.Editor using Awaiter.Hooks(). +// +// TODO(rfindley): consider simply merging Awaiter with the fake.Editor. It +// probably is not worth its own abstraction. +type Awaiter struct { + workdir *fake.Workdir + + mu sync.Mutex + // For simplicity, each waiter gets a unique ID. + nextWaiterID int + state State + waiters map[int]*condition +} + +func NewAwaiter(workdir *fake.Workdir) *Awaiter { + return &Awaiter{ + workdir: workdir, + state: State{ + diagnostics: make(map[string]*protocol.PublishDiagnosticsParams), + work: make(map[protocol.ProgressToken]*workProgress), + startedWork: make(map[string]uint64), + completedWork: make(map[string]uint64), + }, + waiters: make(map[int]*condition), + } +} + +// Hooks returns LSP client hooks required for awaiting asynchronous expectations. +func (a *Awaiter) Hooks() fake.ClientHooks { + return fake.ClientHooks{ + OnDiagnostics: a.onDiagnostics, + OnLogMessage: a.onLogMessage, + OnWorkDoneProgressCreate: a.onWorkDoneProgressCreate, + OnProgress: a.onProgress, + OnShowDocument: a.onShowDocument, + OnShowMessage: a.onShowMessage, + OnShowMessageRequest: a.onShowMessageRequest, + OnRegisterCapability: a.onRegisterCapability, + OnUnregisterCapability: a.onUnregisterCapability, + } +} + +// ResetShownDocuments resets the set of accumulated ShownDocuments seen so far. +func (a *Awaiter) ResetShownDocuments() { a.state.showDocument = nil } + +// State encapsulates the server state TODO: explain more +type State struct { + // diagnostics are a map of relative path->diagnostics params + diagnostics map[string]*protocol.PublishDiagnosticsParams + logs []*protocol.LogMessageParams + showDocument []*protocol.ShowDocumentParams + showMessage []*protocol.ShowMessageParams + showMessageRequest []*protocol.ShowMessageRequestParams + + registrations []*protocol.RegistrationParams + registeredCapabilities map[string]protocol.Registration + unregistrations []*protocol.UnregistrationParams + + // outstandingWork is a map of token->work summary. All tokens are assumed to + // be string, though the spec allows for numeric tokens as well. + work map[protocol.ProgressToken]*workProgress + startedWork map[string]uint64 // title -> count of 'begin' + completedWork map[string]uint64 // title -> count of 'end' +} + +type workProgress struct { + title, msg, endMsg string + percent float64 + complete bool // seen 'end' +} + +// This method, provided for debugging, accesses mutable fields without a lock, +// so it must not be called concurrent with any State mutation. +func (s State) String() string { + var b strings.Builder + b.WriteString("#### log messages (see RPC logs for full text):\n") + for _, msg := range s.logs { + summary := fmt.Sprintf("%v: %q", msg.Type, msg.Message) + if len(summary) > 60 { + summary = summary[:57] + "..." + } + // Some logs are quite long, and since they should be reproduced in the RPC + // logs on any failure we include here just a short summary. + fmt.Fprint(&b, "\t"+summary+"\n") + } + b.WriteString("\n") + b.WriteString("#### diagnostics:\n") + for name, params := range s.diagnostics { + fmt.Fprintf(&b, "\t%s (version %d):\n", name, params.Version) + for _, d := range params.Diagnostics { + fmt.Fprintf(&b, "\t\t%d:%d [%s]: %s\n", d.Range.Start.Line, d.Range.Start.Character, d.Source, d.Message) + } + } + b.WriteString("\n") + b.WriteString("#### outstanding work:\n") + for token, state := range s.work { + if state.complete { + continue + } + name := state.title + if name == "" { + name = fmt.Sprintf("!NO NAME(token: %s)", token) + } + fmt.Fprintf(&b, "\t%s: %.2f\n", name, state.percent) + } + b.WriteString("#### completed work:\n") + for name, count := range s.completedWork { + fmt.Fprintf(&b, "\t%s: %d\n", name, count) + } + return b.String() +} + +// A condition is satisfied when all expectations are simultaneously +// met. At that point, the 'met' channel is closed. On any failure, err is set +// and the failed channel is closed. +type condition struct { + expectations []Expectation + verdict chan Verdict +} + +func (a *Awaiter) onDiagnostics(_ context.Context, d *protocol.PublishDiagnosticsParams) error { + a.mu.Lock() + defer a.mu.Unlock() + + pth := a.workdir.URIToPath(d.URI) + a.state.diagnostics[pth] = d + a.checkConditionsLocked() + return nil +} + +func (a *Awaiter) onShowDocument(_ context.Context, params *protocol.ShowDocumentParams) error { + a.mu.Lock() + defer a.mu.Unlock() + + a.state.showDocument = append(a.state.showDocument, params) + a.checkConditionsLocked() + return nil +} + +func (a *Awaiter) onShowMessage(_ context.Context, m *protocol.ShowMessageParams) error { + a.mu.Lock() + defer a.mu.Unlock() + + a.state.showMessage = append(a.state.showMessage, m) + a.checkConditionsLocked() + return nil +} + +func (a *Awaiter) onShowMessageRequest(_ context.Context, m *protocol.ShowMessageRequestParams) error { + a.mu.Lock() + defer a.mu.Unlock() + + a.state.showMessageRequest = append(a.state.showMessageRequest, m) + a.checkConditionsLocked() + return nil +} + +func (a *Awaiter) onLogMessage(_ context.Context, m *protocol.LogMessageParams) error { + a.mu.Lock() + defer a.mu.Unlock() + + a.state.logs = append(a.state.logs, m) + a.checkConditionsLocked() + return nil +} + +func (a *Awaiter) onWorkDoneProgressCreate(_ context.Context, m *protocol.WorkDoneProgressCreateParams) error { + a.mu.Lock() + defer a.mu.Unlock() + + a.state.work[m.Token] = &workProgress{} + return nil +} + +func (a *Awaiter) onProgress(_ context.Context, m *protocol.ProgressParams) error { + a.mu.Lock() + defer a.mu.Unlock() + work, ok := a.state.work[m.Token] + if !ok { + panic(fmt.Sprintf("got progress report for unknown report %v: %v", m.Token, m)) + } + v := m.Value.(map[string]interface{}) + switch kind := v["kind"]; kind { + case "begin": + work.title = v["title"].(string) + a.state.startedWork[work.title]++ + if msg, ok := v["message"]; ok { + work.msg = msg.(string) + } + case "report": + if pct, ok := v["percentage"]; ok { + work.percent = pct.(float64) + } + if msg, ok := v["message"]; ok { + work.msg = msg.(string) + } + case "end": + work.complete = true + a.state.completedWork[work.title]++ + if msg, ok := v["message"]; ok { + work.endMsg = msg.(string) + } + } + a.checkConditionsLocked() + return nil +} + +func (a *Awaiter) onRegisterCapability(_ context.Context, m *protocol.RegistrationParams) error { + a.mu.Lock() + defer a.mu.Unlock() + + a.state.registrations = append(a.state.registrations, m) + if a.state.registeredCapabilities == nil { + a.state.registeredCapabilities = make(map[string]protocol.Registration) + } + for _, reg := range m.Registrations { + a.state.registeredCapabilities[reg.Method] = reg + } + a.checkConditionsLocked() + return nil +} + +func (a *Awaiter) onUnregisterCapability(_ context.Context, m *protocol.UnregistrationParams) error { + a.mu.Lock() + defer a.mu.Unlock() + + a.state.unregistrations = append(a.state.unregistrations, m) + a.checkConditionsLocked() + return nil +} + +func (a *Awaiter) checkConditionsLocked() { + for id, condition := range a.waiters { + if v, _ := checkExpectations(a.state, condition.expectations); v != Unmet { + delete(a.waiters, id) + condition.verdict <- v + } + } +} + +// checkExpectations reports whether s meets all expectations. +func checkExpectations(s State, expectations []Expectation) (Verdict, string) { + finalVerdict := Met + var summary strings.Builder + for _, e := range expectations { + v := e.Check(s) + if v > finalVerdict { + finalVerdict = v + } + fmt.Fprintf(&summary, "%v: %s\n", v, e.Description) + } + return finalVerdict, summary.String() +} + +// Await blocks until the given expectations are all simultaneously met. +// +// Generally speaking Await should be avoided because it blocks indefinitely if +// gopls ends up in a state where the expectations are never going to be met. +// Use AfterChange or OnceMet instead, so that the runner knows when to stop +// waiting. +func (e *Env) Await(expectations ...Expectation) { + e.T.Helper() + if err := e.Awaiter.Await(e.Ctx, expectations...); err != nil { + e.T.Fatal(err) + } +} + +// OnceMet blocks until the precondition is met by the state or becomes +// unmeetable. If it was met, OnceMet checks that the state meets all +// expectations in mustMeets. +func (e *Env) OnceMet(precondition Expectation, mustMeets ...Expectation) { + e.T.Helper() + e.Await(OnceMet(precondition, mustMeets...)) +} + +// Await waits for all expectations to simultaneously be met. It should only be +// called from the main test goroutine. +func (a *Awaiter) Await(ctx context.Context, expectations ...Expectation) error { + a.mu.Lock() + // Before adding the waiter, we check if the condition is currently met or + // failed to avoid a race where the condition was realized before Await was + // called. + switch verdict, summary := checkExpectations(a.state, expectations); verdict { + case Met: + a.mu.Unlock() + return nil + case Unmeetable: + err := fmt.Errorf("unmeetable expectations:\n%s\nstate:\n%v", summary, a.state) + a.mu.Unlock() + return err + } + cond := &condition{ + expectations: expectations, + verdict: make(chan Verdict), + } + a.waiters[a.nextWaiterID] = cond + a.nextWaiterID++ + a.mu.Unlock() + + var err error + select { + case <-ctx.Done(): + err = ctx.Err() + case v := <-cond.verdict: + if v != Met { + err = fmt.Errorf("condition has final verdict %v", v) + } + } + a.mu.Lock() + defer a.mu.Unlock() + _, summary := checkExpectations(a.state, expectations) + + // Debugging an unmet expectation can be tricky, so we put some effort into + // nicely formatting the failure. + if err != nil { + return fmt.Errorf("waiting on:\n%s\nerr:%v\n\nstate:\n%v", summary, err, a.state) + } + return nil +} diff --git a/contribs/gnopls/internal/test/integration/env_test.go b/contribs/gnopls/internal/test/integration/env_test.go new file mode 100644 index 00000000000..32203f7cb83 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/env_test.go @@ -0,0 +1,68 @@ +// 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 integration + +import ( + "context" + "encoding/json" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +func TestProgressUpdating(t *testing.T) { + a := &Awaiter{ + state: State{ + work: make(map[protocol.ProgressToken]*workProgress), + startedWork: make(map[string]uint64), + completedWork: make(map[string]uint64), + }, + } + ctx := context.Background() + if err := a.onWorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{ + Token: "foo", + }); err != nil { + t.Fatal(err) + } + if err := a.onWorkDoneProgressCreate(ctx, &protocol.WorkDoneProgressCreateParams{ + Token: "bar", + }); err != nil { + t.Fatal(err) + } + updates := []struct { + token string + value interface{} + }{ + {"foo", protocol.WorkDoneProgressBegin{Kind: "begin", Title: "foo work"}}, + {"bar", protocol.WorkDoneProgressBegin{Kind: "begin", Title: "bar work"}}, + {"foo", protocol.WorkDoneProgressEnd{Kind: "end"}}, + {"bar", protocol.WorkDoneProgressReport{Kind: "report", Percentage: 42}}, + } + for _, update := range updates { + params := &protocol.ProgressParams{ + Token: update.token, + Value: update.value, + } + data, err := json.Marshal(params) + if err != nil { + t.Fatal(err) + } + var unmarshaled protocol.ProgressParams + if err := json.Unmarshal(data, &unmarshaled); err != nil { + t.Fatal(err) + } + if err := a.onProgress(ctx, &unmarshaled); err != nil { + t.Fatal(err) + } + } + if got, want := a.state.completedWork["foo work"], uint64(1); got != want { + t.Errorf(`completedWork["foo work"] = %d, want %d`, got, want) + } + got := *a.state.work["bar"] + want := workProgress{title: "bar work", percent: 42} + if got != want { + t.Errorf("work progress for \"bar\": %v, want %v", got, want) + } +} diff --git a/contribs/gnopls/internal/test/integration/expectation.go b/contribs/gnopls/internal/test/integration/expectation.go new file mode 100644 index 00000000000..858daeee18a --- /dev/null +++ b/contribs/gnopls/internal/test/integration/expectation.go @@ -0,0 +1,829 @@ +// 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 integration + +import ( + "fmt" + "regexp" + "sort" + "strings" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/server" +) + +var ( + // InitialWorkspaceLoad is an expectation that the workspace initial load has + // completed. It is verified via workdone reporting. + InitialWorkspaceLoad = CompletedWork(server.DiagnosticWorkTitle(server.FromInitialWorkspaceLoad), 1, false) +) + +// A Verdict is the result of checking an expectation against the current +// editor state. +type Verdict int + +// Order matters for the following constants: verdicts are sorted in order of +// decisiveness. +const ( + // Met indicates that an expectation is satisfied by the current state. + Met Verdict = iota + // Unmet indicates that an expectation is not currently met, but could be met + // in the future. + Unmet + // Unmeetable indicates that an expectation cannot be satisfied in the + // future. + Unmeetable +) + +func (v Verdict) String() string { + switch v { + case Met: + return "Met" + case Unmet: + return "Unmet" + case Unmeetable: + return "Unmeetable" + } + return fmt.Sprintf("unrecognized verdict %d", v) +} + +// An Expectation is an expected property of the state of the LSP client. +// The Check function reports whether the property is met. +// +// Expectations are combinators. By composing them, tests may express +// complex expectations in terms of simpler ones. +// +// TODO(rfindley): as expectations are combined, it becomes harder to identify +// why they failed. A better signature for Check would be +// +// func(State) (Verdict, string) +// +// returning a reason for the verdict that can be composed similarly to +// descriptions. +type Expectation struct { + Check func(State) Verdict + + // Description holds a noun-phrase identifying what the expectation checks. + // + // TODO(rfindley): revisit existing descriptions to ensure they compose nicely. + Description string +} + +// OnceMet returns an Expectation that, once the precondition is met, asserts +// that mustMeet is met. +func OnceMet(precondition Expectation, mustMeets ...Expectation) Expectation { + check := func(s State) Verdict { + switch pre := precondition.Check(s); pre { + case Unmeetable: + return Unmeetable + case Met: + for _, mustMeet := range mustMeets { + verdict := mustMeet.Check(s) + if verdict != Met { + return Unmeetable + } + } + return Met + default: + return Unmet + } + } + description := describeExpectations(mustMeets...) + return Expectation{ + Check: check, + Description: fmt.Sprintf("once %q is met, must have:\n%s", precondition.Description, description), + } +} + +func describeExpectations(expectations ...Expectation) string { + var descriptions []string + for _, e := range expectations { + descriptions = append(descriptions, e.Description) + } + return strings.Join(descriptions, "\n") +} + +// Not inverts the sense of an expectation: a met expectation is unmet, and an +// unmet expectation is met. +func Not(e Expectation) Expectation { + check := func(s State) Verdict { + switch v := e.Check(s); v { + case Met: + return Unmet + case Unmet, Unmeetable: + return Met + default: + panic(fmt.Sprintf("unexpected verdict %v", v)) + } + } + description := describeExpectations(e) + return Expectation{ + Check: check, + Description: fmt.Sprintf("not: %s", description), + } +} + +// AnyOf returns an expectation that is satisfied when any of the given +// expectations is met. +func AnyOf(anyOf ...Expectation) Expectation { + check := func(s State) Verdict { + for _, e := range anyOf { + verdict := e.Check(s) + if verdict == Met { + return Met + } + } + return Unmet + } + description := describeExpectations(anyOf...) + return Expectation{ + Check: check, + Description: fmt.Sprintf("Any of:\n%s", description), + } +} + +// AllOf expects that all given expectations are met. +// +// TODO(rfindley): the problem with these types of combinators (OnceMet, AnyOf +// and AllOf) is that we lose the information of *why* they failed: the Awaiter +// is not smart enough to look inside. +// +// Refactor the API such that the Check function is responsible for explaining +// why an expectation failed. This should allow us to significantly improve +// test output: we won't need to summarize state at all, as the verdict +// explanation itself should describe clearly why the expectation not met. +func AllOf(allOf ...Expectation) Expectation { + check := func(s State) Verdict { + verdict := Met + for _, e := range allOf { + if v := e.Check(s); v > verdict { + verdict = v + } + } + return verdict + } + description := describeExpectations(allOf...) + return Expectation{ + Check: check, + Description: fmt.Sprintf("All of:\n%s", description), + } +} + +// ReadDiagnostics is an Expectation that stores the current diagnostics for +// fileName in into, whenever it is evaluated. +// +// It can be used in combination with OnceMet or AfterChange to capture the +// state of diagnostics when other expectations are satisfied. +func ReadDiagnostics(fileName string, into *protocol.PublishDiagnosticsParams) Expectation { + check := func(s State) Verdict { + diags, ok := s.diagnostics[fileName] + if !ok { + return Unmeetable + } + *into = *diags + return Met + } + return Expectation{ + Check: check, + Description: fmt.Sprintf("read diagnostics for %q", fileName), + } +} + +// ReadAllDiagnostics is an expectation that stores all published diagnostics +// into the provided map, whenever it is evaluated. +// +// It can be used in combination with OnceMet or AfterChange to capture the +// state of diagnostics when other expectations are satisfied. +func ReadAllDiagnostics(into *map[string]*protocol.PublishDiagnosticsParams) Expectation { + check := func(s State) Verdict { + allDiags := make(map[string]*protocol.PublishDiagnosticsParams) + for name, diags := range s.diagnostics { + allDiags[name] = diags + } + *into = allDiags + return Met + } + return Expectation{ + Check: check, + Description: "read all diagnostics", + } +} + +// ShownDocument asserts that the client has received a +// ShowDocumentRequest for the given URI. +func ShownDocument(uri protocol.URI) Expectation { + check := func(s State) Verdict { + for _, params := range s.showDocument { + if params.URI == uri { + return Met + } + } + return Unmet + } + return Expectation{ + Check: check, + Description: fmt.Sprintf("received window/showDocument for URI %s", uri), + } +} + +// ShownDocuments is an expectation that appends each showDocument +// request into the provided slice, whenever it is evaluated. +// +// It can be used in combination with OnceMet or AfterChange to +// capture the set of showDocument requests when other expectations +// are satisfied. +func ShownDocuments(into *[]*protocol.ShowDocumentParams) Expectation { + check := func(s State) Verdict { + *into = append(*into, s.showDocument...) + return Met + } + return Expectation{ + Check: check, + Description: "read shown documents", + } +} + +// NoShownMessage asserts that the editor has not received a ShowMessage. +func NoShownMessage(subString string) Expectation { + check := func(s State) Verdict { + for _, m := range s.showMessage { + if strings.Contains(m.Message, subString) { + return Unmeetable + } + } + return Met + } + return Expectation{ + Check: check, + Description: fmt.Sprintf("no ShowMessage received containing %q", subString), + } +} + +// ShownMessage asserts that the editor has received a ShowMessageRequest +// containing the given substring. +func ShownMessage(containing string) Expectation { + check := func(s State) Verdict { + for _, m := range s.showMessage { + if strings.Contains(m.Message, containing) { + return Met + } + } + return Unmet + } + return Expectation{ + Check: check, + Description: fmt.Sprintf("received window/showMessage containing %q", containing), + } +} + +// ShownMessageRequest asserts that the editor has received a +// ShowMessageRequest with message matching the given regular expression. +func ShownMessageRequest(messageRegexp string) Expectation { + msgRE := regexp.MustCompile(messageRegexp) + check := func(s State) Verdict { + if len(s.showMessageRequest) == 0 { + return Unmet + } + for _, m := range s.showMessageRequest { + if msgRE.MatchString(m.Message) { + return Met + } + } + return Unmet + } + return Expectation{ + Check: check, + Description: fmt.Sprintf("ShowMessageRequest matching %q", messageRegexp), + } +} + +// DoneDiagnosingChanges expects that diagnostics are complete from common +// change notifications: didOpen, didChange, didSave, didChangeWatchedFiles, +// and didClose. +// +// This can be used when multiple notifications may have been sent, such as +// when a didChange is immediately followed by a didSave. It is insufficient to +// simply await NoOutstandingWork, because the LSP client has no control over +// when the server starts processing a notification. Therefore, we must keep +// track of +func (e *Env) DoneDiagnosingChanges() Expectation { + stats := e.Editor.Stats() + statsBySource := map[server.ModificationSource]uint64{ + server.FromDidOpen: stats.DidOpen, + server.FromDidChange: stats.DidChange, + server.FromDidSave: stats.DidSave, + server.FromDidChangeWatchedFiles: stats.DidChangeWatchedFiles, + server.FromDidClose: stats.DidClose, + server.FromDidChangeConfiguration: stats.DidChangeConfiguration, + } + + var expected []server.ModificationSource + for k, v := range statsBySource { + if v > 0 { + expected = append(expected, k) + } + } + + // Sort for stability. + sort.Slice(expected, func(i, j int) bool { + return expected[i] < expected[j] + }) + + var all []Expectation + for _, source := range expected { + all = append(all, CompletedWork(server.DiagnosticWorkTitle(source), statsBySource[source], true)) + } + + return AllOf(all...) +} + +// AfterChange expects that the given expectations will be met after all +// state-changing notifications have been processed by the server. +// Specifically, it awaits the awaits completion of the process of diagnosis +// after the following notifications, before checking the given expectations: +// - textDocument/didOpen +// - textDocument/didChange +// - textDocument/didSave +// - textDocument/didClose +// - workspace/didChangeWatchedFiles +// - workspace/didChangeConfiguration +func (e *Env) AfterChange(expectations ...Expectation) { + e.T.Helper() + e.OnceMet( + e.DoneDiagnosingChanges(), + expectations..., + ) +} + +// DoneWithOpen expects all didOpen notifications currently sent by the editor +// to be completely processed. +func (e *Env) DoneWithOpen() Expectation { + opens := e.Editor.Stats().DidOpen + return CompletedWork(server.DiagnosticWorkTitle(server.FromDidOpen), opens, true) +} + +// StartedChange expects that the server has at least started processing all +// didChange notifications sent from the client. +func (e *Env) StartedChange() Expectation { + changes := e.Editor.Stats().DidChange + return StartedWork(server.DiagnosticWorkTitle(server.FromDidChange), changes) +} + +// DoneWithChange expects all didChange notifications currently sent by the +// editor to be completely processed. +func (e *Env) DoneWithChange() Expectation { + changes := e.Editor.Stats().DidChange + return CompletedWork(server.DiagnosticWorkTitle(server.FromDidChange), changes, true) +} + +// DoneWithSave expects all didSave notifications currently sent by the editor +// to be completely processed. +func (e *Env) DoneWithSave() Expectation { + saves := e.Editor.Stats().DidSave + return CompletedWork(server.DiagnosticWorkTitle(server.FromDidSave), saves, true) +} + +// StartedChangeWatchedFiles expects that the server has at least started +// processing all didChangeWatchedFiles notifications sent from the client. +func (e *Env) StartedChangeWatchedFiles() Expectation { + changes := e.Editor.Stats().DidChangeWatchedFiles + return StartedWork(server.DiagnosticWorkTitle(server.FromDidChangeWatchedFiles), changes) +} + +// DoneWithChangeWatchedFiles expects all didChangeWatchedFiles notifications +// currently sent by the editor to be completely processed. +func (e *Env) DoneWithChangeWatchedFiles() Expectation { + changes := e.Editor.Stats().DidChangeWatchedFiles + return CompletedWork(server.DiagnosticWorkTitle(server.FromDidChangeWatchedFiles), changes, true) +} + +// DoneWithClose expects all didClose notifications currently sent by the +// editor to be completely processed. +func (e *Env) DoneWithClose() Expectation { + changes := e.Editor.Stats().DidClose + return CompletedWork(server.DiagnosticWorkTitle(server.FromDidClose), changes, true) +} + +// StartedWork expect a work item to have been started >= atLeast times. +// +// See CompletedWork. +func StartedWork(title string, atLeast uint64) Expectation { + check := func(s State) Verdict { + if s.startedWork[title] >= atLeast { + return Met + } + return Unmet + } + return Expectation{ + Check: check, + Description: fmt.Sprintf("started work %q at least %d time(s)", title, atLeast), + } +} + +// CompletedWork expects a work item to have been completed >= atLeast times. +// +// Since the Progress API doesn't include any hidden metadata, we must use the +// progress notification title to identify the work we expect to be completed. +func CompletedWork(title string, count uint64, atLeast bool) Expectation { + check := func(s State) Verdict { + completed := s.completedWork[title] + if completed == count || atLeast && completed > count { + return Met + } + return Unmet + } + desc := fmt.Sprintf("completed work %q %v times", title, count) + if atLeast { + desc = fmt.Sprintf("completed work %q at least %d time(s)", title, count) + } + return Expectation{ + Check: check, + Description: desc, + } +} + +type WorkStatus struct { + // Last seen message from either `begin` or `report` progress. + Msg string + // Message sent with `end` progress message. + EndMsg string +} + +// CompletedProgress expects that workDone progress is complete for the given +// progress token. When non-nil WorkStatus is provided, it will be filled +// when the expectation is met. +// +// If the token is not a progress token that the client has seen, this +// expectation is Unmeetable. +func CompletedProgress(token protocol.ProgressToken, into *WorkStatus) Expectation { + check := func(s State) Verdict { + work, ok := s.work[token] + if !ok { + return Unmeetable // TODO(rfindley): refactor to allow the verdict to explain this result + } + if work.complete { + if into != nil { + into.Msg = work.msg + into.EndMsg = work.endMsg + } + return Met + } + return Unmet + } + desc := fmt.Sprintf("completed work for token %v", token) + return Expectation{ + Check: check, + Description: desc, + } +} + +// OutstandingWork expects a work item to be outstanding. The given title must +// be an exact match, whereas the given msg must only be contained in the work +// item's message. +func OutstandingWork(title, msg string) Expectation { + check := func(s State) Verdict { + for _, work := range s.work { + if work.complete { + continue + } + if work.title == title && strings.Contains(work.msg, msg) { + return Met + } + } + return Unmet + } + return Expectation{ + Check: check, + Description: fmt.Sprintf("outstanding work: %q containing %q", title, msg), + } +} + +// NoOutstandingWork asserts that there is no work initiated using the LSP +// $/progress API that has not completed. +// +// If non-nil, the ignore func is used to ignore certain work items for the +// purpose of this check. +// +// TODO(rfindley): consider refactoring to treat outstanding work the same way +// we treat diagnostics: with an algebra of filters. +func NoOutstandingWork(ignore func(title, msg string) bool) Expectation { + check := func(s State) Verdict { + for _, w := range s.work { + if w.complete { + continue + } + if w.title == "" { + // A token that has been created but not yet used. + // + // TODO(rfindley): this should be separated in the data model: until + // the "begin" notification, work should not be in progress. + continue + } + if ignore != nil && ignore(w.title, w.msg) { + continue + } + return Unmet + } + return Met + } + return Expectation{ + Check: check, + Description: "no outstanding work", + } +} + +// IgnoreTelemetryPromptWork may be used in conjunction with NoOutStandingWork +// to ignore the telemetry prompt. +func IgnoreTelemetryPromptWork(title, msg string) bool { + return title == server.TelemetryPromptWorkTitle +} + +// NoErrorLogs asserts that the client has not received any log messages of +// error severity. +func NoErrorLogs() Expectation { + return NoLogMatching(protocol.Error, "") +} + +// LogMatching asserts that the client has received a log message +// of type typ matching the regexp re a certain number of times. +// +// The count argument specifies the expected number of matching logs. If +// atLeast is set, this is a lower bound, otherwise there must be exactly count +// matching logs. +// +// Logs are asynchronous to other LSP messages, so this expectation should not +// be used with combinators such as OnceMet or AfterChange that assert on +// ordering with respect to other operations. +func LogMatching(typ protocol.MessageType, re string, count int, atLeast bool) Expectation { + rec, err := regexp.Compile(re) + if err != nil { + panic(err) + } + check := func(state State) Verdict { + var found int + for _, msg := range state.logs { + if msg.Type == typ && rec.Match([]byte(msg.Message)) { + found++ + } + } + // Check for an exact or "at least" match. + if found == count || (found >= count && atLeast) { + return Met + } + // If we require an exact count, and have received more than expected, the + // expectation can never be met. + if found > count && !atLeast { + return Unmeetable + } + return Unmet + } + desc := fmt.Sprintf("log message matching %q expected %v times", re, count) + if atLeast { + desc = fmt.Sprintf("log message matching %q expected at least %v times", re, count) + } + return Expectation{ + Check: check, + Description: desc, + } +} + +// NoLogMatching asserts that the client has not received a log message +// of type typ matching the regexp re. If re is an empty string, any log +// message is considered a match. +func NoLogMatching(typ protocol.MessageType, re string) Expectation { + var r *regexp.Regexp + if re != "" { + var err error + r, err = regexp.Compile(re) + if err != nil { + panic(err) + } + } + check := func(state State) Verdict { + for _, msg := range state.logs { + if msg.Type != typ { + continue + } + if r == nil || r.Match([]byte(msg.Message)) { + return Unmeetable + } + } + return Met + } + return Expectation{ + Check: check, + Description: fmt.Sprintf("no log message matching %q", re), + } +} + +// FileWatchMatching expects that a file registration matches re. +func FileWatchMatching(re string) Expectation { + return Expectation{ + Check: checkFileWatch(re, Met, Unmet), + Description: fmt.Sprintf("file watch matching %q", re), + } +} + +// NoFileWatchMatching expects that no file registration matches re. +func NoFileWatchMatching(re string) Expectation { + return Expectation{ + Check: checkFileWatch(re, Unmet, Met), + Description: fmt.Sprintf("no file watch matching %q", re), + } +} + +func checkFileWatch(re string, onMatch, onNoMatch Verdict) func(State) Verdict { + rec := regexp.MustCompile(re) + return func(s State) Verdict { + r := s.registeredCapabilities["workspace/didChangeWatchedFiles"] + watchers := jsonProperty(r.RegisterOptions, "watchers").([]interface{}) + for _, watcher := range watchers { + pattern := jsonProperty(watcher, "globPattern").(string) + if rec.MatchString(pattern) { + return onMatch + } + } + return onNoMatch + } +} + +// jsonProperty extracts a value from a path of JSON property names, assuming +// the default encoding/json unmarshaling to the empty interface (i.e.: that +// JSON objects are unmarshalled as map[string]interface{}) +// +// For example, if obj is unmarshalled from the following json: +// +// { +// "foo": { "bar": 3 } +// } +// +// Then jsonProperty(obj, "foo", "bar") will be 3. +func jsonProperty(obj interface{}, path ...string) interface{} { + if len(path) == 0 || obj == nil { + return obj + } + m := obj.(map[string]interface{}) + return jsonProperty(m[path[0]], path[1:]...) +} + +// Diagnostics asserts that there is at least one diagnostic matching the given +// filters. +func Diagnostics(filters ...DiagnosticFilter) Expectation { + check := func(s State) Verdict { + diags := flattenDiagnostics(s) + for _, filter := range filters { + var filtered []flatDiagnostic + for _, d := range diags { + if filter.check(d.name, d.diag) { + filtered = append(filtered, d) + } + } + if len(filtered) == 0 { + // TODO(rfindley): if/when expectations describe their own failure, we + // can provide more useful information here as to which filter caused + // the failure. + return Unmet + } + diags = filtered + } + return Met + } + var descs []string + for _, filter := range filters { + descs = append(descs, filter.desc) + } + return Expectation{ + Check: check, + Description: "any diagnostics " + strings.Join(descs, ", "), + } +} + +// NoDiagnostics asserts that there are no diagnostics matching the given +// filters. Notably, if no filters are supplied this assertion checks that +// there are no diagnostics at all, for any file. +func NoDiagnostics(filters ...DiagnosticFilter) Expectation { + check := func(s State) Verdict { + diags := flattenDiagnostics(s) + for _, filter := range filters { + var filtered []flatDiagnostic + for _, d := range diags { + if filter.check(d.name, d.diag) { + filtered = append(filtered, d) + } + } + diags = filtered + } + if len(diags) > 0 { + return Unmet + } + return Met + } + var descs []string + for _, filter := range filters { + descs = append(descs, filter.desc) + } + return Expectation{ + Check: check, + Description: "no diagnostics " + strings.Join(descs, ", "), + } +} + +type flatDiagnostic struct { + name string + diag protocol.Diagnostic +} + +func flattenDiagnostics(state State) []flatDiagnostic { + var result []flatDiagnostic + for name, diags := range state.diagnostics { + for _, diag := range diags.Diagnostics { + result = append(result, flatDiagnostic{name, diag}) + } + } + return result +} + +// -- Diagnostic filters -- + +// A DiagnosticFilter filters the set of diagnostics, for assertion with +// Diagnostics or NoDiagnostics. +type DiagnosticFilter struct { + desc string + check func(name string, _ protocol.Diagnostic) bool +} + +// ForFile filters to diagnostics matching the sandbox-relative file name. +func ForFile(name string) DiagnosticFilter { + return DiagnosticFilter{ + desc: fmt.Sprintf("for file %q", name), + check: func(diagName string, _ protocol.Diagnostic) bool { + return diagName == name + }, + } +} + +// FromSource filters to diagnostics matching the given diagnostics source. +func FromSource(source string) DiagnosticFilter { + return DiagnosticFilter{ + desc: fmt.Sprintf("with source %q", source), + check: func(_ string, d protocol.Diagnostic) bool { + return d.Source == source + }, + } +} + +// AtRegexp filters to diagnostics in the file with sandbox-relative path name, +// at the first position matching the given regexp pattern. +// +// TODO(rfindley): pass in the editor to expectations, so that they may depend +// on editor state and AtRegexp can be a function rather than a method. +func (e *Env) AtRegexp(name, pattern string) DiagnosticFilter { + loc := e.RegexpSearch(name, pattern) + return DiagnosticFilter{ + desc: fmt.Sprintf("at the first position (%v) matching %#q in %q", loc.Range.Start, pattern, name), + check: func(diagName string, d protocol.Diagnostic) bool { + return diagName == name && d.Range.Start == loc.Range.Start + }, + } +} + +// AtPosition filters to diagnostics at location name:line:character, for a +// sandbox-relative path name. +// +// Line and character are 0-based, and character measures UTF-16 codes. +// +// Note: prefer the more readable AtRegexp. +func AtPosition(name string, line, character uint32) DiagnosticFilter { + pos := protocol.Position{Line: line, Character: character} + return DiagnosticFilter{ + desc: fmt.Sprintf("at %s:%d:%d", name, line, character), + check: func(diagName string, d protocol.Diagnostic) bool { + return diagName == name && d.Range.Start == pos + }, + } +} + +// WithMessage filters to diagnostics whose message contains the given +// substring. +func WithMessage(substring string) DiagnosticFilter { + return DiagnosticFilter{ + desc: fmt.Sprintf("with message containing %q", substring), + check: func(_ string, d protocol.Diagnostic) bool { + return strings.Contains(d.Message, substring) + }, + } +} + +// WithSeverityTags filters to diagnostics whose severity and tags match +// the given expectation. +func WithSeverityTags(diagName string, severity protocol.DiagnosticSeverity, tags []protocol.DiagnosticTag) DiagnosticFilter { + return DiagnosticFilter{ + desc: fmt.Sprintf("with diagnostic %q with severity %q and tag %#q", diagName, severity, tags), + check: func(_ string, d protocol.Diagnostic) bool { + return d.Source == diagName && d.Severity == severity && cmp.Equal(d.Tags, tags) + }, + } +} diff --git a/contribs/gnopls/internal/test/integration/fake/client.go b/contribs/gnopls/internal/test/integration/fake/client.go new file mode 100644 index 00000000000..8fdddd92574 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/client.go @@ -0,0 +1,221 @@ +// 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 fake + +import ( + "context" + "encoding/json" + "fmt" + "path" + "path/filepath" + "sync/atomic" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/integration/fake/glob" +) + +// ClientHooks are a set of optional hooks called during handling of +// the corresponding client method (see protocol.Client for the +// LSP server-to-client RPCs) in order to make test expectations +// awaitable. +type ClientHooks struct { + OnLogMessage func(context.Context, *protocol.LogMessageParams) error + OnDiagnostics func(context.Context, *protocol.PublishDiagnosticsParams) error + OnWorkDoneProgressCreate func(context.Context, *protocol.WorkDoneProgressCreateParams) error + OnProgress func(context.Context, *protocol.ProgressParams) error + OnShowDocument func(context.Context, *protocol.ShowDocumentParams) error + OnShowMessage func(context.Context, *protocol.ShowMessageParams) error + OnShowMessageRequest func(context.Context, *protocol.ShowMessageRequestParams) error + OnRegisterCapability func(context.Context, *protocol.RegistrationParams) error + OnUnregisterCapability func(context.Context, *protocol.UnregistrationParams) error +} + +// Client is an implementation of the [protocol.Client] interface +// based on the test's fake [Editor]. It mostly delegates +// functionality to hooks that can be configured by tests. +type Client struct { + editor *Editor + hooks ClientHooks + onApplyEdit atomic.Pointer[ApplyEditHandler] // hook for marker tests to intercept edits +} + +type ApplyEditHandler = func(context.Context, *protocol.WorkspaceEdit) error + +// SetApplyEditHandler sets the (non-nil) handler for ApplyEdit +// downcalls, and returns a function to restore the previous one. +// Use it around client-to-server RPCs to capture the edits. +// The default handler is c.Editor.onApplyEdit +func (c *Client) SetApplyEditHandler(h ApplyEditHandler) func() { + if h == nil { + panic("h is nil") + } + prev := c.onApplyEdit.Swap(&h) + return func() { + if c.onApplyEdit.Swap(prev) != &h { + panic("improper nesting of SetApplyEditHandler, restore") + } + } +} + +func (c *Client) CodeLensRefresh(context.Context) error { return nil } + +func (c *Client) InlayHintRefresh(context.Context) error { return nil } + +func (c *Client) DiagnosticRefresh(context.Context) error { return nil } + +func (c *Client) FoldingRangeRefresh(context.Context) error { return nil } + +func (c *Client) InlineValueRefresh(context.Context) error { return nil } + +func (c *Client) SemanticTokensRefresh(context.Context) error { return nil } + +func (c *Client) LogTrace(context.Context, *protocol.LogTraceParams) error { return nil } + +func (c *Client) ShowMessage(ctx context.Context, params *protocol.ShowMessageParams) error { + if c.hooks.OnShowMessage != nil { + return c.hooks.OnShowMessage(ctx, params) + } + return nil +} + +func (c *Client) ShowMessageRequest(ctx context.Context, params *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) { + if c.hooks.OnShowMessageRequest != nil { + if err := c.hooks.OnShowMessageRequest(ctx, params); err != nil { + return nil, err + } + } + if c.editor.config.MessageResponder != nil { + return c.editor.config.MessageResponder(params) + } + return nil, nil // don't choose, which is effectively dismissing the message +} + +func (c *Client) LogMessage(ctx context.Context, params *protocol.LogMessageParams) error { + if c.hooks.OnLogMessage != nil { + return c.hooks.OnLogMessage(ctx, params) + } + return nil +} + +func (c *Client) Event(ctx context.Context, event *interface{}) error { + return nil +} + +func (c *Client) PublishDiagnostics(ctx context.Context, params *protocol.PublishDiagnosticsParams) error { + if c.hooks.OnDiagnostics != nil { + return c.hooks.OnDiagnostics(ctx, params) + } + return nil +} + +func (c *Client) WorkspaceFolders(context.Context) ([]protocol.WorkspaceFolder, error) { + return []protocol.WorkspaceFolder{}, nil +} + +func (c *Client) Configuration(_ context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) { + results := make([]interface{}, len(p.Items)) + for i, item := range p.Items { + if item.ScopeURI != nil && *item.ScopeURI == "" { + return nil, fmt.Errorf(`malformed ScopeURI ""`) + } + if item.Section == "gopls" { + config := c.editor.Config() + results[i] = makeSettings(c.editor.sandbox, config, item.ScopeURI) + } + } + return results, nil +} + +func (c *Client) RegisterCapability(ctx context.Context, params *protocol.RegistrationParams) error { + if c.hooks.OnRegisterCapability != nil { + if err := c.hooks.OnRegisterCapability(ctx, params); err != nil { + return err + } + } + // Update file watching patterns. + // + // TODO(rfindley): We could verify more here, like verify that the + // registration ID is distinct, and that the capability is not currently + // registered. + for _, registration := range params.Registrations { + if registration.Method == "workspace/didChangeWatchedFiles" { + // Marshal and unmarshal to interpret RegisterOptions as + // DidChangeWatchedFilesRegistrationOptions. + raw, err := json.Marshal(registration.RegisterOptions) + if err != nil { + return fmt.Errorf("marshaling registration options: %v", err) + } + var opts protocol.DidChangeWatchedFilesRegistrationOptions + if err := json.Unmarshal(raw, &opts); err != nil { + return fmt.Errorf("unmarshaling registration options: %v", err) + } + var globs []*glob.Glob + for _, watcher := range opts.Watchers { + var globPattern string + switch pattern := watcher.GlobPattern.Value.(type) { + case protocol.Pattern: + globPattern = pattern + case protocol.RelativePattern: + globPattern = path.Join(filepath.ToSlash(pattern.BaseURI.Path()), pattern.Pattern) + } + // TODO(rfindley): honor the watch kind. + g, err := glob.Parse(globPattern) + if err != nil { + return fmt.Errorf("error parsing glob pattern %q: %v", watcher.GlobPattern, err) + } + globs = append(globs, g) + } + c.editor.mu.Lock() + c.editor.watchPatterns = globs + c.editor.mu.Unlock() + } + } + return nil +} + +func (c *Client) UnregisterCapability(ctx context.Context, params *protocol.UnregistrationParams) error { + if c.hooks.OnUnregisterCapability != nil { + return c.hooks.OnUnregisterCapability(ctx, params) + } + return nil +} + +func (c *Client) Progress(ctx context.Context, params *protocol.ProgressParams) error { + if c.hooks.OnProgress != nil { + return c.hooks.OnProgress(ctx, params) + } + return nil +} + +func (c *Client) WorkDoneProgressCreate(ctx context.Context, params *protocol.WorkDoneProgressCreateParams) error { + if c.hooks.OnWorkDoneProgressCreate != nil { + return c.hooks.OnWorkDoneProgressCreate(ctx, params) + } + return nil +} + +func (c *Client) ShowDocument(ctx context.Context, params *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) { + if c.hooks.OnShowDocument != nil { + if err := c.hooks.OnShowDocument(ctx, params); err != nil { + return nil, err + } + return &protocol.ShowDocumentResult{Success: true}, nil + } + return nil, nil +} + +func (c *Client) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) { + if len(params.Edit.Changes) > 0 { + return &protocol.ApplyWorkspaceEditResult{FailureReason: "Edit.Changes is unsupported"}, nil + } + onApplyEdit := c.editor.applyWorkspaceEdit + if ptr := c.onApplyEdit.Load(); ptr != nil { + onApplyEdit = *ptr + } + if err := onApplyEdit(ctx, ¶ms.Edit); err != nil { + return nil, err + } + return &protocol.ApplyWorkspaceEditResult{Applied: true}, nil +} diff --git a/contribs/gnopls/internal/test/integration/fake/doc.go b/contribs/gnopls/internal/test/integration/fake/doc.go new file mode 100644 index 00000000000..e0fc61b9928 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/doc.go @@ -0,0 +1,20 @@ +// 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 fake provides a fake implementation of an LSP-enabled +// text editor, its LSP client plugin, and a Sandbox environment for +// use in integration tests. +// +// The Editor type provides a high level API for text editor operations +// (open/modify/save/close a buffer, jump to definition, etc.), and the Client +// type exposes an LSP client for the editor that can be connected to a +// language server. By default, the Editor and Client should be compliant with +// the LSP spec: their intended use is to verify server compliance with the +// spec in a variety of environment. Possible future enhancements of these +// types may allow them to misbehave in configurable ways, but that is not +// their primary use. +// +// The Sandbox type provides a facility for executing tests with a temporary +// directory, module proxy, and GOPATH. +package fake diff --git a/contribs/gnopls/internal/test/integration/fake/edit.go b/contribs/gnopls/internal/test/integration/fake/edit.go new file mode 100644 index 00000000000..b06984b3dbc --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/edit.go @@ -0,0 +1,42 @@ +// 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 fake + +import ( + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/diff" +) + +// NewEdit creates an edit replacing all content between the 0-based +// (startLine, startColumn) and (endLine, endColumn) with text. +// +// Columns measure UTF-16 codes. +func NewEdit(startLine, startColumn, endLine, endColumn uint32, text string) protocol.TextEdit { + return protocol.TextEdit{ + Range: protocol.Range{ + Start: protocol.Position{Line: startLine, Character: startColumn}, + End: protocol.Position{Line: endLine, Character: endColumn}, + }, + NewText: text, + } +} + +// applyEdits applies the edits to a file with the specified lines, +// and returns a new slice containing the lines of the patched file. +// It is a wrapper around diff.Apply; see that function for preconditions. +func applyEdits(mapper *protocol.Mapper, edits []protocol.TextEdit, windowsLineEndings bool) ([]byte, error) { + diffEdits, err := protocol.EditsToDiffEdits(mapper, edits) + if err != nil { + return nil, err + } + patched, err := diff.ApplyBytes(mapper.Content, diffEdits) + if err != nil { + return nil, err + } + if windowsLineEndings { + patched = toWindowsLineEndings(patched) + } + return patched, nil +} diff --git a/contribs/gnopls/internal/test/integration/fake/edit_test.go b/contribs/gnopls/internal/test/integration/fake/edit_test.go new file mode 100644 index 00000000000..0d7ac18c414 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/edit_test.go @@ -0,0 +1,96 @@ +// 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 fake + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +func TestApplyEdits(t *testing.T) { + tests := []struct { + label string + content string + edits []protocol.TextEdit + want string + wantErr bool + }{ + { + label: "empty content", + }, + { + label: "empty edit", + content: "hello", + edits: []protocol.TextEdit{}, + want: "hello", + }, + { + label: "unicode edit", + content: "hello, 日本語", + edits: []protocol.TextEdit{ + NewEdit(0, 7, 0, 10, "world"), + }, + want: "hello, world", + }, + { + label: "range edit", + content: "ABC\nDEF\nGHI\nJKL", + edits: []protocol.TextEdit{ + NewEdit(1, 1, 2, 3, "12\n345"), + }, + want: "ABC\nD12\n345\nJKL", + }, + { + label: "regression test for issue #57627", + content: "go 1.18\nuse moda/a", + edits: []protocol.TextEdit{ + NewEdit(1, 0, 1, 0, "\n"), + NewEdit(2, 0, 2, 0, "\n"), + }, + want: "go 1.18\n\nuse moda/a\n", + }, + { + label: "end before start", + content: "ABC\nDEF\nGHI\nJKL", + edits: []protocol.TextEdit{ + NewEdit(2, 3, 1, 1, "12\n345"), + }, + wantErr: true, + }, + { + label: "out of bounds line", + content: "ABC\nDEF\nGHI\nJKL", + edits: []protocol.TextEdit{ + NewEdit(1, 1, 4, 3, "12\n345"), + }, + wantErr: true, + }, + { + label: "out of bounds column", + content: "ABC\nDEF\nGHI\nJKL", + edits: []protocol.TextEdit{ + NewEdit(1, 4, 2, 3, "12\n345"), + }, + wantErr: true, + }, + } + + for _, test := range tests { + test := test + t.Run(test.label, func(t *testing.T) { + got, err := applyEdits(protocol.NewMapper("", []byte(test.content)), test.edits, false) + if (err != nil) != test.wantErr { + t.Errorf("got err %v, want error: %t", err, test.wantErr) + } + if err != nil { + return + } + if got := string(got); got != test.want { + t.Errorf("got %q, want %q", got, test.want) + } + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/fake/editor.go b/contribs/gnopls/internal/test/integration/fake/editor.go new file mode 100644 index 00000000000..09cdbaf8c66 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/editor.go @@ -0,0 +1,1710 @@ +// 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 fake + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path" + "path/filepath" + "regexp" + "slices" + "strings" + "sync" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/test/integration/fake/glob" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/pathutil" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/jsonrpc2/servertest" + "golang.org/x/tools/internal/xcontext" +) + +// Editor is a fake client editor. It keeps track of client state and can be +// used for writing LSP tests. +type Editor struct { + + // Server, client, and sandbox are concurrency safe and written only + // at construction time, so do not require synchronization. + Server protocol.Server + cancelConn func() + serverConn jsonrpc2.Conn + client *Client + sandbox *Sandbox + + // TODO(rfindley): buffers should be keyed by protocol.DocumentURI. + mu sync.Mutex + config EditorConfig // editor configuration + buffers map[string]buffer // open buffers (relative path -> buffer content) + serverCapabilities protocol.ServerCapabilities // capabilities / options + semTokOpts protocol.SemanticTokensOptions + watchPatterns []*glob.Glob // glob patterns to watch + suggestionUseReplaceMode bool + + // Call metrics for the purpose of expectations. This is done in an ad-hoc + // manner for now. Perhaps in the future we should do something more + // systematic. Guarded with a separate mutex as calls may need to be accessed + // asynchronously via callbacks into the Editor. + callsMu sync.Mutex + calls CallCounts +} + +// CallCounts tracks the number of protocol notifications of different types. +type CallCounts struct { + DidOpen, DidChange, DidSave, DidChangeWatchedFiles, DidClose, DidChangeConfiguration uint64 +} + +// buffer holds information about an open buffer in the editor. +type buffer struct { + version int // monotonic version; incremented on edits + path string // relative path in the workspace + mapper *protocol.Mapper // buffer content + dirty bool // if true, content is unsaved (TODO(rfindley): rename this field) +} + +func (b buffer) text() string { + return string(b.mapper.Content) +} + +// EditorConfig configures the editor's LSP session. This is similar to +// golang.UserOptions, but we use a separate type here so that we expose only +// that configuration which we support. +// +// The zero value for EditorConfig is the default configuration. +type EditorConfig struct { + // ClientName sets the clientInfo.name for the LSP session (in the initialize request). + // + // Since this can only be set during initialization, changing this field via + // Editor.ChangeConfiguration has no effect. + // + // If empty, "fake.Editor" is used. + ClientName string + + // Env holds environment variables to apply on top of the default editor + // environment. When applying these variables, the special string + // $SANDBOX_WORKDIR is replaced by the absolute path to the sandbox working + // directory. + Env map[string]string + + // WorkspaceFolders is the workspace folders to configure on the LSP server. + // Each workspace folder is a file path relative to the sandbox workdir, or + // a uri (used when testing behavior with virtual file system or non-'file' + // scheme document uris). + // + // As special cases, if WorkspaceFolders is nil the editor defaults to + // configuring a single workspace folder corresponding to the workdir root. + // To explicitly send no workspace folders, use an empty (non-nil) slice. + WorkspaceFolders []string + + // Whether to edit files with windows line endings. + WindowsLineEndings bool + + // Map of language ID -> regexp to match, used to set the file type of new + // buffers. Applied as an overlay on top of the following defaults: + // "go" -> ".*\.go" + // "go.mod" -> "go\.mod" + // "go.sum" -> "go\.sum" + // "gotmpl" -> ".*tmpl" + FileAssociations map[string]string + + // Settings holds user-provided configuration for the LSP server. + Settings map[string]any + + // FolderSettings holds user-provided per-folder configuration, if any. + // + // It maps each folder (as a relative path to the sandbox workdir) to its + // configuration mapping (like Settings). + FolderSettings map[string]map[string]any + + // CapabilitiesJSON holds JSON client capabilities to overlay over the + // editor's default client capabilities. + // + // Specifically, this JSON string will be unmarshalled into the editor's + // client capabilities struct, before sending to the server. + CapabilitiesJSON []byte + + // If non-nil, MessageResponder is used to respond to ShowMessageRequest + // messages. + MessageResponder func(params *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) +} + +// NewEditor creates a new Editor. +func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor { + return &Editor{ + buffers: make(map[string]buffer), + sandbox: sandbox, + config: config, + } +} + +// Connect configures the editor to communicate with an LSP server on conn. It +// is not concurrency safe, and should be called at most once, before using the +// editor. +// +// It returns the editor, so that it may be called as follows: +// +// editor, err := NewEditor(s).Connect(ctx, conn, hooks) +func (e *Editor) Connect(ctx context.Context, connector servertest.Connector, hooks ClientHooks) (*Editor, error) { + bgCtx, cancelConn := context.WithCancel(xcontext.Detach(ctx)) + conn := connector.Connect(bgCtx) + e.cancelConn = cancelConn + + e.serverConn = conn + e.Server = protocol.ServerDispatcher(conn) + e.client = &Client{editor: e, hooks: hooks} + conn.Go(bgCtx, + protocol.Handlers( + protocol.ClientHandler(e.client, + jsonrpc2.MethodNotFound))) + + if err := e.initialize(ctx); err != nil { + return nil, err + } + e.sandbox.Workdir.AddWatcher(e.onFileChanges) + return e, nil +} + +func (e *Editor) Stats() CallCounts { + e.callsMu.Lock() + defer e.callsMu.Unlock() + return e.calls +} + +// Shutdown issues the 'shutdown' LSP notification. +func (e *Editor) Shutdown(ctx context.Context) error { + if e.Server != nil { + if err := e.Server.Shutdown(ctx); err != nil { + return fmt.Errorf("Shutdown: %w", err) + } + } + return nil +} + +// Exit issues the 'exit' LSP notification. +func (e *Editor) Exit(ctx context.Context) error { + if e.Server != nil { + // Not all LSP clients issue the exit RPC, but we do so here to ensure that + // we gracefully handle it on multi-session servers. + if err := e.Server.Exit(ctx); err != nil { + return fmt.Errorf("Exit: %w", err) + } + } + return nil +} + +// Close disconnects the LSP client session. +// TODO(rfindley): rename to 'Disconnect'. +func (e *Editor) Close(ctx context.Context) error { + if err := e.Shutdown(ctx); err != nil { + return err + } + if err := e.Exit(ctx); err != nil { + return err + } + defer func() { + e.cancelConn() + }() + + // called close on the editor should result in the connection closing + select { + case <-e.serverConn.Done(): + // connection closed itself + return nil + case <-ctx.Done(): + return fmt.Errorf("connection not closed: %w", ctx.Err()) + } +} + +// Client returns the LSP client for this editor. +func (e *Editor) Client() *Client { + return e.client +} + +// makeSettings builds the settings map for use in LSP settings RPCs. +func makeSettings(sandbox *Sandbox, config EditorConfig, scopeURI *protocol.URI) map[string]any { + env := make(map[string]string) + for k, v := range sandbox.GoEnv() { + env[k] = v + } + for k, v := range config.Env { + env[k] = v + } + for k, v := range env { + v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", sandbox.Workdir.RootURI().Path()) + env[k] = v + } + + settings := map[string]any{ + "env": env, + + // Use verbose progress reporting so that integration tests can assert on + // asynchronous operations being completed (such as diagnosing a snapshot). + "verboseWorkDoneProgress": true, + + // Set an unlimited completion budget, so that tests don't flake because + // completions are too slow. + "completionBudget": "0s", + } + + for k, v := range config.Settings { + if k == "env" { + panic("must not provide env via the EditorConfig.Settings field: use the EditorConfig.Env field instead") + } + settings[k] = v + } + + // If the server is requesting configuration for a specific scope, apply + // settings for the nearest folder that has customized settings, if any. + if scopeURI != nil { + var ( + scopePath = protocol.DocumentURI(*scopeURI).Path() + closestDir string // longest dir with settings containing the scope, if any + closestSettings map[string]any // settings for that dir, if any + ) + for relPath, settings := range config.FolderSettings { + dir := sandbox.Workdir.AbsPath(relPath) + if strings.HasPrefix(scopePath+string(filepath.Separator), dir+string(filepath.Separator)) && len(dir) > len(closestDir) { + closestDir = dir + closestSettings = settings + } + } + if closestSettings != nil { + for k, v := range closestSettings { + settings[k] = v + } + } + } + + return settings +} + +func (e *Editor) initialize(ctx context.Context) error { + config := e.Config() + + clientName := config.ClientName + if clientName == "" { + clientName = "fake.Editor" + } + + params := &protocol.ParamInitialize{} + params.ClientInfo = &protocol.ClientInfo{ + Name: clientName, + Version: "v1.0.0", + } + params.InitializationOptions = makeSettings(e.sandbox, config, nil) + params.WorkspaceFolders = makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders) + + capabilities, err := clientCapabilities(config) + if err != nil { + return fmt.Errorf("unmarshalling EditorConfig.CapabilitiesJSON: %v", err) + } + params.Capabilities = capabilities + + trace := protocol.TraceValue("messages") + params.Trace = &trace + // TODO: support workspace folders. + if e.Server != nil { + resp, err := e.Server.Initialize(ctx, params) + if err != nil { + return fmt.Errorf("initialize: %w", err) + } + semTokOpts, err := marshalUnmarshal[protocol.SemanticTokensOptions](resp.Capabilities.SemanticTokensProvider) + if err != nil { + return fmt.Errorf("unmarshalling semantic tokens options: %v", err) + } + e.mu.Lock() + e.serverCapabilities = resp.Capabilities + e.semTokOpts = semTokOpts + e.mu.Unlock() + + if err := e.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil { + return fmt.Errorf("initialized: %w", err) + } + } + // TODO: await initial configuration here, or expect gopls to manage that? + return nil +} + +func clientCapabilities(cfg EditorConfig) (protocol.ClientCapabilities, error) { + var capabilities protocol.ClientCapabilities + // Set various client capabilities that are sought by gopls. + capabilities.Workspace.Configuration = true // support workspace/configuration + capabilities.TextDocument.Completion.CompletionItem.TagSupport = &protocol.CompletionItemTagOptions{} + capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated} + capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true + capabilities.TextDocument.Completion.CompletionItem.InsertReplaceSupport = true + capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true} + capabilities.Window.WorkDoneProgress = true // support window/workDoneProgress + capabilities.TextDocument.SemanticTokens.TokenTypes = []string{ + "namespace", "type", "class", "enum", "interface", + "struct", "typeParameter", "parameter", "variable", "property", "enumMember", + "event", "function", "method", "macro", "keyword", "modifier", "comment", + "string", "number", "regexp", "operator", + // Additional types supported by this client: + "label", + } + capabilities.TextDocument.SemanticTokens.TokenModifiers = []string{ + "declaration", "definition", "readonly", "static", + "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary", + // Additional modifiers supported by this client: + "interface", "struct", "signature", "pointer", "array", "map", "slice", "chan", "string", "number", "bool", "invalid", + } + // The LSP tests have historically enabled this flag, + // but really we should test both ways for older editors. + capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = true + // Glob pattern watching is enabled. + capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true + // "rename" operations are used for package renaming. + // + // TODO(rfindley): add support for other resource operations (create, delete, ...) + capabilities.Workspace.WorkspaceEdit = &protocol.WorkspaceEditClientCapabilities{ + ResourceOperations: []protocol.ResourceOperationKind{ + "rename", + }, + } + + // Apply capabilities overlay. + if cfg.CapabilitiesJSON != nil { + if err := json.Unmarshal(cfg.CapabilitiesJSON, &capabilities); err != nil { + return protocol.ClientCapabilities{}, fmt.Errorf("unmarshalling EditorConfig.CapabilitiesJSON: %v", err) + } + } + return capabilities, nil +} + +// marshalUnmarshal is a helper to json Marshal and then Unmarshal as a +// different type. Used to work around cases where our protocol types are not +// specific. +func marshalUnmarshal[T any](v any) (T, error) { + var t T + data, err := json.Marshal(v) + if err != nil { + return t, err + } + err = json.Unmarshal(data, &t) + return t, err +} + +// HasCommand reports whether the connected server supports the command with the given ID. +func (e *Editor) HasCommand(cmd command.Command) bool { + for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands { + if command == cmd.String() { + return true + } + } + return false +} + +// Examples: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml +var uriRE = regexp.MustCompile(`^[a-z][a-z0-9+\-.]*://\S+`) + +// makeWorkspaceFolders creates a slice of workspace folders to use for +// this editing session, based on the editor configuration. +func makeWorkspaceFolders(sandbox *Sandbox, paths []string) (folders []protocol.WorkspaceFolder) { + if len(paths) == 0 { + paths = []string{string(sandbox.Workdir.RelativeTo)} + } + + for _, path := range paths { + uri := path + if !uriRE.MatchString(path) { // relative file path + uri = string(sandbox.Workdir.URI(path)) + } + folders = append(folders, protocol.WorkspaceFolder{ + URI: uri, + Name: filepath.Base(uri), + }) + } + + return folders +} + +// onFileChanges is registered to be called by the Workdir on any writes that +// go through the Workdir API. It is called synchronously by the Workdir. +func (e *Editor) onFileChanges(ctx context.Context, evts []protocol.FileEvent) { + if e.Server == nil { + return + } + + // e may be locked when onFileChanges is called, but it is important that we + // synchronously increment this counter so that we can subsequently assert on + // the number of expected DidChangeWatchedFiles calls. + e.callsMu.Lock() + e.calls.DidChangeWatchedFiles++ + e.callsMu.Unlock() + + // Since e may be locked, we must run this mutation asynchronously. + go func() { + e.mu.Lock() + defer e.mu.Unlock() + for _, evt := range evts { + // Always send an on-disk change, even for events that seem useless + // because they're shadowed by an open buffer. + path := e.sandbox.Workdir.URIToPath(evt.URI) + if buf, ok := e.buffers[path]; ok { + // Following VS Code, don't honor deletions or changes to dirty buffers. + if buf.dirty || evt.Type == protocol.Deleted { + continue + } + + content, err := e.sandbox.Workdir.ReadFile(path) + if err != nil { + continue // A race with some other operation. + } + // No need to update if the buffer content hasn't changed. + if string(content) == buf.text() { + continue + } + // During shutdown, this call will fail. Ignore the error. + _ = e.setBufferContentLocked(ctx, path, false, content, nil) + } + } + var matchedEvts []protocol.FileEvent + for _, evt := range evts { + filename := filepath.ToSlash(evt.URI.Path()) + for _, g := range e.watchPatterns { + if g.Match(filename) { + matchedEvts = append(matchedEvts, evt) + break + } + } + } + + // TODO(rfindley): don't send notifications while locked. + e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{ + Changes: matchedEvts, + }) + }() +} + +// OpenFile creates a buffer for the given workdir-relative file. +// +// If the file is already open, it is a no-op. +func (e *Editor) OpenFile(ctx context.Context, path string) error { + if e.HasBuffer(path) { + return nil + } + content, err := e.sandbox.Workdir.ReadFile(path) + if err != nil { + return err + } + if e.Config().WindowsLineEndings { + content = toWindowsLineEndings(content) + } + return e.createBuffer(ctx, path, false, content) +} + +// toWindowsLineEndings checks whether content has windows line endings. +// +// If so, it returns content unmodified. If not, it returns a new byte slice modified to use CRLF line endings. +func toWindowsLineEndings(content []byte) []byte { + abnormal := false + for i, b := range content { + if b == '\n' && (i == 0 || content[i-1] != '\r') { + abnormal = true + break + } + } + if !abnormal { + return content + } + var buf bytes.Buffer + for i, b := range content { + if b == '\n' && (i == 0 || content[i-1] != '\r') { + buf.WriteByte('\r') + } + buf.WriteByte(b) + } + return buf.Bytes() +} + +// CreateBuffer creates a new unsaved buffer corresponding to the workdir path, +// containing the given textual content. +func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error { + return e.createBuffer(ctx, path, true, []byte(content)) +} + +func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, content []byte) error { + e.mu.Lock() + + if _, ok := e.buffers[path]; ok { + e.mu.Unlock() + return fmt.Errorf("buffer %q already exists", path) + } + + uri := e.sandbox.Workdir.URI(path) + buf := buffer{ + version: 1, + path: path, + mapper: protocol.NewMapper(uri, content), + dirty: dirty, + } + e.buffers[path] = buf + + item := e.textDocumentItem(buf) + e.mu.Unlock() + + return e.sendDidOpen(ctx, item) +} + +// textDocumentItem builds a protocol.TextDocumentItem for the given buffer. +// +// Precondition: e.mu must be held. +func (e *Editor) textDocumentItem(buf buffer) protocol.TextDocumentItem { + return protocol.TextDocumentItem{ + URI: e.sandbox.Workdir.URI(buf.path), + LanguageID: languageID(buf.path, e.config.FileAssociations), + Version: int32(buf.version), + Text: buf.text(), + } +} + +func (e *Editor) sendDidOpen(ctx context.Context, item protocol.TextDocumentItem) error { + if e.Server != nil { + if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{ + TextDocument: item, + }); err != nil { + return fmt.Errorf("DidOpen: %w", err) + } + e.callsMu.Lock() + e.calls.DidOpen++ + e.callsMu.Unlock() + } + return nil +} + +var defaultFileAssociations = map[string]*regexp.Regexp{ + "go": regexp.MustCompile(`^.*\.go$`), // '$' is important: don't match .gotmpl! + "go.mod": regexp.MustCompile(`^go\.mod$`), + "go.sum": regexp.MustCompile(`^go(\.work)?\.sum$`), + "go.work": regexp.MustCompile(`^go\.work$`), + "gotmpl": regexp.MustCompile(`^.*tmpl$`), +} + +// languageID returns the language identifier for the path p given the user +// configured fileAssociations. +func languageID(p string, fileAssociations map[string]string) protocol.LanguageKind { + base := path.Base(p) + for lang, re := range fileAssociations { + re := regexp.MustCompile(re) + if re.MatchString(base) { + return protocol.LanguageKind(lang) + } + } + for lang, re := range defaultFileAssociations { + if re.MatchString(base) { + return protocol.LanguageKind(lang) + } + } + return "" +} + +// CloseBuffer removes the current buffer (regardless of whether it is saved). +// CloseBuffer returns an error if the buffer is not open. +func (e *Editor) CloseBuffer(ctx context.Context, path string) error { + e.mu.Lock() + _, ok := e.buffers[path] + if !ok { + e.mu.Unlock() + return ErrUnknownBuffer + } + delete(e.buffers, path) + e.mu.Unlock() + + return e.sendDidClose(ctx, e.TextDocumentIdentifier(path)) +} + +func (e *Editor) sendDidClose(ctx context.Context, doc protocol.TextDocumentIdentifier) error { + if e.Server != nil { + if err := e.Server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{ + TextDocument: doc, + }); err != nil { + return fmt.Errorf("DidClose: %w", err) + } + e.callsMu.Lock() + e.calls.DidClose++ + e.callsMu.Unlock() + } + return nil +} + +func (e *Editor) DocumentURI(path string) protocol.DocumentURI { + return e.sandbox.Workdir.URI(path) +} + +func (e *Editor) TextDocumentIdentifier(path string) protocol.TextDocumentIdentifier { + return protocol.TextDocumentIdentifier{ + URI: e.DocumentURI(path), + } +} + +// SaveBuffer writes the content of the buffer specified by the given path to +// the filesystem. +func (e *Editor) SaveBuffer(ctx context.Context, path string) error { + if err := e.OrganizeImports(ctx, path); err != nil { + return fmt.Errorf("organizing imports before save: %w", err) + } + if err := e.FormatBuffer(ctx, path); err != nil { + return fmt.Errorf("formatting before save: %w", err) + } + return e.SaveBufferWithoutActions(ctx, path) +} + +func (e *Editor) SaveBufferWithoutActions(ctx context.Context, path string) error { + e.mu.Lock() + defer e.mu.Unlock() + buf, ok := e.buffers[path] + if !ok { + return fmt.Errorf("unknown buffer: %q", path) + } + content := buf.text() + includeText := false + syncOptions, ok := e.serverCapabilities.TextDocumentSync.(protocol.TextDocumentSyncOptions) + if ok { + includeText = syncOptions.Save.IncludeText + } + + docID := e.TextDocumentIdentifier(buf.path) + if e.Server != nil { + if err := e.Server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{ + TextDocument: docID, + Reason: protocol.Manual, + }); err != nil { + return fmt.Errorf("WillSave: %w", err) + } + } + if err := e.sandbox.Workdir.WriteFile(ctx, path, content); err != nil { + return fmt.Errorf("writing %q: %w", path, err) + } + + buf.dirty = false + e.buffers[path] = buf + + if e.Server != nil { + params := &protocol.DidSaveTextDocumentParams{ + TextDocument: docID, + } + if includeText { + params.Text = &content + } + if err := e.Server.DidSave(ctx, params); err != nil { + return fmt.Errorf("DidSave: %w", err) + } + e.callsMu.Lock() + e.calls.DidSave++ + e.callsMu.Unlock() + } + return nil +} + +// ErrNoMatch is returned if a regexp search fails. +var ( + ErrNoMatch = errors.New("no match") + ErrUnknownBuffer = errors.New("unknown buffer") +) + +// regexpLocation returns the location of the first occurrence of either re +// or its singular subgroup. It returns ErrNoMatch if the regexp doesn't match. +func regexpLocation(mapper *protocol.Mapper, re string) (protocol.Location, error) { + var start, end int + rec, err := regexp.Compile(re) + if err != nil { + return protocol.Location{}, err + } + indexes := rec.FindSubmatchIndex(mapper.Content) + if indexes == nil { + return protocol.Location{}, ErrNoMatch + } + switch len(indexes) { + case 2: + // no subgroups: return the range of the regexp expression + start, end = indexes[0], indexes[1] + case 4: + // one subgroup: return its range + start, end = indexes[2], indexes[3] + default: + return protocol.Location{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1) + } + return mapper.OffsetLocation(start, end) +} + +// RegexpSearch returns the Location of the first match for re in the buffer +// bufName. For convenience, RegexpSearch supports the following two modes: +// 1. If re has no subgroups, return the position of the match for re itself. +// 2. If re has one subgroup, return the position of the first subgroup. +// +// It returns an error re is invalid, has more than one subgroup, or doesn't +// match the buffer. +func (e *Editor) RegexpSearch(bufName, re string) (protocol.Location, error) { + e.mu.Lock() + buf, ok := e.buffers[bufName] + e.mu.Unlock() + if !ok { + return protocol.Location{}, ErrUnknownBuffer + } + return regexpLocation(buf.mapper, re) +} + +// RegexpReplace edits the buffer corresponding to path by replacing the first +// instance of re, or its first subgroup, with the replace text. See +// RegexpSearch for more explanation of these two modes. +// It returns an error if re is invalid, has more than one subgroup, or doesn't +// match the buffer. +func (e *Editor) RegexpReplace(ctx context.Context, path, re, replace string) error { + e.mu.Lock() + defer e.mu.Unlock() + buf, ok := e.buffers[path] + if !ok { + return ErrUnknownBuffer + } + loc, err := regexpLocation(buf.mapper, re) + if err != nil { + return err + } + edits := []protocol.TextEdit{{ + Range: loc.Range, + NewText: replace, + }} + patched, err := applyEdits(buf.mapper, edits, e.config.WindowsLineEndings) + if err != nil { + return fmt.Errorf("editing %q: %v", path, err) + } + return e.setBufferContentLocked(ctx, path, true, patched, edits) +} + +// EditBuffer applies the given test edits to the buffer identified by path. +func (e *Editor) EditBuffer(ctx context.Context, path string, edits []protocol.TextEdit) error { + e.mu.Lock() + defer e.mu.Unlock() + return e.editBufferLocked(ctx, path, edits) +} + +func (e *Editor) SetBufferContent(ctx context.Context, path, content string) error { + e.mu.Lock() + defer e.mu.Unlock() + return e.setBufferContentLocked(ctx, path, true, []byte(content), nil) +} + +// HasBuffer reports whether the file name is open in the editor. +func (e *Editor) HasBuffer(name string) bool { + e.mu.Lock() + defer e.mu.Unlock() + _, ok := e.buffers[name] + return ok +} + +// BufferText returns the content of the buffer with the given name, or "" if +// the file at that path is not open. The second return value reports whether +// the file is open. +func (e *Editor) BufferText(name string) (string, bool) { + e.mu.Lock() + defer e.mu.Unlock() + buf, ok := e.buffers[name] + if !ok { + return "", false + } + return buf.text(), true +} + +// Mapper returns the protocol.Mapper for the given buffer name, if it is open. +func (e *Editor) Mapper(name string) (*protocol.Mapper, error) { + e.mu.Lock() + defer e.mu.Unlock() + buf, ok := e.buffers[name] + if !ok { + return nil, fmt.Errorf("no mapper for %q", name) + } + return buf.mapper, nil +} + +// BufferVersion returns the current version of the buffer corresponding to +// name (or 0 if it is not being edited). +func (e *Editor) BufferVersion(name string) int { + e.mu.Lock() + defer e.mu.Unlock() + return e.buffers[name].version +} + +func (e *Editor) editBufferLocked(ctx context.Context, path string, edits []protocol.TextEdit) error { + buf, ok := e.buffers[path] + if !ok { + return fmt.Errorf("unknown buffer %q", path) + } + content, err := applyEdits(buf.mapper, edits, e.config.WindowsLineEndings) + if err != nil { + return fmt.Errorf("editing %q: %v; edits:\n%v", path, err, edits) + } + return e.setBufferContentLocked(ctx, path, true, content, edits) +} + +func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty bool, content []byte, fromEdits []protocol.TextEdit) error { + buf, ok := e.buffers[path] + if !ok { + return fmt.Errorf("unknown buffer %q", path) + } + buf.mapper = protocol.NewMapper(buf.mapper.URI, content) + buf.version++ + buf.dirty = dirty + e.buffers[path] = buf + + // A simple heuristic: if there is only one edit, send it incrementally. + // Otherwise, send the entire content. + var evt protocol.TextDocumentContentChangeEvent + if len(fromEdits) == 1 { + evt.Range = &fromEdits[0].Range + evt.Text = fromEdits[0].NewText + } else { + evt.Text = buf.text() + } + params := &protocol.DidChangeTextDocumentParams{ + TextDocument: protocol.VersionedTextDocumentIdentifier{ + Version: int32(buf.version), + TextDocumentIdentifier: e.TextDocumentIdentifier(buf.path), + }, + ContentChanges: []protocol.TextDocumentContentChangeEvent{evt}, + } + if e.Server != nil { + if err := e.Server.DidChange(ctx, params); err != nil { + return fmt.Errorf("DidChange: %w", err) + } + e.callsMu.Lock() + e.calls.DidChange++ + e.callsMu.Unlock() + } + return nil +} + +// GoToDefinition jumps to the definition of the symbol at the given position +// in an open buffer. It returns the location of the resulting jump. +func (e *Editor) Definition(ctx context.Context, loc protocol.Location) (protocol.Location, error) { + if err := e.checkBufferLocation(loc); err != nil { + return protocol.Location{}, err + } + params := &protocol.DefinitionParams{} + params.TextDocument.URI = loc.URI + params.Position = loc.Range.Start + + resp, err := e.Server.Definition(ctx, params) + if err != nil { + return protocol.Location{}, fmt.Errorf("definition: %w", err) + } + return e.extractFirstLocation(ctx, resp) +} + +// TypeDefinition jumps to the type definition of the symbol at the given +// location in an open buffer. +func (e *Editor) TypeDefinition(ctx context.Context, loc protocol.Location) (protocol.Location, error) { + if err := e.checkBufferLocation(loc); err != nil { + return protocol.Location{}, err + } + params := &protocol.TypeDefinitionParams{} + params.TextDocument.URI = loc.URI + params.Position = loc.Range.Start + + resp, err := e.Server.TypeDefinition(ctx, params) + if err != nil { + return protocol.Location{}, fmt.Errorf("type definition: %w", err) + } + return e.extractFirstLocation(ctx, resp) +} + +// extractFirstLocation returns the first location. +// It opens the file if needed. +func (e *Editor) extractFirstLocation(ctx context.Context, locs []protocol.Location) (protocol.Location, error) { + if len(locs) == 0 { + return protocol.Location{}, nil + } + + newPath := e.sandbox.Workdir.URIToPath(locs[0].URI) + if !e.HasBuffer(newPath) { + if err := e.OpenFile(ctx, newPath); err != nil { + return protocol.Location{}, fmt.Errorf("OpenFile: %w", err) + } + } + return locs[0], nil +} + +// Symbol performs a workspace symbol search using query +func (e *Editor) Symbol(ctx context.Context, query string) ([]protocol.SymbolInformation, error) { + params := &protocol.WorkspaceSymbolParams{Query: query} + return e.Server.Symbol(ctx, params) +} + +// OrganizeImports requests and performs the source.organizeImports codeAction. +func (e *Editor) OrganizeImports(ctx context.Context, path string) error { + loc := e.sandbox.Workdir.EntireFile(path) + _, err := e.applyCodeActions(ctx, loc, nil, protocol.SourceOrganizeImports) + return err +} + +// RefactorRewrite requests and performs the source.refactorRewrite codeAction. +func (e *Editor) RefactorRewrite(ctx context.Context, loc protocol.Location) error { + applied, err := e.applyCodeActions(ctx, loc, nil, protocol.RefactorRewrite) + if err != nil { + return err + } + if applied == 0 { + return fmt.Errorf("no refactorings were applied") + } + return nil +} + +// ApplyQuickFixes requests and performs the quickfix codeAction. +func (e *Editor) ApplyQuickFixes(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) error { + applied, err := e.applyCodeActions(ctx, loc, diagnostics, protocol.SourceFixAll, protocol.QuickFix) + if applied == 0 { + return fmt.Errorf("no quick fixes were applied") + } + return err +} + +// ApplyCodeAction applies the given code action. +func (e *Editor) ApplyCodeAction(ctx context.Context, action protocol.CodeAction) error { + // Resolve the code actions if necessary and supported. + if action.Edit == nil { + editSupport, err := e.EditResolveSupport() + if err != nil { + return err + } + if editSupport { + ca, err := e.Server.ResolveCodeAction(ctx, &action) + if err != nil { + return err + } + action.Edit = ca.Edit + } + } + + if action.Edit != nil { + for _, change := range action.Edit.DocumentChanges { + if change.TextDocumentEdit != nil { + path := e.sandbox.Workdir.URIToPath(change.TextDocumentEdit.TextDocument.URI) + if int32(e.buffers[path].version) != change.TextDocumentEdit.TextDocument.Version { + // Skip edits for old versions. + continue + } + if err := e.EditBuffer(ctx, path, protocol.AsTextEdits(change.TextDocumentEdit.Edits)); err != nil { + return fmt.Errorf("editing buffer %q: %w", path, err) + } + } + } + } + // Execute any commands. The specification says that commands are + // executed after edits are applied. + if action.Command != nil { + if _, err := e.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{ + Command: action.Command.Command, + Arguments: action.Command.Arguments, + }); err != nil { + return err + } + } + // Some commands may edit files on disk. + return e.sandbox.Workdir.CheckForFileChanges(ctx) +} + +// GetQuickFixes returns the available quick fix code actions. +func (e *Editor) GetQuickFixes(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) { + return e.CodeActions(ctx, loc, diagnostics, protocol.QuickFix, protocol.SourceFixAll) +} + +func (e *Editor) applyCodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) (int, error) { + actions, err := e.CodeActions(ctx, loc, diagnostics, only...) + if err != nil { + return 0, err + } + applied := 0 + for _, action := range actions { + if action.Title == "" { + return 0, fmt.Errorf("empty title for code action") + } + applied++ + if err := e.ApplyCodeAction(ctx, action); err != nil { + return 0, err + } + } + return applied, nil +} + +func (e *Editor) CodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) { + if e.Server == nil { + return nil, nil + } + params := &protocol.CodeActionParams{} + params.TextDocument.URI = loc.URI + params.Context.Only = only + params.Range = loc.Range // may be zero => whole file + if diagnostics != nil { + params.Context.Diagnostics = diagnostics + } + return e.Server.CodeAction(ctx, params) +} + +func (e *Editor) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { + if e.Server == nil { + return nil, nil + } + var match bool + if e.serverCapabilities.ExecuteCommandProvider != nil { + // Ensure that this command was actually listed as a supported command. + for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands { + if command == params.Command { + match = true + break + } + } + } + if !match { + return nil, fmt.Errorf("unsupported command %q", params.Command) + } + result, err := e.Server.ExecuteCommand(ctx, params) + if err != nil { + return nil, err + } + // Some commands use the go command, which writes directly to disk. + // For convenience, check for those changes. + if err := e.sandbox.Workdir.CheckForFileChanges(ctx); err != nil { + return nil, fmt.Errorf("checking for file changes: %v", err) + } + return result, nil +} + +// FormatBuffer gofmts a Go file. +func (e *Editor) FormatBuffer(ctx context.Context, path string) error { + if e.Server == nil { + return nil + } + e.mu.Lock() + version := e.buffers[path].version + e.mu.Unlock() + params := &protocol.DocumentFormattingParams{} + params.TextDocument.URI = e.sandbox.Workdir.URI(path) + edits, err := e.Server.Formatting(ctx, params) + if err != nil { + return fmt.Errorf("textDocument/formatting: %w", err) + } + e.mu.Lock() + defer e.mu.Unlock() + if versionAfter := e.buffers[path].version; versionAfter != version { + return fmt.Errorf("before receipt of formatting edits, buffer version changed from %d to %d", version, versionAfter) + } + if len(edits) == 0 { + return nil + } + return e.editBufferLocked(ctx, path, edits) +} + +func (e *Editor) checkBufferLocation(loc protocol.Location) error { + e.mu.Lock() + defer e.mu.Unlock() + path := e.sandbox.Workdir.URIToPath(loc.URI) + buf, ok := e.buffers[path] + if !ok { + return fmt.Errorf("buffer %q is not open", path) + } + + _, _, err := buf.mapper.RangeOffsets(loc.Range) + return err +} + +// RunGenerate runs `go generate` non-recursively in the workdir-relative dir +// path. It does not report any resulting file changes as a watched file +// change, so must be followed by a call to Workdir.CheckForFileChanges once +// the generate command has completed. +// TODO(rFindley): this shouldn't be necessary anymore. Delete it. +func (e *Editor) RunGenerate(ctx context.Context, dir string) error { + if e.Server == nil { + return nil + } + absDir := e.sandbox.Workdir.AbsPath(dir) + cmd := command.NewGenerateCommand("", command.GenerateArgs{ + Dir: protocol.URIFromPath(absDir), + Recursive: false, + }) + params := &protocol.ExecuteCommandParams{ + Command: cmd.Command, + Arguments: cmd.Arguments, + } + if _, err := e.ExecuteCommand(ctx, params); err != nil { + return fmt.Errorf("running generate: %v", err) + } + // Unfortunately we can't simply poll the workdir for file changes here, + // because server-side command may not have completed. In integration tests, we can + // Await this state change, but here we must delegate that responsibility to + // the caller. + return nil +} + +// CodeLens executes a codelens request on the server. +func (e *Editor) CodeLens(ctx context.Context, path string) ([]protocol.CodeLens, error) { + if e.Server == nil { + return nil, nil + } + e.mu.Lock() + _, ok := e.buffers[path] + e.mu.Unlock() + if !ok { + return nil, fmt.Errorf("buffer %q is not open", path) + } + params := &protocol.CodeLensParams{ + TextDocument: e.TextDocumentIdentifier(path), + } + lens, err := e.Server.CodeLens(ctx, params) + if err != nil { + return nil, err + } + return lens, nil +} + +// Completion executes a completion request on the server. +func (e *Editor) Completion(ctx context.Context, loc protocol.Location) (*protocol.CompletionList, error) { + if e.Server == nil { + return nil, nil + } + path := e.sandbox.Workdir.URIToPath(loc.URI) + e.mu.Lock() + _, ok := e.buffers[path] + e.mu.Unlock() + if !ok { + return nil, fmt.Errorf("buffer %q is not open", path) + } + params := &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + completions, err := e.Server.Completion(ctx, params) + if err != nil { + return nil, err + } + return completions, nil +} + +func (e *Editor) SetSuggestionInsertReplaceMode(_ context.Context, useReplaceMode bool) { + e.mu.Lock() + defer e.mu.Unlock() + e.suggestionUseReplaceMode = useReplaceMode +} + +// AcceptCompletion accepts a completion for the given item +// at the given position based on the editor's suggestion insert mode. +// The server provides separate insert/replace ranges only if the +// Editor declares `InsertReplaceSupport` capability during initialization. +// Otherwise, it returns a single range and the insert/replace mode is ignored. +func (e *Editor) AcceptCompletion(ctx context.Context, loc protocol.Location, item protocol.CompletionItem) error { + if e.Server == nil { + return nil + } + e.mu.Lock() + defer e.mu.Unlock() + path := e.sandbox.Workdir.URIToPath(loc.URI) + _, ok := e.buffers[path] + if !ok { + return fmt.Errorf("buffer %q is not open", path) + } + edit, err := protocol.SelectCompletionTextEdit(item, e.suggestionUseReplaceMode) + if err != nil { + return err + } + return e.editBufferLocked(ctx, path, append([]protocol.TextEdit{ + edit, + }, item.AdditionalTextEdits...)) +} + +// Symbols executes a workspace/symbols request on the server. +func (e *Editor) Symbols(ctx context.Context, sym string) ([]protocol.SymbolInformation, error) { + if e.Server == nil { + return nil, nil + } + params := &protocol.WorkspaceSymbolParams{Query: sym} + ans, err := e.Server.Symbol(ctx, params) + return ans, err +} + +// CodeLens executes a codelens request on the server. +func (e *Editor) InlayHint(ctx context.Context, path string) ([]protocol.InlayHint, error) { + if e.Server == nil { + return nil, nil + } + e.mu.Lock() + _, ok := e.buffers[path] + e.mu.Unlock() + if !ok { + return nil, fmt.Errorf("buffer %q is not open", path) + } + params := &protocol.InlayHintParams{ + TextDocument: e.TextDocumentIdentifier(path), + } + hints, err := e.Server.InlayHint(ctx, params) + if err != nil { + return nil, err + } + return hints, nil +} + +// References returns references to the object at loc, as returned by +// the connected LSP server. If no server is connected, it returns (nil, nil). +func (e *Editor) References(ctx context.Context, loc protocol.Location) ([]protocol.Location, error) { + if e.Server == nil { + return nil, nil + } + path := e.sandbox.Workdir.URIToPath(loc.URI) + e.mu.Lock() + _, ok := e.buffers[path] + e.mu.Unlock() + if !ok { + return nil, fmt.Errorf("buffer %q is not open", path) + } + params := &protocol.ReferenceParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + Context: protocol.ReferenceContext{ + IncludeDeclaration: true, + }, + } + locations, err := e.Server.References(ctx, params) + if err != nil { + return nil, err + } + return locations, nil +} + +// Rename performs a rename of the object at loc to newName, using the +// connected LSP server. If no server is connected, it returns nil. +func (e *Editor) Rename(ctx context.Context, loc protocol.Location, newName string) error { + if e.Server == nil { + return nil + } + path := e.sandbox.Workdir.URIToPath(loc.URI) + + // Verify that PrepareRename succeeds. + prepareParams := &protocol.PrepareRenameParams{} + prepareParams.TextDocument = e.TextDocumentIdentifier(path) + prepareParams.Position = loc.Range.Start + if _, err := e.Server.PrepareRename(ctx, prepareParams); err != nil { + return fmt.Errorf("preparing rename: %v", err) + } + + params := &protocol.RenameParams{ + TextDocument: e.TextDocumentIdentifier(path), + Position: loc.Range.Start, + NewName: newName, + } + wsedit, err := e.Server.Rename(ctx, params) + if err != nil { + return err + } + return e.applyWorkspaceEdit(ctx, wsedit) +} + +// Implementations returns implementations for the object at loc, as +// returned by the connected LSP server. If no server is connected, it returns +// (nil, nil). +func (e *Editor) Implementations(ctx context.Context, loc protocol.Location) ([]protocol.Location, error) { + if e.Server == nil { + return nil, nil + } + path := e.sandbox.Workdir.URIToPath(loc.URI) + e.mu.Lock() + _, ok := e.buffers[path] + e.mu.Unlock() + if !ok { + return nil, fmt.Errorf("buffer %q is not open", path) + } + params := &protocol.ImplementationParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + return e.Server.Implementation(ctx, params) +} + +func (e *Editor) SignatureHelp(ctx context.Context, loc protocol.Location) (*protocol.SignatureHelp, error) { + if e.Server == nil { + return nil, nil + } + path := e.sandbox.Workdir.URIToPath(loc.URI) + e.mu.Lock() + _, ok := e.buffers[path] + e.mu.Unlock() + if !ok { + return nil, fmt.Errorf("buffer %q is not open", path) + } + params := &protocol.SignatureHelpParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + return e.Server.SignatureHelp(ctx, params) +} + +func (e *Editor) RenameFile(ctx context.Context, oldPath, newPath string) error { + closed, opened, err := e.renameBuffers(oldPath, newPath) + if err != nil { + return err + } + + for _, c := range closed { + if err := e.sendDidClose(ctx, c); err != nil { + return err + } + } + for _, o := range opened { + if err := e.sendDidOpen(ctx, o); err != nil { + return err + } + } + + // Finally, perform the renaming on disk. + if err := e.sandbox.Workdir.RenameFile(ctx, oldPath, newPath); err != nil { + return fmt.Errorf("renaming sandbox file: %w", err) + } + return nil +} + +// renameBuffers renames in-memory buffers affected by the renaming of +// oldPath->newPath, returning the resulting text documents that must be closed +// and opened over the LSP. +func (e *Editor) renameBuffers(oldPath, newPath string) (closed []protocol.TextDocumentIdentifier, opened []protocol.TextDocumentItem, _ error) { + e.mu.Lock() + defer e.mu.Unlock() + + // In case either oldPath or newPath is absolute, convert to absolute paths + // before checking for containment. + oldAbs := e.sandbox.Workdir.AbsPath(oldPath) + newAbs := e.sandbox.Workdir.AbsPath(newPath) + + // Collect buffers that are affected by the given file or directory renaming. + buffersToRename := make(map[string]string) // old path -> new path + + for path := range e.buffers { + abs := e.sandbox.Workdir.AbsPath(path) + if oldAbs == abs || pathutil.InDir(oldAbs, abs) { + rel, err := filepath.Rel(oldAbs, abs) + if err != nil { + return nil, nil, fmt.Errorf("filepath.Rel(%q, %q): %v", oldAbs, abs, err) + } + nabs := filepath.Join(newAbs, rel) + newPath := e.sandbox.Workdir.RelPath(nabs) + buffersToRename[path] = newPath + } + } + + // Update buffers, and build protocol changes. + for old, new := range buffersToRename { + buf := e.buffers[old] + delete(e.buffers, old) + buf.version = 1 + buf.path = new + e.buffers[new] = buf + + closed = append(closed, e.TextDocumentIdentifier(old)) + opened = append(opened, e.textDocumentItem(buf)) + } + + return closed, opened, nil +} + +// applyWorkspaceEdit applies the sequence of document changes in +// wsedit to the Editor. +// +// See also: +// - changedFiles in ../../marker/marker_test.go for the +// handler used by the marker test to intercept edits. +// - cmdClient.applyWorkspaceEdit in ../../../cmd/cmd.go for the +// CLI variant. +func (e *Editor) applyWorkspaceEdit(ctx context.Context, wsedit *protocol.WorkspaceEdit) error { + uriToPath := e.sandbox.Workdir.URIToPath + + for _, change := range wsedit.DocumentChanges { + switch { + case change.TextDocumentEdit != nil: + if err := e.applyTextDocumentEdit(ctx, *change.TextDocumentEdit); err != nil { + return err + } + + case change.RenameFile != nil: + old := uriToPath(change.RenameFile.OldURI) + new := uriToPath(change.RenameFile.NewURI) + return e.RenameFile(ctx, old, new) + + case change.CreateFile != nil: + path := uriToPath(change.CreateFile.URI) + if err := e.CreateBuffer(ctx, path, ""); err != nil { + return err // e.g. already exists + } + + case change.DeleteFile != nil: + path := uriToPath(change.CreateFile.URI) + _ = e.CloseBuffer(ctx, path) // returns error if not open + if err := e.sandbox.Workdir.RemoveFile(ctx, path); err != nil { + return err // e.g. doesn't exist + } + + default: + return bug.Errorf("invalid DocumentChange") + } + } + return nil +} + +func (e *Editor) applyTextDocumentEdit(ctx context.Context, change protocol.TextDocumentEdit) error { + path := e.sandbox.Workdir.URIToPath(change.TextDocument.URI) + if ver := int32(e.BufferVersion(path)); ver != change.TextDocument.Version { + return fmt.Errorf("buffer versions for %q do not match: have %d, editing %d", path, ver, change.TextDocument.Version) + } + if !e.HasBuffer(path) { + err := e.OpenFile(ctx, path) + if os.IsNotExist(err) { + // TODO: it's unclear if this is correct. Here we create the buffer (with + // version 1), then apply edits. Perhaps we should apply the edits before + // sending the didOpen notification. + e.CreateBuffer(ctx, path, "") + err = nil + } + if err != nil { + return err + } + } + return e.EditBuffer(ctx, path, protocol.AsTextEdits(change.Edits)) +} + +// Config returns the current editor configuration. +func (e *Editor) Config() EditorConfig { + e.mu.Lock() + defer e.mu.Unlock() + return e.config +} + +func (e *Editor) SetConfig(cfg EditorConfig) { + e.mu.Lock() + e.config = cfg + e.mu.Unlock() +} + +// ChangeConfiguration sets the new editor configuration, and if applicable +// sends a didChangeConfiguration notification. +// +// An error is returned if the change notification failed to send. +func (e *Editor) ChangeConfiguration(ctx context.Context, newConfig EditorConfig) error { + e.SetConfig(newConfig) + if e.Server != nil { + var params protocol.DidChangeConfigurationParams // empty: gopls ignores the Settings field + if err := e.Server.DidChangeConfiguration(ctx, ¶ms); err != nil { + return err + } + e.callsMu.Lock() + e.calls.DidChangeConfiguration++ + e.callsMu.Unlock() + } + return nil +} + +// ChangeWorkspaceFolders sets the new workspace folders, and sends a +// didChangeWorkspaceFolders notification to the server. +// +// The given folders must all be unique. +func (e *Editor) ChangeWorkspaceFolders(ctx context.Context, folders []string) error { + config := e.Config() + + // capture existing folders so that we can compute the change. + oldFolders := makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders) + newFolders := makeWorkspaceFolders(e.sandbox, folders) + config.WorkspaceFolders = folders + e.SetConfig(config) + + if e.Server == nil { + return nil + } + + var params protocol.DidChangeWorkspaceFoldersParams + + // Keep track of old workspace folders that must be removed. + toRemove := make(map[protocol.URI]protocol.WorkspaceFolder) + for _, folder := range oldFolders { + toRemove[folder.URI] = folder + } + + // Sanity check: if we see a folder twice the algorithm below doesn't work, + // so track seen folders to ensure that we panic in that case. + seen := make(map[protocol.URI]protocol.WorkspaceFolder) + for _, folder := range newFolders { + if _, ok := seen[folder.URI]; ok { + panic(fmt.Sprintf("folder %s seen twice", folder.URI)) + } + + // If this folder already exists, we don't want to remove it. + // Otherwise, we need to add it. + if _, ok := toRemove[folder.URI]; ok { + delete(toRemove, folder.URI) + } else { + params.Event.Added = append(params.Event.Added, folder) + } + } + + for _, v := range toRemove { + params.Event.Removed = append(params.Event.Removed, v) + } + + return e.Server.DidChangeWorkspaceFolders(ctx, ¶ms) +} + +// CodeAction executes a codeAction request on the server. +// If loc.Range is zero, the whole file is implied. +// To reduce distraction, the trigger action (unknown, automatic, invoked) +// may affect what actions are offered. +func (e *Editor) CodeAction(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, trigger protocol.CodeActionTriggerKind) ([]protocol.CodeAction, error) { + if e.Server == nil { + return nil, nil + } + path := e.sandbox.Workdir.URIToPath(loc.URI) + e.mu.Lock() + _, ok := e.buffers[path] + e.mu.Unlock() + if !ok { + return nil, fmt.Errorf("buffer %q is not open", path) + } + params := &protocol.CodeActionParams{ + TextDocument: e.TextDocumentIdentifier(path), + Context: protocol.CodeActionContext{ + Diagnostics: diagnostics, + TriggerKind: &trigger, + }, + Range: loc.Range, // may be zero + } + lens, err := e.Server.CodeAction(ctx, params) + if err != nil { + return nil, err + } + return lens, nil +} + +func (e *Editor) EditResolveSupport() (bool, error) { + capabilities, err := clientCapabilities(e.Config()) + if err != nil { + return false, err + } + return capabilities.TextDocument.CodeAction.ResolveSupport != nil && slices.Contains(capabilities.TextDocument.CodeAction.ResolveSupport.Properties, "edit"), nil +} + +// Hover triggers a hover at the given position in an open buffer. +// It may return (nil, zero) if no symbol was selected. +func (e *Editor) Hover(ctx context.Context, loc protocol.Location) (*protocol.MarkupContent, protocol.Location, error) { + if err := e.checkBufferLocation(loc); err != nil { + return nil, protocol.Location{}, err + } + params := &protocol.HoverParams{} + params.TextDocument.URI = loc.URI + params.Position = loc.Range.Start + + resp, err := e.Server.Hover(ctx, params) + if err != nil { + return nil, protocol.Location{}, fmt.Errorf("hover: %w", err) + } + if resp == nil { + return nil, protocol.Location{}, nil // e.g. no selected symbol + } + return &resp.Contents, protocol.Location{URI: loc.URI, Range: resp.Range}, nil +} + +func (e *Editor) DocumentLink(ctx context.Context, path string) ([]protocol.DocumentLink, error) { + if e.Server == nil { + return nil, nil + } + params := &protocol.DocumentLinkParams{} + params.TextDocument.URI = e.sandbox.Workdir.URI(path) + return e.Server.DocumentLink(ctx, params) +} + +func (e *Editor) DocumentHighlight(ctx context.Context, loc protocol.Location) ([]protocol.DocumentHighlight, error) { + if e.Server == nil { + return nil, nil + } + if err := e.checkBufferLocation(loc); err != nil { + return nil, err + } + params := &protocol.DocumentHighlightParams{} + params.TextDocument.URI = loc.URI + params.Position = loc.Range.Start + + return e.Server.DocumentHighlight(ctx, params) +} + +// SemanticTokensFull invokes textDocument/semanticTokens/full, and interprets +// its result. +func (e *Editor) SemanticTokensFull(ctx context.Context, path string) ([]SemanticToken, error) { + p := &protocol.SemanticTokensParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: e.sandbox.Workdir.URI(path), + }, + } + resp, err := e.Server.SemanticTokensFull(ctx, p) + if err != nil { + return nil, err + } + content, ok := e.BufferText(path) + if !ok { + return nil, fmt.Errorf("buffer %s is not open", path) + } + return e.interpretTokens(resp.Data, content), nil +} + +// SemanticTokensRange invokes textDocument/semanticTokens/range, and +// interprets its result. +func (e *Editor) SemanticTokensRange(ctx context.Context, loc protocol.Location) ([]SemanticToken, error) { + p := &protocol.SemanticTokensRangeParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Range: loc.Range, + } + resp, err := e.Server.SemanticTokensRange(ctx, p) + if err != nil { + return nil, err + } + path := e.sandbox.Workdir.URIToPath(loc.URI) + // As noted above: buffers should be keyed by protocol.DocumentURI. + content, ok := e.BufferText(path) + if !ok { + return nil, fmt.Errorf("buffer %s is not open", path) + } + return e.interpretTokens(resp.Data, content), nil +} + +// A SemanticToken is an interpreted semantic token value. +type SemanticToken struct { + Token string + TokenType string + Mod string +} + +// Note: previously this function elided comment, string, and number tokens. +// Instead, filtering of token types should be done by the caller. +func (e *Editor) interpretTokens(x []uint32, contents string) []SemanticToken { + e.mu.Lock() + legend := e.semTokOpts.Legend + e.mu.Unlock() + lines := strings.Split(contents, "\n") + ans := []SemanticToken{} + line, col := 1, 1 + for i := 0; i < len(x); i += 5 { + line += int(x[i]) + col += int(x[i+1]) + if x[i] != 0 { // new line + col = int(x[i+1]) + 1 // 1-based column numbers + } + sz := x[i+2] + t := legend.TokenTypes[x[i+3]] + l := x[i+4] + var mods []string + for i, mod := range legend.TokenModifiers { + if l&(1<<i) != 0 { + mods = append(mods, mod) + } + } + // Preexisting note: "col is a utf-8 offset" + // TODO(rfindley): is that true? Or is it UTF-16, like other columns in the LSP? + tok := lines[line-1][col-1 : col-1+int(sz)] + ans = append(ans, SemanticToken{tok, t, strings.Join(mods, " ")}) + } + return ans +} diff --git a/contribs/gnopls/internal/test/integration/fake/editor_test.go b/contribs/gnopls/internal/test/integration/fake/editor_test.go new file mode 100644 index 00000000000..68983bda50c --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/editor_test.go @@ -0,0 +1,61 @@ +// 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 fake + +import ( + "context" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" +) + +const exampleProgram = ` +-- go.mod -- +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println("Hello World.") +} +` + +func TestClientEditing(t *testing.T) { + ws, err := NewSandbox(&SandboxConfig{Files: UnpackTxt(exampleProgram)}) + if err != nil { + t.Fatal(err) + } + defer ws.Close() + ctx := context.Background() + editor := NewEditor(ws, EditorConfig{}) + if err := editor.OpenFile(ctx, "main.go"); err != nil { + t.Fatal(err) + } + if err := editor.EditBuffer(ctx, "main.go", []protocol.TextEdit{ + { + Range: protocol.Range{ + Start: protocol.Position{Line: 5, Character: 14}, + End: protocol.Position{Line: 5, Character: 26}, + }, + NewText: "Hola, mundo.", + }, + }); err != nil { + t.Fatal(err) + } + got := editor.buffers["main.go"].text() + want := `package main + +import "fmt" + +func main() { + fmt.Println("Hola, mundo.") +} +` + if got != want { + t.Errorf("got text %q, want %q", got, want) + } +} diff --git a/contribs/gnopls/internal/test/integration/fake/glob/glob.go b/contribs/gnopls/internal/test/integration/fake/glob/glob.go new file mode 100644 index 00000000000..a540ebefac5 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/glob/glob.go @@ -0,0 +1,349 @@ +// 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 glob implements an LSP-compliant glob pattern matcher for testing. +package glob + +import ( + "errors" + "fmt" + "strings" + "unicode/utf8" +) + +// A Glob is an LSP-compliant glob pattern, as defined by the spec: +// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentFilter +// +// NOTE: this implementation is currently only intended for testing. In order +// to make it production ready, we'd need to: +// - verify it against the VS Code implementation +// - add more tests +// - microbenchmark, likely avoiding the element interface +// - resolve the question of what is meant by "character". If it's a UTF-16 +// code (as we suspect) it'll be a bit more work. +// +// Quoting from the spec: +// Glob patterns can have the following syntax: +// - `*` to match one or more characters in a path segment +// - `?` to match on one character in a path segment +// - `**` to match any number of path segments, including none +// - `{}` to group sub patterns into an OR expression. (e.g. `**/*.{ts,js}` +// matches all TypeScript and JavaScript files) +// - `[]` to declare a range of characters to match in a path segment +// (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +// - `[!...]` to negate a range of characters to match in a path segment +// (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but +// not `example.0`) +// +// Expanding on this: +// - '/' matches one or more literal slashes. +// - any other character matches itself literally. +type Glob struct { + elems []element // pattern elements +} + +// Parse builds a Glob for the given pattern, returning an error if the pattern +// is invalid. +func Parse(pattern string) (*Glob, error) { + g, _, err := parse(pattern, false) + return g, err +} + +func parse(pattern string, nested bool) (*Glob, string, error) { + g := new(Glob) + for len(pattern) > 0 { + switch pattern[0] { + case '/': + pattern = pattern[1:] + g.elems = append(g.elems, slash{}) + + case '*': + if len(pattern) > 1 && pattern[1] == '*' { + if (len(g.elems) > 0 && g.elems[len(g.elems)-1] != slash{}) || (len(pattern) > 2 && pattern[2] != '/') { + return nil, "", errors.New("** may only be adjacent to '/'") + } + pattern = pattern[2:] + g.elems = append(g.elems, starStar{}) + break + } + pattern = pattern[1:] + g.elems = append(g.elems, star{}) + + case '?': + pattern = pattern[1:] + g.elems = append(g.elems, anyChar{}) + + case '{': + var gs group + for pattern[0] != '}' { + pattern = pattern[1:] + g, pat, err := parse(pattern, true) + if err != nil { + return nil, "", err + } + if len(pat) == 0 { + return nil, "", errors.New("unmatched '{'") + } + pattern = pat + gs = append(gs, g) + } + pattern = pattern[1:] + g.elems = append(g.elems, gs) + + case '}', ',': + if nested { + return g, pattern, nil + } + pattern = g.parseLiteral(pattern, false) + + case '[': + pattern = pattern[1:] + if len(pattern) == 0 { + return nil, "", errBadRange + } + negate := false + if pattern[0] == '!' { + pattern = pattern[1:] + negate = true + } + low, sz, err := readRangeRune(pattern) + if err != nil { + return nil, "", err + } + pattern = pattern[sz:] + if len(pattern) == 0 || pattern[0] != '-' { + return nil, "", errBadRange + } + pattern = pattern[1:] + high, sz, err := readRangeRune(pattern) + if err != nil { + return nil, "", err + } + pattern = pattern[sz:] + if len(pattern) == 0 || pattern[0] != ']' { + return nil, "", errBadRange + } + pattern = pattern[1:] + g.elems = append(g.elems, charRange{negate, low, high}) + + default: + pattern = g.parseLiteral(pattern, nested) + } + } + return g, "", nil +} + +// helper for decoding a rune in range elements, e.g. [a-z] +func readRangeRune(input string) (rune, int, error) { + r, sz := utf8.DecodeRuneInString(input) + var err error + if r == utf8.RuneError { + // See the documentation for DecodeRuneInString. + switch sz { + case 0: + err = errBadRange + case 1: + err = errInvalidUTF8 + } + } + return r, sz, err +} + +var ( + errBadRange = errors.New("'[' patterns must be of the form [x-y]") + errInvalidUTF8 = errors.New("invalid UTF-8 encoding") +) + +func (g *Glob) parseLiteral(pattern string, nested bool) string { + var specialChars string + if nested { + specialChars = "*?{[/}," + } else { + specialChars = "*?{[/" + } + end := strings.IndexAny(pattern, specialChars) + if end == -1 { + end = len(pattern) + } + g.elems = append(g.elems, literal(pattern[:end])) + return pattern[end:] +} + +func (g *Glob) String() string { + var b strings.Builder + for _, e := range g.elems { + fmt.Fprint(&b, e) + } + return b.String() +} + +// element holds a glob pattern element, as defined below. +type element fmt.Stringer + +// element types. +type ( + slash struct{} // One or more '/' separators + literal string // string literal, not containing /, *, ?, {}, or [] + star struct{} // * + anyChar struct{} // ? + starStar struct{} // ** + group []*Glob // {foo, bar, ...} grouping + charRange struct { // [a-z] character range + negate bool + low, high rune + } +) + +func (s slash) String() string { return "/" } +func (l literal) String() string { return string(l) } +func (s star) String() string { return "*" } +func (a anyChar) String() string { return "?" } +func (s starStar) String() string { return "**" } +func (g group) String() string { + var parts []string + for _, g := range g { + parts = append(parts, g.String()) + } + return "{" + strings.Join(parts, ",") + "}" +} +func (r charRange) String() string { + return "[" + string(r.low) + "-" + string(r.high) + "]" +} + +// Match reports whether the input string matches the glob pattern. +func (g *Glob) Match(input string) bool { + return match(g.elems, input) +} + +func match(elems []element, input string) (ok bool) { + var elem interface{} + for len(elems) > 0 { + elem, elems = elems[0], elems[1:] + switch elem := elem.(type) { + case slash: + if len(input) == 0 || input[0] != '/' { + return false + } + for input[0] == '/' { + input = input[1:] + } + + case starStar: + // Special cases: + // - **/a matches "a" + // - **/ matches everything + // + // Note that if ** is followed by anything, it must be '/' (this is + // enforced by Parse). + if len(elems) > 0 { + elems = elems[1:] + } + + // A trailing ** matches anything. + if len(elems) == 0 { + return true + } + + // Backtracking: advance pattern segments until the remaining pattern + // elements match. + for len(input) != 0 { + if match(elems, input) { + return true + } + _, input = split(input) + } + return false + + case literal: + if !strings.HasPrefix(input, string(elem)) { + return false + } + input = input[len(elem):] + + case star: + var segInput string + segInput, input = split(input) + + elemEnd := len(elems) + for i, e := range elems { + if e == (slash{}) { + elemEnd = i + break + } + } + segElems := elems[:elemEnd] + elems = elems[elemEnd:] + + // A trailing * matches the entire segment. + if len(segElems) == 0 { + break + } + + // Backtracking: advance characters until remaining subpattern elements + // match. + matched := false + for i := range segInput { + if match(segElems, segInput[i:]) { + matched = true + break + } + } + if !matched { + return false + } + + case anyChar: + if len(input) == 0 || input[0] == '/' { + return false + } + input = input[1:] + + case group: + // Append remaining pattern elements to each group member looking for a + // match. + var branch []element + for _, m := range elem { + branch = branch[:0] + branch = append(branch, m.elems...) + branch = append(branch, elems...) + if match(branch, input) { + return true + } + } + return false + + case charRange: + if len(input) == 0 || input[0] == '/' { + return false + } + c, sz := utf8.DecodeRuneInString(input) + if c < elem.low || c > elem.high { + return false + } + input = input[sz:] + + default: + panic(fmt.Sprintf("segment type %T not implemented", elem)) + } + } + + return len(input) == 0 +} + +// split returns the portion before and after the first slash +// (or sequence of consecutive slashes). If there is no slash +// it returns (input, nil). +func split(input string) (first, rest string) { + i := strings.IndexByte(input, '/') + if i < 0 { + return input, "" + } + first = input[:i] + for j := i; j < len(input); j++ { + if input[j] != '/' { + return first, input[j:] + } + } + return first, "" +} diff --git a/contribs/gnopls/internal/test/integration/fake/glob/glob_test.go b/contribs/gnopls/internal/test/integration/fake/glob/glob_test.go new file mode 100644 index 00000000000..8accd908e7a --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/glob/glob_test.go @@ -0,0 +1,118 @@ +// 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 glob_test + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/test/integration/fake/glob" +) + +func TestParseErrors(t *testing.T) { + tests := []string{ + "***", + "ab{c", + "[]", + "[a-]", + "ab{c{d}", + } + + for _, test := range tests { + _, err := glob.Parse(test) + if err == nil { + t.Errorf("Parse(%q) succeeded unexpectedly", test) + } + } +} + +func TestMatch(t *testing.T) { + tests := []struct { + pattern, input string + want bool + }{ + // Basic cases. + {"", "", true}, + {"", "a", false}, + {"", "/", false}, + {"abc", "abc", true}, + + // ** behavior + {"**", "abc", true}, + {"**/abc", "abc", true}, + {"**", "abc/def", true}, + {"{a/**/c,a/**/d}", "a/b/c", true}, + {"{a/**/c,a/**/d}", "a/b/c/d", true}, + {"{a/**/c,a/**/e}", "a/b/c/d", false}, + {"{a/**/c,a/**/e,a/**/d}", "a/b/c/d", true}, + {"{/a/**/c,a/**/e,a/**/d}", "a/b/c/d", true}, + {"{/a/**/c,a/**/e,a/**/d}", "/a/b/c/d", false}, + {"{/a/**/c,a/**/e,a/**/d}", "/a/b/c", true}, + {"{/a/**/e,a/**/e,a/**/d}", "/a/b/c", false}, + + // * and ? behavior + {"/*", "/a", true}, + {"*", "foo", true}, + {"*o", "foo", true}, + {"*o", "foox", false}, + {"f*o", "foo", true}, + {"f*o", "fo", true}, + {"fo?", "foo", true}, + {"fo?", "fox", true}, + {"fo?", "fooo", false}, + {"fo?", "fo", false}, + {"?", "a", true}, + {"?", "ab", false}, + {"?", "", false}, + {"*?", "", false}, + {"?b", "ab", true}, + {"?c", "ab", false}, + + // {} behavior + {"ab{c,d}e", "abce", true}, + {"ab{c,d}e", "abde", true}, + {"ab{c,d}e", "abxe", false}, + {"ab{c,d}e", "abe", false}, + {"{a,b}c", "ac", true}, + {"{a,b}c", "bc", true}, + {"{a,b}c", "ab", false}, + {"a{b,c}", "ab", true}, + {"a{b,c}", "ac", true}, + {"a{b,c}", "bc", false}, + {"ab{c{1,2},d}e", "abc1e", true}, + {"ab{c{1,2},d}e", "abde", true}, + {"ab{c{1,2},d}e", "abc1f", false}, + {"ab{c{1,2},d}e", "abce", false}, + {"ab{c[}-~]}d", "abc}d", true}, + {"ab{c[}-~]}d", "abc~d", true}, + {"ab{c[}-~],y}d", "abcxd", false}, + {"ab{c[}-~],y}d", "abyd", true}, + {"ab{c[}-~],y}d", "abd", false}, + {"{a/b/c,d/e/f}", "a/b/c", true}, + {"/ab{/c,d}e", "/ab/ce", true}, + {"/ab{/c,d}e", "/ab/cf", false}, + + // [-] behavior + {"[a-c]", "a", true}, + {"[a-c]", "b", true}, + {"[a-c]", "c", true}, + {"[a-c]", "d", false}, + {"[a-c]", " ", false}, + + // Realistic examples. + {"**/*.{ts,js}", "path/to/foo.ts", true}, + {"**/*.{ts,js}", "path/to/foo.js", true}, + {"**/*.{ts,js}", "path/to/foo.go", false}, + } + + for _, test := range tests { + g, err := glob.Parse(test.pattern) + if err != nil { + t.Fatalf("New(%q) failed unexpectedly: %v", test.pattern, err) + } + if got := g.Match(test.input); got != test.want { + t.Errorf("New(%q).Match(%q) = %t, want %t", test.pattern, test.input, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/test/integration/fake/proxy.go b/contribs/gnopls/internal/test/integration/fake/proxy.go new file mode 100644 index 00000000000..9e56efeb17f --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/proxy.go @@ -0,0 +1,35 @@ +// 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 fake + +import ( + "fmt" + + "golang.org/x/tools/internal/proxydir" +) + +// WriteProxy creates a new proxy file tree using the txtar-encoded content, +// and returns its URL. +func WriteProxy(tmpdir string, files map[string][]byte) (string, error) { + type moduleVersion struct { + modulePath, version string + } + // Transform into the format expected by the proxydir package. + filesByModule := make(map[moduleVersion]map[string][]byte) + for name, data := range files { + modulePath, version, suffix := splitModuleVersionPath(name) + mv := moduleVersion{modulePath, version} + if _, ok := filesByModule[mv]; !ok { + filesByModule[mv] = make(map[string][]byte) + } + filesByModule[mv][suffix] = data + } + for mv, files := range filesByModule { + if err := proxydir.WriteModuleVersion(tmpdir, mv.modulePath, mv.version, files); err != nil { + return "", fmt.Errorf("error writing %s@%s: %v", mv.modulePath, mv.version, err) + } + } + return proxydir.ToURL(tmpdir), nil +} diff --git a/contribs/gnopls/internal/test/integration/fake/sandbox.go b/contribs/gnopls/internal/test/integration/fake/sandbox.go new file mode 100644 index 00000000000..fcaa50f0a76 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/sandbox.go @@ -0,0 +1,285 @@ +// 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 fake + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/robustio" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/txtar" +) + +// Sandbox holds a collection of temporary resources to use for working with Go +// code in tests. +type Sandbox struct { + gopath string + rootdir string + goproxy string + Workdir *Workdir + goCommandRunner gocommand.Runner +} + +// SandboxConfig controls the behavior of a test sandbox. The zero value +// defines a reasonable default. +type SandboxConfig struct { + // RootDir sets the base directory to use when creating temporary + // directories. If not specified, defaults to a new temporary directory. + RootDir string + // Files holds a txtar-encoded archive of files to populate the initial state + // of the working directory. + // + // For convenience, the special substring "$SANDBOX_WORKDIR" is replaced with + // the sandbox's resolved working directory before writing files. + Files map[string][]byte + // InGoPath specifies that the working directory should be within the + // temporary GOPATH. + InGoPath bool + // Workdir configures the working directory of the Sandbox. It behaves as + // follows: + // - if set to an absolute path, use that path as the working directory. + // - if set to a relative path, create and use that path relative to the + // sandbox. + // - if unset, default to a the 'work' subdirectory of the sandbox. + // + // This option is incompatible with InGoPath or Files. + Workdir string + // ProxyFiles holds a txtar-encoded archive of files to populate a file-based + // Go proxy. + ProxyFiles map[string][]byte + // GOPROXY is the explicit GOPROXY value that should be used for the sandbox. + // + // This option is incompatible with ProxyFiles. + GOPROXY string +} + +// NewSandbox creates a collection of named temporary resources, with a +// working directory populated by the txtar-encoded content in srctxt, and a +// file-based module proxy populated with the txtar-encoded content in +// proxytxt. +// +// If rootDir is non-empty, it will be used as the root of temporary +// directories created for the sandbox. Otherwise, a new temporary directory +// will be used as root. +// +// TODO(rfindley): the sandbox abstraction doesn't seem to carry its weight. +// Sandboxes should be composed out of their building-blocks, rather than via a +// monolithic configuration. +func NewSandbox(config *SandboxConfig) (_ *Sandbox, err error) { + if config == nil { + config = new(SandboxConfig) + } + if err := validateConfig(*config); err != nil { + return nil, fmt.Errorf("invalid SandboxConfig: %v", err) + } + + sb := &Sandbox{} + defer func() { + // Clean up if we fail at any point in this constructor. + if err != nil { + sb.Close() + } + }() + + rootDir := config.RootDir + if rootDir == "" { + rootDir, err = os.MkdirTemp(config.RootDir, "gopls-sandbox-") + if err != nil { + return nil, fmt.Errorf("creating temporary workdir: %v", err) + } + } + sb.rootdir = rootDir + sb.gopath = filepath.Join(sb.rootdir, "gopath") + if err := os.Mkdir(sb.gopath, 0755); err != nil { + return nil, err + } + if config.GOPROXY != "" { + sb.goproxy = config.GOPROXY + } else { + proxydir := filepath.Join(sb.rootdir, "proxy") + if err := os.Mkdir(proxydir, 0755); err != nil { + return nil, err + } + sb.goproxy, err = WriteProxy(proxydir, config.ProxyFiles) + if err != nil { + return nil, err + } + } + // Short-circuit writing the workdir if we're given an absolute path, since + // this is used for running in an existing directory. + // TODO(findleyr): refactor this to be less of a workaround. + if filepath.IsAbs(config.Workdir) { + sb.Workdir, err = NewWorkdir(config.Workdir, nil) + if err != nil { + return nil, err + } + return sb, nil + } + var workdir string + if config.Workdir == "" { + if config.InGoPath { + // Set the working directory as $GOPATH/src. + workdir = filepath.Join(sb.gopath, "src") + } else if workdir == "" { + workdir = filepath.Join(sb.rootdir, "work") + } + } else { + // relative path + workdir = filepath.Join(sb.rootdir, config.Workdir) + } + if err := os.MkdirAll(workdir, 0755); err != nil { + return nil, err + } + sb.Workdir, err = NewWorkdir(workdir, config.Files) + if err != nil { + return nil, err + } + return sb, nil +} + +func UnpackTxt(txt string) map[string][]byte { + dataMap := make(map[string][]byte) + archive := txtar.Parse([]byte(txt)) + for _, f := range archive.Files { + if _, ok := dataMap[f.Name]; ok { + panic(fmt.Sprintf("found file %q twice", f.Name)) + } + dataMap[f.Name] = f.Data + } + return dataMap +} + +func validateConfig(config SandboxConfig) error { + if filepath.IsAbs(config.Workdir) && (len(config.Files) > 0 || config.InGoPath) { + return errors.New("absolute Workdir cannot be set in conjunction with Files or InGoPath") + } + if config.Workdir != "" && config.InGoPath { + return errors.New("Workdir cannot be set in conjunction with InGoPath") + } + if config.GOPROXY != "" && config.ProxyFiles != nil { + return errors.New("GOPROXY cannot be set in conjunction with ProxyFiles") + } + return nil +} + +// splitModuleVersionPath extracts module information from files stored in the +// directory structure modulePath@version/suffix. +// For example: +// +// splitModuleVersionPath("mod.com@v1.2.3/package") = ("mod.com", "v1.2.3", "package") +func splitModuleVersionPath(path string) (modulePath, version, suffix string) { + parts := strings.Split(path, "/") + var modulePathParts []string + for i, p := range parts { + if strings.Contains(p, "@") { + mv := strings.SplitN(p, "@", 2) + modulePathParts = append(modulePathParts, mv[0]) + return strings.Join(modulePathParts, "/"), mv[1], strings.Join(parts[i+1:], "/") + } + modulePathParts = append(modulePathParts, p) + } + // Default behavior: this is just a module path. + return path, "", "" +} + +func (sb *Sandbox) RootDir() string { + return sb.rootdir +} + +// GOPATH returns the value of the Sandbox GOPATH. +func (sb *Sandbox) GOPATH() string { + return sb.gopath +} + +// GoEnv returns the default environment variables that can be used for +// invoking Go commands in the sandbox. +func (sb *Sandbox) GoEnv() map[string]string { + vars := map[string]string{ + "GOPATH": sb.GOPATH(), + "GOPROXY": sb.goproxy, + "GO111MODULE": "", + "GOSUMDB": "off", + "GOPACKAGESDRIVER": "off", + } + if testenv.Go1Point() >= 5 { + vars["GOMODCACHE"] = "" + } + return vars +} + +// goCommandInvocation returns a new gocommand.Invocation initialized with the +// sandbox environment variables and working directory. +func (sb *Sandbox) goCommandInvocation() gocommand.Invocation { + var vars []string + for k, v := range sb.GoEnv() { + vars = append(vars, fmt.Sprintf("%s=%s", k, v)) + } + inv := gocommand.Invocation{ + Env: vars, + } + // sb.Workdir may be nil if we exited the constructor with errors (we call + // Close to clean up any partial state from the constructor, which calls + // RunGoCommand). + if sb.Workdir != nil { + inv.WorkingDir = string(sb.Workdir.RelativeTo) + } + return inv +} + +// RunGoCommand executes a go command in the sandbox. If checkForFileChanges is +// true, the sandbox scans the working directory and emits file change events +// for any file changes it finds. +func (sb *Sandbox) RunGoCommand(ctx context.Context, dir, verb string, args, env []string, checkForFileChanges bool) error { + inv := sb.goCommandInvocation() + inv.Verb = verb + inv.Args = args + inv.Env = append(inv.Env, env...) + if dir != "" { + inv.WorkingDir = sb.Workdir.AbsPath(dir) + } + stdout, stderr, _, err := sb.goCommandRunner.RunRaw(ctx, inv) + if err != nil { + return fmt.Errorf("go command failed (stdout: %s) (stderr: %s): %v", stdout.String(), stderr.String(), err) + } + // Since running a go command may result in changes to workspace files, + // check if we need to send any "watched" file events. + // + // TODO(rFindley): this side-effect can impact the usability of the sandbox + // for benchmarks. Consider refactoring. + if sb.Workdir != nil && checkForFileChanges { + if err := sb.Workdir.CheckForFileChanges(ctx); err != nil { + return fmt.Errorf("checking for file changes: %w", err) + } + } + return nil +} + +// GoVersion checks the version of the go command. +// It returns the X in Go 1.X. +func (sb *Sandbox) GoVersion(ctx context.Context) (int, error) { + inv := sb.goCommandInvocation() + return gocommand.GoVersion(ctx, inv, &sb.goCommandRunner) +} + +// Close removes all state associated with the sandbox. +func (sb *Sandbox) Close() error { + var goCleanErr error + if sb.gopath != "" { + // Important: run this command in RootDir so that it doesn't interact with + // any toolchain downloads that may occur + goCleanErr = sb.RunGoCommand(context.Background(), sb.RootDir(), "clean", []string{"-modcache"}, nil, false) + } + err := robustio.RemoveAll(sb.rootdir) + if err != nil || goCleanErr != nil { + return fmt.Errorf("error(s) cleaning sandbox: cleaning modcache: %v; removing files: %v", goCleanErr, err) + } + return nil +} diff --git a/contribs/gnopls/internal/test/integration/fake/workdir.go b/contribs/gnopls/internal/test/integration/fake/workdir.go new file mode 100644 index 00000000000..be3cb3bcf15 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/workdir.go @@ -0,0 +1,429 @@ +// 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 fake + +import ( + "bytes" + "context" + "crypto/sha256" + "fmt" + "io/fs" + "os" + "path/filepath" + "runtime" + "slices" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/robustio" +) + +// RelativeTo is a helper for operations relative to a given directory. +type RelativeTo string + +// AbsPath returns an absolute filesystem path for the workdir-relative path. +func (r RelativeTo) AbsPath(path string) string { + fp := filepath.FromSlash(path) + if filepath.IsAbs(fp) { + return fp + } + return filepath.Join(string(r), filepath.FromSlash(path)) +} + +// RelPath returns a '/'-encoded path relative to the working directory (or an +// absolute path if the file is outside of workdir) +func (r RelativeTo) RelPath(fp string) string { + root := string(r) + if rel, err := filepath.Rel(root, fp); err == nil && !strings.HasPrefix(rel, "..") { + return filepath.ToSlash(rel) + } + return filepath.ToSlash(fp) +} + +// writeFileData writes content to the relative path, replacing the special +// token $SANDBOX_WORKDIR with the relative root given by rel. It does not +// trigger any file events. +func writeFileData(path string, content []byte, rel RelativeTo) error { + content = bytes.ReplaceAll(content, []byte("$SANDBOX_WORKDIR"), []byte(rel)) + fp := rel.AbsPath(path) + if err := os.MkdirAll(filepath.Dir(fp), 0755); err != nil { + return fmt.Errorf("creating nested directory: %w", err) + } + backoff := 1 * time.Millisecond + for { + err := os.WriteFile(fp, content, 0644) + if err != nil { + // This lock file violation is not handled by the robustio package, as it + // indicates a real race condition that could be avoided. + if isWindowsErrLockViolation(err) { + time.Sleep(backoff) + backoff *= 2 + continue + } + return fmt.Errorf("writing %q: %w", path, err) + } + return nil + } +} + +// isWindowsErrLockViolation reports whether err is ERROR_LOCK_VIOLATION +// on Windows. +var isWindowsErrLockViolation = func(err error) bool { return false } + +// Workdir is a temporary working directory for tests. It exposes file +// operations in terms of relative paths, and fakes file watching by triggering +// events on file operations. +type Workdir struct { + RelativeTo + + watcherMu sync.Mutex + watchers []func(context.Context, []protocol.FileEvent) + + fileMu sync.Mutex + // File identities we know about, for the purpose of detecting changes. + // + // Since files is only used for detecting _changes_, we are tolerant of + // fileIDs that may have hash and mtime coming from different states of the + // file: if either are out of sync, then the next poll should detect a + // discrepancy. It is OK if we detect too many changes, but not OK if we miss + // changes. + // + // For that matter, this mechanism for detecting changes can still be flaky + // on platforms where mtime is very coarse (such as older versions of WSL). + // It would be much better to use a proper fs event library, but we can't + // currently import those into x/tools. + // + // TODO(golang/go#52284): replace this polling mechanism with a + // cross-platform library for filesystem notifications. + files map[string]fileID +} + +// NewWorkdir writes the txtar-encoded file data in txt to dir, and returns a +// Workir for operating on these files using +func NewWorkdir(dir string, files map[string][]byte) (*Workdir, error) { + w := &Workdir{RelativeTo: RelativeTo(dir)} + for name, data := range files { + if err := writeFileData(name, data, w.RelativeTo); err != nil { + return nil, fmt.Errorf("writing to workdir: %w", err) + } + } + _, err := w.pollFiles() // poll files to populate the files map. + return w, err +} + +// fileID identifies a file version on disk. +type fileID struct { + mtime time.Time + hash string // empty if mtime is old enough to be reliable; otherwise a file digest +} + +func hashFile(data []byte) string { + return fmt.Sprintf("%x", sha256.Sum256(data)) +} + +// RootURI returns the root URI for this working directory of this scratch +// environment. +func (w *Workdir) RootURI() protocol.DocumentURI { + return protocol.URIFromPath(string(w.RelativeTo)) +} + +// AddWatcher registers the given func to be called on any file change. +func (w *Workdir) AddWatcher(watcher func(context.Context, []protocol.FileEvent)) { + w.watcherMu.Lock() + w.watchers = append(w.watchers, watcher) + w.watcherMu.Unlock() +} + +// URI returns the URI to a the workdir-relative path. +func (w *Workdir) URI(path string) protocol.DocumentURI { + return protocol.URIFromPath(w.AbsPath(path)) +} + +// URIToPath converts a uri to a workdir-relative path (or an absolute path, +// if the uri is outside of the workdir). +func (w *Workdir) URIToPath(uri protocol.DocumentURI) string { + return w.RelPath(uri.Path()) +} + +// EntireFile returns the entire extent of the file named by the workdir-relative path. +func (w *Workdir) EntireFile(path string) protocol.Location { + return protocol.Location{URI: w.URI(path)} +} + +// ReadFile reads a text file specified by a workdir-relative path. +func (w *Workdir) ReadFile(path string) ([]byte, error) { + backoff := 1 * time.Millisecond + for { + b, err := os.ReadFile(w.AbsPath(path)) + if err != nil { + if runtime.GOOS == "plan9" && strings.HasSuffix(err.Error(), " exclusive use file already open") { + // Plan 9 enforces exclusive access to locked files. + // Give the owner time to unlock it and retry. + time.Sleep(backoff) + backoff *= 2 + continue + } + return nil, err + } + return b, nil + } +} + +// RegexpSearch searches the file corresponding to path for the first position +// matching re. +func (w *Workdir) RegexpSearch(path string, re string) (protocol.Location, error) { + content, err := w.ReadFile(path) + if err != nil { + return protocol.Location{}, err + } + mapper := protocol.NewMapper(w.URI(path), content) + return regexpLocation(mapper, re) +} + +// RemoveFile removes a workdir-relative file path and notifies watchers of the +// change. +func (w *Workdir) RemoveFile(ctx context.Context, path string) error { + fp := w.AbsPath(path) + if err := robustio.RemoveAll(fp); err != nil { + return fmt.Errorf("removing %q: %w", path, err) + } + + return w.CheckForFileChanges(ctx) +} + +// WriteFiles writes the text file content to workdir-relative paths and +// notifies watchers of the changes. +func (w *Workdir) WriteFiles(ctx context.Context, files map[string]string) error { + for path, content := range files { + fp := w.AbsPath(path) + _, err := os.Stat(fp) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("checking if %q exists: %w", path, err) + } + if err := writeFileData(path, []byte(content), w.RelativeTo); err != nil { + return err + } + } + return w.CheckForFileChanges(ctx) +} + +// WriteFile writes text file content to a workdir-relative path and notifies +// watchers of the change. +func (w *Workdir) WriteFile(ctx context.Context, path, content string) error { + return w.WriteFiles(ctx, map[string]string{path: content}) +} + +// RenameFile performs an on disk-renaming of the workdir-relative oldPath to +// workdir-relative newPath, and notifies watchers of the changes. +// +// oldPath must either be a regular file or in the same directory as newPath. +func (w *Workdir) RenameFile(ctx context.Context, oldPath, newPath string) error { + oldAbs := w.AbsPath(oldPath) + newAbs := w.AbsPath(newPath) + + // For os.Rename, “OS-specific restrictions may apply when oldpath and newpath + // are in different directories.” If that applies here, we may fall back to + // ReadFile, WriteFile, and RemoveFile to perform the rename non-atomically. + // + // However, the fallback path only works for regular files: renaming a + // directory would be much more complex and isn't needed for our tests. + fallbackOk := false + if filepath.Dir(oldAbs) != filepath.Dir(newAbs) { + fi, err := os.Stat(oldAbs) + if err == nil && !fi.Mode().IsRegular() { + return &os.PathError{ + Op: "RenameFile", + Path: oldPath, + Err: fmt.Errorf("%w: file is not regular and not in the same directory as %s", os.ErrInvalid, newPath), + } + } + fallbackOk = true + } + + var renameErr error + const debugFallback = false + if fallbackOk && debugFallback { + renameErr = fmt.Errorf("%w: debugging fallback path", os.ErrInvalid) + } else { + renameErr = robustio.Rename(oldAbs, newAbs) + } + if renameErr != nil { + if !fallbackOk { + return renameErr // The OS-specific Rename restrictions do not apply. + } + + content, err := w.ReadFile(oldPath) + if err != nil { + // If we can't even read the file, the error from Rename may be accurate. + return renameErr + } + fi, err := os.Stat(newAbs) + if err == nil { + if fi.IsDir() { + // “If newpath already exists and is not a directory, Rename replaces it.” + // But if it is a directory, maybe not? + return renameErr + } + // On most platforms, Rename replaces the named file with a new file, + // rather than overwriting the existing file it in place. Mimic that + // behavior here. + if err := robustio.RemoveAll(newAbs); err != nil { + // Maybe we don't have permission to replace newPath? + return renameErr + } + } else if !os.IsNotExist(err) { + // If the destination path already exists or there is some problem with it, + // the error from Rename may be accurate. + return renameErr + } + if writeErr := writeFileData(newPath, content, w.RelativeTo); writeErr != nil { + // At this point we have tried to actually write the file. + // If it still doesn't exist, assume that the error from Rename was accurate: + // for example, maybe we don't have permission to create the new path. + // Otherwise, return the error from the write, which may indicate some + // other problem (such as a full disk). + if _, statErr := os.Stat(newAbs); !os.IsNotExist(statErr) { + return writeErr + } + return renameErr + } + if err := robustio.RemoveAll(oldAbs); err != nil { + // If we failed to remove the old file, that may explain the Rename error too. + // Make a best effort to back out the write to the new path. + robustio.RemoveAll(newAbs) + return renameErr + } + } + + return w.CheckForFileChanges(ctx) +} + +// ListFiles returns a new sorted list of the relative paths of files in dir, +// recursively. +func (w *Workdir) ListFiles(dir string) ([]string, error) { + absDir := w.AbsPath(dir) + var paths []string + if err := filepath.Walk(absDir, func(fp string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode()&(fs.ModeDir|fs.ModeSymlink) == 0 { + paths = append(paths, w.RelPath(fp)) + } + return nil + }); err != nil { + return nil, err + } + sort.Strings(paths) + return paths, nil +} + +// CheckForFileChanges walks the working directory and checks for any files +// that have changed since the last poll. +func (w *Workdir) CheckForFileChanges(ctx context.Context) error { + evts, err := w.pollFiles() + if err != nil { + return err + } + if len(evts) == 0 { + return nil + } + w.watcherMu.Lock() + watchers := slices.Clone(w.watchers) + w.watcherMu.Unlock() + for _, w := range watchers { + w(ctx, evts) + } + return nil +} + +// pollFiles updates w.files and calculates FileEvents corresponding to file +// state changes since the last poll. It does not call sendEvents. +func (w *Workdir) pollFiles() ([]protocol.FileEvent, error) { + w.fileMu.Lock() + defer w.fileMu.Unlock() + + newFiles := make(map[string]fileID) + var evts []protocol.FileEvent + if err := filepath.Walk(string(w.RelativeTo), func(fp string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // Skip directories and symbolic links (which may be links to directories). + // + // The latter matters for repos like Kubernetes, which use symlinks. + if info.Mode()&(fs.ModeDir|fs.ModeSymlink) != 0 { + return nil + } + + // Opt: avoid reading the file if mtime is sufficiently old to be reliable. + // + // If mtime is recent, it may not sufficiently identify the file contents: + // a subsequent write could result in the same mtime. For these cases, we + // must read the file contents. + id := fileID{mtime: info.ModTime()} + if time.Since(info.ModTime()) < 2*time.Second { + data, err := os.ReadFile(fp) + if err != nil { + return err + } + id.hash = hashFile(data) + } + path := w.RelPath(fp) + newFiles[path] = id + + if w.files != nil { + oldID, ok := w.files[path] + delete(w.files, path) + switch { + case !ok: + evts = append(evts, protocol.FileEvent{ + URI: w.URI(path), + Type: protocol.Created, + }) + case oldID != id: + changed := true + + // Check whether oldID and id do not match because oldID was polled at + // a recent enough to time such as to require hashing. + // + // In this case, read the content to check whether the file actually + // changed. + if oldID.mtime.Equal(id.mtime) && oldID.hash != "" && id.hash == "" { + data, err := os.ReadFile(fp) + if err != nil { + return err + } + if hashFile(data) == oldID.hash { + changed = false + } + } + if changed { + evts = append(evts, protocol.FileEvent{ + URI: w.URI(path), + Type: protocol.Changed, + }) + } + } + } + + return nil + }); err != nil { + return nil, err + } + + // Any remaining files must have been deleted. + for path := range w.files { + evts = append(evts, protocol.FileEvent{ + URI: w.URI(path), + Type: protocol.Deleted, + }) + } + w.files = newFiles + return evts, nil +} diff --git a/contribs/gnopls/internal/test/integration/fake/workdir_test.go b/contribs/gnopls/internal/test/integration/fake/workdir_test.go new file mode 100644 index 00000000000..153a3576b4e --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/workdir_test.go @@ -0,0 +1,219 @@ +// 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 fake + +import ( + "context" + "os" + "sync" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" +) + +const sharedData = ` +-- go.mod -- +go 1.12 +-- nested/README.md -- +Hello World! +` + +// newWorkdir sets up a temporary Workdir with the given txtar-encoded content. +// It also configures an eventBuffer to receive file event notifications. These +// notifications are sent synchronously for each operation, such that once a +// workdir file operation has returned the caller can expect that any relevant +// file notifications are present in the buffer. +// +// It is the caller's responsibility to call the returned cleanup function. +func newWorkdir(t *testing.T, txt string) (*Workdir, *eventBuffer, func()) { + t.Helper() + + tmpdir, err := os.MkdirTemp("", "goplstest-workdir-") + if err != nil { + t.Fatal(err) + } + wd, err := NewWorkdir(tmpdir, UnpackTxt(txt)) + if err != nil { + t.Fatal(err) + } + cleanup := func() { + if err := os.RemoveAll(tmpdir); err != nil { + t.Error(err) + } + } + + buf := new(eventBuffer) + wd.AddWatcher(buf.onEvents) + return wd, buf, cleanup +} + +// eventBuffer collects events from a file watcher. +type eventBuffer struct { + mu sync.Mutex + events []protocol.FileEvent +} + +// onEvents collects adds events to the buffer; to be used with Workdir.AddWatcher. +func (c *eventBuffer) onEvents(_ context.Context, events []protocol.FileEvent) { + c.mu.Lock() + defer c.mu.Unlock() + + c.events = append(c.events, events...) +} + +// take empties the buffer, returning its previous contents. +func (c *eventBuffer) take() []protocol.FileEvent { + c.mu.Lock() + defer c.mu.Unlock() + + evts := c.events + c.events = nil + return evts +} + +func TestWorkdir_ReadFile(t *testing.T) { + wd, _, cleanup := newWorkdir(t, sharedData) + defer cleanup() + + got, err := wd.ReadFile("nested/README.md") + if err != nil { + t.Fatal(err) + } + want := "Hello World!\n" + if got := string(got); got != want { + t.Errorf("reading workdir file, got %q, want %q", got, want) + } +} + +func TestWorkdir_WriteFile(t *testing.T) { + wd, events, cleanup := newWorkdir(t, sharedData) + defer cleanup() + ctx := context.Background() + + tests := []struct { + path string + wantType protocol.FileChangeType + }{ + {"data.txt", protocol.Created}, + {"nested/README.md", protocol.Changed}, + } + + for _, test := range tests { + if err := wd.WriteFile(ctx, test.path, "42"); err != nil { + t.Fatal(err) + } + es := events.take() + if got := len(es); got != 1 { + t.Fatalf("len(events) = %d, want 1", got) + } + path := wd.URIToPath(es[0].URI) + if path != test.path { + t.Errorf("event path = %q, want %q", path, test.path) + } + if es[0].Type != test.wantType { + t.Errorf("event type = %v, want %v", es[0].Type, test.wantType) + } + got, err := wd.ReadFile(test.path) + if err != nil { + t.Fatal(err) + } + want := "42" + if got := string(got); got != want { + t.Errorf("ws.ReadFile(%q) = %q, want %q", test.path, got, want) + } + } +} + +// Test for file notifications following file operations. +func TestWorkdir_FileWatching(t *testing.T) { + wd, events, cleanup := newWorkdir(t, "") + defer cleanup() + ctx := context.Background() + + must := func(err error) { + if err != nil { + t.Fatal(err) + } + } + + type changeMap map[string]protocol.FileChangeType + checkEvent := func(wantChanges changeMap) { + gotChanges := make(changeMap) + for _, e := range events.take() { + gotChanges[wd.URIToPath(e.URI)] = e.Type + } + if diff := cmp.Diff(wantChanges, gotChanges); diff != "" { + t.Errorf("mismatching file events (-want +got):\n%s", diff) + } + } + + must(wd.WriteFile(ctx, "foo.go", "package foo")) + checkEvent(changeMap{"foo.go": protocol.Created}) + + must(wd.RenameFile(ctx, "foo.go", "bar.go")) + checkEvent(changeMap{"foo.go": protocol.Deleted, "bar.go": protocol.Created}) + + must(wd.RemoveFile(ctx, "bar.go")) + checkEvent(changeMap{"bar.go": protocol.Deleted}) +} + +func TestWorkdir_CheckForFileChanges(t *testing.T) { + t.Skip("broken on darwin-amd64-10_12") + wd, events, cleanup := newWorkdir(t, sharedData) + defer cleanup() + ctx := context.Background() + + checkChange := func(wantPath string, wantType protocol.FileChangeType) { + if err := wd.CheckForFileChanges(ctx); err != nil { + t.Fatal(err) + } + ev := events.take() + if len(ev) == 0 { + t.Fatal("no file events received") + } + gotEvt := ev[0] + gotPath := wd.URIToPath(gotEvt.URI) + // Only check relative path and Type + if gotPath != wantPath || gotEvt.Type != wantType { + t.Errorf("file events: got %v, want {Path: %s, Type: %v}", gotEvt, wantPath, wantType) + } + } + // Sleep some positive amount of time to ensure a distinct mtime. + if err := writeFileData("go.mod", []byte("module foo.test\n"), wd.RelativeTo); err != nil { + t.Fatal(err) + } + checkChange("go.mod", protocol.Changed) + if err := writeFileData("newFile", []byte("something"), wd.RelativeTo); err != nil { + t.Fatal(err) + } + checkChange("newFile", protocol.Created) + fp := wd.AbsPath("newFile") + if err := os.Remove(fp); err != nil { + t.Fatal(err) + } + checkChange("newFile", protocol.Deleted) +} + +func TestSplitModuleVersionPath(t *testing.T) { + tests := []struct { + path string + wantModule, wantVersion, wantSuffix string + }{ + {"foo.com@v1.2.3/bar", "foo.com", "v1.2.3", "bar"}, + {"foo.com/module@v1.2.3/bar", "foo.com/module", "v1.2.3", "bar"}, + {"foo.com@v1.2.3", "foo.com", "v1.2.3", ""}, + {"std@v1.14.0", "std", "v1.14.0", ""}, + {"another/module/path", "another/module/path", "", ""}, + } + + for _, test := range tests { + module, version, suffix := splitModuleVersionPath(test.path) + if module != test.wantModule || version != test.wantVersion || suffix != test.wantSuffix { + t.Errorf("splitModuleVersionPath(%q) =\n\t(%q, %q, %q)\nwant\n\t(%q, %q, %q)", + test.path, module, version, suffix, test.wantModule, test.wantVersion, test.wantSuffix) + } + } +} diff --git a/contribs/gnopls/internal/test/integration/fake/workdir_windows.go b/contribs/gnopls/internal/test/integration/fake/workdir_windows.go new file mode 100644 index 00000000000..4d4f0152764 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/fake/workdir_windows.go @@ -0,0 +1,21 @@ +// 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 fake + +import ( + "errors" + "syscall" +) + +func init() { + // constants copied from GOROOT/src/internal/syscall/windows/syscall_windows.go + const ( + ERROR_LOCK_VIOLATION syscall.Errno = 33 + ) + + isWindowsErrLockViolation = func(err error) bool { + return errors.Is(err, ERROR_LOCK_VIOLATION) + } +} diff --git a/contribs/gnopls/internal/test/integration/inlayhints/inlayhints_test.go b/contribs/gnopls/internal/test/integration/inlayhints/inlayhints_test.go new file mode 100644 index 00000000000..6c55ee7601c --- /dev/null +++ b/contribs/gnopls/internal/test/integration/inlayhints/inlayhints_test.go @@ -0,0 +1,69 @@ +// 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 inlayhint + +import ( + "os" + "testing" + + "golang.org/x/tools/gopls/internal/settings" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/util/bug" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(Main(m)) +} + +func TestEnablingInlayHints(t *testing.T) { + const workspace = ` +-- go.mod -- +module inlayHint.test +go 1.12 +-- lib.go -- +package lib +type Number int +const ( + Zero Number = iota + One + Two +) +` + tests := []struct { + label string + enabled map[string]bool + wantInlayHint bool + }{ + { + label: "default", + wantInlayHint: false, + }, + { + label: "enable const", + enabled: map[string]bool{string(settings.ConstantValues): true}, + wantInlayHint: true, + }, + { + label: "enable parameter names", + enabled: map[string]bool{string(settings.ParameterNames): true}, + wantInlayHint: false, + }, + } + for _, test := range tests { + t.Run(test.label, func(t *testing.T) { + WithOptions( + Settings{ + "hints": test.enabled, + }, + ).Run(t, workspace, func(t *testing.T, env *Env) { + env.OpenFile("lib.go") + lens := env.InlayHints("lib.go") + if gotInlayHint := len(lens) > 0; gotInlayHint != test.wantInlayHint { + t.Errorf("got inlayHint: %t, want %t", gotInlayHint, test.wantInlayHint) + } + }) + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/misc/call_hierarchy_test.go b/contribs/gnopls/internal/test/integration/misc/call_hierarchy_test.go new file mode 100644 index 00000000000..4d16dba2b3c --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/call_hierarchy_test.go @@ -0,0 +1,36 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// Test for golang/go#49125 +func TestCallHierarchy_Issue49125(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- p.go -- +package pkg +` + // TODO(rfindley): this could probably just be a marker test. + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("p.go") + loc := env.RegexpSearch("p.go", "pkg") + + var params protocol.CallHierarchyPrepareParams + params.TextDocument.URI = loc.URI + params.Position = loc.Range.Start + + // Check that this doesn't panic. + env.Editor.Server.PrepareCallHierarchy(env.Ctx, ¶ms) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/codeactions_test.go b/contribs/gnopls/internal/test/integration/misc/codeactions_test.go new file mode 100644 index 00000000000..354921afc01 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/codeactions_test.go @@ -0,0 +1,120 @@ +// 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 misc + +import ( + "fmt" + "slices" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/settings" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// This test exercises the filtering of code actions in generated files. +// Most code actions, being potential edits, are discarded, but +// some (GoTest, GoDoc) are pure queries, and so are allowed. +func TestCodeActionsInGeneratedFiles(t *testing.T) { + const src = ` +-- go.mod -- +module example.com +go 1.19 + +-- src/a.go -- +package a + +func f() { g() } +func g() {} +-- gen/a.go -- +// Code generated by hand; DO NOT EDIT. +package a + +func f() { g() } +func g() {} +` + + Run(t, src, func(t *testing.T, env *Env) { + check := func(filename string, wantKind ...protocol.CodeActionKind) { + env.OpenFile(filename) + loc := env.RegexpSearch(filename, `g\(\)`) + actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger) + if err != nil { + t.Fatal(err) + } + + type kinds = map[protocol.CodeActionKind]bool + got := make(kinds) + for _, act := range actions { + got[act.Kind] = true + } + want := make(kinds) + for _, kind := range wantKind { + want[kind] = true + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("%s: unexpected CodeActionKinds: (-want +got):\n%s", + filename, diff) + t.Log(actions) + } + } + + check("src/a.go", + settings.GoAssembly, + settings.GoDoc, + settings.GoFreeSymbols, + settings.GoplsDocFeatures, + settings.RefactorExtractVariable, + settings.RefactorInlineCall) + check("gen/a.go", + settings.GoAssembly, + settings.GoDoc, + settings.GoFreeSymbols, + settings.GoplsDocFeatures) + }) +} + +// Test refactor.inline.call is not included in automatically triggered code action +// unless users want refactoring. +func TestVSCodeIssue65167(t *testing.T) { + const vim1 = `package main + +func main() { + Func() // range to be selected +} + +func Func() int { return 0 } +` + + Run(t, "", func(t *testing.T, env *Env) { + env.CreateBuffer("main.go", vim1) + for _, trigger := range []protocol.CodeActionTriggerKind{ + protocol.CodeActionUnknownTrigger, + protocol.CodeActionInvoked, + protocol.CodeActionAutomatic, + } { + t.Run(fmt.Sprintf("trigger=%v", trigger), func(t *testing.T) { + for _, selectedRange := range []bool{false, true} { + t.Run(fmt.Sprintf("range=%t", selectedRange), func(t *testing.T) { + loc := env.RegexpSearch("main.go", "Func") + if !selectedRange { + // assume the cursor is placed at the beginning of `Func`, so end==start. + loc.Range.End = loc.Range.Start + } + actions := env.CodeAction(loc, nil, trigger) + want := trigger != protocol.CodeActionAutomatic || selectedRange + if got := slices.ContainsFunc(actions, func(act protocol.CodeAction) bool { + return act.Kind == settings.RefactorInlineCall + }); got != want { + t.Errorf("got refactor.inline.call = %t, want %t", got, want) + } + }) + } + }) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/configuration_test.go b/contribs/gnopls/internal/test/integration/misc/configuration_test.go new file mode 100644 index 00000000000..e96fe5dd806 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/configuration_test.go @@ -0,0 +1,255 @@ +// 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 misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" + + "golang.org/x/tools/internal/testenv" +) + +// Test that enabling and disabling produces the expected results of showing +// and hiding staticcheck analysis results. +func TestChangeConfiguration(t *testing.T) { + // Staticcheck only supports Go versions >= 1.20. + // Note: keep this in sync with TestStaticcheckWarning. Below this version we + // should get an error when setting staticcheck configuration. + testenv.NeedsGo1Point(t, 20) + + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/a.go -- +package a + +import "errors" + +// FooErr should be called ErrFoo (ST1012) +var FooErr = errors.New("foo") +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + cfg := env.Editor.Config() + cfg.Settings = map[string]any{ + "staticcheck": true, + } + env.ChangeConfiguration(cfg) + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "var (FooErr)")), + ) + }) +} + +func TestIdenticalConfiguration(t *testing.T) { + // This test checks that changing configuration does not cause views to be + // recreated if there is no configuration change. + const files = ` +-- a.go -- +package p + +func _() { + var x *int + y := *x + _ = y +} +` + Run(t, files, func(t *testing.T, env *Env) { + // Sanity check: before disabling the nilness analyzer, we should have a + // diagnostic for the nil dereference. + env.OpenFile("a.go") + env.AfterChange( + Diagnostics( + ForFile("a.go"), + WithMessage("nil dereference"), + ), + ) + + // Collect the view ID before changing configuration. + viewID := func() string { + t.Helper() + views := env.Views() + if len(views) != 1 { + t.Fatalf("got %d views, want 1", len(views)) + } + return views[0].ID + } + before := viewID() + + // Now disable the nilness analyzer. + cfg := env.Editor.Config() + cfg.Settings = map[string]any{ + "analyses": map[string]any{ + "nilness": false, + }, + } + + // This should cause the diagnostic to disappear... + env.ChangeConfiguration(cfg) + env.AfterChange( + NoDiagnostics(), + ) + // ...and we should be on the second view. + after := viewID() + if after == before { + t.Errorf("after configuration change, got view %q (same as before), want new view", after) + } + + // Now change configuration again, this time with the same configuration as + // before. We should still have no diagnostics... + env.ChangeConfiguration(cfg) + env.AfterChange( + NoDiagnostics(), + ) + // ...and we should still be on the second view. + if got := viewID(); got != after { + t.Errorf("after second configuration change, got view %q, want %q", got, after) + } + }) +} + +// Test that clients can configure per-workspace configuration, which is +// queried via the scopeURI of a workspace/configuration request. +// (this was broken in golang/go#65519). +func TestWorkspaceConfiguration(t *testing.T) { + const files = ` +-- go.mod -- +module example.com/config + +go 1.18 + +-- a/a.go -- +package a + +import "example.com/config/b" + +func _() { + _ = b.B{2} +} + +-- b/b.go -- +package b + +type B struct { + F int +} +` + + WithOptions( + WorkspaceFolders("a"), + FolderSettings{ + "a": { + "analyses": map[string]bool{ + "composites": false, + }, + }, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange(NoDiagnostics()) + }) +} + +// TestMajorOptionsChange is like TestChangeConfiguration, but modifies an +// an open buffer before making a major (but inconsequential) change that +// causes gopls to recreate the view. +// +// Gopls should not get confused about buffer content when recreating the view. +func TestMajorOptionsChange(t *testing.T) { + testenv.NeedsGo1Point(t, 20) // needs staticcheck + + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/a.go -- +package a + +import "errors" + +var ErrFoo = errors.New("foo") +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + // Introduce a staticcheck diagnostic. It should be detected when we enable + // staticcheck later. + env.RegexpReplace("a/a.go", "ErrFoo", "FooErr") + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + cfg := env.Editor.Config() + // Any change to environment recreates the view, but this should not cause + // gopls to get confused about the content of a/a.go: we should get the + // staticcheck diagnostic below. + cfg.Env = map[string]string{ + "AN_ARBITRARY_VAR": "FOO", + } + cfg.Settings = map[string]interface{}{ + "staticcheck": true, + } + env.ChangeConfiguration(cfg) + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "var (FooErr)")), + ) + }) +} + +func TestStaticcheckWarning(t *testing.T) { + // Note: keep this in sync with TestChangeConfiguration. + testenv.SkipAfterGo1Point(t, 19) + + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/a.go -- +package a + +import "errors" + +// FooErr should be called ErrFoo (ST1012) +var FooErr = errors.New("foo") +` + + WithOptions( + Settings{"staticcheck": true}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + ShownMessage("staticcheck is not supported"), + ) + }) +} + +func TestDeprecatedSettings(t *testing.T) { + WithOptions( + Settings{ + "experimentalUseInvalidMetadata": true, + "experimentalWatchedFileDelay": "1s", + "experimentalWorkspaceModule": true, + "tempModfile": true, + "allowModfileModifications": true, + "allowImplicitNetworkAccess": true, + }, + ).Run(t, "", func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + ShownMessage("experimentalWorkspaceModule"), + ShownMessage("experimentalUseInvalidMetadata"), + ShownMessage("experimentalWatchedFileDelay"), + ShownMessage("tempModfile"), + ShownMessage("allowModfileModifications"), + ShownMessage("allowImplicitNetworkAccess"), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/debugserver_test.go b/contribs/gnopls/internal/test/integration/misc/debugserver_test.go new file mode 100644 index 00000000000..87f892f7443 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/debugserver_test.go @@ -0,0 +1,46 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "net/http" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestStartDebugging(t *testing.T) { + WithOptions( + Modes(Forwarded), + ).Run(t, "", func(t *testing.T, env *Env) { + args, err := command.MarshalArgs(command.DebuggingArgs{}) + if err != nil { + t.Fatal(err) + } + params := &protocol.ExecuteCommandParams{ + Command: command.StartDebugging.String(), + Arguments: args, + } + var result command.DebuggingResult + env.ExecuteCommand(params, &result) + if got, want := len(result.URLs), 2; got != want { + t.Fatalf("got %d urls, want %d; urls: %#v", got, want, result.URLs) + } + for i, u := range result.URLs { + resp, err := http.Get(u) + if err != nil { + t.Errorf("getting url #%d (%q): %v", i, u, err) + continue + } + defer resp.Body.Close() + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("debug server #%d returned HTTP %d, want %d", i, got, want) + } + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/definition_test.go b/contribs/gnopls/internal/test/integration/misc/definition_test.go new file mode 100644 index 00000000000..71f255b52e2 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/definition_test.go @@ -0,0 +1,644 @@ +// 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 misc + +import ( + "fmt" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +const internalDefinition = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println(message) +} +-- const.go -- +package main + +const message = "Hello World." +` + +func TestGoToInternalDefinition(t *testing.T) { + Run(t, internalDefinition, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.GoToDefinition(env.RegexpSearch("main.go", "message")) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "const.go"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + if want := env.RegexpSearch("const.go", "message"); loc != want { + t.Errorf("GoToDefinition: got location %v, want %v", loc, want) + } + }) +} + +const linknameDefinition = ` +-- go.mod -- +module mod.com + +-- upper/upper.go -- +package upper + +import ( + _ "unsafe" + + _ "mod.com/middle" +) + +//go:linkname foo mod.com/lower.bar +func foo() string + +-- middle/middle.go -- +package middle + +import ( + _ "mod.com/lower" +) + +-- lower/lower.s -- + +-- lower/lower.go -- +package lower + +func bar() string { + return "bar as foo" +}` + +func TestGoToLinknameDefinition(t *testing.T) { + Run(t, linknameDefinition, func(t *testing.T, env *Env) { + env.OpenFile("upper/upper.go") + + // Jump from directives 2nd arg. + start := env.RegexpSearch("upper/upper.go", `lower.bar`) + loc := env.GoToDefinition(start) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "lower/lower.go"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + if want := env.RegexpSearch("lower/lower.go", `bar`); loc != want { + t.Errorf("GoToDefinition: got position %v, want %v", loc, want) + } + }) +} + +const linknameDefinitionReverse = ` +-- go.mod -- +module mod.com + +-- upper/upper.s -- + +-- upper/upper.go -- +package upper + +import ( + _ "mod.com/middle" +) + +func foo() string + +-- middle/middle.go -- +package middle + +import ( + _ "mod.com/lower" +) + +-- lower/lower.go -- +package lower + +import _ "unsafe" + +//go:linkname bar mod.com/upper.foo +func bar() string { + return "bar as foo" +}` + +func TestGoToLinknameDefinitionInReverseDep(t *testing.T) { + Run(t, linknameDefinitionReverse, func(t *testing.T, env *Env) { + env.OpenFile("lower/lower.go") + + // Jump from directives 2nd arg. + start := env.RegexpSearch("lower/lower.go", `upper.foo`) + loc := env.GoToDefinition(start) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "upper/upper.go"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + if want := env.RegexpSearch("upper/upper.go", `foo`); loc != want { + t.Errorf("GoToDefinition: got position %v, want %v", loc, want) + } + }) +} + +// The linkname directive connects two packages not related in the import graph. +const linknameDefinitionDisconnected = ` +-- go.mod -- +module mod.com + +-- a/a.go -- +package a + +import ( + _ "unsafe" +) + +//go:linkname foo mod.com/b.bar +func foo() string + +-- b/b.go -- +package b + +func bar() string { + return "bar as foo" +}` + +func TestGoToLinknameDefinitionDisconnected(t *testing.T) { + Run(t, linknameDefinitionDisconnected, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + + // Jump from directives 2nd arg. + start := env.RegexpSearch("a/a.go", `b.bar`) + loc := env.GoToDefinition(start) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "b/b.go"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + if want := env.RegexpSearch("b/b.go", `bar`); loc != want { + t.Errorf("GoToDefinition: got position %v, want %v", loc, want) + } + }) +} + +const stdlibDefinition = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Printf() +}` + +func TestGoToStdlibDefinition_Issue37045(t *testing.T) { + Run(t, stdlibDefinition, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Printf)`)) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if got, want := path.Base(name), "print.go"; got != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + + // Test that we can jump to definition from outside our workspace. + // See golang.org/issues/37045. + newLoc := env.GoToDefinition(loc) + newName := env.Sandbox.Workdir.URIToPath(newLoc.URI) + if newName != name { + t.Errorf("GoToDefinition is not idempotent: got %q, want %q", newName, name) + } + if newLoc != loc { + t.Errorf("GoToDefinition is not idempotent: got %v, want %v", newLoc, loc) + } + }) +} + +func TestUnexportedStdlib_Issue40809(t *testing.T) { + Run(t, stdlibDefinition, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Printf)`)) + name := env.Sandbox.Workdir.URIToPath(loc.URI) + + loc = env.RegexpSearch(name, `:=\s*(newPrinter)\(\)`) + + // Check that we can find references on a reference + refs := env.References(loc) + if len(refs) < 5 { + t.Errorf("expected 5+ references to newPrinter, found: %#v", refs) + } + + loc = env.GoToDefinition(loc) + content, _ := env.Hover(loc) + if !strings.Contains(content.Value, "newPrinter") { + t.Fatal("definition of newPrinter went to the incorrect place") + } + // And on the definition too. + refs = env.References(loc) + if len(refs) < 5 { + t.Errorf("expected 5+ references to newPrinter, found: %#v", refs) + } + }) +} + +// Test the hover on an error's Error function. +// This can't be done via the marker tests because Error is a builtin. +func TestHoverOnError(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { + var err error + err.Error() +}` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + content, _ := env.Hover(env.RegexpSearch("main.go", "Error")) + if content == nil { + t.Fatalf("nil hover content for Error") + } + want := "```go\nfunc (error).Error() string\n```" + if content.Value != want { + t.Fatalf("hover failed:\n%s", compare.Text(want, content.Value)) + } + }) +} + +func TestImportShortcut(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() {} +` + for _, tt := range []struct { + wantLinks int + importShortcut string + }{ + {1, "Link"}, + {0, "Definition"}, + {1, "Both"}, + } { + t.Run(tt.importShortcut, func(t *testing.T) { + WithOptions( + Settings{"importShortcut": tt.importShortcut}, + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.GoToDefinition(env.RegexpSearch("main.go", `"fmt"`)) + if loc == (protocol.Location{}) { + t.Fatalf("expected definition, got none") + } + links := env.DocumentLink("main.go") + if len(links) != tt.wantLinks { + t.Fatalf("expected %v links, got %v", tt.wantLinks, len(links)) + } + }) + }) + } +} + +func TestGoToTypeDefinition_Issue38589(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +type Int int + +type Struct struct{} + +func F1() {} +func F2() (int, error) { return 0, nil } +func F3() (**Struct, bool, *Int, error) { return nil, false, nil, nil } +func F4() (**Struct, bool, *float64, error) { return nil, false, nil, nil } + +func main() {} +` + + for _, tt := range []struct { + re string + wantError bool + wantTypeRe string + }{ + {re: `F1`, wantError: true}, + {re: `F2`, wantError: true}, + {re: `F3`, wantError: true}, + {re: `F4`, wantError: false, wantTypeRe: `type (Struct)`}, + } { + t.Run(tt.re, func(t *testing.T) { + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + + loc, err := env.Editor.TypeDefinition(env.Ctx, env.RegexpSearch("main.go", tt.re)) + if tt.wantError { + if err == nil { + t.Fatal("expected error, got nil") + } + return + } + if err != nil { + t.Fatalf("expected nil error, got %s", err) + } + + typeLoc := env.RegexpSearch("main.go", tt.wantTypeRe) + if loc != typeLoc { + t.Errorf("invalid pos: want %+v, got %+v", typeLoc, loc) + } + }) + }) + } +} + +func TestGoToTypeDefinition_Issue60544(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.19 +-- main.go -- +package main + +func F[T comparable]() {} +` + + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + + _ = env.TypeDefinition(env.RegexpSearch("main.go", "comparable")) // must not panic + }) +} + +// Test for golang/go#47825. +func TestImportTestVariant(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- client/test/role.go -- +package test + +import _ "mod.com/client" + +type RoleSetup struct{} +-- client/client_role_test.go -- +package client_test + +import ( + "testing" + _ "mod.com/client" + ctest "mod.com/client/test" +) + +func TestClient(t *testing.T) { + _ = ctest.RoleSetup{} +} +-- client/client_test.go -- +package client + +import "testing" + +func TestClient(t *testing.T) {} +-- client.go -- +package client +` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("client/client_role_test.go") + env.GoToDefinition(env.RegexpSearch("client/client_role_test.go", "RoleSetup")) + }) +} + +// This test exercises a crashing pattern from golang/go#49223. +func TestGoToCrashingDefinition_Issue49223(t *testing.T) { + Run(t, "", func(t *testing.T, env *Env) { + params := &protocol.DefinitionParams{} + params.TextDocument.URI = protocol.DocumentURI("fugitive%3A///Users/user/src/mm/ems/.git//0/pkg/domain/treasury/provider.go") + params.Position.Character = 18 + params.Position.Line = 0 + env.Editor.Server.Definition(env.Ctx, params) + }) +} + +// TestVendoringInvalidatesMetadata ensures that gopls uses the +// correct metadata even after an external 'go mod vendor' command +// causes packages to move; see issue #55995. +// See also TestImplementationsInVendor, which tests the same fix. +func TestVendoringInvalidatesMetadata(t *testing.T) { + t.Skip("golang/go#56169: file watching does not capture vendor dirs") + + const proxy = ` +-- other.com/b@v1.0.0/go.mod -- +module other.com/b +go 1.14 + +-- other.com/b@v1.0.0/b.go -- +package b +const K = 0 +` + const src = ` +-- go.mod -- +module example.com/a +go 1.14 +require other.com/b v1.0.0 + +-- go.sum -- +other.com/b v1.0.0 h1:1wb3PMGdet5ojzrKl+0iNksRLnOM9Jw+7amBNqmYwqk= +other.com/b v1.0.0/go.mod h1:TgHQFucl04oGT+vrUm/liAzukYHNxCwKNkQZEyn3m9g= + +-- a.go -- +package a +import "other.com/b" +const _ = b.K + +` + WithOptions( + ProxyFiles(proxy), + Modes(Default), // fails in 'experimental' mode + ).Run(t, src, func(t *testing.T, env *Env) { + // Enable to debug go.sum mismatch, which may appear as + // "module lookup disabled by GOPROXY=off", confusingly. + if false { + env.DumpGoSum(".") + } + + env.OpenFile("a.go") + refLoc := env.RegexpSearch("a.go", "K") // find "b.K" reference + + // Initially, b.K is defined in the module cache. + gotLoc := env.GoToDefinition(refLoc) + gotFile := env.Sandbox.Workdir.URIToPath(gotLoc.URI) + wantCache := filepath.ToSlash(env.Sandbox.GOPATH()) + "/pkg/mod/other.com/b@v1.0.0/b.go" + if gotFile != wantCache { + t.Errorf("GoToDefinition, before: got file %q, want %q", gotFile, wantCache) + } + + // Run 'go mod vendor' outside the editor. + env.RunGoCommand("mod", "vendor") + + // Synchronize changes to watched files. + env.Await(env.DoneWithChangeWatchedFiles()) + + // Now, b.K is defined in the vendor tree. + gotLoc = env.GoToDefinition(refLoc) + wantVendor := "vendor/other.com/b/b.go" + if gotFile != wantVendor { + t.Errorf("GoToDefinition, after go mod vendor: got file %q, want %q", gotFile, wantVendor) + } + + // Delete the vendor tree. + if err := os.RemoveAll(env.Sandbox.Workdir.AbsPath("vendor")); err != nil { + t.Fatal(err) + } + // Notify the server of the deletion. + if err := env.Sandbox.Workdir.CheckForFileChanges(env.Ctx); err != nil { + t.Fatal(err) + } + + // Synchronize again. + env.Await(env.DoneWithChangeWatchedFiles()) + + // b.K is once again defined in the module cache. + gotLoc = env.GoToDefinition(gotLoc) + gotFile = env.Sandbox.Workdir.URIToPath(gotLoc.URI) + if gotFile != wantCache { + t.Errorf("GoToDefinition, after rm -rf vendor: got file %q, want %q", gotFile, wantCache) + } + }) +} + +const embedDefinition = ` +-- go.mod -- +module mod.com + +-- main.go -- +package main + +import ( + "embed" +) + +//go:embed *.txt +var foo embed.FS + +func main() {} + +-- skip.sql -- +SKIP + +-- foo.txt -- +FOO + +-- skip.bat -- +SKIP +` + +func TestGoToEmbedDefinition(t *testing.T) { + Run(t, embedDefinition, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + + start := env.RegexpSearch("main.go", `\*.txt`) + loc := env.GoToDefinition(start) + + name := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "foo.txt"; name != want { + t.Errorf("GoToDefinition: got file %q, want %q", name, want) + } + }) +} + +func TestDefinitionOfErrorErrorMethod(t *testing.T) { + const src = `Regression test for a panic in definition of error.Error (of course). +golang/go#64086 + +-- go.mod -- +module mod.com +go 1.18 + +-- a.go -- +package a + +func _(err error) { + _ = err.Error() +} + +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + + start := env.RegexpSearch("a.go", `Error`) + loc := env.GoToDefinition(start) + + if !strings.HasSuffix(string(loc.URI), "builtin.go") { + t.Errorf("GoToDefinition(err.Error) = %#v, want builtin.go", loc) + } + }) +} + +func TestAssemblyDefinition(t *testing.T) { + // This test cannot be expressed as a marker test because + // the expect package ignores markers (@loc) within a .s file. + const src = ` +-- go.mod -- +module mod.com + +-- foo_darwin_arm64.s -- + +// assembly implementation +TEXT ·foo(SB),NOSPLIT,$0 + RET + +-- a.go -- +//go:build darwin && arm64 + +package a + +// Go declaration +func foo(int) int + +var _ = foo(123) // call +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + + locString := func(loc protocol.Location) string { + return fmt.Sprintf("%s:%s", filepath.Base(loc.URI.Path()), loc.Range) + } + + // Definition at the call"foo(123)" takes us to the Go declaration. + callLoc := env.RegexpSearch("a.go", regexp.QuoteMeta("foo(123)")) + declLoc := env.GoToDefinition(callLoc) + if got, want := locString(declLoc), "a.go:5:5-5:8"; got != want { + t.Errorf("Definition(call): got %s, want %s", got, want) + } + + // Definition a second time takes us to the assembly implementation. + implLoc := env.GoToDefinition(declLoc) + if got, want := locString(implLoc), "foo_darwin_arm64.s:2:6-2:9"; got != want { + t.Errorf("Definition(go decl): got %s, want %s", got, want) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/embed_test.go b/contribs/gnopls/internal/test/integration/misc/embed_test.go new file mode 100644 index 00000000000..894cff9f5a3 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/embed_test.go @@ -0,0 +1,41 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestMissingPatternDiagnostic(t *testing.T) { + const files = ` +-- go.mod -- +module example.com +-- x.go -- +package x + +import ( + _ "embed" +) + +// Issue 47436 +func F() {} + +//go:embed NONEXISTENT +var foo string +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("x.go") + env.AfterChange( + Diagnostics( + env.AtRegexp("x.go", `NONEXISTENT`), + WithMessage("no matching files found"), + ), + ) + env.RegexpReplace("x.go", `NONEXISTENT`, "x.go") + env.AfterChange(NoDiagnostics(ForFile("x.go"))) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/extract_test.go b/contribs/gnopls/internal/test/integration/misc/extract_test.go new file mode 100644 index 00000000000..569d53e8bba --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/extract_test.go @@ -0,0 +1,67 @@ +// 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 misc + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" + + "golang.org/x/tools/gopls/internal/protocol" +) + +func TestExtractFunction(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func Foo() int { + a := 5 + return a +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.RegexpSearch("main.go", `a := 5\n.*return a`) + actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger) + if err != nil { + t.Fatal(err) + } + + // Find the extract function code action. + var extractFunc *protocol.CodeAction + for _, action := range actions { + if action.Kind == settings.RefactorExtractFunction { + extractFunc = &action + break + } + } + if extractFunc == nil { + t.Fatal("could not find extract function action") + } + + env.ApplyCodeAction(*extractFunc) + want := `package main + +func Foo() int { + return newFunction() +} + +func newFunction() int { + a := 5 + return a +} +` + if got := env.BufferText("main.go"); got != want { + t.Fatalf("TestFillStruct failed:\n%s", compare.Text(want, got)) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/failures_test.go b/contribs/gnopls/internal/test/integration/misc/failures_test.go new file mode 100644 index 00000000000..81fa17deb9b --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/failures_test.go @@ -0,0 +1,82 @@ +// 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 misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/compare" +) + +// This is a slight variant of TestHoverOnError in definition_test.go +// that includes a line directive, which makes no difference since +// gopls ignores line directives. +func TestHoverFailure(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a.y -- +DWIM(main) + +-- main.go -- +//line a.y:1 +package main + +func main() { + var err error + err.Error() +}` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + content, _ := env.Hover(env.RegexpSearch("main.go", "Error")) + if content == nil { + t.Fatalf("Hover('Error') returned nil") + } + want := "```go\nfunc (error).Error() string\n```" + if content.Value != want { + t.Fatalf("wrong Hover('Error') content:\n%s", compare.Text(want, content.Value)) + } + }) +} + +// This test demonstrates a case where gopls is not at all confused by +// line directives, because it completely ignores them. +func TestFailingDiagnosticClearingOnEdit(t *testing.T) { + // badPackageDup contains a duplicate definition of the 'a' const. + // This is a minor variant of TestDiagnosticClearingOnEdit from + // diagnostics_test.go, with a line directive, which makes no difference. + const badPackageDup = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a.go -- +package consts + +const a = 1 +-- b.go -- +package consts +//line gen.go:5 +const a = 2 +` + + Run(t, badPackageDup, func(t *testing.T, env *Env) { + env.OpenFile("b.go") + env.AfterChange( + Diagnostics(env.AtRegexp("b.go", `a = 2`), WithMessage("a redeclared")), + Diagnostics(env.AtRegexp("a.go", `a = 1`), WithMessage("other declaration")), + ) + + // Fix the error by editing the const name in b.go to `b`. + env.RegexpReplace("b.go", "(a) = 2", "b") + env.AfterChange( + NoDiagnostics(ForFile("a.go")), + NoDiagnostics(ForFile("b.go")), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/fix_test.go b/contribs/gnopls/internal/test/integration/misc/fix_test.go new file mode 100644 index 00000000000..5a01afe2400 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/fix_test.go @@ -0,0 +1,162 @@ +// 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 misc + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/settings" + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// A basic test for fillstruct, now that it uses a command and supports resolve edits. +func TestFillStruct(t *testing.T) { + tc := []struct { + name string + capabilities string + wantCommand bool + }{ + {"default", "{}", true}, + {"no data", `{ "textDocument": {"codeAction": { "resolveSupport": { "properties": ["edit"] } } } }`, true}, + {"resolve support", `{ "textDocument": {"codeAction": { "dataSupport": true, "resolveSupport": { "properties": ["edit"] } } } }`, false}, + } + + const basic = ` +-- go.mod -- +module mod.com + +go 1.14 +-- main.go -- +package main + +type Info struct { + WordCounts map[string]int + Words []string +} + +func Foo() { + _ = Info{} +} +` + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + runner := WithOptions(CapabilitiesJSON([]byte(tt.capabilities))) + + runner.Run(t, basic, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + fixes, err := env.Editor.CodeActions(env.Ctx, env.RegexpSearch("main.go", "Info{}"), nil, settings.RefactorRewriteFillStruct) + if err != nil { + t.Fatal(err) + } + + if len(fixes) != 1 { + t.Fatalf("expected 1 code action, got %v", len(fixes)) + } + if tt.wantCommand { + if fixes[0].Command == nil || fixes[0].Data != nil { + t.Errorf("expected code action to have command not data, got %v", fixes[0]) + } + } else { + if fixes[0].Command != nil || fixes[0].Data == nil { + t.Errorf("expected code action to have command not data, got %v", fixes[0]) + } + } + + // Apply the code action (handles resolving the code action), and check that the result is correct. + if err := env.Editor.RefactorRewrite(env.Ctx, env.RegexpSearch("main.go", "Info{}")); err != nil { + t.Fatal(err) + } + want := `package main + +type Info struct { + WordCounts map[string]int + Words []string +} + +func Foo() { + _ = Info{ + WordCounts: map[string]int{}, + Words: []string{}, + } +} +` + if got := env.BufferText("main.go"); got != want { + t.Fatalf("TestFillStruct failed:\n%s", compare.Text(want, got)) + } + }) + }) + } +} + +func TestFillReturns(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func Foo() error { + return +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + // The error message here changed in 1.18; "return values" covers both forms. + Diagnostics(env.AtRegexp("main.go", `return`), WithMessage("return values")), + ReadDiagnostics("main.go", &d), + ) + var quickFixes []*protocol.CodeAction + for _, act := range env.CodeActionForFile("main.go", d.Diagnostics) { + if act.Kind == protocol.QuickFix { + act := act // remove in go1.22 + quickFixes = append(quickFixes, &act) + } + } + if len(quickFixes) != 1 { + t.Fatalf("expected 1 quick fix, got %d:\n%v", len(quickFixes), quickFixes) + } + env.ApplyQuickFixes("main.go", d.Diagnostics) + env.AfterChange(NoDiagnostics(ForFile("main.go"))) + }) +} + +func TestUnusedParameter_Issue63755(t *testing.T) { + // This test verifies the fix for #63755, where codeActions panicked on parameters + // of functions with no function body. + + // We should not detect parameters as unused for external functions. + + const files = ` +-- go.mod -- +module unused.mod + +go 1.18 + +-- external.go -- +package external + +func External(z int) + +func _() { + External(1) +} + ` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("external.go") + _, err := env.Editor.CodeAction(env.Ctx, env.RegexpSearch("external.go", "z"), nil, protocol.CodeActionUnknownTrigger) + if err != nil { + t.Fatal(err) + } + // yay, no panic + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/formatting_test.go b/contribs/gnopls/internal/test/integration/misc/formatting_test.go new file mode 100644 index 00000000000..1808dbc8791 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/formatting_test.go @@ -0,0 +1,394 @@ +// 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 misc + +import ( + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/internal/testenv" +) + +const unformattedProgram = ` +-- main.go -- +package main +import "fmt" +func main( ) { + fmt.Println("Hello World.") +} +-- main.go.golden -- +package main + +import "fmt" + +func main() { + fmt.Println("Hello World.") +} +` + +func TestFormatting(t *testing.T) { + Run(t, unformattedProgram, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.FormatBuffer("main.go") + got := env.BufferText("main.go") + want := env.ReadWorkspaceFile("main.go.golden") + if got != want { + t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got)) + } + }) +} + +// Tests golang/go#36824. +func TestFormattingOneLine36824(t *testing.T) { + const onelineProgram = ` +-- a.go -- +package main; func f() {} + +-- a.go.formatted -- +package main + +func f() {} +` + Run(t, onelineProgram, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.FormatBuffer("a.go") + got := env.BufferText("a.go") + want := env.ReadWorkspaceFile("a.go.formatted") + if got != want { + t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got)) + } + }) +} + +// Tests golang/go#36824. +func TestFormattingOneLineImports36824(t *testing.T) { + const onelineProgramA = ` +-- a.go -- +package x; func f() {fmt.Println()} + +-- a.go.imported -- +package x + +import "fmt" + +func f() { fmt.Println() } +` + Run(t, onelineProgramA, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.OrganizeImports("a.go") + got := env.BufferText("a.go") + want := env.ReadWorkspaceFile("a.go.imported") + if got != want { + t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got)) + } + }) +} + +func TestFormattingOneLineRmImports36824(t *testing.T) { + const onelineProgramB = ` +-- a.go -- +package x; import "os"; func f() {} + +-- a.go.imported -- +package x + +func f() {} +` + Run(t, onelineProgramB, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.OrganizeImports("a.go") + got := env.BufferText("a.go") + want := env.ReadWorkspaceFile("a.go.imported") + if got != want { + t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got)) + } + }) +} + +const disorganizedProgram = ` +-- main.go -- +package main + +import ( + "fmt" + "errors" +) +func main( ) { + fmt.Println(errors.New("bad")) +} +-- main.go.organized -- +package main + +import ( + "errors" + "fmt" +) +func main( ) { + fmt.Println(errors.New("bad")) +} +-- main.go.formatted -- +package main + +import ( + "errors" + "fmt" +) + +func main() { + fmt.Println(errors.New("bad")) +} +` + +func TestOrganizeImports(t *testing.T) { + Run(t, disorganizedProgram, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.OrganizeImports("main.go") + got := env.BufferText("main.go") + want := env.ReadWorkspaceFile("main.go.organized") + if got != want { + t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got)) + } + }) +} + +func TestFormattingOnSave(t *testing.T) { + Run(t, disorganizedProgram, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.SaveBuffer("main.go") + got := env.BufferText("main.go") + want := env.ReadWorkspaceFile("main.go.formatted") + if got != want { + t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got)) + } + }) +} + +// Tests various possibilities for comments in files with CRLF line endings. +// Import organization in these files has historically been a source of bugs. +func TestCRLFLineEndings(t *testing.T) { + for _, tt := range []struct { + issue, input, want string + }{ + { + issue: "41057", + want: `package main + +/* +Hi description +*/ +func Hi() { +} +`, + }, + { + issue: "42646", + want: `package main + +import ( + "fmt" +) + +/* +func upload(c echo.Context) error { + if err := r.ParseForm(); err != nil { + fmt.Fprintf(w, "ParseForm() err: %v", err) + return + } + fmt.Fprintf(w, "POST request successful") + path_ver := r.FormValue("path_ver") + ukclin_ver := r.FormValue("ukclin_ver") + + fmt.Fprintf(w, "Name = %s\n", path_ver) + fmt.Fprintf(w, "Address = %s\n", ukclin_ver) +} +*/ + +func main() { + const server_port = 8080 + fmt.Printf("port: %d\n", server_port) +} +`, + }, + { + issue: "42923", + want: `package main + +// Line 1. +// aa +type Tree struct { + arr []string +} +`, + }, + { + issue: "47200", + input: `package main + +import "fmt" + +func main() { + math.Sqrt(9) + fmt.Println("hello") +} +`, + want: `package main + +import ( + "fmt" + "math" +) + +func main() { + math.Sqrt(9) + fmt.Println("hello") +} +`, + }, + } { + t.Run(tt.issue, func(t *testing.T) { + Run(t, "-- main.go --", func(t *testing.T, env *Env) { + input := tt.input + if input == "" { + input = tt.want + } + crlf := strings.ReplaceAll(input, "\n", "\r\n") + env.CreateBuffer("main.go", crlf) + env.Await(env.DoneWithOpen()) + env.OrganizeImports("main.go") + got := env.BufferText("main.go") + got = strings.ReplaceAll(got, "\r\n", "\n") // convert everything to LF for simplicity + if tt.want != got { + t.Errorf("unexpected content after save:\n%s", compare.Text(tt.want, got)) + } + }) + }) + } +} + +func TestFormattingOfGeneratedFile_Issue49555(t *testing.T) { + const input = ` +-- main.go -- +// Code generated by generator.go. DO NOT EDIT. + +package main + +import "fmt" + +func main() { + + + + + fmt.Print("hello") +} +` + + Run(t, input, func(t *testing.T, env *Env) { + wantErrSuffix := "file is generated" + + env.OpenFile("main.go") + err := env.Editor.FormatBuffer(env.Ctx, "main.go") + if err == nil { + t.Fatal("expected error, got nil") + } + // Check only the suffix because an error contains a dynamic path to main.go + if !strings.HasSuffix(err.Error(), wantErrSuffix) { + t.Fatalf("unexpected error %q, want suffix %q", err.Error(), wantErrSuffix) + } + }) +} + +func TestGofumptFormatting(t *testing.T) { + testenv.NeedsGo1Point(t, 20) // gofumpt requires go 1.20+ + // Exercise some gofumpt formatting rules: + // - No empty lines following an assignment operator + // - Octal integer literals should use the 0o prefix on modules using Go + // 1.13 and later. Requires LangVersion to be correctly resolved. + // - std imports must be in a separate group at the top. Requires ModulePath + // to be correctly resolved. + const input = ` +-- go.mod -- +module foo + +go 1.17 +-- foo.go -- +package foo + +import ( + "foo/bar" + "fmt" +) + +const perm = 0755 + +func foo() { + foo := + "bar" + fmt.Println(foo, bar.Bar) +} +-- foo.go.formatted -- +package foo + +import ( + "fmt" + + "foo/bar" +) + +const perm = 0o755 + +func foo() { + foo := "bar" + fmt.Println(foo, bar.Bar) +} +-- bar/bar.go -- +package bar + +const Bar = 42 +` + + WithOptions( + Settings{ + "gofumpt": true, + }, + ).Run(t, input, func(t *testing.T, env *Env) { + env.OpenFile("foo.go") + env.FormatBuffer("foo.go") + got := env.BufferText("foo.go") + want := env.ReadWorkspaceFile("foo.go.formatted") + if got != want { + t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got)) + } + }) +} + +func TestGofumpt_Issue61692(t *testing.T) { + testenv.NeedsGo1Point(t, 21) + + const input = ` +-- go.mod -- +module foo + +go 1.21rc3 +-- foo.go -- +package foo + +func _() { + foo := + "bar" +} +` + + WithOptions( + Settings{ + "gofumpt": true, + }, + ).Run(t, input, func(t *testing.T, env *Env) { + env.OpenFile("foo.go") + env.FormatBuffer("foo.go") // golang/go#61692: must not panic + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/generate_test.go b/contribs/gnopls/internal/test/integration/misc/generate_test.go new file mode 100644 index 00000000000..548f3bd5f5e --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/generate_test.go @@ -0,0 +1,105 @@ +// 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. + +// TODO(rfindley): figure out why go generate fails on android builders. + +//go:build !android +// +build !android + +package misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestGenerateProgress(t *testing.T) { + const generatedWorkspace = ` +-- go.mod -- +module fake.test + +go 1.14 +-- generate.go -- +// +build ignore + +package main + +import ( + "os" +) + +func main() { + os.WriteFile("generated.go", []byte("package " + os.Args[1] + "\n\nconst Answer = 21"), 0644) +} + +-- lib1/lib.go -- +package lib1 + +//` + `go:generate go run ../generate.go lib1 + +-- lib2/lib.go -- +package lib2 + +//` + `go:generate go run ../generate.go lib2 + +-- main.go -- +package main + +import ( + "fake.test/lib1" + "fake.test/lib2" +) + +func main() { + println(lib1.Answer + lib2.Answer) +} +` + + Run(t, generatedWorkspace, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", "lib1.(Answer)")), + ) + env.RunGenerate("./lib1") + env.RunGenerate("./lib2") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +func TestGenerateUseNetwork(t *testing.T) { + const proxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.21 +-- example.com@v1.2.3/main.go -- +package main + +func main() { + println("hello world") +} +` + const generatedWorkspace = ` +-- go.mod -- +module fake.test + +go 1.21 +-- main.go -- + +package main + +//go:` + /* hide this string from the go command */ `generate go run example.com@latest + +` + WithOptions(ProxyFiles(proxy)). + Run(t, generatedWorkspace, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + ) + env.RunGenerate("./") + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/highlight_test.go b/contribs/gnopls/internal/test/integration/misc/highlight_test.go new file mode 100644 index 00000000000..9e3dd980464 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/highlight_test.go @@ -0,0 +1,153 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "sort" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestWorkspacePackageHighlight(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { + var A string = "A" + x := "x-" + A + println(A, x) +}` + + Run(t, mod, func(t *testing.T, env *Env) { + const file = "main.go" + env.OpenFile(file) + loc := env.GoToDefinition(env.RegexpSearch(file, `var (A) string`)) + + checkHighlights(env, loc, 3) + }) +} + +func TestStdPackageHighlight_Issue43511(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Printf() +}` + + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + defLoc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt\.(Printf)`)) + file := env.Sandbox.Workdir.URIToPath(defLoc.URI) + loc := env.RegexpSearch(file, `func Printf\((format) string`) + + checkHighlights(env, loc, 2) + }) +} + +func TestThirdPartyPackageHighlight_Issue43511(t *testing.T) { + const proxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/global/global.go -- +package global + +const A = 1 + +func foo() { + _ = A +} + +func bar() int { + return A + A +} +-- example.com@v1.2.3/local/local.go -- +package local + +func foo() int { + const b = 2 + + return b * b * (b+1) + b +}` + + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 + +require example.com v1.2.3 +-- go.sum -- +example.com v1.2.3 h1:WFzrgiQJwEDJNLDUOV1f9qlasQkvzXf2UNLaNIqbWsI= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- main.go -- +package main + +import ( + _ "example.com/global" + _ "example.com/local" +) + +func main() {}` + + WithOptions( + ProxyFiles(proxy), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + + defLoc := env.GoToDefinition(env.RegexpSearch("main.go", `"example.com/global"`)) + file := env.Sandbox.Workdir.URIToPath(defLoc.URI) + loc := env.RegexpSearch(file, `const (A)`) + checkHighlights(env, loc, 4) + + defLoc = env.GoToDefinition(env.RegexpSearch("main.go", `"example.com/local"`)) + file = env.Sandbox.Workdir.URIToPath(defLoc.URI) + loc = env.RegexpSearch(file, `const (b)`) + checkHighlights(env, loc, 5) + }) +} + +func checkHighlights(env *Env, loc protocol.Location, highlightCount int) { + t := env.T + t.Helper() + + highlights := env.DocumentHighlight(loc) + if len(highlights) != highlightCount { + t.Fatalf("expected %v highlight(s), got %v", highlightCount, len(highlights)) + } + + references := env.References(loc) + if len(highlights) != len(references) { + t.Fatalf("number of highlights and references is expected to be equal: %v != %v", len(highlights), len(references)) + } + + sort.Slice(highlights, func(i, j int) bool { + return protocol.CompareRange(highlights[i].Range, highlights[j].Range) < 0 + }) + sort.Slice(references, func(i, j int) bool { + return protocol.CompareRange(references[i].Range, references[j].Range) < 0 + }) + for i := range highlights { + if highlights[i].Range != references[i].Range { + t.Errorf("highlight and reference ranges are expected to be equal: %v != %v", highlights[i].Range, references[i].Range) + } + } +} diff --git a/contribs/gnopls/internal/test/integration/misc/hover_test.go b/contribs/gnopls/internal/test/integration/misc/hover_test.go new file mode 100644 index 00000000000..9c679f02d53 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/hover_test.go @@ -0,0 +1,724 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "fmt" + "regexp" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/internal/testenv" +) + +func TestHoverUnexported(t *testing.T) { + const proxy = ` +-- golang.org/x/structs@v1.0.0/go.mod -- +module golang.org/x/structs + +go 1.12 + +-- golang.org/x/structs@v1.0.0/types.go -- +package structs + +type Mixed struct { + // Exported comment + Exported int + unexported string +} + +func printMixed(m Mixed) { + println(m) +} +` + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 + +require golang.org/x/structs v1.0.0 +-- go.sum -- +golang.org/x/structs v1.0.0 h1:Ito/a7hBYZaNKShFrZKjfBA/SIPvmBrcPCBWPx5QeKk= +golang.org/x/structs v1.0.0/go.mod h1:47gkSIdo5AaQaWJS0upVORsxfEr1LL1MWv9dmYF3iq4= +-- main.go -- +package main + +import "golang.org/x/structs" + +func main() { + var m structs.Mixed + _ = m.Exported +} +` + + // TODO: use a nested workspace folder here. + WithOptions( + ProxyFiles(proxy), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + mixedLoc := env.RegexpSearch("main.go", "Mixed") + got, _ := env.Hover(mixedLoc) + if !strings.Contains(got.Value, "unexported") { + t.Errorf("Workspace hover: missing expected field 'unexported'. Got:\n%q", got.Value) + } + + cacheLoc := env.GoToDefinition(mixedLoc) + cacheFile := env.Sandbox.Workdir.URIToPath(cacheLoc.URI) + argLoc := env.RegexpSearch(cacheFile, "printMixed.*(Mixed)") + got, _ = env.Hover(argLoc) + if !strings.Contains(got.Value, "unexported") { + t.Errorf("Non-workspace hover: missing expected field 'unexported'. Got:\n%q", got.Value) + } + + exportedFieldLoc := env.RegexpSearch("main.go", "Exported") + got, _ = env.Hover(exportedFieldLoc) + if !strings.Contains(got.Value, "comment") { + t.Errorf("Workspace hover: missing comment for field 'Exported'. Got:\n%q", got.Value) + } + }) +} + +func TestHoverIntLiteral(t *testing.T) { + const source = ` +-- main.go -- +package main + +var ( + bigBin = 0b1001001 +) + +var hex = 0xe34e + +func main() { +} +` + Run(t, source, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + hexExpected := "58190" + got, _ := env.Hover(env.RegexpSearch("main.go", "0xe")) + if got != nil && !strings.Contains(got.Value, hexExpected) { + t.Errorf("Hover: missing expected field '%s'. Got:\n%q", hexExpected, got.Value) + } + + binExpected := "73" + got, _ = env.Hover(env.RegexpSearch("main.go", "0b1")) + if got != nil && !strings.Contains(got.Value, binExpected) { + t.Errorf("Hover: missing expected field '%s'. Got:\n%q", binExpected, got.Value) + } + }) +} + +// Tests that hovering does not trigger the panic in golang/go#48249. +func TestPanicInHoverBrokenCode(t *testing.T) { + // Note: this test can not be expressed as a marker test, as it must use + // content without a trailing newline. + const source = ` +-- main.go -- +package main + +type Example struct` + Run(t, source, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", "Example")) + }) +} + +func TestHoverRune_48492(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- main.go -- +package main +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.EditBuffer("main.go", fake.NewEdit(0, 0, 1, 0, "package main\nfunc main() {\nconst x = `\nfoo\n`\n}")) + env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", "foo")) + }) +} + +func TestHoverImport(t *testing.T) { + const packageDoc1 = "Package lib1 hover documentation" + const packageDoc2 = "Package lib2 hover documentation" + tests := []struct { + hoverPackage string + want string + wantError bool + }{ + { + "mod.com/lib1", + packageDoc1, + false, + }, + { + "mod.com/lib2", + packageDoc2, + false, + }, + { + "mod.com/lib3", + "", + false, + }, + { + "mod.com/lib4", + "", + true, + }, + } + source := fmt.Sprintf(` +-- go.mod -- +module mod.com + +go 1.12 +-- lib1/a.go -- +// %s +package lib1 + +const C = 1 + +-- lib1/b.go -- +package lib1 + +const D = 1 + +-- lib2/a.go -- +// %s +package lib2 + +const E = 1 + +-- lib3/a.go -- +package lib3 + +const F = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib1" + "mod.com/lib2" + "mod.com/lib3" + "mod.com/lib4" +) + +func main() { + println("Hello") +} + `, packageDoc1, packageDoc2) + Run(t, source, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + for _, test := range tests { + got, _, err := env.Editor.Hover(env.Ctx, env.RegexpSearch("main.go", test.hoverPackage)) + if test.wantError { + if err == nil { + t.Errorf("Hover(%q) succeeded unexpectedly", test.hoverPackage) + } + } else if !strings.Contains(got.Value, test.want) { + t.Errorf("Hover(%q): got:\n%q\nwant:\n%q", test.hoverPackage, got.Value, test.want) + } + } + }) +} + +// for x/tools/gopls: unhandled named anchor on the hover #57048 +func TestHoverTags(t *testing.T) { + const source = ` +-- go.mod -- +module mod.com + +go 1.19 + +-- lib/a.go -- + +// variety of execution modes. +// +// # Test package setup +// +// The regression test package uses a couple of uncommon patterns to reduce +package lib + +-- a.go -- + package main + import "mod.com/lib" + + const A = 1 + +} +` + Run(t, source, func(t *testing.T, env *Env) { + t.Run("tags", func(t *testing.T) { + env.OpenFile("a.go") + z := env.RegexpSearch("a.go", "lib") + t.Logf("%#v", z) + got, _ := env.Hover(env.RegexpSearch("a.go", "lib")) + if strings.Contains(got.Value, "{#hdr-") { + t.Errorf("Hover: got {#hdr- tag:\n%q", got) + } + }) + }) +} + +// This is a regression test for Go issue #57625. +func TestHoverModMissingModuleStmt(t *testing.T) { + const source = ` +-- go.mod -- +go 1.16 +` + Run(t, source, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.Hover(env.RegexpSearch("go.mod", "go")) // no panic + }) +} + +func TestHoverCompletionMarkdown(t *testing.T) { + testenv.NeedsGo1Point(t, 19) + const source = ` +-- go.mod -- +module mod.com +go 1.19 +-- main.go -- +package main +// Just says [hello]. +// +// [hello]: https://en.wikipedia.org/wiki/Hello +func Hello() string { + Hello() //Here + return "hello" +} +` + Run(t, source, func(t *testing.T, env *Env) { + // Hover, Completion, and SignatureHelp should all produce markdown + // check that the markdown for SignatureHelp and Completion are + // the same, and contained in that for Hover (up to trailing \n) + env.OpenFile("main.go") + loc := env.RegexpSearch("main.go", "func (Hello)") + hover, _ := env.Hover(loc) + hoverContent := hover.Value + + loc = env.RegexpSearch("main.go", "//Here") + loc.Range.Start.Character -= 3 // Hello(_) //Here + completions := env.Completion(loc) + signatures := env.SignatureHelp(loc) + + if len(completions.Items) != 1 { + t.Errorf("got %d completions, expected 1", len(completions.Items)) + } + if len(signatures.Signatures) != 1 { + t.Errorf("got %d signatures, expected 1", len(signatures.Signatures)) + } + item := completions.Items[0].Documentation.Value + var itemContent string + if x, ok := item.(protocol.MarkupContent); !ok || x.Kind != protocol.Markdown { + t.Fatalf("%#v is not markdown", item) + } else { + itemContent = strings.Trim(x.Value, "\n") + } + sig := signatures.Signatures[0].Documentation.Value + var sigContent string + if x, ok := sig.(protocol.MarkupContent); !ok || x.Kind != protocol.Markdown { + t.Fatalf("%#v is not markdown", item) + } else { + sigContent = x.Value + } + if itemContent != sigContent { + t.Errorf("item:%q not sig:%q", itemContent, sigContent) + } + if !strings.Contains(hoverContent, itemContent) { + t.Errorf("hover:%q does not containt sig;%q", hoverContent, sigContent) + } + }) +} + +// Test that the generated markdown contains links for Go references. +// https://github.com/golang/go/issues/58352 +func TestHoverLinks(t *testing.T) { + testenv.NeedsGo1Point(t, 19) + const input = ` +-- go.mod -- +go 1.19 +module mod.com +-- main.go -- +package main +// [fmt] +var A int +// [fmt.Println] +var B int +// [golang.org/x/tools/go/packages.Package.String] +var C int +` + var tests = []struct { + pat string + ans string + }{ + {"A", "fmt"}, + {"B", "fmt#Println"}, + {"C", "golang.org/x/tools/go/packages#Package.String"}, + } + for _, test := range tests { + Run(t, input, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.RegexpSearch("main.go", test.pat) + hover, _ := env.Hover(loc) + hoverContent := hover.Value + want := fmt.Sprintf("%s/%s", "https://pkg.go.dev", test.ans) + if !strings.Contains(hoverContent, want) { + t.Errorf("hover:%q does not contain link %q", hoverContent, want) + } + }) + } +} + +const linknameHover = ` +-- go.mod -- +module mod.com + +-- upper/upper.go -- +package upper + +import ( + _ "unsafe" + _ "mod.com/lower" +) + +//go:linkname foo mod.com/lower.bar +func foo() string + +-- lower/lower.go -- +package lower + +// bar does foo. +func bar() string { + return "foo by bar" +}` + +func TestHoverLinknameDirective(t *testing.T) { + Run(t, linknameHover, func(t *testing.T, env *Env) { + // Jump from directives 2nd arg. + env.OpenFile("upper/upper.go") + from := env.RegexpSearch("upper/upper.go", `lower.bar`) + + hover, _ := env.Hover(from) + content := hover.Value + + expect := "bar does foo" + if !strings.Contains(content, expect) { + t.Errorf("hover: %q does not contain: %q", content, expect) + } + }) +} + +func TestHoverGoWork_Issue60821(t *testing.T) { + const files = ` +-- go.work -- +go 1.19 + +use ( + moda + modb +) +-- moda/go.mod -- + +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + // Neither of the requests below should crash gopls. + _, _, _ = env.Editor.Hover(env.Ctx, env.RegexpSearch("go.work", "moda")) + _, _, _ = env.Editor.Hover(env.Ctx, env.RegexpSearch("go.work", "modb")) + }) +} + +const embedHover = ` +-- go.mod -- +module mod.com +go 1.19 +-- main.go -- +package main + +import "embed" + +//go:embed *.txt +var foo embed.FS + +func main() { +} +-- foo.txt -- +FOO +-- bar.txt -- +BAR +-- baz.txt -- +BAZ +-- other.sql -- +SKIPPED +-- dir.txt/skip.txt -- +SKIPPED +` + +func TestHoverEmbedDirective(t *testing.T) { + testenv.NeedsGo1Point(t, 19) + Run(t, embedHover, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + from := env.RegexpSearch("main.go", `\*.txt`) + + got, _ := env.Hover(from) + if got == nil { + t.Fatalf("hover over //go:embed arg not found") + } + content := got.Value + + wants := []string{"foo.txt", "bar.txt", "baz.txt"} + for _, want := range wants { + if !strings.Contains(content, want) { + t.Errorf("hover: %q does not contain: %q", content, want) + } + } + + // A directory should never be matched, even if it happens to have a matching name. + // Content in subdirectories should not match on only one asterisk. + skips := []string{"other.sql", "dir.txt", "skip.txt"} + for _, skip := range skips { + if strings.Contains(content, skip) { + t.Errorf("hover: %q should not contain: %q", content, skip) + } + } + }) +} + +func TestHoverBrokenImport_Issue60592(t *testing.T) { + const files = ` +-- go.mod -- +module testdata +go 1.18 + +-- p.go -- +package main + +import foo "a" + +func _() { + foo.Print() +} + +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("p.go") + // This request should not crash gopls. + _, _, _ = env.Editor.Hover(env.Ctx, env.RegexpSearch("p.go", "foo[.]")) + }) +} + +func TestHoverInternalLinks(t *testing.T) { + const src = ` +-- main.go -- +package main + +import "errors" + +func main() { + errors.New("oops") +} +` + for _, test := range []struct { + linksInHover any // JSON configuration value + wantRE string // pattern to match the Hover Markdown output + }{ + { + true, // default: use options.LinkTarget domain + regexp.QuoteMeta("[`errors.New` on pkg.go.dev](https://pkg.go.dev/errors#New)"), + }, + { + "gopls", // use gopls' internal viewer + "\\[`errors.New` in gopls doc viewer\\]\\(http://127.0.0.1:[0-9]+/gopls/[^/]+/pkg/errors\\?view=[0-9]+#New\\)", + }, + } { + WithOptions( + Settings{"linksInHover": test.linksInHover}, + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + got, _ := env.Hover(env.RegexpSearch("main.go", "New")) + if m, err := regexp.MatchString(test.wantRE, got.Value); err != nil { + t.Fatalf("bad regexp in test: %v", err) + } else if !m { + t.Fatalf("hover output does not match %q; got:\n\n%s", test.wantRE, got.Value) + } + }) + } +} + +func TestHoverInternalLinksIssue68116(t *testing.T) { + // Links for the internal viewer should not include a module version suffix: + // the package path and the view are an unambiguous key; see #68116. + + const proxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 + +-- example.com@v1.2.3/a/a.go -- +package a + +// F is a function. +func F() +` + + const mod = ` +-- go.mod -- +module main + +go 1.12 + +require example.com v1.2.3 + +-- main.go -- +package main + +import "example.com/a" + +func main() { + a.F() +} +` + WithOptions( + ProxyFiles(proxy), + Settings{"linksInHover": "gopls"}, + WriteGoSum("."), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + got, _ := env.Hover(env.RegexpSearch("main.go", "F")) + const wantRE = "\\[`a.F` in gopls doc viewer\\]\\(http://127.0.0.1:[0-9]+/gopls/[^/]+/pkg/example.com\\?view=[0-9]+#F\\)" // no version + if m, err := regexp.MatchString(wantRE, got.Value); err != nil { + t.Fatalf("bad regexp in test: %v", err) + } else if !m { + t.Fatalf("hover output does not match %q; got:\n\n%s", wantRE, got.Value) + } + }) +} + +func TestHoverBuiltinFile(t *testing.T) { + testenv.NeedsGo1Point(t, 21) // uses 'min' + + // This test verifies that hovering in the builtin file provides the same + // hover content as hovering over a use of a builtin. + + const src = ` +-- p.go -- +package p + +func _() { + const ( + _ = iota + _ = true + ) + var ( + _ any + err error = e{} // avoid nil deref warning + ) + _ = err.Error + println("Hello") + _ = min(1, 2) +} + +// e implements Error, for use above. +type e struct{} +func (e) Error() string +` + + // Test hovering over various builtins with different kinds of declarations. + tests := []string{ + "iota", + "true", + "any", + "error", + "Error", + "println", + "min", + } + + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("p.go") + env.AfterChange(NoDiagnostics()) // avoid accidental compiler errors + + for _, builtin := range tests { + useLocation := env.RegexpSearch("p.go", builtin) + calleeHover, _ := env.Hover(useLocation) + declLocation := env.GoToDefinition(useLocation) + declHover, _ := env.Hover(declLocation) + if diff := cmp.Diff(calleeHover, declHover); diff != "" { + t.Errorf("Hover mismatch (-callee hover +decl hover):\n%s", diff) + } + } + }) +} + +func TestHoverStdlibWithAvailableVersion(t *testing.T) { + const src = ` +-- stdlib.go -- +package stdlib + +import "fmt" +import "context" +import "crypto" +import "regexp" +import "go/doc/comment" + +type testRegexp = *regexp.Regexp + +func _() { + var ctx context.Context + ctx = context.Background() + if ctx.Err(); e == context.Canceled { + fmt.Println("Canceled") + fmt.Printf("%v", crypto.SHA512_224) + } + _ := fmt.Appendf(make([]byte, 100), "world, %d", 23) + + var re = regexp.MustCompile("\n{2,}") + copy := re.Copy() + var testRE testRegexp + testRE.Longest() + + var pr comment.Printer + pr.HeadingID = func(*comment.Heading) string { return "" } +} +` + + testcases := []struct { + symbolRE string // regexp matching symbol to hover over + shouldContain bool + targetString string + }{ + {"Println", false, "go1.0"}, // package-level func + {"Appendf", true, "go1.19"}, // package-level func + {"Background", true, "go1.7"}, // package-level func + {"Canceled", true, "go1.7"}, // package-level var + {"Context", true, "go1.7"}, // package-level type + {"SHA512_224", true, "go1.5"}, // package-level const + {"Copy", true, "go1.6"}, // method + {"Longest", true, "go1.1"}, // method with alias receiver + {"HeadingID", true, "go1.19"}, // field + } + + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("stdlib.go") + for _, tc := range testcases { + content, _ := env.Hover(env.RegexpSearch("stdlib.go", tc.symbolRE)) + if tc.shouldContain && !strings.Contains(content.Value, tc.targetString) { + t.Errorf("Hover(%q) should contain string %s", tc.symbolRE, tc.targetString) + } + if !tc.shouldContain && strings.Contains(content.Value, tc.targetString) { + t.Errorf("Hover(%q) should not contain string %s", tc.symbolRE, tc.targetString) + } + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/import_test.go b/contribs/gnopls/internal/test/integration/misc/import_test.go new file mode 100644 index 00000000000..671d72d27b6 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/import_test.go @@ -0,0 +1,127 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestAddImport(t *testing.T) { + const before = `package main + +import "fmt" + +func main() { + fmt.Println("hello world") +} +` + + const want = `package main + +import ( + "bytes" + "fmt" +) + +func main() { + fmt.Println("hello world") +} +` + + Run(t, "", func(t *testing.T, env *Env) { + env.CreateBuffer("main.go", before) + cmd := command.NewAddImportCommand("Add Import", command.AddImportArgs{ + URI: env.Sandbox.Workdir.URI("main.go"), + ImportPath: "bytes", + }) + env.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: command.AddImport.String(), + Arguments: cmd.Arguments, + }, nil) + got := env.BufferText("main.go") + if got != want { + t.Fatalf("gopls.add_import failed\n%s", compare.Text(want, got)) + } + }) +} + +func TestListImports(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- foo.go -- +package foo +const C = 1 +-- import_strings_test.go -- +package foo +import ( + x "strings" + "testing" +) + +func TestFoo(t *testing.T) {} +-- import_testing_test.go -- +package foo + +import "testing" + +func TestFoo2(t *testing.T) {} +` + tests := []struct { + filename string + want command.ListImportsResult + }{ + { + filename: "import_strings_test.go", + want: command.ListImportsResult{ + Imports: []command.FileImport{ + {Name: "x", Path: "strings"}, + {Path: "testing"}, + }, + PackageImports: []command.PackageImport{ + {Path: "strings"}, + {Path: "testing"}, + }, + }, + }, + { + filename: "import_testing_test.go", + want: command.ListImportsResult{ + Imports: []command.FileImport{ + {Path: "testing"}, + }, + PackageImports: []command.PackageImport{ + {Path: "strings"}, + {Path: "testing"}, + }, + }, + }, + } + + Run(t, files, func(t *testing.T, env *Env) { + for _, tt := range tests { + cmd := command.NewListImportsCommand("List Imports", command.URIArg{ + URI: env.Sandbox.Workdir.URI(tt.filename), + }) + var result command.ListImportsResult + env.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: command.ListImports.String(), + Arguments: cmd.Arguments, + }, &result) + if diff := cmp.Diff(tt.want, result); diff != "" { + t.Errorf("unexpected list imports result for %q (-want +got):\n%s", tt.filename, diff) + } + } + + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/imports_test.go b/contribs/gnopls/internal/test/integration/misc/imports_test.go new file mode 100644 index 00000000000..15fbd87e0fd --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/imports_test.go @@ -0,0 +1,388 @@ +// 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 misc + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" + + "golang.org/x/tools/gopls/internal/protocol" +) + +// Tests golang/go#38815. +func TestIssue38815(t *testing.T) { + const needs = ` +-- go.mod -- +module foo + +go 1.12 +-- a.go -- +package main +func f() {} +` + const ntest = `package main +func TestZ(t *testing.T) { + f() +} +` + const want = `package main + +import "testing" + +func TestZ(t *testing.T) { + f() +} +` + + // it was returning + // "package main\nimport \"testing\"\npackage main..." + Run(t, needs, func(t *testing.T, env *Env) { + env.CreateBuffer("a_test.go", ntest) + env.SaveBuffer("a_test.go") + got := env.BufferText("a_test.go") + if want != got { + t.Errorf("got\n%q, wanted\n%q", got, want) + } + }) +} + +func TestIssue59124(t *testing.T) { + const stuff = ` +-- go.mod -- +module foo +go 1.19 +-- a.go -- +//line foo.y:102 +package main + +import "fmt" + +//this comment is necessary for failure +func a() { + fmt.Println("hello") +} +` + Run(t, stuff, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + was := env.BufferText("a.go") + env.AfterChange(NoDiagnostics()) + env.OrganizeImports("a.go") + is := env.BufferText("a.go") + if diff := compare.Text(was, is); diff != "" { + t.Errorf("unexpected diff after organizeImports:\n%s", diff) + } + }) +} + +func TestIssue66407(t *testing.T) { + const files = ` +-- go.mod -- +module foo +go 1.21 +-- a.go -- +package foo + +func f(x float64) float64 { + return x + rand.Float64() +} +-- b.go -- +package foo + +func g() { + _ = rand.Int63() +} +` + WithOptions(Modes(Default)). + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + was := env.BufferText("a.go") + env.OrganizeImports("a.go") + is := env.BufferText("a.go") + // expect complaint that module is before 1.22 + env.AfterChange(Diagnostics(ForFile("a.go"))) + diff := compare.Text(was, is) + // check that it found the 'right' rand + if !strings.Contains(diff, `import "math/rand/v2"`) { + t.Errorf("expected rand/v2, got %q", diff) + } + env.OpenFile("b.go") + was = env.BufferText("b.go") + env.OrganizeImports("b.go") + // a.go still has its module problem but b.go is fine + env.AfterChange(Diagnostics(ForFile("a.go")), + NoDiagnostics(ForFile("b.go"))) + is = env.BufferText("b.go") + diff = compare.Text(was, is) + if !strings.Contains(diff, `import "math/rand"`) { + t.Errorf("expected math/rand, got %q", diff) + } + }) +} + +func TestVim1(t *testing.T) { + const vim1 = `package main + +import "fmt" + +var foo = 1 +var bar = 2 + +func main() { + fmt.Printf("This is a test %v\n", foo) + fmt.Printf("This is another test %v\n", foo) + fmt.Printf("This is also a test %v\n", foo) +} +` + + // The file remains unchanged, but if there any quick fixes + // are returned, they confuse vim (according to CL 233117). + // Therefore check for no QuickFix CodeActions. + Run(t, "", func(t *testing.T, env *Env) { + env.CreateBuffer("main.go", vim1) + env.OrganizeImports("main.go") + + // Assert no quick fixes. + for _, act := range env.CodeActionForFile("main.go", nil) { + if act.Kind == protocol.QuickFix { + t.Errorf("unexpected quick fix action: %#v", act) + } + } + if t.Failed() { + got := env.BufferText("main.go") + if got == vim1 { + t.Errorf("no changes") + } else { + t.Errorf("got\n%q", got) + t.Errorf("was\n%q", vim1) + } + } + }) +} + +func TestVim2(t *testing.T) { + const vim2 = `package main + +import ( + "fmt" + + "example.com/blah" + + "rubbish.com/useless" +) + +func main() { + fmt.Println(blah.Name, useless.Name) +} +` + + Run(t, "", func(t *testing.T, env *Env) { + env.CreateBuffer("main.go", vim2) + env.OrganizeImports("main.go") + + // Assert no quick fixes. + for _, act := range env.CodeActionForFile("main.go", nil) { + if act.Kind == protocol.QuickFix { + t.Errorf("unexpected quick-fix action: %#v", act) + } + } + }) +} + +const exampleProxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/x/x.go -- +package x + +const X = 1 +-- example.com@v1.2.3/y/y.go -- +package y + +const Y = 2 +` + +func TestGOMODCACHE(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 + +require example.com v1.2.3 +-- main.go -- +package main + +import "example.com/x" + +var _, _ = x.X, y.Y +` + modcache := t.TempDir() + defer cleanModCache(t, modcache) // see doc comment of cleanModCache + + WithOptions( + EnvVars{"GOMODCACHE": modcache}, + ProxyFiles(exampleProxy), + WriteGoSum("."), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange(Diagnostics(env.AtRegexp("main.go", `y.Y`))) + env.SaveBuffer("main.go") + env.AfterChange(NoDiagnostics(ForFile("main.go"))) + loc := env.GoToDefinition(env.RegexpSearch("main.go", `y.(Y)`)) + path := env.Sandbox.Workdir.URIToPath(loc.URI) + if !strings.HasPrefix(path, filepath.ToSlash(modcache)) { + t.Errorf("found module dependency outside of GOMODCACHE: got %v, wanted subdir of %v", path, filepath.ToSlash(modcache)) + } + }) +} + +func TestRelativeReplace(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com/a + +go 1.20 + +require ( + example.com v1.2.3 +) + +replace example.com/b => ../b +-- main.go -- +package main + +import "example.com/x" + +var _, _ = x.X, y.Y +` + modcache := t.TempDir() + base := filepath.Base(modcache) + defer cleanModCache(t, modcache) // see doc comment of cleanModCache + + // Construct a very unclean module cache whose length exceeds the length of + // the clean directory path, to reproduce the crash in golang/go#67156 + const sep = string(filepath.Separator) + modcache += strings.Repeat(sep+".."+sep+base, 10) + + WithOptions( + EnvVars{"GOMODCACHE": modcache}, + ProxyFiles(exampleProxy), + WriteGoSum("."), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange(Diagnostics(env.AtRegexp("main.go", `y.Y`))) + env.SaveBuffer("main.go") + env.AfterChange(NoDiagnostics(ForFile("main.go"))) + }) +} + +// TODO(rfindley): this is only necessary as the module cache cleaning of the +// sandbox does not respect GOMODCACHE set via EnvVars. We should fix this, but +// that is probably part of a larger refactoring of the sandbox that I'm not +// inclined to undertake. +func cleanModCache(t *testing.T, modcache string) { + cmd := exec.Command("go", "clean", "-modcache") + cmd.Env = append(os.Environ(), "GOMODCACHE="+modcache) + if err := cmd.Run(); err != nil { + t.Errorf("cleaning modcache: %v", err) + } +} + +// Tests golang/go#40685. +func TestAcceptImportsQuickFixTestVariant(t *testing.T) { + const pkg = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/a.go -- +package a + +import ( + "fmt" +) + +func _() { + fmt.Println("") + os.Stat("") +} +-- a/a_test.go -- +package a + +import ( + "os" + "testing" +) + +func TestA(t *testing.T) { + os.Stat("") +} +` + Run(t, pkg, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "os.Stat")), + ReadDiagnostics("a/a.go", &d), + ) + env.ApplyQuickFixes("a/a.go", d.Diagnostics) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + }) +} + +// Test for golang/go#52784 +func TestGoWorkImports(t *testing.T) { + const pkg = ` +-- go.work -- +go 1.19 + +use ( + ./caller + ./mod +) +-- caller/go.mod -- +module caller.com + +go 1.18 + +require mod.com v0.0.0 + +replace mod.com => ../mod +-- caller/caller.go -- +package main + +func main() { + a.Test() +} +-- mod/go.mod -- +module mod.com + +go 1.18 +-- mod/a/a.go -- +package a + +func Test() { +} +` + Run(t, pkg, func(t *testing.T, env *Env) { + env.OpenFile("caller/caller.go") + env.AfterChange(Diagnostics(env.AtRegexp("caller/caller.go", "a.Test"))) + + // Saving caller.go should trigger goimports, which should find a.Test in + // the mod.com module, thanks to the go.work file. + env.SaveBuffer("caller/caller.go") + env.AfterChange(NoDiagnostics(ForFile("caller/caller.go"))) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/link_test.go b/contribs/gnopls/internal/test/integration/misc/link_test.go new file mode 100644 index 00000000000..53b0f0818f3 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/link_test.go @@ -0,0 +1,94 @@ +// 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 misc + +import ( + "strings" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestHoverAndDocumentLink(t *testing.T) { + const program = ` +-- go.mod -- +module mod.test + +go 1.12 + +require import.test v1.2.3 +-- main.go -- +package main + +import "import.test/pkg" + +func main() { + // Issue 43990: this is not a link that most users can open from an LSP + // client: mongodb://not.a.link.com + println(pkg.Hello) +}` + + const proxy = ` +-- import.test@v1.2.3/go.mod -- +module import.test + +go 1.12 +-- import.test@v1.2.3/pkg/const.go -- +package pkg + +const Hello = "Hello" +` + WithOptions( + ProxyFiles(proxy), + WriteGoSum("."), + ).Run(t, program, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.OpenFile("go.mod") + + modLink := "https://pkg.go.dev/mod/import.test@v1.2.3" + pkgLink := "https://pkg.go.dev/import.test@v1.2.3/pkg" + + // First, check that we get the expected links via hover and documentLink. + content, _ := env.Hover(env.RegexpSearch("main.go", "pkg.Hello")) + if content == nil || !strings.Contains(content.Value, pkgLink) { + t.Errorf("hover: got %v in main.go, want contains %q", content, pkgLink) + } + content, _ = env.Hover(env.RegexpSearch("go.mod", "import.test")) + if content == nil || !strings.Contains(content.Value, pkgLink) { + t.Errorf("hover: got %v in go.mod, want contains %q", content, pkgLink) + } + links := env.DocumentLink("main.go") + if len(links) != 1 || *links[0].Target != pkgLink { + t.Errorf("documentLink: got links %+v for main.go, want one link with target %q", links, pkgLink) + } + links = env.DocumentLink("go.mod") + if len(links) != 1 || *links[0].Target != modLink { + t.Errorf("documentLink: got links %+v for go.mod, want one link with target %q", links, modLink) + } + + // Then change the environment to make these links private. + cfg := env.Editor.Config() + cfg.Env = map[string]string{"GOPRIVATE": "import.test"} + env.ChangeConfiguration(cfg) + + // Finally, verify that the links are gone. + content, _ = env.Hover(env.RegexpSearch("main.go", "pkg.Hello")) + if content == nil || strings.Contains(content.Value, pkgLink) { + t.Errorf("hover: got %v in main.go, want non-empty hover without %q", content, pkgLink) + } + content, _ = env.Hover(env.RegexpSearch("go.mod", "import.test")) + if content == nil || strings.Contains(content.Value, modLink) { + t.Errorf("hover: got %v in go.mod, want contains %q", content, modLink) + } + links = env.DocumentLink("main.go") + if len(links) != 0 { + t.Errorf("documentLink: got %d document links for main.go, want 0\nlinks: %v", len(links), links) + } + links = env.DocumentLink("go.mod") + if len(links) != 0 { + t.Errorf("documentLink: got %d document links for go.mod, want 0\nlinks: %v", len(links), links) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/misc_test.go b/contribs/gnopls/internal/test/integration/misc/misc_test.go new file mode 100644 index 00000000000..ca0125894c8 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/misc_test.go @@ -0,0 +1,72 @@ +// 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 misc + +import ( + "os" + "strings" + "testing" + + "golang.org/x/telemetry/counter/countertest" + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/util/bug" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + tmp, err := os.MkdirTemp("", "gopls-misc-test-counters") + if err != nil { + panic(err) + } + countertest.Open(tmp) + code := Main(m) + os.RemoveAll(tmp) // golang/go#68243: ignore error; cleanup fails on Windows + os.Exit(code) +} + +// TestDocumentURIFix ensures that a DocumentURI supplied by the +// client is subject to the "fixing" operation documented at +// [protocol.DocumentURI.UnmarshalText]. The details of the fixing are +// tested in the protocol package; here we aim to test only that it +// occurs at all. +func TestDocumentURIFix(t *testing.T) { + const mod = ` +-- go.mod -- +module testdata +go 1.18 + +-- a.go -- +package a + +const K = 1 +` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + loc := env.RegexpSearch("a.go", "K") + path := strings.TrimPrefix(string(loc.URI), "file://") // (absolute) + + check := func() { + t.Helper() + t.Logf("URI = %s", loc.URI) + content, _ := env.Hover(loc) // must succeed + if content == nil || !strings.Contains(content.Value, "const K") { + t.Errorf("wrong content: %#v", content) + } + } + + // Regular URI (e.g. file://$TMPDIR/TestDocumentURIFix/default/work/a.go) + check() + + // URL-encoded path (e.g. contains %2F instead of last /) + loc.URI = protocol.DocumentURI("file://" + strings.Replace(path, "/a.go", "%2Fa.go", 1)) + check() + + // We intentionally do not test further cases (e.g. + // file:// without a third slash) as it would quickly + // get bogged down in irrelevant details of the + // fake editor's own handling of URIs. + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/multiple_adhoc_test.go b/contribs/gnopls/internal/test/integration/misc/multiple_adhoc_test.go new file mode 100644 index 00000000000..aba7e987968 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/multiple_adhoc_test.go @@ -0,0 +1,44 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestMultipleAdHocPackages(t *testing.T) { + Run(t, ` +-- a/a.go -- +package main + +import "fmt" + +func main() { + fmt.Println("") +} +-- a/b.go -- +package main + +import "fmt" + +func main() () { + fmt.Println("") +} +`, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + if list := env.Completion(env.RegexpSearch("a/a.go", "Println")); list == nil || len(list.Items) == 0 { + t.Fatal("expected completions, got none") + } + env.OpenFile("a/b.go") + if list := env.Completion(env.RegexpSearch("a/b.go", "Println")); list == nil || len(list.Items) == 0 { + t.Fatal("expected completions, got none") + } + if list := env.Completion(env.RegexpSearch("a/a.go", "Println")); list == nil || len(list.Items) == 0 { + t.Fatal("expected completions, got none") + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/prompt_test.go b/contribs/gnopls/internal/test/integration/misc/prompt_test.go new file mode 100644 index 00000000000..9e87bd9ba36 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/prompt_test.go @@ -0,0 +1,488 @@ +// 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 misc + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strconv" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "golang.org/x/telemetry/counter" + "golang.org/x/telemetry/counter/countertest" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/server" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// Test prompt file in old and new formats are handled as expected. +func TestTelemetryPrompt_PromptFile(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() {} +` + + defaultTelemetryStartTime := "1714521600" // 2024-05-01 + defaultToken := "7" + samplesPerMille := "500" + + testCases := []struct { + name, in, want string + wantPrompt bool + }{ + { + name: "empty", + in: "", + want: "failed 1 1714521600 7", + wantPrompt: true, + }, + { + name: "v0.15-format/invalid", + in: "pending", + want: "failed 1 1714521600 7", + wantPrompt: true, + }, + { + name: "v0.15-format/pPending", + in: "pending 1", + want: "failed 2 1714521600 7", + wantPrompt: true, + }, + { + name: "v0.15-format/pPending", + in: "failed 1", + want: "failed 2 1714521600 7", + wantPrompt: true, + }, + { + name: "v0.15-format/pYes", + in: "yes 1", + want: "yes 1", // untouched since short-circuited + }, + { + name: "v0.16-format/pNotReady", + in: "- 0 1714521600 1000", + want: "- 0 1714521600 1000", + }, + { + name: "v0.16-format/pPending", + in: "pending 1 1714521600 1", + want: "failed 2 1714521600 1", + wantPrompt: true, + }, + { + name: "v0.16-format/pFailed", + in: "failed 2 1714521600 1", + want: "failed 3 1714521600 1", + wantPrompt: true, + }, + { + name: "v0.16-format/invalid", + in: "xxx 0 12345 678", + want: "failed 1 1714521600 7", + wantPrompt: true, + }, + { + name: "v0.16-format/extra", + in: "- 0 1714521600 1000 7777 xxx", + want: "- 0 1714521600 1000", // drop extra + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + modeFile := filepath.Join(t.TempDir(), "mode") + goplsConfigDir := t.TempDir() + promptDir := filepath.Join(goplsConfigDir, "prompt") + promptFile := filepath.Join(promptDir, "telemetry") + + if err := os.MkdirAll(promptDir, 0777); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(promptFile, []byte(tc.in), 0666); err != nil { + t.Fatal(err) + } + WithOptions( + Modes(Default), // no need to run this in all modes + EnvVars{ + server.GoplsConfigDirEnvvar: goplsConfigDir, + server.FakeTelemetryModefileEnvvar: modeFile, + server.GoTelemetryGoplsClientStartTimeEnvvar: defaultTelemetryStartTime, + server.GoTelemetryGoplsClientTokenEnvvar: defaultToken, + server.FakeSamplesPerMille: samplesPerMille, + }, + Settings{ + "telemetryPrompt": true, + }, + ).Run(t, src, func(t *testing.T, env *Env) { + expectation := ShownMessageRequest(".*Would you like to enable Go telemetry?") + if !tc.wantPrompt { + expectation = Not(expectation) + } + env.OnceMet( + CompletedWork(server.TelemetryPromptWorkTitle, 1, true), + expectation, + ) + if got, err := os.ReadFile(promptFile); err != nil || string(got) != tc.want { + t.Fatalf("(%q) -> (%q, %v), want %q", tc.in, got, err, tc.want) + } + }) + }) + } +} + +// Test that gopls prompts for telemetry only when it is supposed to. +func TestTelemetryPrompt_Conditions_Mode(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { +} +` + + for _, enabled := range []bool{true, false} { + t.Run(fmt.Sprintf("telemetryPrompt=%v", enabled), func(t *testing.T) { + for _, initialMode := range []string{"", "local", "off", "on"} { + t.Run(fmt.Sprintf("initial_mode=%s", initialMode), func(t *testing.T) { + modeFile := filepath.Join(t.TempDir(), "mode") + if initialMode != "" { + if err := os.WriteFile(modeFile, []byte(initialMode), 0666); err != nil { + t.Fatal(err) + } + } + telemetryStartTime := time.Now().Add(-8 * 24 * time.Hour) // telemetry started a while ago + WithOptions( + Modes(Default), // no need to run this in all modes + EnvVars{ + server.GoplsConfigDirEnvvar: t.TempDir(), + server.FakeTelemetryModefileEnvvar: modeFile, + server.GoTelemetryGoplsClientStartTimeEnvvar: strconv.FormatInt(telemetryStartTime.Unix(), 10), + server.GoTelemetryGoplsClientTokenEnvvar: "1", // always sample because samplingPerMille >= 1. + }, + Settings{ + "telemetryPrompt": enabled, + }, + ).Run(t, src, func(t *testing.T, env *Env) { + wantPrompt := enabled && (initialMode == "" || initialMode == "local") + expectation := ShownMessageRequest(".*Would you like to enable Go telemetry?") + if !wantPrompt { + expectation = Not(expectation) + } + env.OnceMet( + CompletedWork(server.TelemetryPromptWorkTitle, 1, true), + expectation, + ) + }) + }) + } + }) + } +} + +// Test that gopls prompts for telemetry only after instrumenting for a while, and +// when the token is within the range for sample. +func TestTelemetryPrompt_Conditions_StartTimeAndSamplingToken(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { +} +` + day := 24 * time.Hour + samplesPerMille := 50 + for _, token := range []int{1, samplesPerMille, samplesPerMille + 1} { + wantSampled := token <= samplesPerMille + t.Run(fmt.Sprintf("to_sample=%t/tokens=%d", wantSampled, token), func(t *testing.T) { + for _, elapsed := range []time.Duration{8 * day, 1 * day, 0} { + telemetryStartTimeOrEmpty := "" + if elapsed > 0 { + telemetryStartTimeOrEmpty = strconv.FormatInt(time.Now().Add(-elapsed).Unix(), 10) + } + t.Run(fmt.Sprintf("elapsed=%s", elapsed), func(t *testing.T) { + modeFile := filepath.Join(t.TempDir(), "mode") + WithOptions( + Modes(Default), // no need to run this in all modes + EnvVars{ + server.GoplsConfigDirEnvvar: t.TempDir(), + server.FakeTelemetryModefileEnvvar: modeFile, + server.GoTelemetryGoplsClientStartTimeEnvvar: telemetryStartTimeOrEmpty, + server.GoTelemetryGoplsClientTokenEnvvar: strconv.Itoa(token), + server.FakeSamplesPerMille: strconv.Itoa(samplesPerMille), // want token ∈ [1, 50] is always sampled. + }, + Settings{ + "telemetryPrompt": true, + }, + ).Run(t, src, func(t *testing.T, env *Env) { + wantPrompt := wantSampled && elapsed > 7*day + expectation := ShownMessageRequest(".*Would you like to enable Go telemetry?") + if !wantPrompt { + expectation = Not(expectation) + } + env.OnceMet( + CompletedWork(server.TelemetryPromptWorkTitle, 1, true), + expectation, + ) + }) + }) + } + }) + } +} + +// Test that responding to the telemetry prompt results in the expected state. +func TestTelemetryPrompt_Response(t *testing.T) { + if !countertest.SupportedPlatform { + t.Skip("requires counter support") + } + + const src = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { +} +` + + var ( + acceptanceCounter = "gopls/telemetryprompt/accepted" + declinedCounter = "gopls/telemetryprompt/declined" + attempt1Counter = "gopls/telemetryprompt/attempts:1" + allCounters = []string{acceptanceCounter, declinedCounter, attempt1Counter} + ) + + // We must increment counters in order for the initial reads below to + // succeed. + // + // TODO(rfindley): ReadCounter should simply return 0 for uninitialized + // counters. + for _, name := range allCounters { + counter.New(name).Inc() + } + + readCounts := func(t *testing.T) map[string]uint64 { + t.Helper() + counts := make(map[string]uint64) + for _, name := range allCounters { + count, err := countertest.ReadCounter(counter.New(name)) + if err != nil { + t.Fatalf("ReadCounter(%q) failed: %v", name, err) + } + counts[name] = count + } + return counts + } + + tests := []struct { + name string // subtest name + response string // response to choose for the telemetry dialog + wantMode string // resulting telemetry mode + wantMsg string // substring contained in the follow-up popup (if empty, no popup is expected) + wantInc uint64 // expected 'prompt accepted' counter increment + wantCounts map[string]uint64 + }{ + {"yes", server.TelemetryYes, "on", "uploading is now enabled", 1, map[string]uint64{ + acceptanceCounter: 1, + declinedCounter: 0, + attempt1Counter: 1, + }}, + {"no", server.TelemetryNo, "", "", 0, map[string]uint64{ + acceptanceCounter: 0, + declinedCounter: 1, + attempt1Counter: 1, + }}, + {"empty", "", "", "", 0, map[string]uint64{ + acceptanceCounter: 0, + declinedCounter: 0, + attempt1Counter: 1, + }}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + initialCounts := readCounts(t) + modeFile := filepath.Join(t.TempDir(), "mode") + telemetryStartTime := time.Now().Add(-8 * 24 * time.Hour) + msgRE := regexp.MustCompile(".*Would you like to enable Go telemetry?") + respond := func(m *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) { + if msgRE.MatchString(m.Message) { + for _, item := range m.Actions { + if item.Title == test.response { + return &item, nil + } + } + if test.response != "" { + t.Errorf("action item %q not found", test.response) + } + } + return nil, nil + } + WithOptions( + Modes(Default), // no need to run this in all modes + EnvVars{ + server.GoplsConfigDirEnvvar: t.TempDir(), + server.FakeTelemetryModefileEnvvar: modeFile, + server.GoTelemetryGoplsClientStartTimeEnvvar: strconv.FormatInt(telemetryStartTime.Unix(), 10), + server.GoTelemetryGoplsClientTokenEnvvar: "1", // always sample because samplingPerMille >= 1. + }, + Settings{ + "telemetryPrompt": true, + }, + MessageResponder(respond), + ).Run(t, src, func(t *testing.T, env *Env) { + var postConditions []Expectation + if test.wantMsg != "" { + postConditions = append(postConditions, ShownMessage(test.wantMsg)) + } + env.OnceMet( + CompletedWork(server.TelemetryPromptWorkTitle, 1, true), + postConditions..., + ) + gotMode := "" + if contents, err := os.ReadFile(modeFile); err == nil { + gotMode = string(contents) + } else if !os.IsNotExist(err) { + t.Fatal(err) + } + if gotMode != test.wantMode { + t.Errorf("after prompt, mode=%s, want %s", gotMode, test.wantMode) + } + + // We increment the acceptance counter when checking the prompt file + // before prompting, so start a second, transient gopls session and + // verify that the acceptance counter is incremented. + env2 := ConnectGoplsEnv(t, env.Ctx, env.Sandbox, env.Editor.Config(), env.Server) + env2.Await(CompletedWork(server.TelemetryPromptWorkTitle, 1, true)) + if err := env2.Editor.Close(env2.Ctx); err != nil { + t.Errorf("closing second editor: %v", err) + } + + gotCounts := readCounts(t) + for k := range gotCounts { + gotCounts[k] -= initialCounts[k] + } + if diff := cmp.Diff(test.wantCounts, gotCounts); diff != "" { + t.Errorf("counter mismatch (-want +got):\n%s", diff) + } + }) + }) + } +} + +// Test that we stop asking about telemetry after the user ignores the question +// 5 times. +func TestTelemetryPrompt_GivingUp(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { +} +` + + // For this test, we want to share state across gopls sessions. + modeFile := filepath.Join(t.TempDir(), "mode") + telemetryStartTime := time.Now().Add(-30 * 24 * time.Hour) + configDir := t.TempDir() + + const maxPrompts = 5 // internal prompt limit defined by gopls + + for i := 0; i < maxPrompts+1; i++ { + WithOptions( + Modes(Default), // no need to run this in all modes + EnvVars{ + server.GoplsConfigDirEnvvar: configDir, + server.FakeTelemetryModefileEnvvar: modeFile, + server.GoTelemetryGoplsClientStartTimeEnvvar: strconv.FormatInt(telemetryStartTime.Unix(), 10), + server.GoTelemetryGoplsClientTokenEnvvar: "1", // always sample because samplingPerMille >= 1. + }, + Settings{ + "telemetryPrompt": true, + }, + ).Run(t, src, func(t *testing.T, env *Env) { + wantPrompt := i < maxPrompts + expectation := ShownMessageRequest(".*Would you like to enable Go telemetry?") + if !wantPrompt { + expectation = Not(expectation) + } + env.OnceMet( + CompletedWork(server.TelemetryPromptWorkTitle, 1, true), + expectation, + ) + }) + } +} + +// Test that gopls prompts for telemetry only when it is supposed to. +func TestTelemetryPrompt_Conditions_Command(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { +} +` + modeFile := filepath.Join(t.TempDir(), "mode") + telemetryStartTime := time.Now().Add(-8 * 24 * time.Hour) + WithOptions( + Modes(Default), // no need to run this in all modes + EnvVars{ + server.GoplsConfigDirEnvvar: t.TempDir(), + server.FakeTelemetryModefileEnvvar: modeFile, + server.GoTelemetryGoplsClientStartTimeEnvvar: fmt.Sprintf("%d", telemetryStartTime.Unix()), + server.GoTelemetryGoplsClientTokenEnvvar: "1", // always sample because samplingPerMille >= 1. + }, + Settings{ + // off because we are testing + // if we can trigger the prompt with command. + "telemetryPrompt": false, + }, + ).Run(t, src, func(t *testing.T, env *Env) { + cmd := command.NewMaybePromptForTelemetryCommand("prompt") + var err error + env.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: cmd.Command, + }, &err) + if err != nil { + t.Fatal(err) + } + expectation := ShownMessageRequest(".*Would you like to enable Go telemetry?") + env.OnceMet( + CompletedWork(server.TelemetryPromptWorkTitle, 2, true), + expectation, + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/references_test.go b/contribs/gnopls/internal/test/integration/misc/references_test.go new file mode 100644 index 00000000000..73e4fffe3b8 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/references_test.go @@ -0,0 +1,574 @@ +// 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 misc + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/integration" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestStdlibReferences(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Print() +} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.GoToDefinition(env.RegexpSearch("main.go", `fmt.(Print)`)) + refs, err := env.Editor.References(env.Ctx, loc) + if err != nil { + t.Fatal(err) + } + if len(refs) != 2 { + // TODO(adonovan): make this assertion less maintainer-hostile. + t.Fatalf("got %v reference(s), want 2", len(refs)) + } + // The first reference is guaranteed to be the definition. + if got, want := refs[1].URI, env.Sandbox.Workdir.URI("main.go"); got != want { + t.Errorf("found reference in %v, wanted %v", got, want) + } + }) +} + +// This is a regression test for golang/go#48400 (a panic). +func TestReferencesOnErrorMethod(t *testing.T) { + // Ideally this would actually return the correct answer, + // instead of merely failing gracefully. + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +type t interface { + error +} + +type s struct{} + +func (*s) Error() string { + return "" +} + +func _() { + var s s + _ = s.Error() +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.GoToDefinition(env.RegexpSearch("main.go", `Error`)) + refs, err := env.Editor.References(env.Ctx, loc) + if err != nil { + t.Fatalf("references on (*s).Error failed: %v", err) + } + // TODO(adonovan): this test is crying out for marker support in integration tests. + var buf strings.Builder + for _, ref := range refs { + fmt.Fprintf(&buf, "%s %s\n", env.Sandbox.Workdir.URIToPath(ref.URI), ref.Range) + } + got := buf.String() + want := "main.go 8:10-8:15\n" + // (*s).Error decl + "main.go 14:7-14:12\n" // s.Error() call + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("unexpected references on (*s).Error (-want +got):\n%s", diff) + } + }) +} + +func TestDefsRefsBuiltins(t *testing.T) { + // TODO(adonovan): add unsafe.{SliceData,String,StringData} in later go versions. + const files = ` +-- go.mod -- +module example.com +go 1.16 + +-- a.go -- +package a + +import "unsafe" + +const _ = iota +var _ error +var _ int +var _ = append() +var _ = unsafe.Pointer(nil) +var _ = unsafe.Add(nil, nil) +var _ = unsafe.Sizeof(0) +var _ = unsafe.Alignof(0) +var _ = unsafe.Slice(nil, 0) +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + for _, name := range strings.Fields( + "iota error int nil append iota Pointer Sizeof Alignof Add Slice") { + loc := env.RegexpSearch("a.go", `\b`+name+`\b`) + + // definition -> {builtin,unsafe}.go + def := env.GoToDefinition(loc) + if (!strings.HasSuffix(string(def.URI), "builtin.go") && + !strings.HasSuffix(string(def.URI), "unsafe.go")) || + def.Range.Start.Line == 0 { + t.Errorf("definition(%q) = %v, want {builtin,unsafe}.go", + name, def) + } + + // "references to (builtin "Foo"|unsafe.Foo) are not supported" + _, err := env.Editor.References(env.Ctx, loc) + gotErr := fmt.Sprint(err) + if !strings.Contains(gotErr, "references to") || + !strings.Contains(gotErr, "not supported") || + !strings.Contains(gotErr, name) { + t.Errorf("references(%q) error: got %q, want %q", + name, gotErr, "references to ... are not supported") + } + } + }) +} + +func TestPackageReferences(t *testing.T) { + tests := []struct { + packageName string + wantRefCount int + wantFiles []string + }{ + { + "lib1", + 3, + []string{ + "main.go", + "lib1/a.go", + "lib1/b.go", + }, + }, + { + "lib2", + 2, + []string{ + "main.go", + "lib2/a.go", + }, + }, + } + + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib1/a.go -- +package lib1 + +const A = 1 + +-- lib1/b.go -- +package lib1 + +const B = 1 + +-- lib2/a.go -- +package lib2 + +const C = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib1" + "mod.com/lib2" +) + +func main() { + println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + for _, test := range tests { + file := fmt.Sprintf("%s/a.go", test.packageName) + env.OpenFile(file) + loc := env.RegexpSearch(file, test.packageName) + refs := env.References(loc) + if len(refs) != test.wantRefCount { + // TODO(adonovan): make this assertion less maintainer-hostile. + t.Fatalf("got %v reference(s), want %d", len(refs), test.wantRefCount) + } + var refURIs []string + for _, ref := range refs { + refURIs = append(refURIs, string(ref.URI)) + } + for _, base := range test.wantFiles { + hasBase := false + for _, ref := range refURIs { + if strings.HasSuffix(ref, base) { + hasBase = true + break + } + } + if !hasBase { + t.Fatalf("got [%v], want reference ends with \"%v\"", strings.Join(refURIs, ","), base) + } + } + } + }) +} + +// Test for golang/go#43144. +// +// Verify that we search for references and implementations in intermediate +// test variants. +func TestReferencesInTestVariants(t *testing.T) { + const files = ` +-- go.mod -- +module foo.mod + +go 1.12 +-- foo/foo.go -- +package foo + +import "foo.mod/bar" + +const Foo = 42 + +type T int +type InterfaceM interface{ M() } +type InterfaceF interface{ F() } + +func _() { + _ = bar.Blah +} + +-- foo/foo_test.go -- +package foo + +type Fer struct{} +func (Fer) F() {} + +-- bar/bar.go -- +package bar + +var Blah = 123 + +-- bar/bar_test.go -- +package bar + +type Mer struct{} +func (Mer) M() {} + +func TestBar() { + _ = Blah +} +-- bar/bar_x_test.go -- +package bar_test + +import ( + "foo.mod/bar" + "foo.mod/foo" +) + +type Mer struct{} +func (Mer) M() {} + +func _() { + _ = bar.Blah + _ = foo.Foo +} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("foo/foo.go") + + refTests := []struct { + re string + wantRefs []string + }{ + // Blah is referenced: + // - inside the foo.mod/bar (ordinary) package + // - inside the foo.mod/bar [foo.mod/bar.test] test variant package + // - from the foo.mod/bar_test [foo.mod/bar.test] x_test package + // - from the foo.mod/foo package + {"Blah", []string{"bar/bar.go:3", "bar/bar_test.go:7", "bar/bar_x_test.go:12", "foo/foo.go:12"}}, + + // Foo is referenced in bar_x_test.go via the intermediate test variant + // foo.mod/foo [foo.mod/bar.test]. + {"Foo", []string{"bar/bar_x_test.go:13", "foo/foo.go:5"}}, + } + + for _, test := range refTests { + loc := env.RegexpSearch("foo/foo.go", test.re) + refs := env.References(loc) + + got := fileLocations(env, refs) + if diff := cmp.Diff(test.wantRefs, got); diff != "" { + t.Errorf("References(%q) returned unexpected diff (-want +got):\n%s", test.re, diff) + } + } + + implTests := []struct { + re string + wantImpls []string + }{ + // InterfaceM is implemented both in foo.mod/bar [foo.mod/bar.test] (which + // doesn't import foo), and in foo.mod/bar_test [foo.mod/bar.test], which + // imports the test variant of foo. + {"InterfaceM", []string{"bar/bar_test.go:3", "bar/bar_x_test.go:8"}}, + + // A search within the ordinary package to should find implementations + // (Fer) within the augmented test package. + {"InterfaceF", []string{"foo/foo_test.go:3"}}, + } + + for _, test := range implTests { + loc := env.RegexpSearch("foo/foo.go", test.re) + impls := env.Implementations(loc) + + got := fileLocations(env, impls) + if diff := cmp.Diff(test.wantImpls, got); diff != "" { + t.Errorf("Implementations(%q) returned unexpected diff (-want +got):\n%s", test.re, diff) + } + } + }) +} + +// This is a regression test for Issue #56169, in which interface +// implementations in vendored modules were not found. The actual fix +// was the same as for #55995; see TestVendoringInvalidatesMetadata. +func TestImplementationsInVendor(t *testing.T) { + const proxy = ` +-- other.com/b@v1.0.0/go.mod -- +module other.com/b +go 1.14 + +-- other.com/b@v1.0.0/b.go -- +package b +type B int +func (B) F() {} +` + const src = ` +-- go.mod -- +module example.com/a +go 1.14 +require other.com/b v1.0.0 + +-- go.sum -- +other.com/b v1.0.0 h1:9WyCKS+BLAMRQM0CegP6zqP2beP+ShTbPaARpNY31II= +other.com/b v1.0.0/go.mod h1:TgHQFucl04oGT+vrUm/liAzukYHNxCwKNkQZEyn3m9g= + +-- a.go -- +package a +import "other.com/b" +type I interface { F() } +var _ b.B + +` + WithOptions( + ProxyFiles(proxy), + Modes(Default), // fails in 'experimental' mode + ).Run(t, src, func(t *testing.T, env *Env) { + // Enable to debug go.sum mismatch, which may appear as + // "module lookup disabled by GOPROXY=off", confusingly. + if false { + env.DumpGoSum(".") + } + + checkVendor := func(locs []protocol.Location, wantVendor bool) { + if len(locs) != 1 { + t.Errorf("got %d locations, want 1", len(locs)) + } else if strings.Contains(string(locs[0].URI), "/vendor/") != wantVendor { + t.Errorf("got location %s, wantVendor=%t", locs[0], wantVendor) + } + } + + env.OpenFile("a.go") + refLoc := env.RegexpSearch("a.go", "I") // find "I" reference + + // Initially, a.I has one implementation b.B in + // the module cache, not the vendor tree. + checkVendor(env.Implementations(refLoc), false) + + // Run 'go mod vendor' outside the editor. + env.RunGoCommand("mod", "vendor") + + // Synchronize changes to watched files. + env.Await(env.DoneWithChangeWatchedFiles()) + + // Now, b.B is found in the vendor tree. + checkVendor(env.Implementations(refLoc), true) + + // Delete the vendor tree. + if err := os.RemoveAll(env.Sandbox.Workdir.AbsPath("vendor")); err != nil { + t.Fatal(err) + } + // Notify the server of the deletion. + if err := env.Sandbox.Workdir.CheckForFileChanges(env.Ctx); err != nil { + t.Fatal(err) + } + + // Synchronize again. + env.Await(env.DoneWithChangeWatchedFiles()) + + // b.B is once again defined in the module cache. + checkVendor(env.Implementations(refLoc), false) + }) +} + +// This test can't be expressed as a marker test because the marker +// test framework opens all files (which is a bit of a hack), creating +// a <command-line-arguments> package for packages that otherwise +// wouldn't be found from the go.work file. +func TestReferencesFromWorkspacePackages59674(t *testing.T) { + const src = ` +-- a/go.mod -- +module example.com/a +go 1.12 + +-- b/go.mod -- +module example.com/b +go 1.12 + +-- c/go.mod -- +module example.com/c +go 1.12 + +-- lib/go.mod -- +module example.com/lib +go 1.12 + +-- go.work -- +use ./a +use ./b +// don't use ./c +use ./lib + +-- a/a.go -- +package a + +import "example.com/lib" + +var _ = lib.F // query here + +-- b/b.go -- +package b + +import "example.com/lib" + +var _ = lib.F // also found by references + +-- c/c.go -- +package c + +import "example.com/lib" + +var _ = lib.F // this reference should not be reported + +-- lib/lib.go -- +package lib + +func F() {} // declaration +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + refLoc := env.RegexpSearch("a/a.go", "F") + got := fileLocations(env, env.References(refLoc)) + want := []string{"a/a.go:5", "b/b.go:5", "lib/lib.go:3"} + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("incorrect References (-want +got):\n%s", diff) + } + }) +} + +// Test an 'implementation' query on a type that implements 'error'. +// (Unfortunately builtin locations cannot be expressed using @loc +// in the marker test framework.) +func TestImplementationsOfError(t *testing.T) { + const src = ` +-- go.mod -- +module example.com +go 1.12 + +-- a.go -- +package a + +type Error2 interface { + Error() string +} + +type MyError int +func (MyError) Error() string { return "" } + +type MyErrorPtr int +func (*MyErrorPtr) Error() string { return "" } +` + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + + for _, test := range []struct { + re string + want []string + }{ + // error type + {"Error2", []string{"a.go:10", "a.go:7", "std:builtin/builtin.go"}}, + {"MyError", []string{"a.go:3", "std:builtin/builtin.go"}}, + {"MyErrorPtr", []string{"a.go:3", "std:builtin/builtin.go"}}, + // error.Error method + {"(Error).. string", []string{"a.go:11", "a.go:8", "std:builtin/builtin.go"}}, + {"MyError. (Error)", []string{"a.go:4", "std:builtin/builtin.go"}}, + {"MyErrorPtr. (Error)", []string{"a.go:4", "std:builtin/builtin.go"}}, + } { + matchLoc := env.RegexpSearch("a.go", test.re) + impls := env.Implementations(matchLoc) + got := fileLocations(env, impls) + if !reflect.DeepEqual(got, test.want) { + t.Errorf("Implementations(%q) = %q, want %q", + test.re, got, test.want) + } + } + }) +} + +// fileLocations returns a new sorted array of the +// relative file name and line number of each location. +// Duplicates are not removed. +// Standard library filenames are abstracted for robustness. +func fileLocations(env *integration.Env, locs []protocol.Location) []string { + got := make([]string, 0, len(locs)) + for _, loc := range locs { + path := env.Sandbox.Workdir.URIToPath(loc.URI) // (slashified) + if i := strings.LastIndex(path, "/src/"); i >= 0 && filepath.IsAbs(path) { + // Absolute path with "src" segment: assume it's in GOROOT. + // Strip directory and don't add line/column since they are fragile. + path = "std:" + path[i+len("/src/"):] + } else { + path = fmt.Sprintf("%s:%d", path, loc.Range.Start.Line+1) + } + got = append(got, path) + } + sort.Strings(got) + return got +} diff --git a/contribs/gnopls/internal/test/integration/misc/rename_test.go b/contribs/gnopls/internal/test/integration/misc/rename_test.go new file mode 100644 index 00000000000..e3116e1dd2a --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/rename_test.go @@ -0,0 +1,921 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "fmt" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestPrepareRenameMainPackage(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- main.go -- +package main + +import ( + "fmt" +) + +func main() { + fmt.Println(1) +} +` + const wantErr = "can't rename package \"main\"" + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + loc := env.RegexpSearch("main.go", `main`) + params := &protocol.PrepareRenameParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + _, err := env.Editor.Server.PrepareRename(env.Ctx, params) + if err == nil { + t.Errorf("missing can't rename package main error from PrepareRename") + } + + if err.Error() != wantErr { + t.Errorf("got %v, want %v", err.Error(), wantErr) + } + }) +} + +// Test case for golang/go#56227 +func TestRenameWithUnsafeSlice(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- p.go -- +package p + +import "unsafe" + +type T struct{} + +func (T) M() {} + +func _() { + x := [3]int{1, 2, 3} + ptr := unsafe.Pointer(&x) + _ = unsafe.Slice((*int)(ptr), 3) +} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("p.go") + env.Rename(env.RegexpSearch("p.go", "M"), "N") // must not panic + }) +} + +func TestPrepareRenameWithNoPackageDeclaration(t *testing.T) { + const files = ` +go 1.14 +-- lib/a.go -- +import "fmt" + +const A = 1 + +func bar() { + fmt.Println("Bar") +} + +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + err := env.Editor.Rename(env.Ctx, env.RegexpSearch("lib/a.go", "fmt"), "fmt1") + if got, want := fmt.Sprint(err), "no identifier found"; got != want { + t.Errorf("Rename: got error %v, want %v", got, want) + } + }) +} + +func TestPrepareRenameFailWithUnknownModule(t *testing.T) { + const files = ` +go 1.14 +-- lib/a.go -- +package lib + +const A = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib" +) + +func main() { + println("Hello") +} +` + const wantErr = "can't rename package: missing module information for package" + Run(t, files, func(t *testing.T, env *Env) { + loc := env.RegexpSearch("lib/a.go", "lib") + params := &protocol.PrepareRenameParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc), + } + _, err := env.Editor.Server.PrepareRename(env.Ctx, params) + if err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("missing cannot rename packages with unknown module from PrepareRename") + } + }) +} + +// This test ensures that each import of a renamed package +// is also renamed if it would otherwise create a conflict. +func TestRenamePackageWithConflicts(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +const A = 1 + +-- lib/nested/a.go -- +package nested + +const B = 1 + +-- lib/x/a.go -- +package nested1 + +const C = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib" + "mod.com/lib/nested" + nested1 "mod.com/lib/x" +) + +func main() { + println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested") + + // Check if the new package name exists. + env.RegexpSearch("nested/a.go", "package nested") + env.RegexpSearch("main.go", `nested2 "mod.com/nested"`) + env.RegexpSearch("main.go", "mod.com/nested/nested") + env.RegexpSearch("main.go", `nested1 "mod.com/nested/x"`) + }) +} + +func TestRenamePackageWithAlias(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +const A = 1 + +-- lib/nested/a.go -- +package nested + +const B = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib" + lib1 "mod.com/lib/nested" +) + +func main() { + println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested") + + // Check if the new package name exists. + env.RegexpSearch("nested/a.go", "package nested") + env.RegexpSearch("main.go", "mod.com/nested") + env.RegexpSearch("main.go", `lib1 "mod.com/nested/nested"`) + }) +} + +func TestRenamePackageWithDifferentDirectoryPath(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +const A = 1 + +-- lib/nested/a.go -- +package foo + +const B = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib" + foo "mod.com/lib/nested" +) + +func main() { + println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested") + + // Check if the new package name exists. + env.RegexpSearch("nested/a.go", "package nested") + env.RegexpSearch("main.go", "mod.com/nested") + env.RegexpSearch("main.go", `foo "mod.com/nested/nested"`) + }) +} + +func TestRenamePackage(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +const A = 1 + +-- lib/b.go -- +package lib + +const B = 1 + +-- lib/nested/a.go -- +package nested + +const C = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib" + "mod.com/lib/nested" +) + +func main() { + println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "lib"), "lib1") + + // Check if the new package name exists. + env.RegexpSearch("lib1/a.go", "package lib1") + env.RegexpSearch("lib1/b.go", "package lib1") + env.RegexpSearch("main.go", "mod.com/lib1") + env.RegexpSearch("main.go", "mod.com/lib1/nested") + }) +} + +// Test for golang/go#47564. +func TestRenameInTestVariant(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- stringutil/stringutil.go -- +package stringutil + +func Identity(s string) string { + return s +} +-- stringutil/stringutil_test.go -- +package stringutil + +func TestIdentity(t *testing.T) { + if got := Identity("foo"); got != "foo" { + t.Errorf("bad") + } +} +-- main.go -- +package main + +import ( + "fmt" + + "mod.com/stringutil" +) + +func main() { + fmt.Println(stringutil.Identity("hello world")) +} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.Rename(env.RegexpSearch("main.go", `stringutil\.(Identity)`), "Identityx") + env.OpenFile("stringutil/stringutil_test.go") + text := env.BufferText("stringutil/stringutil_test.go") + if !strings.Contains(text, "Identityx") { + t.Errorf("stringutil/stringutil_test.go: missing expected token `Identityx` after rename:\n%s", text) + } + }) +} + +// This is a test that rename operation initiated by the editor function as expected. +func TestRenameFileFromEditor(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.16 +-- a/a.go -- +package a + +const X = 1 +-- a/x.go -- +package a + +const X = 2 +-- b/b.go -- +package b +` + + Run(t, files, func(t *testing.T, env *Env) { + // Rename files and verify that diagnostics are affected accordingly. + + // Initially, we should have diagnostics on both X's, for their duplicate declaration. + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("a/a.go", "X")), + Diagnostics(env.AtRegexp("a/x.go", "X")), + ) + + // Moving x.go should make the diagnostic go away. + env.RenameFile("a/x.go", "b/x.go") + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), // no more duplicate declarations + Diagnostics(env.AtRegexp("b/b.go", "package")), // as package names mismatch + ) + + // Renaming should also work on open buffers. + env.OpenFile("b/x.go") + + // Moving x.go back to a/ should cause the diagnostics to reappear. + env.RenameFile("b/x.go", "a/x.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "X")), + Diagnostics(env.AtRegexp("a/x.go", "X")), + ) + + // Renaming the entire directory should move both the open and closed file. + env.RenameFile("a", "x") + env.AfterChange( + Diagnostics(env.AtRegexp("x/a.go", "X")), + Diagnostics(env.AtRegexp("x/x.go", "X")), + ) + + // As a sanity check, verify that x/x.go is open. + if text := env.BufferText("x/x.go"); text == "" { + t.Fatal("got empty buffer for x/x.go") + } + }) +} + +func TestRenamePackage_Tests(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +const A = 1 + +-- lib/b.go -- +package lib + +const B = 1 + +-- lib/a_test.go -- +package lib_test + +import ( + "mod.com/lib" + "fmt +) + +const C = 1 + +-- lib/b_test.go -- +package lib + +import ( + "fmt +) + +const D = 1 + +-- lib/nested/a.go -- +package nested + +const D = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib" + "mod.com/lib/nested" +) + +func main() { + println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "lib"), "lib1") + + // Check if the new package name exists. + env.RegexpSearch("lib1/a.go", "package lib1") + env.RegexpSearch("lib1/b.go", "package lib1") + env.RegexpSearch("main.go", "mod.com/lib1") + env.RegexpSearch("main.go", "mod.com/lib1/nested") + + // Check if the test package is renamed + env.RegexpSearch("lib1/a_test.go", "package lib1_test") + env.RegexpSearch("lib1/b_test.go", "package lib1") + }) +} + +func TestRenamePackage_NestedModule(t *testing.T) { + const files = ` +-- go.work -- +go 1.18 +use ( + . + ./foo/bar + ./foo/baz +) + +-- go.mod -- +module mod.com + +go 1.18 + +require ( + mod.com/foo/bar v0.0.0 +) + +replace ( + mod.com/foo/bar => ./foo/bar + mod.com/foo/baz => ./foo/baz +) +-- foo/foo.go -- +package foo + +import "fmt" + +func Bar() { + fmt.Println("In foo before renamed to foox.") +} + +-- foo/bar/go.mod -- +module mod.com/foo/bar + +-- foo/bar/bar.go -- +package bar + +const Msg = "Hi from package bar" + +-- foo/baz/go.mod -- +module mod.com/foo/baz + +-- foo/baz/baz.go -- +package baz + +const Msg = "Hi from package baz" + +-- main.go -- +package main + +import ( + "fmt" + "mod.com/foo/bar" + "mod.com/foo/baz" + "mod.com/foo" +) + +func main() { + foo.Bar() + fmt.Println(bar.Msg) + fmt.Println(baz.Msg) +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("foo/foo.go") + env.Rename(env.RegexpSearch("foo/foo.go", "foo"), "foox") + + env.RegexpSearch("foox/foo.go", "package foox") + env.OpenFile("foox/bar/bar.go") + env.OpenFile("foox/bar/go.mod") + + env.RegexpSearch("main.go", "mod.com/foo/bar") + env.RegexpSearch("main.go", "mod.com/foox") + env.RegexpSearch("main.go", "foox.Bar()") + + env.RegexpSearch("go.mod", "./foox/bar") + env.RegexpSearch("go.mod", "./foox/baz") + }) +} + +func TestRenamePackage_DuplicateImport(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +const A = 1 + +-- lib/nested/a.go -- +package nested + +const B = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib" + lib1 "mod.com/lib" + lib2 "mod.com/lib/nested" +) + +func main() { + println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested") + + // Check if the new package name exists. + env.RegexpSearch("nested/a.go", "package nested") + env.RegexpSearch("main.go", "mod.com/nested") + env.RegexpSearch("main.go", `lib1 "mod.com/nested"`) + env.RegexpSearch("main.go", `lib2 "mod.com/nested/nested"`) + }) +} + +func TestRenamePackage_DuplicateBlankImport(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +const A = 1 + +-- lib/nested/a.go -- +package nested + +const B = 1 + +-- main.go -- +package main + +import ( + "mod.com/lib" + _ "mod.com/lib" + lib1 "mod.com/lib/nested" +) + +func main() { + println("Hello") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "lib"), "nested") + + // Check if the new package name exists. + env.RegexpSearch("nested/a.go", "package nested") + env.RegexpSearch("main.go", "mod.com/nested") + env.RegexpSearch("main.go", `_ "mod.com/nested"`) + env.RegexpSearch("main.go", `lib1 "mod.com/nested/nested"`) + }) +} + +func TestRenamePackage_TestVariant(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- foo/foo.go -- +package foo + +const Foo = 42 +-- bar/bar.go -- +package bar + +import "mod.com/foo" + +const Bar = foo.Foo +-- bar/bar_test.go -- +package bar + +import "mod.com/foo" + +const Baz = foo.Foo +-- testdata/bar/bar.go -- +package bar + +import "mod.com/foox" + +const Bar = foox.Foo +-- testdata/bar/bar_test.go -- +package bar + +import "mod.com/foox" + +const Baz = foox.Foo +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("foo/foo.go") + env.Rename(env.RegexpSearch("foo/foo.go", "package (foo)"), "foox") + + checkTestdata(t, env) + }) +} + +func TestRenamePackage_IntermediateTestVariant(t *testing.T) { + // In this test set up, we have the following import edges: + // bar_test -> baz -> foo -> bar + // bar_test -> foo -> bar + // bar_test -> bar + // + // As a consequence, bar_x_test.go is in the reverse closure of both + // `foo [bar.test]` and `baz [bar.test]`. This test confirms that we don't + // produce duplicate edits in this case. + const files = ` +-- go.mod -- +module foo.mod + +go 1.12 +-- foo/foo.go -- +package foo + +import "foo.mod/bar" + +const Foo = 42 + +const _ = bar.Bar +-- baz/baz.go -- +package baz + +import "foo.mod/foo" + +const Baz = foo.Foo +-- bar/bar.go -- +package bar + +var Bar = 123 +-- bar/bar_test.go -- +package bar + +const _ = Bar +-- bar/bar_x_test.go -- +package bar_test + +import ( + "foo.mod/bar" + "foo.mod/baz" + "foo.mod/foo" +) + +const _ = bar.Bar + baz.Baz + foo.Foo +-- testdata/foox/foo.go -- +package foox + +import "foo.mod/bar" + +const Foo = 42 + +const _ = bar.Bar +-- testdata/baz/baz.go -- +package baz + +import "foo.mod/foox" + +const Baz = foox.Foo +-- testdata/bar/bar_x_test.go -- +package bar_test + +import ( + "foo.mod/bar" + "foo.mod/baz" + "foo.mod/foox" +) + +const _ = bar.Bar + baz.Baz + foox.Foo +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("foo/foo.go") + env.Rename(env.RegexpSearch("foo/foo.go", "package (foo)"), "foox") + + checkTestdata(t, env) + }) +} + +func TestRenamePackage_Nesting(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +import "mod.com/lib/nested" + +const A = 1 + nested.B +-- lib/nested/a.go -- +package nested + +const B = 1 +-- other/other.go -- +package other + +import ( + "mod.com/lib" + "mod.com/lib/nested" +) + +const C = lib.A + nested.B +-- testdata/libx/a.go -- +package libx + +import "mod.com/libx/nested" + +const A = 1 + nested.B +-- testdata/other/other.go -- +package other + +import ( + "mod.com/libx" + "mod.com/libx/nested" +) + +const C = libx.A + nested.B +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "package (lib)"), "libx") + + checkTestdata(t, env) + }) +} + +func TestRenamePackage_InvalidName(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +import "mod.com/lib/nested" + +const A = 1 + nested.B +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/a.go") + loc := env.RegexpSearch("lib/a.go", "package (lib)") + + for _, badName := range []string{"$$$", "lib_test"} { + if err := env.Editor.Rename(env.Ctx, loc, badName); err == nil { + t.Errorf("Rename(lib, libx) succeeded, want non-nil error") + } + } + }) +} + +func TestRenamePackage_InternalPackage(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- lib/a.go -- +package lib + +import ( + "fmt" + "mod.com/lib/internal/x" +) + +const A = 1 + +func print() { + fmt.Println(x.B) +} + +-- lib/internal/x/a.go -- +package x + +const B = 1 + +-- main.go -- +package main + +import "mod.com/lib" + +func main() { + lib.print() +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("lib/internal/x/a.go") + env.Rename(env.RegexpSearch("lib/internal/x/a.go", "x"), "utils") + + // Check if the new package name exists. + env.RegexpSearch("lib/a.go", "mod.com/lib/internal/utils") + env.RegexpSearch("lib/a.go", "utils.B") + + // Check if the test package is renamed + env.RegexpSearch("lib/internal/utils/a.go", "package utils") + + env.OpenFile("lib/a.go") + env.Rename(env.RegexpSearch("lib/a.go", "lib"), "lib1") + + // Check if the new package name exists. + env.RegexpSearch("lib1/a.go", "package lib1") + env.RegexpSearch("lib1/a.go", "mod.com/lib1/internal/utils") + env.RegexpSearch("main.go", `import "mod.com/lib1"`) + env.RegexpSearch("main.go", "lib1.print()") + }) +} + +// checkTestdata checks that current buffer contents match their corresponding +// expected content in the testdata directory. +func checkTestdata(t *testing.T, env *Env) { + t.Helper() + files := env.ListFiles("testdata") + if len(files) == 0 { + t.Fatal("no files in testdata directory") + } + for _, file := range files { + suffix := strings.TrimPrefix(file, "testdata/") + got := env.BufferText(suffix) + want := env.ReadWorkspaceFile(file) + if diff := compare.Text(want, got); diff != "" { + t.Errorf("Rename: unexpected buffer content for %s (-want +got):\n%s", suffix, diff) + } + } +} diff --git a/contribs/gnopls/internal/test/integration/misc/semantictokens_test.go b/contribs/gnopls/internal/test/integration/misc/semantictokens_test.go new file mode 100644 index 00000000000..b8d8729c63a --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/semantictokens_test.go @@ -0,0 +1,238 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package misc + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" +) + +func TestBadURICrash_VSCodeIssue1498(t *testing.T) { + const src = ` +-- go.mod -- +module example.com + +go 1.12 + +-- main.go -- +package main + +func main() {} + +` + WithOptions( + Modes(Default), + ).Run(t, src, func(t *testing.T, env *Env) { + params := &protocol.SemanticTokensParams{} + const badURI = "http://foo" + params.TextDocument.URI = badURI + // This call panicked in the past: golang/vscode-go#1498. + _, err := env.Editor.Server.SemanticTokensFull(env.Ctx, params) + + // Requests to an invalid URI scheme now result in an LSP error. + got := fmt.Sprint(err) + want := `DocumentURI scheme is not 'file': http://foo` + if !strings.Contains(got, want) { + t.Errorf("SemanticTokensFull error is %v, want substring %q", got, want) + } + }) +} + +// fix bug involving type parameters and regular parameters +// (golang/vscode-go#2527) +func TestSemantic_2527(t *testing.T) { + // these are the expected types of identifiers in text order + want := []fake.SemanticToken{ + {Token: "package", TokenType: "keyword"}, + {Token: "foo", TokenType: "namespace"}, + {Token: "// Deprecated (for testing)", TokenType: "comment"}, + {Token: "func", TokenType: "keyword"}, + {Token: "Add", TokenType: "function", Mod: "definition deprecated"}, + {Token: "T", TokenType: "typeParameter", Mod: "definition"}, + {Token: "int", TokenType: "type", Mod: "defaultLibrary number"}, + {Token: "target", TokenType: "parameter", Mod: "definition"}, + {Token: "T", TokenType: "typeParameter"}, + {Token: "l", TokenType: "parameter", Mod: "definition"}, + {Token: "T", TokenType: "typeParameter"}, + {Token: "T", TokenType: "typeParameter"}, + {Token: "return", TokenType: "keyword"}, + {Token: "append", TokenType: "function", Mod: "defaultLibrary"}, + {Token: "l", TokenType: "parameter"}, + {Token: "target", TokenType: "parameter"}, + {Token: "for", TokenType: "keyword"}, + {Token: "range", TokenType: "keyword"}, + {Token: "l", TokenType: "parameter"}, + {Token: "// test coverage", TokenType: "comment"}, + {Token: "return", TokenType: "keyword"}, + {Token: "nil", TokenType: "variable", Mod: "readonly defaultLibrary"}, + } + src := ` +-- go.mod -- +module example.com + +go 1.19 +-- main.go -- +package foo +// Deprecated (for testing) +func Add[T int](target T, l []T) []T { + return append(l, target) + for range l {} // test coverage + return nil +} +` + WithOptions( + Modes(Default), + Settings{"semanticTokens": true}, + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "for range")), + ) + seen := env.SemanticTokensFull("main.go") + if x := cmp.Diff(want, seen); x != "" { + t.Errorf("Semantic tokens do not match (-want +got):\n%s", x) + } + }) + +} + +// fix inconsistency in TypeParameters +// https://github.com/golang/go/issues/57619 +func TestSemantic_57619(t *testing.T) { + src := ` +-- go.mod -- +module example.com + +go 1.19 +-- main.go -- +package foo +type Smap[K int, V any] struct { + Store map[K]V +} +func (s *Smap[K, V]) Get(k K) (V, bool) { + v, ok := s.Store[k] + return v, ok +} +func New[K int, V any]() Smap[K, V] { + return Smap[K, V]{Store: make(map[K]V)} +} +` + WithOptions( + Modes(Default), + Settings{"semanticTokens": true}, + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + seen := env.SemanticTokensFull("main.go") + for i, s := range seen { + if (s.Token == "K" || s.Token == "V") && s.TokenType != "typeParameter" { + t.Errorf("%d: expected K and V to be type parameters, but got %v", i, s) + } + } + }) +} + +func TestSemanticGoDirectives(t *testing.T) { + src := ` +-- go.mod -- +module example.com + +go 1.19 +-- main.go -- +package foo + +//go:linkname now time.Now +func now() + +//go:noinline +func foo() {} + +// Mentioning go:noinline should not tokenize. + +//go:notadirective +func bar() {} +` + want := []fake.SemanticToken{ + {Token: "package", TokenType: "keyword"}, + {Token: "foo", TokenType: "namespace"}, + + {Token: "//", TokenType: "comment"}, + {Token: "go:linkname", TokenType: "namespace"}, + {Token: "now time.Now", TokenType: "comment"}, + {Token: "func", TokenType: "keyword"}, + {Token: "now", TokenType: "function", Mod: "definition"}, + + {Token: "//", TokenType: "comment"}, + {Token: "go:noinline", TokenType: "namespace"}, + {Token: "func", TokenType: "keyword"}, + {Token: "foo", TokenType: "function", Mod: "definition"}, + + {Token: "// Mentioning go:noinline should not tokenize.", TokenType: "comment"}, + + {Token: "//go:notadirective", TokenType: "comment"}, + {Token: "func", TokenType: "keyword"}, + {Token: "bar", TokenType: "function", Mod: "definition"}, + } + + WithOptions( + Modes(Default), + Settings{"semanticTokens": true}, + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + seen := env.SemanticTokensFull("main.go") + if x := cmp.Diff(want, seen); x != "" { + t.Errorf("Semantic tokens do not match (-want +got):\n%s", x) + } + }) +} + +// Make sure no zero-length tokens occur +func TestSemantic_65254(t *testing.T) { + src := ` +-- go.mod -- +module example.com + +go 1.21 +-- main.go -- +package main + +/* a comment with an + +empty line +*/ + +const bad = ` + + src += "`foo" + ` + ` + "bar`" + want := []fake.SemanticToken{ + {Token: "package", TokenType: "keyword"}, + {Token: "main", TokenType: "namespace"}, + {Token: "/* a comment with an", TokenType: "comment"}, + // --- Note that the zero length line does not show up + {Token: "empty line", TokenType: "comment"}, + {Token: "*/", TokenType: "comment"}, + {Token: "const", TokenType: "keyword"}, + {Token: "bad", TokenType: "variable", Mod: "definition readonly"}, + {Token: "`foo", TokenType: "string"}, + // --- Note the zero length line does not show up + {Token: "\tbar`", TokenType: "string"}, + } + WithOptions( + Modes(Default), + Settings{"semanticTokens": true}, + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + seen := env.SemanticTokensFull("main.go") + if x := cmp.Diff(want, seen); x != "" { + t.Errorf("Semantic tokens do not match (-want +got):\n%s", x) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/settings_test.go b/contribs/gnopls/internal/test/integration/misc/settings_test.go new file mode 100644 index 00000000000..c367f9fc357 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/settings_test.go @@ -0,0 +1,32 @@ +// 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 misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestEmptyDirectoryFilters_Issue51843(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { +} +` + + WithOptions( + Settings{"directoryFilters": []string{""}}, + ).Run(t, src, func(t *testing.T, env *Env) { + // No need to do anything. Issue golang/go#51843 is triggered by the empty + // directory filter above. + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/shared_test.go b/contribs/gnopls/internal/test/integration/misc/shared_test.go new file mode 100644 index 00000000000..b0bbcaa030a --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/shared_test.go @@ -0,0 +1,58 @@ +// 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 misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// Smoke test that simultaneous editing sessions in the same workspace works. +func TestSimultaneousEdits(t *testing.T) { + const sharedProgram = ` +-- go.mod -- +module mod + +go 1.12 +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println("Hello World.") +}` + + WithOptions( + Modes(DefaultModes()&(Forwarded|SeparateProcess)), + ).Run(t, sharedProgram, func(t *testing.T, env1 *Env) { + // Create a second test session connected to the same workspace and server + // as the first. + env2 := ConnectGoplsEnv(t, env1.Ctx, env1.Sandbox, env1.Editor.Config(), env1.Server) + env2.Await(InitialWorkspaceLoad) + // In editor #1, break fmt.Println as before. + env1.OpenFile("main.go") + env1.RegexpReplace("main.go", "Printl(n)", "") + // In editor #2 remove the closing brace. + env2.OpenFile("main.go") + env2.RegexpReplace("main.go", "\\)\n(})", "") + + // Now check that we got different diagnostics in each environment. + env1.AfterChange(Diagnostics(env1.AtRegexp("main.go", "Printl"))) + env2.AfterChange(Diagnostics(env2.AtRegexp("main.go", "$"))) + + // Now close editor #2, and verify that operation in editor #1 is + // unaffected. + if err := env2.Editor.Close(env2.Ctx); err != nil { + t.Errorf("closing second editor: %v", err) + } + + env1.RegexpReplace("main.go", "Printl", "Println") + env1.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/signature_help_test.go b/contribs/gnopls/internal/test/integration/misc/signature_help_test.go new file mode 100644 index 00000000000..8dffedf48e0 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/signature_help_test.go @@ -0,0 +1,69 @@ +// 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 misc + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestSignatureHelpInNonWorkspacePackage(t *testing.T) { + const files = ` +-- a/go.mod -- +module a.com + +go 1.18 +-- a/a/a.go -- +package a + +func DoSomething(int) {} + +func _() { + DoSomething() +} +-- b/go.mod -- +module b.com +go 1.18 + +require a.com v1.0.0 + +replace a.com => ../a +-- b/b/b.go -- +package b + +import "a.com/a" + +func _() { + a.DoSomething() +} +` + + WithOptions( + WorkspaceFolders("a"), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a/a.go") + env.OpenFile("b/b/b.go") + signatureHelp := func(filename string) *protocol.SignatureHelp { + loc := env.RegexpSearch(filename, `DoSomething\(()\)`) + var params protocol.SignatureHelpParams + params.TextDocument.URI = loc.URI + params.Position = loc.Range.Start + help, err := env.Editor.Server.SignatureHelp(env.Ctx, ¶ms) + if err != nil { + t.Fatal(err) + } + return help + } + ahelp := signatureHelp("a/a/a.go") + bhelp := signatureHelp("b/b/b.go") + + if diff := cmp.Diff(ahelp, bhelp); diff != "" { + t.Fatal(diff) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/staticcheck_test.go b/contribs/gnopls/internal/test/integration/misc/staticcheck_test.go new file mode 100644 index 00000000000..4970064c5f6 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/staticcheck_test.go @@ -0,0 +1,138 @@ +// 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 misc + +import ( + "testing" + + "golang.org/x/tools/internal/aliases" + "golang.org/x/tools/internal/testenv" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestStaticcheckGenerics(t *testing.T) { + testenv.NeedsGo1Point(t, 20) // staticcheck requires go1.20+ + + // CL 583778 causes buildir not to run on packages that use + // range-over-func, since it might otherwise crash. But nearly + // all packages will soon meet this description, so the + // analyzers in this test will not run, and the test will fail. + // TODO(adonovan): reenable once dominikh/go-tools#1494 is fixed. + t.Skip("disabled until buildir supports range-over-func (dominikh/go-tools#1494)") + + // TODO(golang/go#65249): re-enable and fix this test once we + // update go.mod to go1.23 so that gotypesalias=1 becomes the default. + if aliases.Enabled() { + t.Skip("staticheck doesn't yet support aliases (dominikh/go-tools#1523)") + } + + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- a/a.go -- +package a + +import ( + "errors" + "sort" + "strings" +) + +func Zero[P any]() P { + var p P + return p +} + +type Inst[P any] struct { + Field P +} + +func testGenerics[P *T, T any](p P) { + // Calls to instantiated functions should not break checks. + slice := Zero[string]() + sort.Slice(slice, func(i, j int) bool { + return slice[i] < slice[j] + }) + + // Usage of instantiated fields should not break checks. + g := Inst[string]{"hello"} + g.Field = strings.TrimLeft(g.Field, "12234") + + // Use of type parameters should not break checks. + var q P + p = q // SA4009: p is overwritten before its first use + q = &*p // SA4001: &* will be simplified +} + + +// FooErr should be called ErrFoo (ST1012) +var FooErr error = errors.New("foo") +` + + WithOptions( + Settings{"staticcheck": true}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "sort.Slice"), FromSource("sortslice")), + Diagnostics(env.AtRegexp("a/a.go", "sort.Slice.(slice)"), FromSource("SA1028")), + Diagnostics(env.AtRegexp("a/a.go", "var (FooErr)"), FromSource("ST1012")), + Diagnostics(env.AtRegexp("a/a.go", `"12234"`), FromSource("SA1024")), + Diagnostics(env.AtRegexp("a/a.go", "testGenerics.*(p P)"), FromSource("SA4009")), + Diagnostics(env.AtRegexp("a/a.go", "q = (&\\*p)"), FromSource("SA4001")), + ) + }) +} + +// Test for golang/go#56270: an analysis with related info should not panic if +// analysis.RelatedInformation.End is not set. +func TestStaticcheckRelatedInfo(t *testing.T) { + testenv.NeedsGo1Point(t, 20) // staticcheck is only supported at Go 1.20+ + + // CL 583778 causes buildir not to run on packages that use + // range-over-func, since it might otherwise crash. But nearly + // all packages will soon meet this description, so the + // analyzers in this test will not run, and the test will fail. + // TODO(adonovan): reenable once dominikh/go-tools#1494 is fixed. + t.Skip("disabled until buildir supports range-over-func (dominikh/go-tools#1494)") + + // TODO(golang/go#65249): re-enable and fix this test once we + // update go.mod to go1.23 so that gotypesalias=1 becomes the default. + if aliases.Enabled() { + t.Skip("staticheck doesn't yet support aliases (dominikh/go-tools#1523)") + } + + const files = ` +-- go.mod -- +module mod.test + +go 1.18 +-- p.go -- +package p + +import ( + "fmt" +) + +func Foo(enabled interface{}) { + if enabled, ok := enabled.(bool); ok { + } else { + _ = fmt.Sprintf("invalid type %T", enabled) // enabled is always bool here + } +} +` + + WithOptions( + Settings{"staticcheck": true}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("p.go") + env.AfterChange( + Diagnostics(env.AtRegexp("p.go", ", (enabled)"), FromSource("SA9008")), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/vendor_test.go b/contribs/gnopls/internal/test/integration/misc/vendor_test.go new file mode 100644 index 00000000000..f3bed9082b7 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/vendor_test.go @@ -0,0 +1,102 @@ +// 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 misc + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" + + "golang.org/x/tools/gopls/internal/protocol" +) + +const basicProxy = ` +-- golang.org/x/hello@v1.2.3/go.mod -- +module golang.org/x/hello + +go 1.14 +-- golang.org/x/hello@v1.2.3/hi/hi.go -- +package hi + +var Goodbye error +` + +func TestInconsistentVendoring(t *testing.T) { + const pkgThatUsesVendoring = ` +-- go.mod -- +module mod.com + +go 1.14 + +require golang.org/x/hello v1.2.3 +-- go.sum -- +golang.org/x/hello v1.2.3 h1:EcMp5gSkIhaTkPXp8/3+VH+IFqTpk3ZbpOhqk0Ncmho= +golang.org/x/hello v1.2.3/go.mod h1:WW7ER2MRNXWA6c8/4bDIek4Hc/+DofTrMaQQitGXcco= +-- vendor/modules.txt -- +-- a/a1.go -- +package a + +import "golang.org/x/hello/hi" + +func _() { + _ = hi.Goodbye + var q int // hardcode a diagnostic +} +` + WithOptions( + Modes(Default), + ProxyFiles(basicProxy), + ).Run(t, pkgThatUsesVendoring, func(t *testing.T, env *Env) { + env.OpenFile("a/a1.go") + d := &protocol.PublishDiagnosticsParams{} + env.AfterChange( + Diagnostics(env.AtRegexp("go.mod", "module mod.com"), WithMessage("Inconsistent vendoring")), + ReadDiagnostics("go.mod", d), + ) + env.ApplyQuickFixes("go.mod", d.Diagnostics) + + env.AfterChange( + Diagnostics(env.AtRegexp("a/a1.go", `q int`), WithMessage("not used")), + ) + }) +} + +func TestWindowsVendoring_Issue56291(t *testing.T) { + const src = ` +-- go.mod -- +module mod.com + +go 1.14 + +require golang.org/x/hello v1.2.3 +-- go.sum -- +golang.org/x/hello v1.2.3 h1:EcMp5gSkIhaTkPXp8/3+VH+IFqTpk3ZbpOhqk0Ncmho= +golang.org/x/hello v1.2.3/go.mod h1:WW7ER2MRNXWA6c8/4bDIek4Hc/+DofTrMaQQitGXcco= +-- main.go -- +package main + +import "golang.org/x/hello/hi" + +func main() { + _ = hi.Goodbye +} +` + WithOptions( + Modes(Default), + ProxyFiles(basicProxy), + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange(NoDiagnostics()) + env.RunGoCommand("mod", "tidy") + env.RunGoCommand("mod", "vendor") + env.AfterChange(NoDiagnostics()) + env.RegexpReplace("main.go", `import "golang.org/x/hello/hi"`, "") + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", "hi.Goodbye")), + ) + env.SaveBuffer("main.go") + env.AfterChange(NoDiagnostics()) + }) +} diff --git a/contribs/gnopls/internal/test/integration/misc/vuln_test.go b/contribs/gnopls/internal/test/integration/misc/vuln_test.go new file mode 100644 index 00000000000..7be02b3ceb3 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/vuln_test.go @@ -0,0 +1,939 @@ +// 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 misc + +import ( + "context" + "encoding/json" + "sort" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/vulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/vulntest" +) + +func TestRunGovulncheckError(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- foo.go -- +package foo +` + Run(t, files, func(t *testing.T, env *Env) { + cmd := command.NewRunGovulncheckCommand("Run Vulncheck Exp", command.VulncheckArgs{ + URI: "/invalid/file/url", // invalid arg + }) + params := &protocol.ExecuteCommandParams{ + Command: command.RunGovulncheck.String(), + Arguments: cmd.Arguments, + } + + response, err := env.Editor.ExecuteCommand(env.Ctx, params) + // We want an error! + if err == nil { + t.Errorf("got success, want invalid file URL error: %v", response) + } + }) +} + +func TestRunGovulncheckError2(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- foo.go -- +package foo + +func F() { // build error incomplete +` + WithOptions( + EnvVars{ + "_GOPLS_TEST_BINARY_RUN_AS_GOPLS": "true", // needed to run `gopls vulncheck`. + }, + Settings{ + "codelenses": map[string]bool{ + "run_govulncheck": true, + }, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + var result command.RunVulncheckResult + env.ExecuteCodeLensCommand("go.mod", command.RunGovulncheck, &result) + var ws WorkStatus + env.Await( + CompletedProgress(result.Token, &ws), + ) + wantEndMsg, wantMsgPart := "failed", "There are errors with the provided package patterns:" + if ws.EndMsg != "failed" || !strings.Contains(ws.Msg, wantMsgPart) { + t.Errorf("work status = %+v, want {EndMessage: %q, Message: %q}", ws, wantEndMsg, wantMsgPart) + } + }) +} + +const vulnsData = ` +-- GO-2022-01.yaml -- +modules: + - module: golang.org/amod + versions: + - introduced: 1.0.0 + - fixed: 1.0.4 + packages: + - package: golang.org/amod/avuln + symbols: + - VulnData.Vuln1 + - VulnData.Vuln2 +description: > + vuln in amod is found +summary: vuln in amod +references: + - href: pkg.go.dev/vuln/GO-2022-01 +-- GO-2022-03.yaml -- +modules: + - module: golang.org/amod + versions: + - introduced: 1.0.0 + - fixed: 1.0.6 + packages: + - package: golang.org/amod/avuln + symbols: + - nonExisting +description: > + unaffecting vulnerability is found +summary: unaffecting vulnerability +-- GO-2022-02.yaml -- +modules: + - module: golang.org/bmod + packages: + - package: golang.org/bmod/bvuln + symbols: + - Vuln +description: | + vuln in bmod is found. + + This is a long description + of this vulnerability. +summary: vuln in bmod (no fix) +references: + - href: pkg.go.dev/vuln/GO-2022-03 +-- GO-2022-04.yaml -- +modules: + - module: golang.org/bmod + packages: + - package: golang.org/bmod/unused + symbols: + - Vuln +description: | + vuln in bmod/somethingelse is found +summary: vuln in bmod/somethingelse +references: + - href: pkg.go.dev/vuln/GO-2022-04 +-- GOSTDLIB.yaml -- +modules: + - module: stdlib + versions: + - introduced: 1.18.0 + packages: + - package: archive/zip + symbols: + - OpenReader +summary: vuln in GOSTDLIB +references: + - href: pkg.go.dev/vuln/GOSTDLIB +` + +func TestRunGovulncheckStd(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.19 +-- main.go -- +package main + +import ( + "archive/zip" + "fmt" +) + +func main() { + _, err := zip.OpenReader("file.zip") // vulnerability id: GOSTDLIB + fmt.Println(err) +} +` + + db, err := vulntest.NewDatabase(context.Background(), []byte(vulnsData)) + if err != nil { + t.Fatal(err) + } + defer db.Clean() + WithOptions( + EnvVars{ + // Let the analyzer read vulnerabilities data from the testdata/vulndb. + "GOVULNDB": db.URI(), + // When fetchinging stdlib package vulnerability info, + // behave as if our go version is go1.19 for this testing. + // The default behavior is to run `go env GOVERSION` (which isn't mutable env var). + cache.GoVersionForVulnTest: "go1.19", + "_GOPLS_TEST_BINARY_RUN_AS_GOPLS": "true", // needed to run `gopls vulncheck`. + }, + Settings{ + "codelenses": map[string]bool{ + "run_govulncheck": true, + }, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + + // Run Command included in the codelens. + var result command.RunVulncheckResult + env.ExecuteCodeLensCommand("go.mod", command.RunGovulncheck, &result) + + env.OnceMet( + CompletedProgress(result.Token, nil), + ShownMessage("Found GOSTDLIB"), + NoDiagnostics(ForFile("go.mod")), + ) + testFetchVulncheckResult(t, env, map[string]fetchVulncheckResult{ + "go.mod": {IDs: []string{"GOSTDLIB"}, Mode: vulncheck.ModeGovulncheck}}) + }) +} +func TestFetchVulncheckResultStd(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- main.go -- +package main + +import ( + "archive/zip" + "fmt" +) + +func main() { + _, err := zip.OpenReader("file.zip") // vulnerability id: GOSTDLIB + fmt.Println(err) +} +` + + db, err := vulntest.NewDatabase(context.Background(), []byte(vulnsData)) + if err != nil { + t.Fatal(err) + } + defer db.Clean() + WithOptions( + EnvVars{ + // Let the analyzer read vulnerabilities data from the testdata/vulndb. + "GOVULNDB": db.URI(), + // When fetchinging stdlib package vulnerability info, + // behave as if our go version is go1.18 for this testing. + cache.GoVersionForVulnTest: "go1.18", + "_GOPLS_TEST_BINARY_RUN_AS_GOPLS": "true", // needed to run `gopls vulncheck`. + }, + Settings{"ui.diagnostic.vulncheck": "Imports"}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.AfterChange( + NoDiagnostics(ForFile("go.mod")), + // we don't publish diagnostics for standard library vulnerability yet. + ) + testFetchVulncheckResult(t, env, map[string]fetchVulncheckResult{ + "go.mod": { + IDs: []string{"GOSTDLIB"}, + Mode: vulncheck.ModeImports, + }, + }) + }) +} + +type fetchVulncheckResult struct { + IDs []string + Mode vulncheck.AnalysisMode +} + +func testFetchVulncheckResult(t *testing.T, env *Env, want map[string]fetchVulncheckResult) { + t.Helper() + + var result map[protocol.DocumentURI]*vulncheck.Result + fetchCmd := command.NewFetchVulncheckResultCommand("fetch", command.URIArg{ + URI: env.Sandbox.Workdir.URI("go.mod"), + }) + env.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: fetchCmd.Command, + Arguments: fetchCmd.Arguments, + }, &result) + + for _, v := range want { + sort.Strings(v.IDs) + } + got := map[string]fetchVulncheckResult{} + for k, r := range result { + osv := map[string]bool{} + for _, v := range r.Findings { + osv[v.OSV] = true + } + ids := make([]string, 0, len(osv)) + for id := range osv { + ids = append(ids, id) + } + sort.Strings(ids) + modfile := env.Sandbox.Workdir.RelPath(k.Path()) + got[modfile] = fetchVulncheckResult{ + IDs: ids, + Mode: r.Mode, + } + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("fetch vulnchheck result = got %v, want %v: diff %v", got, want, diff) + } +} + +const workspace1 = ` +-- go.mod -- +module golang.org/entry + +go 1.18 + +require golang.org/cmod v1.1.3 + +require ( + golang.org/amod v1.0.0 // indirect + golang.org/bmod v0.5.0 // indirect +) +-- go.sum -- +golang.org/amod v1.0.0 h1:EUQOI2m5NhQZijXZf8WimSnnWubaFNrrKUH/PopTN8k= +golang.org/amod v1.0.0/go.mod h1:yvny5/2OtYFomKt8ax+WJGvN6pfN1pqjGnn7DQLUi6E= +golang.org/bmod v0.5.0 h1:KgvUulMyMiYRB7suKA0x+DfWRVdeyPgVJvcishTH+ng= +golang.org/bmod v0.5.0/go.mod h1:f6o+OhF66nz/0BBc/sbCsshyPRKMSxZIlG50B/bsM4c= +golang.org/cmod v1.1.3 h1:PJ7rZFTk7xGAunBRDa0wDe7rZjZ9R/vr1S2QkVVCngQ= +golang.org/cmod v1.1.3/go.mod h1:eCR8dnmvLYQomdeAZRCPgS5JJihXtqOQrpEkNj5feQA= +-- x/x.go -- +package x + +import ( + "golang.org/cmod/c" + "golang.org/entry/y" +) + +func X() { + c.C1().Vuln1() // vuln use: X -> Vuln1 +} + +func CallY() { + y.Y() // vuln use: CallY -> y.Y -> bvuln.Vuln +} + +-- y/y.go -- +package y + +import "golang.org/cmod/c" + +func Y() { + c.C2()() // vuln use: Y -> bvuln.Vuln +} +` + +// cmod/c imports amod/avuln and bmod/bvuln. +const proxy1 = ` +-- golang.org/cmod@v1.1.3/go.mod -- +module golang.org/cmod + +go 1.12 +-- golang.org/cmod@v1.1.3/c/c.go -- +package c + +import ( + "golang.org/amod/avuln" + "golang.org/bmod/bvuln" +) + +type I interface { + Vuln1() +} + +func C1() I { + v := avuln.VulnData{} + v.Vuln2() // vuln use + return v +} + +func C2() func() { + return bvuln.Vuln +} +-- golang.org/amod@v1.0.0/go.mod -- +module golang.org/amod + +go 1.14 +-- golang.org/amod@v1.0.0/avuln/avuln.go -- +package avuln + +type VulnData struct {} +func (v VulnData) Vuln1() {} +func (v VulnData) Vuln2() {} +-- golang.org/amod@v1.0.4/go.mod -- +module golang.org/amod + +go 1.14 +-- golang.org/amod@v1.0.4/avuln/avuln.go -- +package avuln + +type VulnData struct {} +func (v VulnData) Vuln1() {} +func (v VulnData) Vuln2() {} + +-- golang.org/bmod@v0.5.0/go.mod -- +module golang.org/bmod + +go 1.14 +-- golang.org/bmod@v0.5.0/bvuln/bvuln.go -- +package bvuln + +func Vuln() { + // something evil +} +-- golang.org/bmod@v0.5.0/unused/unused.go -- +package unused + +func Vuln() { + // something evil +} +-- golang.org/amod@v1.0.6/go.mod -- +module golang.org/amod + +go 1.14 +-- golang.org/amod@v1.0.6/avuln/avuln.go -- +package avuln + +type VulnData struct {} +func (v VulnData) Vuln1() {} +func (v VulnData) Vuln2() {} +` + +func vulnTestEnv(proxyData string) (*vulntest.DB, []RunOption, error) { + db, err := vulntest.NewDatabase(context.Background(), []byte(vulnsData)) + if err != nil { + return nil, nil, nil + } + settings := Settings{ + "codelenses": map[string]bool{ + "run_govulncheck": true, + }, + } + ev := EnvVars{ + // Let the analyzer read vulnerabilities data from the testdata/vulndb. + "GOVULNDB": db.URI(), + // When fetching stdlib package vulnerability info, + // behave as if our go version is go1.18 for this testing. + // The default behavior is to run `go env GOVERSION` (which isn't mutable env var). + cache.GoVersionForVulnTest: "go1.18", + "_GOPLS_TEST_BINARY_RUN_AS_GOPLS": "true", // needed to run `gopls vulncheck`. + "GOSUMDB": "off", + } + return db, []RunOption{ProxyFiles(proxyData), ev, settings}, nil +} + +func TestRunVulncheckPackageDiagnostics(t *testing.T) { + db, opts0, err := vulnTestEnv(proxy1) + if err != nil { + t.Fatal(err) + } + defer db.Clean() + + checkVulncheckDiagnostics := func(env *Env, t *testing.T) { + env.OpenFile("go.mod") + + gotDiagnostics := &protocol.PublishDiagnosticsParams{} + env.AfterChange( + Diagnostics(env.AtRegexp("go.mod", `golang.org/amod`)), + ReadDiagnostics("go.mod", gotDiagnostics), + ) + + testFetchVulncheckResult(t, env, map[string]fetchVulncheckResult{ + "go.mod": { + IDs: []string{"GO-2022-01", "GO-2022-02", "GO-2022-03"}, + Mode: vulncheck.ModeImports, + }, + }) + + wantVulncheckDiagnostics := map[string]vulnDiagExpectation{ + "golang.org/amod": { + diagnostics: []vulnDiag{ + { + msg: "golang.org/amod has known vulnerabilities GO-2022-01, GO-2022-03.", + severity: protocol.SeverityInformation, + source: string(cache.Vulncheck), + codeActions: []string{ + "Run govulncheck to verify", + "Upgrade to v1.0.6", + "Upgrade to latest", + }, + }, + }, + codeActions: []string{ + "Run govulncheck to verify", + "Upgrade to v1.0.6", + "Upgrade to latest", + }, + hover: []string{"GO-2022-01", "Fixed in v1.0.4.", "GO-2022-03"}, + }, + "golang.org/bmod": { + diagnostics: []vulnDiag{ + { + msg: "golang.org/bmod has a vulnerability GO-2022-02.", + severity: protocol.SeverityInformation, + source: string(cache.Vulncheck), + codeActions: []string{ + "Run govulncheck to verify", + }, + }, + }, + codeActions: []string{ + "Run govulncheck to verify", + }, + hover: []string{"GO-2022-02", "vuln in bmod (no fix)", "No fix is available."}, + }, + } + + for pattern, want := range wantVulncheckDiagnostics { + modPathDiagnostics := testVulnDiagnostics(t, env, pattern, want, gotDiagnostics) + + gotActions := env.CodeActionForFile("go.mod", modPathDiagnostics) + if diff := diffCodeActions(gotActions, want.codeActions); diff != "" { + t.Errorf("code actions for %q do not match, got %v, want %v\n%v\n", pattern, gotActions, want.codeActions, diff) + continue + } + } + } + + wantNoVulncheckDiagnostics := func(env *Env, t *testing.T) { + env.OpenFile("go.mod") + + gotDiagnostics := &protocol.PublishDiagnosticsParams{} + env.AfterChange( + ReadDiagnostics("go.mod", gotDiagnostics), + ) + + if len(gotDiagnostics.Diagnostics) > 0 { + t.Errorf("Unexpected diagnostics: %v", stringify(gotDiagnostics)) + } + testFetchVulncheckResult(t, env, map[string]fetchVulncheckResult{}) + } + + for _, tc := range []struct { + name string + setting Settings + wantDiagnostics bool + }{ + {"imports", Settings{"ui.diagnostic.vulncheck": "Imports"}, true}, + {"default", Settings{}, false}, + {"invalid", Settings{"ui.diagnostic.vulncheck": "invalid"}, false}, + } { + t.Run(tc.name, func(t *testing.T) { + // override the settings options to enable diagnostics + opts := append(opts0, tc.setting) + WithOptions(opts...).Run(t, workspace1, func(t *testing.T, env *Env) { + // TODO(hyangah): implement it, so we see GO-2022-01, GO-2022-02, and GO-2022-03. + // Check that the actions we get when including all diagnostics at a location return the same result + if tc.wantDiagnostics { + checkVulncheckDiagnostics(env, t) + } else { + wantNoVulncheckDiagnostics(env, t) + } + + if tc.name == "imports" && tc.wantDiagnostics { + // test we get only govulncheck-based diagnostics after "run govulncheck". + var result command.RunVulncheckResult + env.ExecuteCodeLensCommand("go.mod", command.RunGovulncheck, &result) + gotDiagnostics := &protocol.PublishDiagnosticsParams{} + env.OnceMet( + CompletedProgress(result.Token, nil), + ShownMessage("Found"), + ) + env.OnceMet( + Diagnostics(env.AtRegexp("go.mod", "golang.org/bmod")), + ReadDiagnostics("go.mod", gotDiagnostics), + ) + // We expect only one diagnostic for GO-2022-02. + count := 0 + for _, diag := range gotDiagnostics.Diagnostics { + if strings.Contains(diag.Message, "GO-2022-02") { + count++ + if got, want := diag.Severity, protocol.SeverityWarning; got != want { + t.Errorf("Diagnostic for GO-2022-02 = %v, want %v", got, want) + } + } + } + if count != 1 { + t.Errorf("Unexpected number of diagnostics about GO-2022-02 = %v, want 1:\n%+v", count, stringify(gotDiagnostics)) + } + } + }) + }) + } +} + +// TestRunGovulncheck_Expiry checks that govulncheck results expire after a +// certain amount of time. +func TestRunGovulncheck_Expiry(t *testing.T) { + // For this test, set the max age to a duration smaller than the sleep below. + defer func(prev time.Duration) { + cache.MaxGovulncheckResultAge = prev + }(cache.MaxGovulncheckResultAge) + cache.MaxGovulncheckResultAge = 99 * time.Millisecond + + db, opts0, err := vulnTestEnv(proxy1) + if err != nil { + t.Fatal(err) + } + defer db.Clean() + + WithOptions(opts0...).Run(t, workspace1, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.OpenFile("x/x.go") + + var result command.RunVulncheckResult + env.ExecuteCodeLensCommand("go.mod", command.RunGovulncheck, &result) + env.OnceMet( + CompletedProgress(result.Token, nil), + ShownMessage("Found"), + ) + // Sleep long enough for the results to expire. + time.Sleep(100 * time.Millisecond) + // Make an arbitrary edit to force re-diagnosis of the workspace. + env.RegexpReplace("x/x.go", "package x", "package x ") + env.AfterChange( + NoDiagnostics(env.AtRegexp("go.mod", "golang.org/bmod")), + ) + }) +} + +func stringify(a interface{}) string { + data, _ := json.Marshal(a) + return string(data) +} + +func TestRunVulncheckWarning(t *testing.T) { + db, opts, err := vulnTestEnv(proxy1) + if err != nil { + t.Fatal(err) + } + defer db.Clean() + WithOptions(opts...).Run(t, workspace1, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + + var result command.RunVulncheckResult + env.ExecuteCodeLensCommand("go.mod", command.RunGovulncheck, &result) + gotDiagnostics := &protocol.PublishDiagnosticsParams{} + env.OnceMet( + CompletedProgress(result.Token, nil), + ShownMessage("Found"), + ) + // Vulncheck diagnostics asynchronous to the vulncheck command. + env.OnceMet( + Diagnostics(env.AtRegexp("go.mod", `golang.org/amod`)), + ReadDiagnostics("go.mod", gotDiagnostics), + ) + + testFetchVulncheckResult(t, env, map[string]fetchVulncheckResult{ + // All vulnerabilities (symbol-level, import-level, module-level) are reported. + "go.mod": {IDs: []string{"GO-2022-01", "GO-2022-02", "GO-2022-03", "GO-2022-04"}, Mode: vulncheck.ModeGovulncheck}, + }) + env.OpenFile("x/x.go") + env.OpenFile("y/y.go") + wantDiagnostics := map[string]vulnDiagExpectation{ + "golang.org/amod": { + applyAction: "Upgrade to v1.0.6", + diagnostics: []vulnDiag{ + { + msg: "golang.org/amod has a vulnerability used in the code: GO-2022-01.", + severity: protocol.SeverityWarning, + source: string(cache.Govulncheck), + codeActions: []string{ + "Upgrade to v1.0.4", + "Upgrade to latest", + "Reset govulncheck result", + }, + }, + { + msg: "golang.org/amod has a vulnerability GO-2022-03 that is not used in the code.", + severity: protocol.SeverityInformation, + source: string(cache.Govulncheck), + codeActions: []string{ + "Upgrade to v1.0.6", + "Upgrade to latest", + "Reset govulncheck result", + }, + }, + }, + codeActions: []string{ + "Upgrade to v1.0.6", + "Upgrade to latest", + "Reset govulncheck result", + }, + hover: []string{"GO-2022-01", "Fixed in v1.0.4.", "GO-2022-03"}, + }, + "golang.org/bmod": { + diagnostics: []vulnDiag{ + { + msg: "golang.org/bmod has a vulnerability used in the code: GO-2022-02.", + severity: protocol.SeverityWarning, + source: string(cache.Govulncheck), + codeActions: []string{ + "Reset govulncheck result", // no fix, but we should give an option to reset. + }, + }, + }, + codeActions: []string{ + "Reset govulncheck result", // no fix, but we should give an option to reset. + }, + hover: []string{"GO-2022-02", "vuln in bmod (no fix)", "No fix is available."}, + }, + } + + for mod, want := range wantDiagnostics { + modPathDiagnostics := testVulnDiagnostics(t, env, mod, want, gotDiagnostics) + + // Check that the actions we get when including all diagnostics at a location return the same result + gotActions := env.CodeActionForFile("go.mod", modPathDiagnostics) + if diff := diffCodeActions(gotActions, want.codeActions); diff != "" { + t.Errorf("code actions for %q do not match, expected %v, got %v\n%v\n", mod, want.codeActions, gotActions, diff) + continue + } + + // Apply the code action matching applyAction. + if want.applyAction == "" { + continue + } + for _, action := range gotActions { + if action.Title == want.applyAction { + env.ApplyCodeAction(action) + break + } + } + } + + env.Await(env.DoneWithChangeWatchedFiles()) + wantGoMod := `module golang.org/entry + +go 1.18 + +require golang.org/cmod v1.1.3 + +require ( + golang.org/amod v1.0.6 // indirect + golang.org/bmod v0.5.0 // indirect +) +` + if got := env.BufferText("go.mod"); got != wantGoMod { + t.Fatalf("go.mod vulncheck fix failed:\n%s", compare.Text(wantGoMod, got)) + } + }) +} + +func diffCodeActions(gotActions []protocol.CodeAction, want []string) string { + var gotTitles []string + for _, ca := range gotActions { + gotTitles = append(gotTitles, ca.Title) + } + return cmp.Diff(want, gotTitles) +} + +const workspace2 = ` +-- go.mod -- +module golang.org/entry + +go 1.18 + +require golang.org/bmod v0.5.0 + +-- go.sum -- +golang.org/bmod v0.5.0 h1:MT/ysNRGbCiURc5qThRFWaZ5+rK3pQRPo9w7dYZfMDk= +golang.org/bmod v0.5.0/go.mod h1:k+zl+Ucu4yLIjndMIuWzD/MnOHy06wqr3rD++y0abVs= +-- x/x.go -- +package x + +import "golang.org/bmod/bvuln" + +func F() { + // Calls a benign func in bvuln. + bvuln.OK() +} +` + +const proxy2 = ` +-- golang.org/bmod@v0.5.0/bvuln/bvuln.go -- +package bvuln + +func Vuln() {} // vulnerable. +func OK() {} // ok. +` + +func TestGovulncheckInfo(t *testing.T) { + db, opts, err := vulnTestEnv(proxy2) + if err != nil { + t.Fatal(err) + } + defer db.Clean() + WithOptions(opts...).Run(t, workspace2, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + var result command.RunVulncheckResult + env.ExecuteCodeLensCommand("go.mod", command.RunGovulncheck, &result) + gotDiagnostics := &protocol.PublishDiagnosticsParams{} + env.OnceMet( + CompletedProgress(result.Token, nil), + ShownMessage("No vulnerabilities found"), // only count affecting vulnerabilities. + ) + + // Vulncheck diagnostics asynchronous to the vulncheck command. + env.OnceMet( + Diagnostics(env.AtRegexp("go.mod", "golang.org/bmod")), + ReadDiagnostics("go.mod", gotDiagnostics), + ) + + testFetchVulncheckResult(t, env, map[string]fetchVulncheckResult{"go.mod": {IDs: []string{"GO-2022-02", "GO-2022-04"}, Mode: vulncheck.ModeGovulncheck}}) + // wantDiagnostics maps a module path in the require + // section of a go.mod to diagnostics that will be returned + // when running vulncheck. + wantDiagnostics := map[string]vulnDiagExpectation{ + "golang.org/bmod": { + diagnostics: []vulnDiag{ + { + msg: "golang.org/bmod has a vulnerability GO-2022-02 that is not used in the code.", + severity: protocol.SeverityInformation, + source: string(cache.Govulncheck), + codeActions: []string{ + "Reset govulncheck result", + }, + }, + }, + codeActions: []string{ + "Reset govulncheck result", + }, + hover: []string{"GO-2022-02", "vuln in bmod (no fix)", "No fix is available."}, + }, + } + + var allActions []protocol.CodeAction + for mod, want := range wantDiagnostics { + modPathDiagnostics := testVulnDiagnostics(t, env, mod, want, gotDiagnostics) + // Check that the actions we get when including all diagnostics at a location return the same result + gotActions := env.CodeActionForFile("go.mod", modPathDiagnostics) + allActions = append(allActions, gotActions...) + if diff := diffCodeActions(gotActions, want.codeActions); diff != "" { + t.Errorf("code actions for %q do not match, expected %v, got %v\n%v\n", mod, want.codeActions, gotActions, diff) + continue + } + } + + // Clear Diagnostics by using one of the reset code actions. + var reset protocol.CodeAction + for _, a := range allActions { + if a.Title == "Reset govulncheck result" { + reset = a + break + } + } + if reset.Title != "Reset govulncheck result" { + t.Errorf("failed to find a 'Reset govulncheck result' code action, got %v", allActions) + } + env.ApplyCodeAction(reset) + + env.Await(NoDiagnostics(ForFile("go.mod"))) + }) +} + +// testVulnDiagnostics finds the require or module statement line for the requireMod in go.mod file +// and runs checks if diagnostics and code actions associated with the line match expectation. +func testVulnDiagnostics(t *testing.T, env *Env, pattern string, want vulnDiagExpectation, got *protocol.PublishDiagnosticsParams) []protocol.Diagnostic { + t.Helper() + loc := env.RegexpSearch("go.mod", pattern) + var modPathDiagnostics []protocol.Diagnostic + for _, w := range want.diagnostics { + // Find the diagnostics at loc.start. + var diag *protocol.Diagnostic + for _, g := range got.Diagnostics { + g := g + if g.Range.Start == loc.Range.Start && w.msg == g.Message { + modPathDiagnostics = append(modPathDiagnostics, g) + diag = &g + break + } + } + if diag == nil { + t.Errorf("no diagnostic at %q matching %q found\n", pattern, w.msg) + continue + } + if diag.Severity != w.severity || diag.Source != w.source { + t.Errorf("incorrect (severity, source) for %q, want (%s, %s) got (%s, %s)\n", w.msg, w.severity, w.source, diag.Severity, diag.Source) + } + // Check expected code actions appear. + gotActions := env.CodeActionForFile("go.mod", []protocol.Diagnostic{*diag}) + if diff := diffCodeActions(gotActions, w.codeActions); diff != "" { + t.Errorf("code actions for %q do not match, want %v, got %v\n%v\n", w.msg, w.codeActions, gotActions, diff) + continue + } + } + // Check that useful info is supplemented as hover. + if len(want.hover) > 0 { + hover, _ := env.Hover(loc) + for _, part := range want.hover { + if !strings.Contains(hover.Value, part) { + t.Errorf("hover contents for %q do not match, want %v, got %v\n", pattern, strings.Join(want.hover, ","), hover.Value) + break + } + } + } + return modPathDiagnostics +} + +type vulnRelatedInfo struct { + Filename string + Line uint32 + Message string +} + +type vulnDiag struct { + msg string + severity protocol.DiagnosticSeverity + // codeActions is a list titles of code actions that we get with this + // diagnostics as the context. + codeActions []string + // relatedInfo is related info message prefixed by the file base. + // See summarizeRelatedInfo. + relatedInfo []vulnRelatedInfo + // diagnostic source. + source string +} + +// vulnDiagExpectation maps a module path in the require +// section of a go.mod to diagnostics that will be returned +// when running vulncheck. +type vulnDiagExpectation struct { + // applyAction is the title of the code action to run for this module. + // If empty, no code actions will be executed. + applyAction string + // diagnostics is the list of diagnostics we expect at the require line for + // the module path. + diagnostics []vulnDiag + // codeActions is a list titles of code actions that we get with context + // diagnostics. + codeActions []string + // hover message is the list of expected hover message parts for this go.mod require line. + // all parts must appear in the hover message. + hover []string +} diff --git a/contribs/gnopls/internal/test/integration/misc/webserver_test.go b/contribs/gnopls/internal/test/integration/misc/webserver_test.go new file mode 100644 index 00000000000..f8038f5721d --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/webserver_test.go @@ -0,0 +1,495 @@ +// 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 misc + +import ( + "fmt" + "html" + "io" + "net/http" + "regexp" + "runtime" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/settings" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/internal/testenv" +) + +// TestWebServer exercises the web server created on demand +// for code actions such as "Browse package documentation". +func TestWebServer(t *testing.T) { + const files = ` +-- go.mod -- +module example.com + +-- a/a.go -- +package a + +const A = 1 + +type G[T any] int +func (G[T]) F(int, int, int, int, int, int, int, ...int) {} + +// EOF +` + Run(t, files, func(t *testing.T, env *Env) { + // Assert that the HTML page contains the expected const declaration. + // (We may need to make allowances for HTML markup.) + env.OpenFile("a/a.go") + uri1 := viewPkgDoc(t, env, env.Sandbox.Workdir.EntireFile("a/a.go")) + doc1 := get(t, uri1) + checkMatch(t, true, doc1, "const A =.*1") + + // Regression test for signature truncation (#67287, #67294). + checkMatch(t, true, doc1, regexp.QuoteMeta("func (G[T]) F(int, int, int, ...)")) + + // Check that edits to the buffer (even unsaved) are + // reflected in the HTML document. + env.RegexpReplace("a/a.go", "// EOF", "func NewFunc() {}") + env.Await(env.DoneDiagnosingChanges()) + doc2 := get(t, uri1) + checkMatch(t, true, doc2, "func NewFunc") + + // TODO(adonovan): assert some basic properties of the + // HTML document using something like + // golang.org/x/pkgsite/internal/testing/htmlcheck. + + // Grab the URL in the HTML source link for NewFunc. + // (We don't have a DOM or JS interpreter so we have + // to know something of the document internals here.) + rx := regexp.MustCompile(`<h3 id='NewFunc'.*httpGET\("(.*)"\)`) + srcURL := html.UnescapeString(string(rx.FindSubmatch(doc2)[1])) + + // Fetch the document. Its result isn't important, + // but it must have the side effect of another showDocument + // downcall, this time for a "file:" URL, causing the + // client editor to navigate to the source file. + t.Log("extracted /src URL", srcURL) + get(t, srcURL) + + // Check that that shown location is that of NewFunc. + shownSource := shownDocument(t, env, "file:") + gotLoc := protocol.Location{ + URI: protocol.DocumentURI(shownSource.URI), // fishy conversion + Range: *shownSource.Selection, + } + t.Log("showDocument(source file) URL:", gotLoc) + wantLoc := env.RegexpSearch("a/a.go", `func ()NewFunc`) + if gotLoc != wantLoc { + t.Errorf("got location %v, want %v", gotLoc, wantLoc) + } + }) +} + +func TestPkgDocNoPanic66449(t *testing.T) { + // This particular input triggered a latent bug in doc.New + // that would corrupt the AST while filtering out unexported + // symbols such as b, causing nodeHTML to panic. + // Now it doesn't crash. + // + // We also check cross-reference anchors for all symbols. + const files = ` +-- go.mod -- +module example.com + +-- a/a.go -- +package a + +// The 'π' suffix is to elimimate spurious matches with other HTML substrings, +// in particular the random base64 secret tokens that appear in gopls URLs. + +var Vπ, vπ = 0, 0 +const Cπ, cπ = 0, 0 + +func Fπ() +func fπ() + +type Tπ int +type tπ int + +func (Tπ) Mπ() {} +func (Tπ) mπ() {} + +func (tπ) Mπ() {} +func (tπ) mπ() {} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + uri1 := viewPkgDoc(t, env, env.Sandbox.Workdir.EntireFile("a/a.go")) + + doc := get(t, uri1) + // (Ideally our code rendering would also + // eliminate unexported symbols...) + checkMatch(t, true, doc, "var Vπ, vπ = .*0.*0") + checkMatch(t, true, doc, "const Cπ, cπ = .*0.*0") + + // Unexported funcs/types/... must still be discarded. + checkMatch(t, true, doc, "Fπ") + checkMatch(t, false, doc, "fπ") + checkMatch(t, true, doc, "Tπ") + checkMatch(t, false, doc, "tπ") + + // Also, check that anchors exist (only) for exported symbols. + // exported: + checkMatch(t, true, doc, "<a id='Vπ'") + checkMatch(t, true, doc, "<a id='Cπ'") + checkMatch(t, true, doc, "<h3 id='Tπ'") + checkMatch(t, true, doc, "<h3 id='Fπ'") + checkMatch(t, true, doc, "<h4 id='Tπ.Mπ'") + // unexported: + checkMatch(t, false, doc, "<a id='vπ'") + checkMatch(t, false, doc, "<a id='cπ'") + checkMatch(t, false, doc, "<h3 id='tπ'") + checkMatch(t, false, doc, "<h3 id='fπ'") + checkMatch(t, false, doc, "<h4 id='Tπ.mπ'") + checkMatch(t, false, doc, "<h4 id='tπ.Mπ'") + checkMatch(t, false, doc, "<h4 id='tπ.mπ'") + }) +} + +// TestPkgDocNavigation tests that the symbol selector and index of +// symbols are well formed. +func TestPkgDocNavigation(t *testing.T) { + const files = ` +-- go.mod -- +module example.com + +-- a/a.go -- +package a + +func Func1(int, string, bool, []string) (int, error) +func Func2(x, y int, a, b string) (int, error) + +type Type struct {} +func (t Type) Method() {} +func (p *Type) PtrMethod() {} + +func Constructor() Type +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + uri1 := viewPkgDoc(t, env, env.Sandbox.Workdir.EntireFile("a/a.go")) + doc := get(t, uri1) + + q := regexp.QuoteMeta + + // selector + checkMatch(t, true, doc, q(`<option label='Func1(_, _, _, _)' value='#Func1'/>`)) + checkMatch(t, true, doc, q(`<option label='Func2(x, y, a, b)' value='#Func2'/>`)) + checkMatch(t, true, doc, q(`<option label='Type' value='#Type'/>`)) + checkMatch(t, true, doc, q(`<option label='Constructor()' value='#Constructor'/>`)) + checkMatch(t, true, doc, q(`<option label='(t) Method()' value='#Type.Method'/>`)) + checkMatch(t, true, doc, q(`<option label='(p) PtrMethod()' value='#Type.PtrMethod'/>`)) + + // index + checkMatch(t, true, doc, q(`<li><a href='#Func1'>func Func1(int, string, bool, ...) (int, error)</a></li>`)) + checkMatch(t, true, doc, q(`<li><a href='#Func2'>func Func2(x int, y int, a string, ...) (int, error)</a></li>`)) + checkMatch(t, true, doc, q(`<li><a href='#Type'>type Type</a></li>`)) + checkMatch(t, true, doc, q(`<li><a href='#Constructor'>func Constructor() Type</a></li>`)) + checkMatch(t, true, doc, q(`<li><a href='#Type.Method'>func (t Type) Method()</a></li>`)) + checkMatch(t, true, doc, q(`<li><a href='#Type.PtrMethod'>func (p *Type) PtrMethod()</a></li>`)) + }) +} + +// TestPkgDocContext tests that the gopls.doc command title and /pkg +// URL are appropriate for the current selection. It is effectively a +// test of golang.DocFragment. +func TestPkgDocContext(t *testing.T) { + const files = ` +-- go.mod -- +module example.com + +-- a/a.go -- +package a + +import "fmt" +import "bytes" + +func A() { + fmt.Println() + new(bytes.Buffer).Write(nil) +} + +const K = 123 + +type T int +func (*T) M() { /*in T.M*/} + +` + + viewRE := regexp.MustCompile("view=[0-9]*") + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + for _, test := range []struct { + re string // regexp indicating selected portion of input file + want string // suffix of expected URL after /pkg/ + }{ + // current package + {"package a", "example.com/a?view=1"}, // outside any decl + {"in T.M", "example.com/a?view=1#T.M"}, // inside method (*T).M + {"123", "example.com/a?view=1#K"}, // inside const/var decl + {"T int", "example.com/a?view=1#T"}, // inside type decl + + // imported + {"\"fmt\"", "fmt?view=1"}, // in import spec + {"fmt[.]", "fmt?view=1"}, // use of PkgName + {"Println", "fmt?view=1#Println"}, // use of imported pkg-level symbol + {"fmt.Println", "fmt?view=1#Println"}, // qualified identifier + {"Write", "bytes?view=1#Buffer.Write"}, // use of imported method + + // TODO(adonovan): + // - xtest package -> ForTest + // - field of imported struct -> nope + // - exported method of nonexported type from another package + // (e.g. types.Named.Obj) -> nope + // Also: assert that Command.Title looks nice. + } { + uri := viewPkgDoc(t, env, env.RegexpSearch("a/a.go", test.re)) + _, got, ok := strings.Cut(uri, "/pkg/") + if !ok { + t.Errorf("pattern %q => %s (invalid /pkg URL)", test.re, uri) + continue + } + + // Normalize the view ID, which varies by integration test mode. + got = viewRE.ReplaceAllString(got, "view=1") + + if got != test.want { + t.Errorf("pattern %q => %s; want %s", test.re, got, test.want) + } + } + }) +} + +// viewPkgDoc invokes the "Browse package documentation" code action +// at the specified location. It returns the URI of the document, or +// fails the test. +func viewPkgDoc(t *testing.T, env *Env, loc protocol.Location) protocol.URI { + // Invoke the "Browse package documentation" code + // action to start the server. + actions := env.CodeAction(loc, nil, 0) + docAction, err := codeActionByKind(actions, settings.GoDoc) + if err != nil { + t.Fatal(err) + } + + // Execute the command. + // Its side effect should be a single showDocument request. + params := &protocol.ExecuteCommandParams{ + Command: docAction.Command.Command, + Arguments: docAction.Command.Arguments, + } + var result command.DebuggingResult + env.ExecuteCommand(params, &result) + + doc := shownDocument(t, env, "http:") + if doc == nil { + t.Fatalf("no showDocument call had 'http:' prefix") + } + if false { + t.Log("showDocument(package doc) URL:", doc.URI) + } + return doc.URI +} + +// TestFreeSymbols is a basic test of interaction with the "free symbols" web report. +func TestFreeSymbols(t *testing.T) { + const files = ` +-- go.mod -- +module example.com + +-- a/a.go -- +package a + +import "fmt" +import "bytes" + +func f(buf bytes.Buffer, greeting string) { +/* « */ + fmt.Fprintf(&buf, "%s", greeting) + buf.WriteString(fmt.Sprint("foo")) + buf.WriteByte(0) +/* » */ + buf.Write(nil) +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + + // Invoke the "Browse free symbols" code + // action to start the server. + loc := env.RegexpSearch("a/a.go", "«((?:.|\n)*)»") + actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger) + if err != nil { + t.Fatalf("CodeAction: %v", err) + } + action, err := codeActionByKind(actions, settings.GoFreeSymbols) + if err != nil { + t.Fatal(err) + } + + // Execute the command. + // Its side effect should be a single showDocument request. + params := &protocol.ExecuteCommandParams{ + Command: action.Command.Command, + Arguments: action.Command.Arguments, + } + var result command.DebuggingResult + env.ExecuteCommand(params, &result) + doc := shownDocument(t, env, "http:") + if doc == nil { + t.Fatalf("no showDocument call had 'file:' prefix") + } + t.Log("showDocument(package doc) URL:", doc.URI) + + // Get the report and do some minimal checks for sensible results. + report := get(t, doc.URI) + checkMatch(t, true, report, `<li>import "<a .*'>fmt</a>" // for Fprintf, Sprint</li>`) + checkMatch(t, true, report, `<li>var <a .*>buf</a> bytes.Buffer</li>`) + checkMatch(t, true, report, `<li>func <a .*>WriteByte</a> func\(c byte\) error</li>`) + checkMatch(t, true, report, `<li>func <a .*>WriteString</a> func\(s string\) \(n int, err error\)</li>`) + checkMatch(t, false, report, `<li>func <a .*>Write</a>`) // not in selection + checkMatch(t, true, report, `<li>var <a .*>greeting</a> string</li>`) + }) +} + +// TestAssembly is a basic test of the web-based assembly listing. +func TestAssembly(t *testing.T) { + testenv.NeedsGo1Point(t, 22) // for up-to-date assembly listing + + const files = ` +-- go.mod -- +module example.com + +-- a/a.go -- +package a + +func f() { + println("hello") + defer println("world") +} + +func g() { + println("goodbye") +} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + + // Invoke the "Browse assembly" code action to start the server. + loc := env.RegexpSearch("a/a.go", "println") + actions, err := env.Editor.CodeAction(env.Ctx, loc, nil, protocol.CodeActionUnknownTrigger) + if err != nil { + t.Fatalf("CodeAction: %v", err) + } + action, err := codeActionByKind(actions, settings.GoAssembly) + if err != nil { + t.Fatal(err) + } + + // Execute the command. + // Its side effect should be a single showDocument request. + params := &protocol.ExecuteCommandParams{ + Command: action.Command.Command, + Arguments: action.Command.Arguments, + } + var result command.DebuggingResult + env.ExecuteCommand(params, &result) + doc := shownDocument(t, env, "http:") + if doc == nil { + t.Fatalf("no showDocument call had 'file:' prefix") + } + t.Log("showDocument(package doc) URL:", doc.URI) + + // Get the report and do some minimal checks for sensible results. + // + // Use only portable instructions below! Remember that + // this is a test of plumbing, not compilation, so + // it's better to skip the tests, rather than refine + // them, on any architecture that gives us trouble + // (e.g. uses JAL for CALL, or BL<cc> for RET). + // We conservatively test only on the two most popular + // architectures. + report := get(t, doc.URI) + checkMatch(t, true, report, `TEXT.*example.com/a.f`) + switch runtime.GOARCH { + case "amd64", "arm64": + checkMatch(t, true, report, `CALL runtime.printlock`) + checkMatch(t, true, report, `CALL runtime.printstring`) + checkMatch(t, true, report, `CALL runtime.printunlock`) + checkMatch(t, true, report, `CALL example.com/a.f.deferwrap1`) + checkMatch(t, true, report, `RET`) + checkMatch(t, true, report, `CALL runtime.morestack_noctxt`) + } + + // Nested functions are also shown. + checkMatch(t, true, report, `TEXT.*example.com/a.f.deferwrap1`) + + // But other functions are not. + checkMatch(t, false, report, `TEXT.*example.com/a.g`) + }) +} + +// shownDocument returns the first shown document matching the URI prefix. +// It may be nil. +// As a side effect, it clears the list of accumulated shown documents. +func shownDocument(t *testing.T, env *Env, prefix string) *protocol.ShowDocumentParams { + t.Helper() + var shown []*protocol.ShowDocumentParams + env.Await(ShownDocuments(&shown)) + env.Awaiter.ResetShownDocuments() // REVIEWERS: seems like a hack; better ideas? + var first *protocol.ShowDocumentParams + for _, sd := range shown { + if strings.HasPrefix(sd.URI, prefix) { + if first != nil { + t.Errorf("got multiple showDocument requests: %#v", shown) + break + } + first = sd + } + } + return first +} + +// get fetches the content of a document over HTTP. +func get(t *testing.T, url string) []byte { + t.Helper() + resp, err := http.Get(url) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + got, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + return got +} + +// checkMatch asserts that got matches (or doesn't match, if !want) the pattern. +func checkMatch(t *testing.T, want bool, got []byte, pattern string) { + t.Helper() + if regexp.MustCompile(pattern).Match(got) != want { + if want { + t.Errorf("input did not match wanted pattern %q; got:\n%s", pattern, got) + } else { + t.Errorf("input matched unwanted pattern %q; got:\n%s", pattern, got) + } + } +} + +// codeActionByKind returns the first action of (exactly) the specified kind, or an error. +func codeActionByKind(actions []protocol.CodeAction, kind protocol.CodeActionKind) (*protocol.CodeAction, error) { + for _, act := range actions { + if act.Kind == kind { + return &act, nil + } + } + return nil, fmt.Errorf("can't find action with kind %s, only %#v", kind, actions) +} diff --git a/contribs/gnopls/internal/test/integration/misc/workspace_symbol_test.go b/contribs/gnopls/internal/test/integration/misc/workspace_symbol_test.go new file mode 100644 index 00000000000..9420b146d85 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/misc/workspace_symbol_test.go @@ -0,0 +1,114 @@ +// 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 misc + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/settings" +) + +func TestWorkspaceSymbolMissingMetadata(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.17 +-- a.go -- +package p + +const K1 = "a.go" +-- exclude.go -- + +//go:build exclude +// +build exclude + +package exclude + +const K2 = "exclude.go" +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + checkSymbols(env, "K", "K1") + + // Opening up an ignored file will result in an overlay with missing + // metadata, but this shouldn't break workspace symbols requests. + env.OpenFile("exclude.go") + checkSymbols(env, "K", "K1") + }) +} + +func TestWorkspaceSymbolSorting(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.17 +-- a/a.go -- +package a + +const ( + Foo = iota + FooBar + Fooey + Fooex + Fooest +) +` + + var symbolMatcher = string(settings.SymbolFastFuzzy) + WithOptions( + Settings{"symbolMatcher": symbolMatcher}, + ).Run(t, files, func(t *testing.T, env *Env) { + checkSymbols(env, "Foo", + "Foo", // prefer exact segment matches first + "FooBar", // ...followed by exact word matches + "Fooex", // shorter than Fooest, FooBar, lexically before Fooey + "Fooey", // shorter than Fooest, Foobar + "Fooest", + ) + }) +} + +func TestWorkspaceSymbolSpecialPatterns(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.17 +-- a/a.go -- +package a + +const ( + AxxBxxCxx + ABC +) +` + + var symbolMatcher = string(settings.SymbolFastFuzzy) + WithOptions( + Settings{"symbolMatcher": symbolMatcher}, + ).Run(t, files, func(t *testing.T, env *Env) { + checkSymbols(env, "ABC", "ABC", "AxxBxxCxx") + checkSymbols(env, "'ABC", "ABC") + checkSymbols(env, "^mod.com", "mod.com/a.ABC", "mod.com/a.AxxBxxCxx") + checkSymbols(env, "^mod.com Axx", "mod.com/a.AxxBxxCxx") + checkSymbols(env, "C$", "ABC") + }) +} + +func checkSymbols(env *Env, query string, want ...string) { + env.T.Helper() + var got []string + for _, info := range env.Symbol(query) { + got = append(got, info.Name) + } + if diff := cmp.Diff(got, want); diff != "" { + env.T.Errorf("unexpected Symbol(%q) result (+want -got):\n%s", query, diff) + } +} diff --git a/contribs/gnopls/internal/test/integration/modfile/modfile_test.go b/contribs/gnopls/internal/test/integration/modfile/modfile_test.go new file mode 100644 index 00000000000..243bb04e960 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/modfile/modfile_test.go @@ -0,0 +1,1219 @@ +// 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 modfile + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/test/compare" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/util/bug" + + "golang.org/x/tools/gopls/internal/protocol" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(Main(m)) +} + +const workspaceProxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +func SaySomething() { + fmt.Println("something") +} +-- random.org@v1.2.3/go.mod -- +module random.org + +go 1.12 +-- random.org@v1.2.3/bye/bye.go -- +package bye + +func Goodbye() { + println("Bye") +} +` + +const proxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +const Name = "Blah" +-- random.org@v1.2.3/go.mod -- +module random.org + +go 1.12 +-- random.org@v1.2.3/blah/blah.go -- +package hello + +const Name = "Hello" +` + +func TestModFileModification(t *testing.T) { + const untidyModule = ` +-- a/go.mod -- +module mod.com + +-- a/main.go -- +package main + +import "example.com/blah" + +func main() { + println(blah.Name) +} +` + + runner := RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + } + + t.Run("basic", func(t *testing.T) { + runner.Run(t, untidyModule, func(t *testing.T, env *Env) { + // Open the file and make sure that the initial workspace load does not + // modify the go.mod file. + goModContent := env.ReadWorkspaceFile("a/go.mod") + env.OpenFile("a/main.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", "\"example.com/blah\"")), + ) + if got := env.ReadWorkspaceFile("a/go.mod"); got != goModContent { + t.Fatalf("go.mod changed on disk:\n%s", compare.Text(goModContent, got)) + } + // Save the buffer, which will format and organize imports. + // Confirm that the go.mod file still does not change. + env.SaveBuffer("a/main.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", "\"example.com/blah\"")), + ) + if got := env.ReadWorkspaceFile("a/go.mod"); got != goModContent { + t.Fatalf("go.mod changed on disk:\n%s", compare.Text(goModContent, got)) + } + }) + }) + + // Reproduce golang/go#40269 by deleting and recreating main.go. + t.Run("delete main.go", func(t *testing.T) { + runner.Run(t, untidyModule, func(t *testing.T, env *Env) { + goModContent := env.ReadWorkspaceFile("a/go.mod") + mainContent := env.ReadWorkspaceFile("a/main.go") + env.OpenFile("a/main.go") + env.SaveBuffer("a/main.go") + + // Ensure that we're done processing all the changes caused by opening + // and saving above. If not, we may run into a file locking issue on + // windows. + // + // If this proves insufficient, env.RemoveWorkspaceFile can be updated to + // retry file lock errors on windows. + env.AfterChange() + env.RemoveWorkspaceFile("a/main.go") + + // TODO(rfindley): awaiting here shouldn't really be necessary. We should + // be consistent eventually. + // + // Probably this was meant to exercise a race with the change below. + env.AfterChange() + + env.WriteWorkspaceFile("a/main.go", mainContent) + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", "\"example.com/blah\"")), + ) + if got := env.ReadWorkspaceFile("a/go.mod"); got != goModContent { + t.Fatalf("go.mod changed on disk:\n%s", compare.Text(goModContent, got)) + } + }) + }) +} + +func TestGoGetFix(t *testing.T) { + const mod = ` +-- a/go.mod -- +module mod.com + +go 1.12 + +-- a/main.go -- +package main + +import "example.com/blah" + +var _ = blah.Name +` + + const want = `module mod.com + +go 1.12 + +require example.com v1.2.3 +` + + RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + }.Run(t, mod, func(t *testing.T, env *Env) { + if strings.Contains(t.Name(), "workspace_module") { + t.Skip("workspace module mode doesn't set -mod=readonly") + } + env.OpenFile("a/main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", `"example.com/blah"`)), + ReadDiagnostics("a/main.go", &d), + ) + var goGetDiag protocol.Diagnostic + for _, diag := range d.Diagnostics { + if strings.Contains(diag.Message, "could not import") { + goGetDiag = diag + } + } + env.ApplyQuickFixes("a/main.go", []protocol.Diagnostic{goGetDiag}) + if got := env.ReadWorkspaceFile("a/go.mod"); got != want { + t.Fatalf("unexpected go.mod content:\n%s", compare.Text(want, got)) + } + }) +} + +// Tests that multiple missing dependencies gives good single fixes. +func TestMissingDependencyFixes(t *testing.T) { + const mod = ` +-- a/go.mod -- +module mod.com + +go 1.12 + +-- a/main.go -- +package main + +import "example.com/blah" +import "random.org/blah" + +var _, _ = blah.Name, hello.Name +` + + const want = `module mod.com + +go 1.12 + +require random.org v1.2.3 +` + + RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + }.Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a/main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", `"random.org/blah"`)), + ReadDiagnostics("a/main.go", &d), + ) + var randomDiag protocol.Diagnostic + for _, diag := range d.Diagnostics { + if strings.Contains(diag.Message, "random.org") { + randomDiag = diag + } + } + env.ApplyQuickFixes("a/main.go", []protocol.Diagnostic{randomDiag}) + if got := env.ReadWorkspaceFile("a/go.mod"); got != want { + t.Fatalf("unexpected go.mod content:\n%s", compare.Text(want, got)) + } + }) +} + +// Tests that multiple missing dependencies gives good single fixes. +func TestMissingDependencyFixesWithGoWork(t *testing.T) { + const mod = ` +-- go.work -- +go 1.18 + +use ( + ./a +) +-- a/go.mod -- +module mod.com + +go 1.12 + +-- a/main.go -- +package main + +import "example.com/blah" +import "random.org/blah" + +var _, _ = blah.Name, hello.Name +` + + const want = `module mod.com + +go 1.12 + +require random.org v1.2.3 +` + + RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + }.Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a/main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", `"random.org/blah"`)), + ReadDiagnostics("a/main.go", &d), + ) + var randomDiag protocol.Diagnostic + for _, diag := range d.Diagnostics { + if strings.Contains(diag.Message, "random.org") { + randomDiag = diag + } + } + env.ApplyQuickFixes("a/main.go", []protocol.Diagnostic{randomDiag}) + if got := env.ReadWorkspaceFile("a/go.mod"); got != want { + t.Fatalf("unexpected go.mod content:\n%s", compare.Text(want, got)) + } + }) +} + +func TestIndirectDependencyFix(t *testing.T) { + const mod = ` +-- a/go.mod -- +module mod.com + +go 1.12 + +require example.com v1.2.3 // indirect +-- a/go.sum -- +example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- a/main.go -- +package main + +import "example.com/blah" + +func main() { + fmt.Println(blah.Name) +` + const want = `module mod.com + +go 1.12 + +require example.com v1.2.3 +` + + RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + }.Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("a/go.mod") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("a/go.mod", "// indirect")), + ReadDiagnostics("a/go.mod", &d), + ) + env.ApplyQuickFixes("a/go.mod", d.Diagnostics) + if got := env.BufferText("a/go.mod"); got != want { + t.Fatalf("unexpected go.mod content:\n%s", compare.Text(want, got)) + } + }) +} + +// Test to reproduce golang/go#39041. It adds a new require to a go.mod file +// that already has an unused require. +func TestNewDepWithUnusedDep(t *testing.T) { + + const proxy = ` +-- github.com/esimov/caire@v1.2.5/go.mod -- +module github.com/esimov/caire + +go 1.12 +-- github.com/esimov/caire@v1.2.5/caire.go -- +package caire + +func RemoveTempImage() {} +-- google.golang.org/protobuf@v1.20.0/go.mod -- +module google.golang.org/protobuf + +go 1.12 +-- google.golang.org/protobuf@v1.20.0/hello/hello.go -- +package hello +` + const repro = ` +-- a/go.mod -- +module mod.com + +go 1.14 + +require google.golang.org/protobuf v1.20.0 +-- a/go.sum -- +github.com/esimov/caire v1.2.5 h1:OcqDII/BYxcBYj3DuwDKjd+ANhRxRqLa2n69EGje7qw= +github.com/esimov/caire v1.2.5/go.mod h1:mXnjRjg3+WUtuhfSC1rKRmdZU9vJZyS1ZWU0qSvJhK8= +google.golang.org/protobuf v1.20.0 h1:y9T1vAtFKQg0faFNMOxJU7WuEqPWolVkjIkU6aI8qCY= +google.golang.org/protobuf v1.20.0/go.mod h1:FcqsytGClbtLv1ot8NvsJHjBi0h22StKVP+K/j2liKA= +-- a/main.go -- +package main + +import ( + "github.com/esimov/caire" +) + +func _() { + caire.RemoveTempImage() +}` + + RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + }.Run(t, repro, func(t *testing.T, env *Env) { + env.OpenFile("a/main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", `"github.com/esimov/caire"`)), + ReadDiagnostics("a/main.go", &d), + ) + env.ApplyQuickFixes("a/main.go", d.Diagnostics) + want := `module mod.com + +go 1.14 + +require ( + github.com/esimov/caire v1.2.5 + google.golang.org/protobuf v1.20.0 +) +` + if got := env.ReadWorkspaceFile("a/go.mod"); got != want { + t.Fatalf("TestNewDepWithUnusedDep failed:\n%s", compare.Text(want, got)) + } + }) +} + +// TODO: For this test to be effective, the sandbox's file watcher must respect +// the file watching GlobPattern in the capability registration. See +// golang/go#39384. +func TestModuleChangesOnDisk(t *testing.T) { + const mod = ` +-- a/go.mod -- +module mod.com + +go 1.12 + +require example.com v1.2.3 +-- a/go.sum -- +example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- a/main.go -- +package main + +func main() { + fmt.Println(blah.Name) +` + RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + }.Run(t, mod, func(t *testing.T, env *Env) { + // With zero-config gopls, we must open a/main.go to have a View including a/go.mod. + env.OpenFile("a/main.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/go.mod", "require")), + ) + env.RunGoCommandInDir("a", "mod", "tidy") + env.AfterChange( + NoDiagnostics(ForFile("a/go.mod")), + ) + }) +} + +// Tests golang/go#39784: a missing indirect dependency, necessary +// due to blah@v2.0.0's incomplete go.mod file. +func TestBadlyVersionedModule(t *testing.T) { + const proxy = ` +-- example.com/blah/@v/v1.0.0.mod -- +module example.com + +go 1.12 +-- example.com/blah@v1.0.0/blah.go -- +package blah + +const Name = "Blah" +-- example.com/blah/v2/@v/v2.0.0.mod -- +module example.com + +go 1.12 +-- example.com/blah/v2@v2.0.0/blah.go -- +package blah + +import "example.com/blah" + +var V1Name = blah.Name +const Name = "Blah" +` + const files = ` +-- a/go.mod -- +module mod.com + +go 1.12 + +require example.com/blah/v2 v2.0.0 +-- a/go.sum -- +example.com/blah v1.0.0 h1:kGPlWJbMsn1P31H9xp/q2mYI32cxLnCvauHN0AVaHnc= +example.com/blah v1.0.0/go.mod h1:PZUQaGFeVjyDmAE8ywmLbmDn3fj4Ws8epg4oLuDzW3M= +example.com/blah/v2 v2.0.0 h1:DNPsFPkKtTdxclRheaMCiYAoYizp6PuBzO0OmLOO0pY= +example.com/blah/v2 v2.0.0/go.mod h1:UZiKbTwobERo/hrqFLvIQlJwQZQGxWMVY4xere8mj7w= +-- a/main.go -- +package main + +import "example.com/blah/v2" + +var _ = blah.Name +` + RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + }.Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/main.go") + env.OpenFile("a/go.mod") + var modDiags protocol.PublishDiagnosticsParams + env.AfterChange( + // We would like for the error to appear in the v2 module, but + // as of writing non-workspace packages are not diagnosed. + Diagnostics(env.AtRegexp("a/main.go", `"example.com/blah/v2"`), WithMessage("no required module provides")), + Diagnostics(env.AtRegexp("a/go.mod", `require example.com/blah/v2`), WithMessage("no required module provides")), + ReadDiagnostics("a/go.mod", &modDiags), + ) + + env.ApplyQuickFixes("a/go.mod", modDiags.Diagnostics) + const want = `module mod.com + +go 1.12 + +require ( + example.com/blah v1.0.0 // indirect + example.com/blah/v2 v2.0.0 +) +` + env.SaveBuffer("a/go.mod") + env.AfterChange(NoDiagnostics(ForFile("a/main.go"))) + if got := env.BufferText("a/go.mod"); got != want { + t.Fatalf("suggested fixes failed:\n%s", compare.Text(want, got)) + } + }) +} + +// Reproduces golang/go#38232. +func TestUnknownRevision(t *testing.T) { + if runtime.GOOS == "plan9" { + t.Skipf("skipping test that fails for unknown reasons on plan9; see https://go.dev/issue/50477") + } + const unknown = ` +-- a/go.mod -- +module mod.com + +require ( + example.com v1.2.2 +) +-- a/main.go -- +package main + +import "example.com/blah" + +func main() { + var x = blah.Name +} +` + + runner := RunMultiple{ + {"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(proxy))}, + } + // Start from a bad state/bad IWL, and confirm that we recover. + t.Run("bad", func(t *testing.T) { + runner.Run(t, unknown, func(t *testing.T, env *Env) { + env.OpenFile("a/go.mod") + env.AfterChange( + Diagnostics(env.AtRegexp("a/go.mod", "example.com v1.2.2")), + ) + env.RegexpReplace("a/go.mod", "v1.2.2", "v1.2.3") + env.SaveBuffer("a/go.mod") // Save to trigger diagnostics. + + d := protocol.PublishDiagnosticsParams{} + env.AfterChange( + // Make sure the diagnostic mentions the new version -- the old diagnostic is in the same place. + Diagnostics(env.AtRegexp("a/go.mod", "example.com v1.2.3"), WithMessage("example.com@v1.2.3")), + ReadDiagnostics("a/go.mod", &d), + ) + qfs := env.GetQuickFixes("a/go.mod", d.Diagnostics) + if len(qfs) == 0 { + t.Fatalf("got 0 code actions to fix %v, wanted at least 1", d.Diagnostics) + } + env.ApplyCodeAction(qfs[0]) // Arbitrarily pick a single fix to apply. Applying all of them seems to cause trouble in this particular test. + env.SaveBuffer("a/go.mod") // Save to trigger diagnostics. + env.AfterChange( + NoDiagnostics(ForFile("a/go.mod")), + Diagnostics(env.AtRegexp("a/main.go", "x = ")), + ) + }) + }) + + const known = ` +-- a/go.mod -- +module mod.com + +require ( + example.com v1.2.3 +) +-- a/go.sum -- +example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- a/main.go -- +package main + +import "example.com/blah" + +func main() { + var x = blah.Name +} +` + // Start from a good state, transform to a bad state, and confirm that we + // still recover. + t.Run("good", func(t *testing.T) { + runner.Run(t, known, func(t *testing.T, env *Env) { + env.OpenFile("a/go.mod") + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", "x = ")), + ) + env.RegexpReplace("a/go.mod", "v1.2.3", "v1.2.2") + env.Editor.SaveBuffer(env.Ctx, "a/go.mod") // go.mod changes must be on disk + env.AfterChange( + Diagnostics(env.AtRegexp("a/go.mod", "example.com v1.2.2")), + ) + env.RegexpReplace("a/go.mod", "v1.2.2", "v1.2.3") + env.Editor.SaveBuffer(env.Ctx, "a/go.mod") // go.mod changes must be on disk + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", "x = ")), + ) + }) + }) +} + +// Confirm that an error in an indirect dependency of a requirement is surfaced +// as a diagnostic in the go.mod file. +func TestErrorInIndirectDependency(t *testing.T) { + const badProxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 + +require random.org v1.2.3 // indirect +-- example.com@v1.2.3/blah/blah.go -- +package blah + +const Name = "Blah" +-- random.org@v1.2.3/go.mod -- +module bob.org + +go 1.12 +-- random.org@v1.2.3/blah/blah.go -- +package hello + +const Name = "Hello" +` + const module = ` +-- a/go.mod -- +module mod.com + +go 1.14 + +require example.com v1.2.3 +-- a/main.go -- +package main + +import "example.com/blah" + +func main() { + println(blah.Name) +} +` + RunMultiple{ + {"default", WithOptions(ProxyFiles(badProxy), WorkspaceFolders("a"))}, + {"nested", WithOptions(ProxyFiles(badProxy))}, + }.Run(t, module, func(t *testing.T, env *Env) { + env.OpenFile("a/go.mod") + env.AfterChange( + Diagnostics(env.AtRegexp("a/go.mod", "require example.com v1.2.3")), + ) + }) +} + +// A copy of govim's config_set_env_goflags_mod_readonly test. +func TestGovimModReadonly(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.13 +-- main.go -- +package main + +import "example.com/blah" + +func main() { + println(blah.Name) +} +` + WithOptions( + EnvVars{"GOFLAGS": "-mod=readonly"}, + ProxyFiles(proxy), + Modes(Default), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + original := env.ReadWorkspaceFile("go.mod") + env.AfterChange( + Diagnostics(env.AtRegexp("main.go", `"example.com/blah"`)), + ) + got := env.ReadWorkspaceFile("go.mod") + if got != original { + t.Fatalf("go.mod file modified:\n%s", compare.Text(original, got)) + } + env.RunGoCommand("get", "example.com/blah@v1.2.3") + env.RunGoCommand("mod", "tidy") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +func TestMultiModuleModDiagnostics(t *testing.T) { + const mod = ` +-- go.work -- +go 1.18 + +use ( + a + b +) +-- a/go.mod -- +module moda.com + +go 1.14 + +require ( + example.com v1.2.3 +) +-- a/go.sum -- +example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- a/main.go -- +package main + +func main() {} +-- b/go.mod -- +module modb.com + +require example.com v1.2.3 + +go 1.14 +-- b/main.go -- +package main + +import "example.com/blah" + +func main() { + blah.SaySomething() +} +` + WithOptions( + ProxyFiles(workspaceProxy), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.AfterChange( + Diagnostics( + env.AtRegexp("a/go.mod", "example.com v1.2.3"), + WithMessage("is not used"), + ), + ) + }) +} + +func TestModTidyWithBuildTags(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.14 +-- main.go -- +// +build bob + +package main + +import "example.com/blah" + +func main() { + blah.SaySomething() +} +` + WithOptions( + ProxyFiles(workspaceProxy), + Settings{"buildFlags": []string{"-tags", "bob"}}, + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", `"example.com/blah"`)), + ) + }) +} + +func TestModTypoDiagnostic(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() {} +` + Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.RegexpReplace("go.mod", "module", "modul") + env.AfterChange( + Diagnostics(env.AtRegexp("go.mod", "modul")), + ) + }) +} + +func TestSumUpdateFixesDiagnostics(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 + +require ( + example.com v1.2.3 +) +-- go.sum -- +-- main.go -- +package main + +import ( + "example.com/blah" +) + +func main() { + println(blah.Name) +} +` + WithOptions( + ProxyFiles(workspaceProxy), + ).Run(t, mod, func(t *testing.T, env *Env) { + d := &protocol.PublishDiagnosticsParams{} + env.OpenFile("go.mod") + env.AfterChange( + Diagnostics( + env.AtRegexp("go.mod", `example.com v1.2.3`), + WithMessage("go.sum is out of sync"), + ), + ReadDiagnostics("go.mod", d), + ) + env.ApplyQuickFixes("go.mod", d.Diagnostics) + env.SaveBuffer("go.mod") // Save to trigger diagnostics. + env.AfterChange( + NoDiagnostics(ForFile("go.mod")), + ) + }) +} + +// This test confirms that editing a go.mod file only causes metadata +// to be invalidated when it's saved. +func TestGoModInvalidatesOnSave(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- main.go -- +package main + +func main() { + hello() +} +-- hello.go -- +package main + +func hello() {} +` + WithOptions( + // TODO(rFindley) this doesn't work in multi-module workspace mode, because + // it keeps around the last parsing modfile. Update this test to also + // exercise the workspace module. + Modes(Default), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + env.Await(env.DoneWithOpen()) + env.RegexpReplace("go.mod", "module", "modul") + // Confirm that we still have metadata with only on-disk edits. + env.OpenFile("main.go") + loc := env.GoToDefinition(env.RegexpSearch("main.go", "hello")) + if filepath.Base(string(loc.URI)) != "hello.go" { + t.Fatalf("expected definition in hello.go, got %s", loc.URI) + } + // Confirm that we no longer have metadata when the file is saved. + env.SaveBufferWithoutActions("go.mod") + _, err := env.Editor.Definition(env.Ctx, env.RegexpSearch("main.go", "hello")) + if err == nil { + t.Fatalf("expected error, got none") + } + }) +} + +func TestRemoveUnusedDependency(t *testing.T) { + const proxy = ` +-- hasdep.com@v1.2.3/go.mod -- +module hasdep.com + +go 1.12 + +require example.com v1.2.3 +-- hasdep.com@v1.2.3/a/a.go -- +package a +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +const Name = "Blah" +-- random.com@v1.2.3/go.mod -- +module random.com + +go 1.12 +-- random.com@v1.2.3/blah/blah.go -- +package blah + +const Name = "Blah" +` + t.Run("almost tidied", func(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 + +require hasdep.com v1.2.3 +-- go.sum -- +example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +hasdep.com v1.2.3 h1:00y+N5oD+SpKoqV1zP2VOPawcW65Zb9NebANY3GSzGI= +hasdep.com v1.2.3/go.mod h1:ePVZOlez+KZEOejfLPGL2n4i8qiAjrkhQZ4wcImqAes= +-- main.go -- +package main + +func main() {} +` + WithOptions( + ProxyFiles(proxy), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + d := &protocol.PublishDiagnosticsParams{} + env.AfterChange( + Diagnostics(env.AtRegexp("go.mod", "require hasdep.com v1.2.3")), + ReadDiagnostics("go.mod", d), + ) + const want = `module mod.com + +go 1.12 +` + env.ApplyQuickFixes("go.mod", d.Diagnostics) + if got := env.BufferText("go.mod"); got != want { + t.Fatalf("unexpected content in go.mod:\n%s", compare.Text(want, got)) + } + }) + }) + + t.Run("not tidied", func(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 + +require hasdep.com v1.2.3 +require random.com v1.2.3 +-- go.sum -- +example.com v1.2.3 h1:ihBTGWGjTU3V4ZJ9OmHITkU9WQ4lGdQkMjgyLFk0FaY= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +hasdep.com v1.2.3 h1:00y+N5oD+SpKoqV1zP2VOPawcW65Zb9NebANY3GSzGI= +hasdep.com v1.2.3/go.mod h1:ePVZOlez+KZEOejfLPGL2n4i8qiAjrkhQZ4wcImqAes= +random.com v1.2.3 h1:PzYTykzqqH6+qU0dIgh9iPFbfb4Mm8zNBjWWreRKtx0= +random.com v1.2.3/go.mod h1:8EGj+8a4Hw1clAp8vbaeHAsKE4sbm536FP7nKyXO+qQ= +-- main.go -- +package main + +func main() {} +` + WithOptions( + ProxyFiles(proxy), + ).Run(t, mod, func(t *testing.T, env *Env) { + d := &protocol.PublishDiagnosticsParams{} + env.OpenFile("go.mod") + pos := env.RegexpSearch("go.mod", "require hasdep.com v1.2.3").Range.Start + env.AfterChange( + Diagnostics(AtPosition("go.mod", pos.Line, pos.Character)), + ReadDiagnostics("go.mod", d), + ) + const want = `module mod.com + +go 1.12 + +require random.com v1.2.3 +` + var diagnostics []protocol.Diagnostic + for _, d := range d.Diagnostics { + if d.Range.Start.Line != pos.Line { + continue + } + diagnostics = append(diagnostics, d) + } + env.ApplyQuickFixes("go.mod", diagnostics) + if got := env.BufferText("go.mod"); got != want { + t.Fatalf("unexpected content in go.mod:\n%s", compare.Text(want, got)) + } + }) + }) +} + +func TestSumUpdateQuickFix(t *testing.T) { + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 + +require ( + example.com v1.2.3 +) +-- go.sum -- +-- main.go -- +package main + +import ( + "example.com/blah" +) + +func main() { + blah.Hello() +} +` + WithOptions( + ProxyFiles(workspaceProxy), + Modes(Default), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("go.mod") + params := &protocol.PublishDiagnosticsParams{} + env.AfterChange( + Diagnostics( + env.AtRegexp("go.mod", `example.com`), + WithMessage("go.sum is out of sync"), + ), + ReadDiagnostics("go.mod", params), + ) + env.ApplyQuickFixes("go.mod", params.Diagnostics) + const want = `example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +` + if got := env.ReadWorkspaceFile("go.sum"); got != want { + t.Fatalf("unexpected go.sum contents:\n%s", compare.Text(want, got)) + } + }) +} + +func TestDownloadDeps(t *testing.T) { + const proxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 + +require random.org v1.2.3 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +import "random.org/bye" + +func SaySomething() { + bye.Goodbye() +} +-- random.org@v1.2.3/go.mod -- +module random.org + +go 1.12 +-- random.org@v1.2.3/bye/bye.go -- +package bye + +func Goodbye() { + println("Bye") +} +` + + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 +-- go.sum -- +-- main.go -- +package main + +import ( + "example.com/blah" +) + +func main() { + blah.SaySomething() +} +` + WithOptions( + ProxyFiles(proxy), + Modes(Default), + ).Run(t, mod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + d := &protocol.PublishDiagnosticsParams{} + env.AfterChange( + Diagnostics( + env.AtRegexp("main.go", `"example.com/blah"`), + WithMessage(`could not import example.com/blah (no required module provides package "example.com/blah")`), + ), + ReadDiagnostics("main.go", d), + ) + env.ApplyQuickFixes("main.go", d.Diagnostics) + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + NoDiagnostics(ForFile("go.mod")), + ) + }) +} + +func TestInvalidGoVersion(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go foo +-- main.go -- +package main +` + Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("go.mod", `go foo`), WithMessage("invalid go version")), + ) + env.WriteWorkspaceFile("go.mod", "module mod.com \n\ngo 1.12\n") + env.AfterChange(NoDiagnostics(ForFile("go.mod"))) + }) +} + +// This is a regression test for a bug in the line-oriented implementation +// of the "apply diffs" operation used by the fake editor. +func TestIssue57627(t *testing.T) { + const files = ` +-- go.work -- +package main +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + env.SetBufferContent("go.work", "go 1.18\nuse moda/a") + env.SaveBuffer("go.work") // doesn't fail + }) +} + +func TestInconsistentMod(t *testing.T) { + const proxy = ` +-- golang.org/x/mod@v0.7.0/go.mod -- +go 1.20 +module golang.org/x/mod +-- golang.org/x/mod@v0.7.0/a.go -- +package mod +func AutoQuote(string) string { return ""} +-- golang.org/x/mod@v0.9.0/go.mod -- +go 1.20 +module golang.org/x/mod +-- golang.org/x/mod@v0.9.0/a.go -- +package mod +func AutoQuote(string) string { return ""} +` + const files = ` +-- go.work -- +go 1.20 +use ( + ./a + ./b +) + +-- a/go.mod -- +module a.mod.com +go 1.20 +require golang.org/x/mod v0.6.0 // yyy +replace golang.org/x/mod v0.6.0 => golang.org/x/mod v0.7.0 +-- a/main.go -- +package main +import "golang.org/x/mod" +import "fmt" +func main() {fmt.Println(mod.AutoQuote(""))} + +-- b/go.mod -- +module b.mod.com +go 1.20 +require golang.org/x/mod v0.9.0 // xxx +-- b/main.go -- +package aaa +import "golang.org/x/mod" +import "fmt" +func main() {fmt.Println(mod.AutoQuote(""))} +var A int + +-- b/c/go.mod -- +module c.b.mod.com +go 1.20 +require b.mod.com v0.4.2 +replace b.mod.com => ../ +-- b/c/main.go -- +package main +import "b.mod.com/aaa" +import "fmt" +func main() {fmt.Println(aaa.A)} +` + WithOptions( + ProxyFiles(proxy), + Modes(Default), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/go.mod") + ahints := env.InlayHints("a/go.mod") + if len(ahints) != 1 { + t.Errorf("expected exactly one hint, got %d: %#v", len(ahints), ahints) + } + env.OpenFile("b/c/go.mod") + bhints := env.InlayHints("b/c/go.mod") + if len(bhints) != 0 { + t.Errorf("expected no hints, got %d: %#v", len(bhints), bhints) + } + }) + +} diff --git a/contribs/gnopls/internal/test/integration/modfile/tempmodfile_test.go b/contribs/gnopls/internal/test/integration/modfile/tempmodfile_test.go new file mode 100644 index 00000000000..9f8972dc13f --- /dev/null +++ b/contribs/gnopls/internal/test/integration/modfile/tempmodfile_test.go @@ -0,0 +1,41 @@ +// 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 modfile + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// This test replaces an older, problematic test (golang/go#57784). But it has +// been a long time since the go command would mutate go.mod files. +// +// TODO(golang/go#61970): the tempModfile setting should be removed entirely. +func TestTempModfileUnchanged(t *testing.T) { + // badMod has a go.mod file that is missing a go directive. + const badMod = ` +-- go.mod -- +module badmod.test/p +-- p.go -- +package p +` + + WithOptions( + Modes(Default), // no reason to test this with a remote gopls + ProxyFiles(workspaceProxy), + Settings{ + "tempModfile": true, + }, + ).Run(t, badMod, func(t *testing.T, env *Env) { + env.OpenFile("p.go") + env.AfterChange() + want := "module badmod.test/p\n" + got := env.ReadWorkspaceFile("go.mod") + if got != want { + t.Errorf("go.mod content:\n%s\nwant:\n%s", got, want) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/options.go b/contribs/gnopls/internal/test/integration/options.go new file mode 100644 index 00000000000..87be2114eaa --- /dev/null +++ b/contribs/gnopls/internal/test/integration/options.go @@ -0,0 +1,194 @@ +// 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 integration + +import ( + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/internal/drivertest" +) + +type runConfig struct { + editor fake.EditorConfig + sandbox fake.SandboxConfig + modes Mode + noLogsOnError bool + writeGoSum []string +} + +func defaultConfig() runConfig { + return runConfig{ + editor: fake.EditorConfig{ + Settings: map[string]interface{}{ + // Shorten the diagnostic delay to speed up test execution (else we'd add + // the default delay to each assertion about diagnostics) + "diagnosticsDelay": "10ms", + }, + }, + } +} + +// A RunOption augments the behavior of the test runner. +type RunOption interface { + set(*runConfig) +} + +type optionSetter func(*runConfig) + +func (f optionSetter) set(opts *runConfig) { + f(opts) +} + +// ProxyFiles configures a file proxy using the given txtar-encoded string. +func ProxyFiles(txt string) RunOption { + return optionSetter(func(opts *runConfig) { + opts.sandbox.ProxyFiles = fake.UnpackTxt(txt) + }) +} + +// WriteGoSum causes the environment to write a go.sum file for the requested +// relative directories (via `go list -mod=mod`), before starting gopls. +// +// Useful for tests that use ProxyFiles, but don't care about crafting the +// go.sum content. +func WriteGoSum(dirs ...string) RunOption { + return optionSetter(func(opts *runConfig) { + opts.writeGoSum = dirs + }) +} + +// Modes configures the execution modes that the test should run in. +// +// By default, modes are configured by the test runner. If this option is set, +// it overrides the set of default modes and the test runs in exactly these +// modes. +func Modes(modes Mode) RunOption { + return optionSetter(func(opts *runConfig) { + if opts.modes != 0 { + panic("modes set more than once") + } + opts.modes = modes + }) +} + +// NoLogsOnError turns off dumping the LSP logs on test failures. +func NoLogsOnError() RunOption { + return optionSetter(func(opts *runConfig) { + opts.noLogsOnError = true + }) +} + +// WindowsLineEndings configures the editor to use windows line endings. +func WindowsLineEndings() RunOption { + return optionSetter(func(opts *runConfig) { + opts.editor.WindowsLineEndings = true + }) +} + +// ClientName sets the LSP client name. +func ClientName(name string) RunOption { + return optionSetter(func(opts *runConfig) { + opts.editor.ClientName = name + }) +} + +// CapabilitiesJSON sets the capabalities json. +func CapabilitiesJSON(capabilities []byte) RunOption { + return optionSetter(func(opts *runConfig) { + opts.editor.CapabilitiesJSON = capabilities + }) +} + +// Settings sets user-provided configuration for the LSP server. +// +// As a special case, the env setting must not be provided via Settings: use +// EnvVars instead. +type Settings map[string]interface{} + +func (s Settings) set(opts *runConfig) { + if opts.editor.Settings == nil { + opts.editor.Settings = make(map[string]interface{}) + } + for k, v := range s { + opts.editor.Settings[k] = v + } +} + +// WorkspaceFolders configures the workdir-relative workspace folders or uri +// to send to the LSP server. By default the editor sends a single workspace folder +// corresponding to the workdir root. To explicitly configure no workspace +// folders, use WorkspaceFolders with no arguments. +func WorkspaceFolders(relFolders ...string) RunOption { + if len(relFolders) == 0 { + // Use an empty non-nil slice to signal explicitly no folders. + relFolders = []string{} + } + + return optionSetter(func(opts *runConfig) { + opts.editor.WorkspaceFolders = relFolders + }) +} + +// FolderSettings defines per-folder workspace settings, keyed by relative path +// to the folder. +// +// Use in conjunction with WorkspaceFolders to have different settings for +// different folders. +type FolderSettings map[string]Settings + +func (fs FolderSettings) set(opts *runConfig) { + // Re-use the Settings type, for symmetry, but translate back into maps for + // the editor config. + folders := make(map[string]map[string]any) + for k, v := range fs { + folders[k] = v + } + opts.editor.FolderSettings = folders +} + +// EnvVars sets environment variables for the LSP session. When applying these +// variables to the session, the special string $SANDBOX_WORKDIR is replaced by +// the absolute path to the sandbox working directory. +type EnvVars map[string]string + +func (e EnvVars) set(opts *runConfig) { + if opts.editor.Env == nil { + opts.editor.Env = make(map[string]string) + } + for k, v := range e { + opts.editor.Env[k] = v + } +} + +// FakeGoPackagesDriver configures gopls to run with a fake GOPACKAGESDRIVER +// environment variable. +func FakeGoPackagesDriver(t *testing.T) RunOption { + env := drivertest.Env(t) + vars := make(EnvVars) + for _, e := range env { + kv := strings.SplitN(e, "=", 2) + vars[kv[0]] = kv[1] + } + return vars +} + +// InGOPATH configures the workspace working directory to be GOPATH, rather +// than a separate working directory for use with modules. +func InGOPATH() RunOption { + return optionSetter(func(opts *runConfig) { + opts.sandbox.InGoPath = true + }) +} + +// MessageResponder configures the editor to respond to +// window/showMessageRequest messages using the provided function. +func MessageResponder(f func(*protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error)) RunOption { + return optionSetter(func(opts *runConfig) { + opts.editor.MessageResponder = f + }) +} diff --git a/contribs/gnopls/internal/test/integration/regtest.go b/contribs/gnopls/internal/test/integration/regtest.go new file mode 100644 index 00000000000..b676fd4c500 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/regtest.go @@ -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 integration + +import ( + "context" + "flag" + "fmt" + "os" + "runtime" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/cmd" + "golang.org/x/tools/internal/drivertest" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/memoize" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/internal/tool" +) + +var ( + runSubprocessTests = flag.Bool("enable_gopls_subprocess_tests", false, "run integration tests against a gopls subprocess (default: in-process)") + goplsBinaryPath = flag.String("gopls_test_binary", "", "path to the gopls binary for use as a remote, for use with the -enable_gopls_subprocess_tests flag") + timeout = flag.Duration("timeout", defaultTimeout(), "if nonzero, default timeout for each integration test; defaults to GOPLS_INTEGRATION_TEST_TIMEOUT") + skipCleanup = flag.Bool("skip_cleanup", false, "whether to skip cleaning up temp directories") + printGoroutinesOnFailure = flag.Bool("print_goroutines", false, "whether to print goroutines info on failure") + printLogs = flag.Bool("print_logs", false, "whether to print LSP logs") +) + +func defaultTimeout() time.Duration { + s := os.Getenv("GOPLS_INTEGRATION_TEST_TIMEOUT") + if s == "" { + return 0 + } + d, err := time.ParseDuration(s) + if err != nil { + fmt.Fprintf(os.Stderr, "invalid GOPLS_INTEGRATION_TEST_TIMEOUT %q: %v\n", s, err) + os.Exit(2) + } + return d +} + +var runner *Runner + +func Run(t *testing.T, files string, f TestFunc) { + runner.Run(t, files, f) +} + +func WithOptions(opts ...RunOption) configuredRunner { + return configuredRunner{opts: opts} +} + +type configuredRunner struct { + opts []RunOption +} + +func (r configuredRunner) Run(t *testing.T, files string, f TestFunc) { + // Print a warning if the test's temporary directory is not + // suitable as a workspace folder, as this may lead to + // otherwise-cryptic failures. This situation typically occurs + // when an arbitrary string (e.g. "foo.") is used as a subtest + // name, on a platform with filename restrictions (e.g. no + // trailing period on Windows). + tmp := t.TempDir() + if err := cache.CheckPathValid(tmp); err != nil { + t.Logf("Warning: testing.T.TempDir(%s) is not valid as a workspace folder: %s", + tmp, err) + } + + runner.Run(t, files, f, r.opts...) +} + +// RunMultiple runs a test multiple times, with different options. +// The runner should be constructed with [WithOptions]. +// +// TODO(rfindley): replace Modes with selective use of RunMultiple. +type RunMultiple []struct { + Name string + Runner interface { + Run(t *testing.T, files string, f TestFunc) + } +} + +func (r RunMultiple) Run(t *testing.T, files string, f TestFunc) { + for _, runner := range r { + t.Run(runner.Name, func(t *testing.T) { + runner.Runner.Run(t, files, f) + }) + } +} + +// DefaultModes returns the default modes to run for each regression test (they +// may be reconfigured by the tests themselves). +func DefaultModes() Mode { + modes := Default + if !testing.Short() { + // TODO(rfindley): we should just run a few select integration tests in + // "Forwarded" mode, and call it a day. No need to run every single test in + // two ways. + modes |= Forwarded + } + if *runSubprocessTests { + modes |= SeparateProcess + } + return modes +} + +var runFromMain = false // true if Main has been called + +// Main sets up and tears down the shared integration test state. +func Main(m *testing.M) (code int) { + // Provide an entrypoint for tests that use a fake go/packages driver. + drivertest.RunIfChild() + + defer func() { + if runner != nil { + if err := runner.Close(); err != nil { + fmt.Fprintf(os.Stderr, "closing test runner: %v\n", err) + // Cleanup is broken in go1.12 and earlier, and sometimes flakes on + // Windows due to file locking, but this is OK for our CI. + // + // Fail on go1.13+, except for windows and android which have shutdown problems. + if testenv.Go1Point() >= 13 && runtime.GOOS != "windows" && runtime.GOOS != "android" { + if code == 0 { + code = 1 + } + } + } + } + }() + + runFromMain = true + + // golang/go#54461: enable additional debugging around hanging Go commands. + gocommand.DebugHangingGoCommands = true + + // If this magic environment variable is set, run gopls instead of the test + // suite. See the documentation for runTestAsGoplsEnvvar for more details. + if os.Getenv(runTestAsGoplsEnvvar) == "true" { + tool.Main(context.Background(), cmd.New(), os.Args[1:]) + return 0 + } + + if !testenv.HasExec() { + fmt.Printf("skipping all tests: exec not supported on %s/%s\n", runtime.GOOS, runtime.GOARCH) + return 0 + } + testenv.ExitIfSmallMachine() + + flag.Parse() + + // Disable GOPACKAGESDRIVER, as it can cause spurious test failures. + os.Setenv("GOPACKAGESDRIVER", "off") + + if skipReason := checkBuilder(); skipReason != "" { + fmt.Printf("Skipping all tests: %s\n", skipReason) + return 0 + } + + if err := testenv.HasTool("go"); err != nil { + fmt.Println("Missing go command") + return 1 + } + + runner = &Runner{ + DefaultModes: DefaultModes(), + Timeout: *timeout, + PrintGoroutinesOnFailure: *printGoroutinesOnFailure, + SkipCleanup: *skipCleanup, + store: memoize.NewStore(memoize.NeverEvict), + } + + runner.goplsPath = *goplsBinaryPath + if runner.goplsPath == "" { + var err error + runner.goplsPath, err = os.Executable() + if err != nil { + panic(fmt.Sprintf("finding test binary path: %v", err)) + } + } + + dir, err := os.MkdirTemp("", "gopls-test-") + if err != nil { + panic(fmt.Errorf("creating temp directory: %v", err)) + } + runner.tempDir = dir + + return m.Run() +} diff --git a/contribs/gnopls/internal/test/integration/runner.go b/contribs/gnopls/internal/test/integration/runner.go new file mode 100644 index 00000000000..7b3b757536f --- /dev/null +++ b/contribs/gnopls/internal/test/integration/runner.go @@ -0,0 +1,435 @@ +// 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 integration + +import ( + "bytes" + "context" + "fmt" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "runtime" + "runtime/pprof" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/lsprpc" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/jsonrpc2/servertest" + "golang.org/x/tools/internal/memoize" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/internal/xcontext" +) + +// Mode is a bitmask that defines for which execution modes a test should run. +// +// Each mode controls several aspects of gopls' configuration: +// - Which server options to use for gopls sessions +// - Whether to use a shared cache +// - Whether to use a shared server +// - Whether to run the server in-process or in a separate process +// +// The behavior of each mode with respect to these aspects is summarized below. +// TODO(rfindley, cleanup): rather than using arbitrary names for these modes, +// we can compose them explicitly out of the features described here, allowing +// individual tests more freedom in constructing problematic execution modes. +// For example, a test could assert on a certain behavior when running on a +// separate process. Moreover, we could unify 'Modes' with 'Options', and use +// RunMultiple rather than a hard-coded loop through modes. +// +// Mode | Options | Shared Cache? | Shared Server? | In-process? +// --------------------------------------------------------------------------- +// Default | Default | Y | N | Y +// Forwarded | Default | Y | Y | Y +// SeparateProcess | Default | Y | Y | N +type Mode int + +const ( + // Default mode runs gopls with the default options, communicating over pipes + // to emulate the lsp sidecar execution mode, which communicates over + // stdin/stdout. + // + // It uses separate servers for each test, but a shared cache, to avoid + // duplicating work when processing GOROOT. + Default Mode = 1 << iota + + // Forwarded uses the default options, but forwards connections to a shared + // in-process gopls server. + Forwarded + + // SeparateProcess uses the default options, but forwards connection to an + // external gopls daemon. + // + // Only supported on GOOS=linux. + SeparateProcess +) + +func (m Mode) String() string { + switch m { + case Default: + return "default" + case Forwarded: + return "forwarded" + case SeparateProcess: + return "separate process" + default: + return "unknown mode" + } +} + +// A Runner runs tests in gopls execution environments, as specified by its +// modes. For modes that share state (for example, a shared cache or common +// remote), any tests that execute on the same Runner will share the same +// state. +type Runner struct { + // Configuration + DefaultModes Mode // modes to run for each test + Timeout time.Duration // per-test timeout, if set + PrintGoroutinesOnFailure bool // whether to dump goroutines on test failure + SkipCleanup bool // if set, don't delete test data directories when the test exits + + // Immutable state shared across test invocations + goplsPath string // path to the gopls executable (for SeparateProcess mode) + tempDir string // shared parent temp directory + store *memoize.Store // shared store + + // Lazily allocated resources + tsOnce sync.Once + ts *servertest.TCPServer // shared in-process test server ("forwarded" mode) + + startRemoteOnce sync.Once + remoteSocket string // unix domain socket for shared daemon ("separate process" mode) + remoteErr error + cancelRemote func() +} + +type TestFunc func(t *testing.T, env *Env) + +// Run executes the test function in the default configured gopls execution +// modes. For each a test run, a new workspace is created containing the +// un-txtared files specified by filedata. +func (r *Runner) Run(t *testing.T, files string, test TestFunc, opts ...RunOption) { + // TODO(rfindley): this function has gotten overly complicated, and warrants + // refactoring. + + if !runFromMain { + // Main performs various setup precondition checks. + // While it could theoretically be made OK for a Runner to be used outside + // of Main, it is simpler to enforce that we only use the Runner from + // integration test suites. + t.Fatal("integration.Runner.Run must be run from integration.Main") + } + + tests := []struct { + name string + mode Mode + getServer func() jsonrpc2.StreamServer + }{ + {"default", Default, r.defaultServer}, + {"forwarded", Forwarded, r.forwardedServer}, + {"separate_process", SeparateProcess, r.separateProcessServer}, + } + + for _, tc := range tests { + tc := tc + config := defaultConfig() + for _, opt := range opts { + opt.set(&config) + } + modes := r.DefaultModes + if config.modes != 0 { + modes = config.modes + } + if modes&tc.mode == 0 { + continue + } + + t.Run(tc.name, func(t *testing.T) { + // TODO(rfindley): once jsonrpc2 shutdown is fixed, we should not leak + // goroutines in this test function. + // stacktest.NoLeak(t) + + ctx := context.Background() + if r.Timeout != 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, r.Timeout) + defer cancel() + } else if d, ok := testenv.Deadline(t); ok { + timeout := time.Until(d) * 19 / 20 // Leave an arbitrary 5% for cleanup. + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, timeout) + defer cancel() + } + + // TODO(rfindley): do we need an instance at all? Can it be removed? + ctx = debug.WithInstance(ctx, "off") + + rootDir := filepath.Join(r.tempDir, filepath.FromSlash(t.Name())) + if err := os.MkdirAll(rootDir, 0755); err != nil { + t.Fatal(err) + } + + files := fake.UnpackTxt(files) + if config.editor.WindowsLineEndings { + for name, data := range files { + files[name] = bytes.ReplaceAll(data, []byte("\n"), []byte("\r\n")) + } + } + config.sandbox.Files = files + config.sandbox.RootDir = rootDir + sandbox, err := fake.NewSandbox(&config.sandbox) + if err != nil { + t.Fatal(err) + } + defer func() { + if !r.SkipCleanup { + if err := sandbox.Close(); err != nil { + pprof.Lookup("goroutine").WriteTo(os.Stderr, 1) + t.Errorf("closing the sandbox: %v", err) + } + } + }() + + // Write the go.sum file for the requested directories, before starting the server. + for _, dir := range config.writeGoSum { + if err := sandbox.RunGoCommand(context.Background(), dir, "list", []string{"-mod=mod", "./..."}, []string{"GOWORK=off"}, true); err != nil { + t.Fatal(err) + } + } + + ss := tc.getServer() + + framer := jsonrpc2.NewRawStream + ls := &loggingFramer{} + framer = ls.framer(jsonrpc2.NewRawStream) + ts := servertest.NewPipeServer(ss, framer) + + env := ConnectGoplsEnv(t, ctx, sandbox, config.editor, ts) + defer func() { + if t.Failed() && r.PrintGoroutinesOnFailure { + pprof.Lookup("goroutine").WriteTo(os.Stderr, 1) + } + if (t.Failed() && !config.noLogsOnError) || *printLogs { + ls.printBuffers(t.Name(), os.Stderr) + } + // For tests that failed due to a timeout, don't fail to shutdown + // because ctx is done. + // + // There is little point to setting an arbitrary timeout for closing + // the editor: in general we want to clean up before proceeding to the + // next test, and if there is a deadlock preventing closing it will + // eventually be handled by the `go test` timeout. + if err := env.Editor.Close(xcontext.Detach(ctx)); err != nil { + t.Errorf("closing editor: %v", err) + } + }() + // Always await the initial workspace load. + env.Await(InitialWorkspaceLoad) + test(t, env) + }) + } +} + +// ConnectGoplsEnv creates a new Gopls environment for the given sandbox, +// editor config, and server connector. +// +// TODO(rfindley): significantly refactor the way testing environments are +// constructed. +func ConnectGoplsEnv(t testing.TB, ctx context.Context, sandbox *fake.Sandbox, config fake.EditorConfig, connector servertest.Connector) *Env { + awaiter := NewAwaiter(sandbox.Workdir) + editor, err := fake.NewEditor(sandbox, config).Connect(ctx, connector, awaiter.Hooks()) + if err != nil { + t.Fatal(err) + } + env := &Env{ + T: t, + Ctx: ctx, + Sandbox: sandbox, + Server: connector, + Editor: editor, + Awaiter: awaiter, + } + return env +} + +// longBuilders maps builders that are skipped when -short is set to a +// (possibly empty) justification. +var longBuilders = map[string]string{ + "openbsd-amd64-64": "go.dev/issue/42789", + "openbsd-386-64": "go.dev/issue/42789", + "openbsd-386-68": "go.dev/issue/42789", + "openbsd-amd64-68": "go.dev/issue/42789", + "darwin-amd64-10_12": "", + "freebsd-amd64-race": "", + "illumos-amd64": "", + "netbsd-arm-bsiegert": "", + "solaris-amd64-oraclerel": "", + "windows-arm-zx2c4": "", + "linux-ppc64le-power9osu": "go.dev/issue/66748", +} + +// TODO(rfindley): inline into Main. +func checkBuilder() string { + builder := os.Getenv("GO_BUILDER_NAME") + if reason, ok := longBuilders[builder]; ok && testing.Short() { + if reason != "" { + return fmt.Sprintf("skipping %s with -short due to %s", builder, reason) + } else { + return fmt.Sprintf("skipping %s with -short", builder) + } + } + return "" +} + +type loggingFramer struct { + mu sync.Mutex + buf *safeBuffer +} + +// safeBuffer is a threadsafe buffer for logs. +type safeBuffer struct { + mu sync.Mutex + buf bytes.Buffer +} + +func (b *safeBuffer) Write(p []byte) (int, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.buf.Write(p) +} + +func (s *loggingFramer) framer(f jsonrpc2.Framer) jsonrpc2.Framer { + return func(nc net.Conn) jsonrpc2.Stream { + s.mu.Lock() + framed := false + if s.buf == nil { + s.buf = &safeBuffer{buf: bytes.Buffer{}} + framed = true + } + s.mu.Unlock() + stream := f(nc) + if framed { + return protocol.LoggingStream(stream, s.buf) + } + return stream + } +} + +func (s *loggingFramer) printBuffers(testname string, w io.Writer) { + s.mu.Lock() + defer s.mu.Unlock() + + if s.buf == nil { + return + } + fmt.Fprintf(os.Stderr, "#### Start Gopls Test Logs for %q\n", testname) + s.buf.mu.Lock() + io.Copy(w, &s.buf.buf) + s.buf.mu.Unlock() + fmt.Fprintf(os.Stderr, "#### End Gopls Test Logs for %q\n", testname) +} + +// defaultServer handles the Default execution mode. +func (r *Runner) defaultServer() jsonrpc2.StreamServer { + return lsprpc.NewStreamServer(cache.New(r.store), false, nil) +} + +// forwardedServer handles the Forwarded execution mode. +func (r *Runner) forwardedServer() jsonrpc2.StreamServer { + r.tsOnce.Do(func() { + ctx := context.Background() + ctx = debug.WithInstance(ctx, "off") + ss := lsprpc.NewStreamServer(cache.New(nil), false, nil) + r.ts = servertest.NewTCPServer(ctx, ss, nil) + }) + return newForwarder("tcp", r.ts.Addr) +} + +// runTestAsGoplsEnvvar triggers TestMain to run gopls instead of running +// tests. It's a trick to allow tests to find a binary to use to start a gopls +// subprocess. +const runTestAsGoplsEnvvar = "_GOPLS_TEST_BINARY_RUN_AS_GOPLS" + +// separateProcessServer handles the SeparateProcess execution mode. +func (r *Runner) separateProcessServer() jsonrpc2.StreamServer { + if runtime.GOOS != "linux" { + panic("separate process execution mode is only supported on linux") + } + + r.startRemoteOnce.Do(func() { + socketDir, err := os.MkdirTemp(r.tempDir, "gopls-test-socket") + if err != nil { + r.remoteErr = err + return + } + r.remoteSocket = filepath.Join(socketDir, "gopls-test-daemon") + + // The server should be killed by when the test runner exits, but to be + // conservative also set a listen timeout. + args := []string{"serve", "-listen", "unix;" + r.remoteSocket, "-listen.timeout", "1m"} + + ctx, cancel := context.WithCancel(context.Background()) + cmd := exec.CommandContext(ctx, r.goplsPath, args...) + cmd.Env = append(os.Environ(), runTestAsGoplsEnvvar+"=true") + + // Start the external gopls process. This is still somewhat racy, as we + // don't know when gopls binds to the socket, but the gopls forwarder + // client has built-in retry behavior that should mostly mitigate this + // problem (and if it doesn't, we probably want to improve the retry + // behavior). + if err := cmd.Start(); err != nil { + cancel() + r.remoteSocket = "" + r.remoteErr = err + } else { + r.cancelRemote = cancel + // Spin off a goroutine to wait, so that we free up resources when the + // server exits. + go cmd.Wait() + } + }) + + return newForwarder("unix", r.remoteSocket) +} + +func newForwarder(network, address string) jsonrpc2.StreamServer { + server, err := lsprpc.NewForwarder(network+";"+address, nil) + if err != nil { + // This should never happen, as we are passing an explicit address. + panic(fmt.Sprintf("internal error: unable to create forwarder: %v", err)) + } + return server +} + +// Close cleans up resource that have been allocated to this workspace. +func (r *Runner) Close() error { + var errmsgs []string + if r.ts != nil { + if err := r.ts.Close(); err != nil { + errmsgs = append(errmsgs, err.Error()) + } + } + if r.cancelRemote != nil { + r.cancelRemote() + } + if !r.SkipCleanup { + if err := os.RemoveAll(r.tempDir); err != nil { + errmsgs = append(errmsgs, err.Error()) + } + } + if len(errmsgs) > 0 { + return fmt.Errorf("errors closing the test runner:\n\t%s", strings.Join(errmsgs, "\n\t")) + } + return nil +} diff --git a/contribs/gnopls/internal/test/integration/template/template_test.go b/contribs/gnopls/internal/test/integration/template/template_test.go new file mode 100644 index 00000000000..47398f5a3a2 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/template/template_test.go @@ -0,0 +1,231 @@ +// 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 template + +import ( + "os" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/util/bug" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(Main(m)) +} + +func TestMultilineTokens(t *testing.T) { + // 51731: panic: runtime error: slice bounds out of range [38:3] + const files = ` +-- go.mod -- +module mod.com + +go 1.17 +-- hi.tmpl -- +{{if (foÜx .X.Y)}}😀{{$A := + "hi" + }}{{.Z $A}}{{else}} +{{$A.X 12}} +{{foo (.X.Y) 23 ($A.Z)}} +{{end}} +` + WithOptions( + Settings{ + "templateExtensions": []string{"tmpl"}, + "semanticTokens": true, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + var p protocol.SemanticTokensParams + p.TextDocument.URI = env.Sandbox.Workdir.URI("hi.tmpl") + toks, err := env.Editor.Server.SemanticTokensFull(env.Ctx, &p) + if err != nil { + t.Errorf("semantic token failed: %v", err) + } + if toks == nil || len(toks.Data) == 0 { + t.Errorf("got no semantic tokens") + } + }) +} + +func TestTemplatesFromExtensions(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- hello.tmpl -- +{{range .Planets}} +Hello {{}} <-- missing body +{{end}} +` + WithOptions( + Settings{ + "templateExtensions": []string{"tmpl"}, + "semanticTokens": true, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + // TODO: can we move this diagnostic onto {{}}? + var diags protocol.PublishDiagnosticsParams + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("hello.tmpl", "()Hello {{}}")), + ReadDiagnostics("hello.tmpl", &diags), + ) + d := diags.Diagnostics // issue 50786: check for Source + if len(d) != 1 { + t.Errorf("expected 1 diagnostic, got %d", len(d)) + return + } + if d[0].Source != "template" { + t.Errorf("expected Source 'template', got %q", d[0].Source) + } + // issue 50801 (even broken templates could return some semantic tokens) + var p protocol.SemanticTokensParams + p.TextDocument.URI = env.Sandbox.Workdir.URI("hello.tmpl") + toks, err := env.Editor.Server.SemanticTokensFull(env.Ctx, &p) + if err != nil { + t.Errorf("semantic token failed: %v", err) + } + if toks == nil || len(toks.Data) == 0 { + t.Errorf("got no semantic tokens") + } + + env.WriteWorkspaceFile("hello.tmpl", "{{range .Planets}}\nHello {{.}}\n{{end}}") + env.AfterChange(NoDiagnostics(ForFile("hello.tmpl"))) + }) +} + +func TestTemplatesObserveDirectoryFilters(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/a.tmpl -- +A {{}} <-- missing body +-- b/b.tmpl -- +B {{}} <-- missing body +` + + WithOptions( + Settings{ + "directoryFilters": []string{"-b"}, + "templateExtensions": []string{"tmpl"}, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("a/a.tmpl", "()A")), + NoDiagnostics(ForFile("b/b.tmpl")), + ) + }) +} + +func TestTemplatesFromLangID(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +` + + Run(t, files, func(t *testing.T, env *Env) { + env.CreateBuffer("hello.tmpl", "") + env.AfterChange( + NoDiagnostics(ForFile("hello.tmpl")), // Don't get spurious errors for empty templates. + ) + env.SetBufferContent("hello.tmpl", "{{range .Planets}}\nHello {{}}\n{{end}}") + env.Await(Diagnostics(env.AtRegexp("hello.tmpl", "()Hello {{}}"))) + env.RegexpReplace("hello.tmpl", "{{}}", "{{.}}") + env.Await(NoDiagnostics(ForFile("hello.tmpl"))) + }) +} + +func TestClosingTemplatesMakesDiagnosticsDisappear(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- hello.tmpl -- +{{range .Planets}} +Hello {{}} <-- missing body +{{end}} +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("hello.tmpl") + env.AfterChange( + Diagnostics(env.AtRegexp("hello.tmpl", "()Hello {{}}")), + ) + // Since we don't have templateExtensions configured, closing hello.tmpl + // should make its diagnostics disappear. + env.CloseBuffer("hello.tmpl") + env.AfterChange( + NoDiagnostics(ForFile("hello.tmpl")), + ) + }) +} + +func TestMultipleSuffixes(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- b.gotmpl -- +{{define "A"}}goo{{end}} +-- a.tmpl -- +{{template "A"}} +` + + WithOptions( + Settings{ + "templateExtensions": []string{"tmpl", "gotmpl"}, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.tmpl") + x := env.RegexpSearch("a.tmpl", `A`) + loc := env.GoToDefinition(x) + refs := env.References(loc) + if len(refs) != 2 { + t.Fatalf("got %v reference(s), want 2", len(refs)) + } + // make sure we got one from b.gotmpl + want := env.Sandbox.Workdir.URI("b.gotmpl") + if refs[0].URI != want && refs[1].URI != want { + t.Errorf("failed to find reference to %s", shorten(want)) + for i, r := range refs { + t.Logf("%d: URI:%s %v", i, shorten(r.URI), r.Range) + } + } + + content, nloc := env.Hover(loc) + if loc != nloc { + t.Errorf("loc? got %v, wanted %v", nloc, loc) + } + if content.Value != "template A defined" { + t.Errorf("got %s, wanted 'template A defined", content.Value) + } + }) +} + +// shorten long URIs +func shorten(fn protocol.DocumentURI) string { + if len(fn) <= 20 { + return string(fn) + } + pieces := strings.Split(string(fn), "/") + if len(pieces) < 2 { + return string(fn) + } + j := len(pieces) + return pieces[j-2] + "/" + pieces[j-1] +} + +// Hover needs tests diff --git a/contribs/gnopls/internal/test/integration/watch/setting_test.go b/contribs/gnopls/internal/test/integration/watch/setting_test.go new file mode 100644 index 00000000000..abd9799c584 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/watch/setting_test.go @@ -0,0 +1,85 @@ +// 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 watch + +import ( + "fmt" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestSubdirWatchPatterns(t *testing.T) { + const files = ` +-- go.mod -- +module mod.test + +go 1.18 +-- subdir/subdir.go -- +package subdir +` + + tests := []struct { + clientName string + subdirWatchPatterns string + wantWatched bool + }{ + {"other client", "on", true}, + {"other client", "off", false}, + {"other client", "auto", false}, + {"Visual Studio Code", "auto", true}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%s_%s", test.clientName, test.subdirWatchPatterns), func(t *testing.T) { + WithOptions( + ClientName(test.clientName), + Settings{ + "subdirWatchPatterns": test.subdirWatchPatterns, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + var expectation Expectation + if test.wantWatched { + expectation = FileWatchMatching("subdir") + } else { + expectation = NoFileWatchMatching("subdir") + } + env.OnceMet( + InitialWorkspaceLoad, + expectation, + ) + }) + }) + } +} + +// This test checks that we surface errors for invalid subdir watch patterns, +// as the triple of ("off"|"on"|"auto") may be confusing to users inclined to +// use (true|false) or some other truthy value. +func TestSubdirWatchPatterns_BadValues(t *testing.T) { + tests := []struct { + badValue interface{} + wantMessage string + }{ + {true, "invalid type bool (want string)"}, + {false, "invalid type bool (want string)"}, + {"yes", `invalid option "yes"`}, + } + + for _, test := range tests { + t.Run(fmt.Sprint(test.badValue), func(t *testing.T) { + WithOptions( + Settings{ + "subdirWatchPatterns": test.badValue, + }, + ).Run(t, "", func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + ShownMessage(test.wantMessage), + ) + }) + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/watch/watch_test.go b/contribs/gnopls/internal/test/integration/watch/watch_test.go new file mode 100644 index 00000000000..7f41511d140 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/watch/watch_test.go @@ -0,0 +1,713 @@ +// 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 watch + +import ( + "os" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/util/bug" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/integration/fake" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(Main(m)) +} + +func TestEditFile(t *testing.T) { + const pkg = ` +-- go.mod -- +module mod.com + +go 1.14 +-- a/a.go -- +package a + +func _() { + var x int +} +` + // Edit the file when it's *not open* in the workspace, and check that + // diagnostics are updated. + t.Run("unopened", func(t *testing.T) { + Run(t, pkg, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("a/a.go", "x")), + ) + env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + }) + }) + + // Edit the file when it *is open* in the workspace, and check that + // diagnostics are *not* updated. + t.Run("opened", func(t *testing.T) { + Run(t, pkg, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + // Insert a trivial edit so that we don't automatically update the buffer + // (see CL 267577). + env.EditBuffer("a/a.go", fake.NewEdit(0, 0, 0, 0, " ")) + env.AfterChange() + env.WriteWorkspaceFile("a/a.go", `package a; func _() {};`) + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "x")), + ) + }) + }) +} + +// Edit a dependency on disk and expect a new diagnostic. +func TestEditDependency(t *testing.T) { + const pkg = ` +-- go.mod -- +module mod.com + +go 1.14 +-- b/b.go -- +package b + +func B() int { return 0 } +-- a/a.go -- +package a + +import ( + "mod.com/b" +) + +func _() { + _ = b.B() +} +` + Run(t, pkg, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange() + env.WriteWorkspaceFile("b/b.go", `package b; func B() {};`) + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "b.B")), + ) + }) +} + +// Edit both the current file and one of its dependencies on disk and +// expect diagnostic changes. +func TestEditFileAndDependency(t *testing.T) { + const pkg = ` +-- go.mod -- +module mod.com + +go 1.14 +-- b/b.go -- +package b + +func B() int { return 0 } +-- a/a.go -- +package a + +import ( + "mod.com/b" +) + +func _() { + var x int + _ = b.B() +} +` + Run(t, pkg, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("a/a.go", "x")), + ) + env.WriteWorkspaceFiles(map[string]string{ + "b/b.go": `package b; func B() {};`, + "a/a.go": `package a + +import "mod.com/b" + +func _() { + b.B() +}`, + }) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + NoDiagnostics(ForFile("b/b.go")), + ) + }) +} + +// Delete a dependency and expect a new diagnostic. +func TestDeleteDependency(t *testing.T) { + const pkg = ` +-- go.mod -- +module mod.com + +go 1.14 +-- b/b.go -- +package b + +func B() int { return 0 } +-- a/a.go -- +package a + +import ( + "mod.com/b" +) + +func _() { + _ = b.B() +} +` + Run(t, pkg, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange() + env.RemoveWorkspaceFile("b/b.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "\"mod.com/b\"")), + ) + }) +} + +// Create a dependency on disk and expect the diagnostic to go away. +func TestCreateDependency(t *testing.T) { + const missing = ` +-- go.mod -- +module mod.com + +go 1.14 +-- b/b.go -- +package b + +func B() int { return 0 } +-- a/a.go -- +package a + +import ( + "mod.com/c" +) + +func _() { + c.C() +} +` + Run(t, missing, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("a/a.go", "\"mod.com/c\"")), + ) + env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + }) +} + +// Create a new dependency and add it to the file on disk. +// This is similar to what might happen if you switch branches. +func TestCreateAndAddDependency(t *testing.T) { + const original = ` +-- go.mod -- +module mod.com + +go 1.14 +-- a/a.go -- +package a + +func _() {} +` + Run(t, original, func(t *testing.T, env *Env) { + env.WriteWorkspaceFile("c/c.go", `package c; func C() {};`) + env.WriteWorkspaceFile("a/a.go", `package a; import "mod.com/c"; func _() { c.C() }`) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + }) +} + +// Create a new file that defines a new symbol, in the same package. +func TestCreateFile(t *testing.T) { + const pkg = ` +-- go.mod -- +module mod.com + +go 1.14 +-- a/a.go -- +package a + +func _() { + hello() +} +` + Run(t, pkg, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("a/a.go", "hello")), + ) + env.WriteWorkspaceFile("a/a2.go", `package a; func hello() {};`) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + }) +} + +// Add a new method to an interface and implement it. +// Inspired by the structure of internal/golang and internal/cache. +func TestCreateImplementation(t *testing.T) { + const pkg = ` +-- go.mod -- +module mod.com + +go 1.14 +-- b/b.go -- +package b + +type B interface{ + Hello() string +} + +func SayHello(bee B) { + println(bee.Hello()) +} +-- a/a.go -- +package a + +import "mod.com/b" + +type X struct {} + +func (_ X) Hello() string { + return "" +} + +func _() { + x := X{} + b.SayHello(x) +} +` + const newMethod = `package b +type B interface{ + Hello() string + Bye() string +} + +func SayHello(bee B) { + println(bee.Hello()) +}` + const implementation = `package a + +import "mod.com/b" + +type X struct {} + +func (_ X) Hello() string { + return "" +} + +func (_ X) Bye() string { + return "" +} + +func _() { + x := X{} + b.SayHello(x) +}` + + // Add the new method before the implementation. Expect diagnostics. + t.Run("method before implementation", func(t *testing.T) { + Run(t, pkg, func(t *testing.T, env *Env) { + env.WriteWorkspaceFile("b/b.go", newMethod) + env.AfterChange( + Diagnostics(AtPosition("a/a.go", 12, 12)), + ) + env.WriteWorkspaceFile("a/a.go", implementation) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + }) + }) + // Add the new implementation before the new method. Expect no diagnostics. + t.Run("implementation before method", func(t *testing.T) { + Run(t, pkg, func(t *testing.T, env *Env) { + env.WriteWorkspaceFile("a/a.go", implementation) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + env.WriteWorkspaceFile("b/b.go", newMethod) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + ) + }) + }) + // Add both simultaneously. Expect no diagnostics. + t.Run("implementation and method simultaneously", func(t *testing.T) { + Run(t, pkg, func(t *testing.T, env *Env) { + env.WriteWorkspaceFiles(map[string]string{ + "a/a.go": implementation, + "b/b.go": newMethod, + }) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + NoDiagnostics(ForFile("b/b.go")), + ) + }) + }) +} + +// Tests golang/go#38498. Delete a file and then force a reload. +// Assert that we no longer try to load the file. +func TestDeleteFiles(t *testing.T) { + // TODO(rfindley): this test is brittle, because it depends on underspecified + // logging behavior around loads. + // + // We should have a robust way to test loads. It should be possible to assert + // on the specific loads that have occurred, and without the synchronization + // problems associated with logging. + + const pkg = ` +-- go.mod -- +module mod.com + +go 1.14 +-- a/a.go -- +package a + +func _() { + var _ int +} +-- a/a_unneeded.go -- +package a +` + t.Run("close then delete", func(t *testing.T) { + WithOptions( + // verboseOutput causes Snapshot.load to log package files. + // (see the TODO above: this is brittle) + Settings{"verboseOutput": true}, + ).Run(t, pkg, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.OpenFile("a/a_unneeded.go") + env.Await( + // Log messages are asynchronous to other events on the LSP stream, so we + // can't use OnceMet or AfterChange here. + LogMatching(protocol.Info, "a_unneeded.go", 1, false), + ) + + // Close and delete the open file, mimicking what an editor would do. + env.CloseBuffer("a/a_unneeded.go") + env.RemoveWorkspaceFile("a/a_unneeded.go") + env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "fmt")), + ) + env.SaveBuffer("a/a.go") + env.Await( + // There should only be one log message containing + // a_unneeded.go, from the initial workspace load, which we + // check for earlier. If there are more, there's a bug. + LogMatching(protocol.Info, "a_unneeded.go", 1, false), + NoDiagnostics(ForFile("a/a.go")), + ) + }) + }) + + t.Run("delete then close", func(t *testing.T) { + WithOptions( + Settings{"verboseOutput": true}, + ).Run(t, pkg, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.OpenFile("a/a_unneeded.go") + env.Await( + LogMatching(protocol.Info, "a_unneeded.go", 1, false), + ) + + // Delete and then close the file. + env.RemoveWorkspaceFile("a/a_unneeded.go") + env.CloseBuffer("a/a_unneeded.go") + env.RegexpReplace("a/a.go", "var _ int", "fmt.Println(\"\")") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "fmt")), + ) + env.SaveBuffer("a/a.go") + env.Await( + // There should only be one log message containing + // a_unneeded.go, from the initial workspace load, which we + // check for earlier. If there are more, there's a bug. + LogMatching(protocol.Info, "a_unneeded.go", 1, false), + NoDiagnostics(ForFile("a/a.go")), + ) + }) + }) +} + +// This change reproduces the behavior of switching branches, with multiple +// files being created and deleted. The key change here is the movement of a +// symbol from one file to another in a given package through a deletion and +// creation. To reproduce an issue with metadata invalidation in batched +// changes, the last change in the batch is an on-disk file change that doesn't +// require metadata invalidation. +func TestMoveSymbol(t *testing.T) { + const pkg = ` +-- go.mod -- +module mod.com + +go 1.14 +-- main.go -- +package main + +import "mod.com/a" + +func main() { + var x int + x = a.Hello + println(x) +} +-- a/a1.go -- +package a + +var Hello int +-- a/a2.go -- +package a + +func _() {} +` + Run(t, pkg, func(t *testing.T, env *Env) { + env.WriteWorkspaceFile("a/a3.go", "package a\n\nvar Hello int\n") + env.RemoveWorkspaceFile("a/a1.go") + env.WriteWorkspaceFile("a/a2.go", "package a; func _() {};") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +// Reproduce golang/go#40456. +func TestChangeVersion(t *testing.T) { + const proxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +const Name = "Blah" + +func X(x int) {} +-- example.com@v1.2.2/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.2/blah/blah.go -- +package blah + +const Name = "Blah" + +func X() {} +-- random.org@v1.2.3/go.mod -- +module random.org + +go 1.12 +-- random.org@v1.2.3/blah/blah.go -- +package hello + +const Name = "Hello" +` + const mod = ` +-- go.mod -- +module mod.com + +go 1.12 + +require example.com v1.2.2 +-- go.sum -- +example.com v1.2.3 h1:OnPPkx+rW63kj9pgILsu12MORKhSlnFa3DVRJq1HZ7g= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- main.go -- +package main + +import "example.com/blah" + +func main() { + blah.X() +} +` + WithOptions(ProxyFiles(proxy)).Run(t, mod, func(t *testing.T, env *Env) { + env.WriteWorkspaceFiles(map[string]string{ + "go.mod": `module mod.com + +go 1.12 + +require example.com v1.2.3 +`, + "main.go": `package main + +import ( + "example.com/blah" +) + +func main() { + blah.X(1) +} +`, + }) + env.AfterChange( + env.DoneWithChangeWatchedFiles(), + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +// Reproduces golang/go#40340. +func TestSwitchFromGOPATHToModuleMode(t *testing.T) { + const files = ` +-- foo/blah/blah.go -- +package blah + +const Name = "" +-- main.go -- +package main + +import "foo/blah" + +func main() { + _ = blah.Name +} +` + WithOptions( + InGOPATH(), + Modes(Default), // golang/go#57521: this test is temporarily failing in 'experimental' mode + EnvVars{"GO111MODULE": "auto"}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + if err := env.Sandbox.RunGoCommand(env.Ctx, "", "mod", []string{"init", "mod.com"}, nil, true); err != nil { + t.Fatal(err) + } + + // TODO(golang/go#57558, golang/go#57512): file watching is asynchronous, + // and we must wait for the view to be reconstructed before touching + // main.go, so that the new view "knows" about main.go. This is a bug, but + // awaiting the change here avoids it. + env.AfterChange() + + env.RegexpReplace("main.go", `"foo/blah"`, `"mod.com/foo/blah"`) + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + ) + }) +} + +// Reproduces golang/go#40487. +func TestSwitchFromModulesToGOPATH(t *testing.T) { + const files = ` +-- foo/go.mod -- +module mod.com + +go 1.14 +-- foo/blah/blah.go -- +package blah + +const Name = "" +-- foo/main.go -- +package main + +import "mod.com/blah" + +func main() { + _ = blah.Name +} +` + WithOptions( + InGOPATH(), + EnvVars{"GO111MODULE": "auto"}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("foo/main.go") + env.RemoveWorkspaceFile("foo/go.mod") + env.AfterChange( + Diagnostics(env.AtRegexp("foo/main.go", `"mod.com/blah"`)), + ) + env.RegexpReplace("foo/main.go", `"mod.com/blah"`, `"foo/blah"`) + env.AfterChange( + NoDiagnostics(ForFile("foo/main.go")), + ) + }) +} + +func TestNewSymbolInTestVariant(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.12 +-- a/a.go -- +package a + +func bob() {} +-- a/a_test.go -- +package a + +import "testing" + +func TestBob(t *testing.T) { + bob() +} +` + Run(t, files, func(t *testing.T, env *Env) { + // Add a new symbol to the package under test and use it in the test + // variant. Expect no diagnostics. + env.WriteWorkspaceFiles(map[string]string{ + "a/a.go": `package a + +func bob() {} +func george() {} +`, + "a/a_test.go": `package a + +import "testing" + +func TestAll(t *testing.T) { + bob() + george() +} +`, + }) + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + NoDiagnostics(ForFile("a/a_test.go")), + ) + // Now, add a new file to the test variant and use its symbol in the + // original test file. Expect no diagnostics. + env.WriteWorkspaceFiles(map[string]string{ + "a/a_test.go": `package a + +import "testing" + +func TestAll(t *testing.T) { + bob() + george() + hi() +} +`, + "a/a2_test.go": `package a + +import "testing" + +func hi() {} + +func TestSomething(t *testing.T) {} +`, + }) + env.AfterChange( + NoDiagnostics(ForFile("a/a_test.go")), + NoDiagnostics(ForFile("a/a2_test.go")), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/adhoc_test.go b/contribs/gnopls/internal/test/integration/workspace/adhoc_test.go new file mode 100644 index 00000000000..3d451dd5f08 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/adhoc_test.go @@ -0,0 +1,39 @@ +// 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 workspace + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// Test for golang/go#57209: editing a file in an ad-hoc package should not +// trigger conflicting diagnostics. +func TestAdhoc_Edits(t *testing.T) { + const files = ` +-- a.go -- +package foo + +const X = 1 + +-- b.go -- +package foo + +// import "errors" + +const Y = X +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("b.go") + + for i := 0; i < 10; i++ { + env.RegexpReplace("b.go", `// import "errors"`, `import "errors"`) + env.RegexpReplace("b.go", `import "errors"`, `// import "errors"`) + env.AfterChange(NoDiagnostics()) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/broken_test.go b/contribs/gnopls/internal/test/integration/workspace/broken_test.go new file mode 100644 index 00000000000..8f00be775e4 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/broken_test.go @@ -0,0 +1,265 @@ +// 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 workspace + +import ( + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/server" + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/internal/testenv" +) + +// This file holds various tests for UX with respect to broken workspaces. +// +// TODO: consolidate other tests here. +// +// TODO: write more tests: +// - an explicit GOWORK value that doesn't exist +// - using modules and/or GOWORK inside of GOPATH? + +// Test for golang/go#53933 +func TestBrokenWorkspace_DuplicateModules(t *testing.T) { + // The go command error message was improved in Go 1.20 to mention multiple + // modules. + testenv.NeedsGo1Point(t, 20) + + // This proxy module content is replaced by the workspace, but is still + // required for module resolution to function in the Go command. + const proxy = ` +-- example.com/foo@v0.0.1/go.mod -- +module example.com/foo + +go 1.12 +` + + const src = ` +-- go.work -- +go 1.18 + +use ( + ./package1 + ./package1/vendor/example.com/foo + ./package2 + ./package2/vendor/example.com/foo +) + +-- package1/go.mod -- +module mod.test + +go 1.18 + +require example.com/foo v0.0.1 +-- package1/main.go -- +package main + +import "example.com/foo" + +func main() { + _ = foo.CompleteMe +} +-- package1/vendor/example.com/foo/go.mod -- +module example.com/foo + +go 1.18 +-- package1/vendor/example.com/foo/foo.go -- +package foo + +const CompleteMe = 111 +-- package2/go.mod -- +module mod2.test + +go 1.18 + +require example.com/foo v0.0.1 +-- package2/main.go -- +package main + +import "example.com/foo" + +func main() { + _ = foo.CompleteMe +} +-- package2/vendor/example.com/foo/go.mod -- +module example.com/foo + +go 1.18 +-- package2/vendor/example.com/foo/foo.go -- +package foo + +const CompleteMe = 222 +` + + WithOptions( + ProxyFiles(proxy), + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("package1/main.go") + env.AfterChange( + OutstandingWork(server.WorkspaceLoadFailure, `module example.com/foo appears multiple times in workspace`), + ) + + // Remove the redundant vendored copy of example.com. + env.WriteWorkspaceFile("go.work", `go 1.18 + use ( + ./package1 + ./package2 + ./package2/vendor/example.com/foo + ) + `) + env.AfterChange(NoOutstandingWork(IgnoreTelemetryPromptWork)) + + // Check that definitions in package1 go to the copy vendored in package2. + location := string(env.GoToDefinition(env.RegexpSearch("package1/main.go", "CompleteMe")).URI) + const wantLocation = "package2/vendor/example.com/foo/foo.go" + if !strings.HasSuffix(location, wantLocation) { + t.Errorf("got definition of CompleteMe at %q, want %q", location, wantLocation) + } + }) +} + +// Test for golang/go#43186: correcting the module path should fix errors +// without restarting gopls. +func TestBrokenWorkspace_WrongModulePath(t *testing.T) { + const files = ` +-- go.mod -- +module mod.testx + +go 1.18 +-- p/internal/foo/foo.go -- +package foo + +const C = 1 +-- p/internal/bar/bar.go -- +package bar + +import "mod.test/p/internal/foo" + +const D = foo.C + 1 +-- p/internal/bar/bar_test.go -- +package bar_test + +import ( + "mod.test/p/internal/foo" + . "mod.test/p/internal/bar" +) + +const E = D + foo.C +-- p/internal/baz/baz_test.go -- +package baz_test + +import ( + named "mod.test/p/internal/bar" +) + +const F = named.D - 3 +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("p/internal/bar/bar.go") + env.AfterChange( + Diagnostics(env.AtRegexp("p/internal/bar/bar.go", "\"mod.test/p/internal/foo\"")), + ) + env.OpenFile("go.mod") + env.RegexpReplace("go.mod", "mod.testx", "mod.test") + env.SaveBuffer("go.mod") // saving triggers a reload + env.AfterChange(NoDiagnostics()) + }) +} + +func TestMultipleModules_Warning(t *testing.T) { + t.Skip("temporary skip for golang/go#57979: revisit after zero-config logic is in place") + + msgForVersion := func(ver int) string { + if ver >= 18 { + return `gopls was not able to find modules in your workspace.` + } else { + return `gopls requires a module at the root of your workspace.` + } + } + + const modules = ` +-- a/go.mod -- +module a.com + +go 1.12 +-- a/a.go -- +package a +-- a/empty.go -- +// an empty file +-- b/go.mod -- +module b.com + +go 1.12 +-- b/b.go -- +package b +` + for _, go111module := range []string{"on", "auto"} { + t.Run("GO111MODULE="+go111module, func(t *testing.T) { + WithOptions( + Modes(Default), + EnvVars{"GO111MODULE": go111module}, + ).Run(t, modules, func(t *testing.T, env *Env) { + ver := env.GoVersion() + msg := msgForVersion(ver) + env.OpenFile("a/a.go") + env.OpenFile("a/empty.go") + env.OpenFile("b/go.mod") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "package a")), + Diagnostics(env.AtRegexp("b/go.mod", "module b.com")), + OutstandingWork(server.WorkspaceLoadFailure, msg), + ) + + // Changing the workspace folders to the valid modules should resolve + // the workspace errors and diagnostics. + // + // TODO(rfindley): verbose work tracking doesn't follow changing the + // workspace folder, therefore we can't invoke AfterChange here. + env.ChangeWorkspaceFolders("a", "b") + env.Await( + NoDiagnostics(ForFile("a/a.go")), + NoDiagnostics(ForFile("b/go.mod")), + NoOutstandingWork(IgnoreTelemetryPromptWork), + ) + + env.ChangeWorkspaceFolders(".") + + // TODO(rfindley): when GO111MODULE=auto, we need to open or change a + // file here in order to detect a critical error. This is because gopls + // has forgotten about a/a.go, and therefore doesn't hit the heuristic + // "all packages are command-line-arguments". + // + // This is broken, and could be fixed by adjusting the heuristic to + // account for the scenario where there are *no* workspace packages, or + // (better) trying to get workspace packages for each open file. See + // also golang/go#54261. + env.OpenFile("b/b.go") + env.AfterChange( + // TODO(rfindley): fix these missing diagnostics. + // Diagnostics(env.AtRegexp("a/a.go", "package a")), + // Diagnostics(env.AtRegexp("b/go.mod", "module b.com")), + Diagnostics(env.AtRegexp("b/b.go", "package b")), + OutstandingWork(server.WorkspaceLoadFailure, msg), + ) + }) + }) + } + + // Expect no warning if GO111MODULE=auto in a directory in GOPATH. + t.Run("GOPATH_GO111MODULE_auto", func(t *testing.T) { + WithOptions( + Modes(Default), + EnvVars{"GO111MODULE": "auto"}, + InGOPATH(), + ).Run(t, modules, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange( + NoDiagnostics(ForFile("a/a.go")), + NoOutstandingWork(IgnoreTelemetryPromptWork), + ) + }) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/directoryfilters_test.go b/contribs/gnopls/internal/test/integration/workspace/directoryfilters_test.go new file mode 100644 index 00000000000..6eec8377233 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/directoryfilters_test.go @@ -0,0 +1,207 @@ +// 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 workspace + +import ( + "sort" + "strings" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// This file contains regression tests for the directoryFilters setting. +// +// TODO: +// - consolidate some of these tests into a single test +// - add more tests for changing directory filters + +func TestDirectoryFilters(t *testing.T) { + WithOptions( + ProxyFiles(workspaceProxy), + WorkspaceFolders("pkg"), + Settings{ + "directoryFilters": []string{"-inner"}, + }, + ).Run(t, workspaceModule, func(t *testing.T, env *Env) { + syms := env.Symbol("Hi") + sort.Slice(syms, func(i, j int) bool { return syms[i].ContainerName < syms[j].ContainerName }) + for _, s := range syms { + if strings.Contains(s.ContainerName, "inner") { + t.Errorf("WorkspaceSymbol: found symbol %q with container %q, want \"inner\" excluded", s.Name, s.ContainerName) + } + } + }) +} + +func TestDirectoryFiltersLoads(t *testing.T) { + // exclude, and its error, should be excluded from the workspace. + const files = ` +-- go.mod -- +module example.com + +go 1.12 +-- exclude/exclude.go -- +package exclude + +const _ = Nonexistant +` + + WithOptions( + Settings{"directoryFilters": []string{"-exclude"}}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + NoDiagnostics(ForFile("exclude/x.go")), + ) + }) +} + +func TestDirectoryFiltersTransitiveDep(t *testing.T) { + // Even though exclude is excluded from the workspace, it should + // still be importable as a non-workspace package. + const files = ` +-- go.mod -- +module example.com + +go 1.12 +-- include/include.go -- +package include +import "example.com/exclude" + +const _ = exclude.X +-- exclude/exclude.go -- +package exclude + +const _ = Nonexistant // should be ignored, since this is a non-workspace package +const X = 1 +` + + WithOptions( + Settings{"directoryFilters": []string{"-exclude"}}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + NoDiagnostics(ForFile("exclude/exclude.go")), // filtered out + NoDiagnostics(ForFile("include/include.go")), // successfully builds + ) + }) +} + +// Test for golang/go#46438: support for '**' in directory filters. +func TestDirectoryFilters_Wildcard(t *testing.T) { + filters := []string{"-**/bye"} + WithOptions( + ProxyFiles(workspaceProxy), + WorkspaceFolders("pkg"), + Settings{ + "directoryFilters": filters, + }, + ).Run(t, workspaceModule, func(t *testing.T, env *Env) { + syms := env.Symbol("Bye") + sort.Slice(syms, func(i, j int) bool { return syms[i].ContainerName < syms[j].ContainerName }) + for _, s := range syms { + if strings.Contains(s.ContainerName, "bye") { + t.Errorf("WorkspaceSymbol: found symbol %q with container %q with filters %v", s.Name, s.ContainerName, filters) + } + } + }) +} + +// Test for golang/go#52993: wildcard directoryFilters should apply to +// goimports scanning as well. +func TestDirectoryFilters_ImportScanning(t *testing.T) { + const files = ` +-- go.mod -- +module mod.test + +go 1.12 +-- main.go -- +package main + +func main() { + bye.Goodbye() + hi.Hello() +} +-- p/bye/bye.go -- +package bye + +func Goodbye() {} +-- hi/hi.go -- +package hi + +func Hello() {} +` + + WithOptions( + Settings{ + "directoryFilters": []string{"-**/bye", "-hi"}, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + beforeSave := env.BufferText("main.go") + env.OrganizeImports("main.go") + got := env.BufferText("main.go") + if got != beforeSave { + t.Errorf("after organizeImports code action, got modified buffer:\n%s", got) + } + }) +} + +// Test for golang/go#52993: non-wildcard directoryFilters should still be +// applied relative to the workspace folder, not the module root. +func TestDirectoryFilters_MultiRootImportScanning(t *testing.T) { + const files = ` +-- go.work -- +go 1.18 + +use ( + a + b +) +-- a/go.mod -- +module mod1.test + +go 1.18 +-- a/main.go -- +package main + +func main() { + hi.Hi() +} +-- a/hi/hi.go -- +package hi + +func Hi() {} +-- b/go.mod -- +module mod2.test + +go 1.18 +-- b/main.go -- +package main + +func main() { + hi.Hi() +} +-- b/hi/hi.go -- +package hi + +func Hi() {} +` + + WithOptions( + Settings{ + "directoryFilters": []string{"-hi"}, // this test fails with -**/hi + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a/main.go") + beforeSave := env.BufferText("a/main.go") + env.OrganizeImports("a/main.go") + got := env.BufferText("a/main.go") + if got == beforeSave { + t.Errorf("after organizeImports code action, got identical buffer:\n%s", got) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/fromenv_test.go b/contribs/gnopls/internal/test/integration/workspace/fromenv_test.go new file mode 100644 index 00000000000..bc909c7deca --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/fromenv_test.go @@ -0,0 +1,76 @@ +// 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 workspace + +import ( + "fmt" + "path/filepath" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// Test that setting go.work via environment variables or settings works. +func TestUseGoWorkOutsideTheWorkspace(t *testing.T) { + // As discussed in + // https://github.com/golang/go/issues/59458#issuecomment-1513794691, we must + // use \-separated paths in go.work use directives for this test to work + // correctly on windows. + var files = fmt.Sprintf(` +-- work/a/go.mod -- +module a.com + +go 1.12 +-- work/a/a.go -- +package a +-- work/b/go.mod -- +module b.com + +go 1.12 +-- work/b/b.go -- +package b + +func _() { + x := 1 // unused +} +-- other/c/go.mod -- +module c.com + +go 1.18 +-- other/c/c.go -- +package c +-- config/go.work -- +go 1.18 + +use ( + %s + %s + %s +) +`, + filepath.Join("$SANDBOX_WORKDIR", "work", "a"), + filepath.Join("$SANDBOX_WORKDIR", "work", "b"), + filepath.Join("$SANDBOX_WORKDIR", "other", "c"), + ) + + WithOptions( + WorkspaceFolders("work"), // use a nested workspace dir, so that GOWORK is outside the workspace + EnvVars{"GOWORK": filepath.Join("$SANDBOX_WORKDIR", "config", "go.work")}, + ).Run(t, files, func(t *testing.T, env *Env) { + // When we have an explicit GOWORK set, we should get a file watch request. + env.OnceMet( + InitialWorkspaceLoad, + FileWatchMatching(`other`), + FileWatchMatching(`config.go\.work`), + ) + env.Await(FileWatchMatching(`config.go\.work`)) + // Even though work/b is not open, we should get its diagnostics as it is + // included in the workspace. + env.OpenFile("work/a/a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("work/b/b.go", "x := 1"), WithMessage("not used")), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/goversion_test.go b/contribs/gnopls/internal/test/integration/workspace/goversion_test.go new file mode 100644 index 00000000000..0a2f91505c2 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/goversion_test.go @@ -0,0 +1,126 @@ +// 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 workspace + +import ( + "flag" + "os" + "os/exec" + "runtime" + "strings" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/internal/testenv" +) + +var go121bin = flag.String("go121bin", "", "bin directory containing go 1.21 or later") + +// TODO(golang/go#65917): delete this test once we no longer support building +// gopls with older Go versions. +func TestCanHandlePatchVersions(t *testing.T) { + // This test verifies the fixes for golang/go#66195 and golang/go#66636 -- + // that gopls does not crash when encountering a go version with a patch + // number in the go.mod file. + // + // This is tricky to test, because the regression requires that gopls is + // built with an older go version, and then the environment is upgraded to + // have a more recent go. To set up this scenario, the test requires a path + // to a bin directory containing go1.21 or later. + if *go121bin == "" { + t.Skip("-go121bin directory is not set") + } + + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + t.Skip("requires linux or darwin") // for PATH separator + } + + path := os.Getenv("PATH") + t.Setenv("PATH", *go121bin+":"+path) + + const files = ` +-- go.mod -- +module example.com/bar + +go 1.21.1 + +-- p.go -- +package bar + +type I interface { string } +` + + WithOptions( + EnvVars{ + "PATH": path, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("p.go") + env.AfterChange( + NoDiagnostics(ForFile("p.go")), + ) + }) +} + +func TestTypeCheckingFutureVersions(t *testing.T) { + // This test checks the regression in golang/go#66677, where go/types fails + // silently when the language version is 1.22. + // + // It does this by recreating the scenario of a toolchain upgrade to 1.22, as + // reported in the issue. For this to work, the test must be able to download + // toolchains from proxy.golang.org. + // + // This is really only a problem for Go 1.21, because with Go 1.23, the bug + // is fixed, and starting with 1.23 we're going to *require* 1.23 to build + // gopls. + // + // TODO(golang/go#65917): delete this test after Go 1.23 is released and + // gopls requires the latest Go to build. + testenv.SkipAfterGo1Point(t, 21) + + if testing.Short() { + t.Skip("skipping with -short, as this test uses the network") + } + + // If go 1.22.2 is already available in the module cache, reuse it rather + // than downloading it anew. + out, err := exec.Command("go", "env", "GOPATH").Output() + if err != nil { + t.Fatal(err) + } + gopath := strings.TrimSpace(string(out)) // use the ambient 1.22.2 toolchain if available + + const files = ` +-- go.mod -- +module example.com/foo + +go 1.22.2 + +-- main.go -- +package main + +func main() { + x := 1 +} +` + + WithOptions( + Modes(Default), // slow test, only run in one mode + EnvVars{ + "GOPATH": gopath, + "GOTOOLCHAIN": "", // not local + "GOPROXY": "https://proxy.golang.org", + "GOSUMDB": "sum.golang.org", + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.AfterChange( + Diagnostics( + env.AtRegexp("main.go", "x"), + WithMessage("not used"), + ), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/metadata_test.go b/contribs/gnopls/internal/test/integration/workspace/metadata_test.go new file mode 100644 index 00000000000..59dfec3ad97 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/metadata_test.go @@ -0,0 +1,238 @@ +// 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 workspace + +import ( + "strings" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// TODO(rfindley): move workspace tests related to metadata bugs into this +// file. + +func TestFixImportDecl(t *testing.T) { + const src = ` +-- go.mod -- +module mod.test + +go 1.12 +-- p.go -- +package p + +import ( + _ "fmt" + +const C = 42 +` + + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("p.go") + env.RegexpReplace("p.go", "\"fmt\"", "\"fmt\"\n)") + env.AfterChange( + NoDiagnostics(ForFile("p.go")), + ) + }) +} + +// Test that moving ignoring a file via build constraints causes diagnostics to +// be resolved. +func TestIgnoreFile(t *testing.T) { + const src = ` +-- go.mod -- +module mod.test + +go 1.12 +-- foo.go -- +package main + +func main() {} +-- bar.go -- +package main + +func main() {} + ` + + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("foo.go") + env.OpenFile("bar.go") + env.OnceMet( + env.DoneWithOpen(), + Diagnostics(env.AtRegexp("foo.go", "func (main)")), + Diagnostics(env.AtRegexp("bar.go", "func (main)")), + ) + + // Ignore bar.go. This should resolve diagnostics. + env.RegexpReplace("bar.go", "package main", "//go:build ignore\n\npackage main") + + // To make this test pass with experimentalUseInvalidMetadata, we could make + // an arbitrary edit that invalidates the snapshot, at which point the + // orphaned diagnostics will be invalidated. + // + // But of course, this should not be necessary: we should invalidate stale + // information when fresh metadata arrives. + // env.RegexpReplace("foo.go", "package main", "package main // test") + env.AfterChange( + NoDiagnostics(ForFile("foo.go")), + NoDiagnostics(ForFile("bar.go")), + ) + + // If instead of 'ignore' (which gopls treats as a standalone package) we + // used a different build tag, we should get a warning about having no + // packages for bar.go + env.RegexpReplace("bar.go", "ignore", "excluded") + env.AfterChange( + Diagnostics(env.AtRegexp("bar.go", "package (main)"), WithMessage("excluded due to its build tags")), + ) + }) +} + +func TestReinitializeRepeatedly(t *testing.T) { + const multiModule = ` +-- go.work -- +go 1.18 + +use ( + moda/a + modb +) +-- moda/a/go.mod -- +module a.com + +require b.com v1.2.3 +-- moda/a/go.sum -- +b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= +b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= +-- moda/a/a.go -- +package a + +import ( + "b.com/b" +) + +func main() { + var x int + _ = b.Hello() + // AAA +} +-- modb/go.mod -- +module b.com + +-- modb/b/b.go -- +package b + +func Hello() int { + var x int +} +` + WithOptions( + ProxyFiles(workspaceModuleProxy), + Settings{ + // For this test, we want workspace diagnostics to start immediately + // during change processing. + "diagnosticsDelay": "0", + }, + ).Run(t, multiModule, func(t *testing.T, env *Env) { + env.OpenFile("moda/a/a.go") + env.AfterChange() + + // This test verifies that we fully process workspace reinitialization + // (which allows GOPROXY), even when the reinitialized snapshot is + // invalidated by subsequent changes. + // + // First, update go.work to remove modb. This will cause reinitialization + // to fetch b.com from the proxy. + env.WriteWorkspaceFile("go.work", "go 1.18\nuse moda/a") + // Next, wait for gopls to start processing the change. Because we've set + // diagnosticsDelay to zero, this will start diagnosing the workspace (and + // try to reinitialize on the snapshot context). + env.Await(env.StartedChangeWatchedFiles()) + // Finally, immediately make a file change to cancel the previous + // operation. This is racy, but will usually cause initialization to be + // canceled. + env.RegexpReplace("moda/a/a.go", "AAA", "BBB") + env.AfterChange() + // Now, to satisfy a definition request, gopls will try to reload moda. But + // without access to the proxy (because this is no longer a + // reinitialization), this loading will fail. + loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) + got := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(got, want) { + t.Errorf("expected %s, got %v", want, got) + } + }) +} + +// Test for golang/go#59458. With lazy module loading, we may not need +// transitively required modules. +func TestNestedModuleLoading_Issue59458(t *testing.T) { + // In this test, module b.com/nested requires b.com/other, which in turn + // requires b.com, but b.com/nested does not reach b.com through the package + // graph. Therefore, b.com/nested does not need b.com on 1.17 and later, + // thanks to graph pruning. + // + // We verify that we can load b.com/nested successfully. Previously, we + // couldn't, because loading the pattern b.com/nested/... matched the module + // b.com, which exists in the module graph but does not have a go.sum entry. + + const proxy = ` +-- b.com@v1.2.3/go.mod -- +module b.com + +go 1.18 +-- b.com@v1.2.3/b/b.go -- +package b + +func Hello() {} + +-- b.com/other@v1.4.6/go.mod -- +module b.com/other + +go 1.18 + +require b.com v1.2.3 +-- b.com/other@v1.4.6/go.sun -- +b.com v1.2.3 h1:AGjCxWRJLUuJiZ21IUTByr9buoa6+B6Qh5LFhVLKpn4= +-- b.com/other@v1.4.6/bar/bar.go -- +package bar + +import "b.com/b" + +func _() { + b.Hello() +} +-- b.com/other@v1.4.6/foo/foo.go -- +package foo + +const Foo = 0 +` + + const files = ` +-- go.mod -- +module b.com/nested + +go 1.18 + +require b.com/other v1.4.6 +-- go.sum -- +b.com/other v1.4.6 h1:pHXSzGsk6DamYXp9uRdDB9A/ZQqAN9it+JudU0sBf94= +b.com/other v1.4.6/go.mod h1:T0TYuGdAHw4p/l0+1P/yhhYHfZRia7PaadNVDu58OWM= +-- nested.go -- +package nested + +import "b.com/other/foo" + +const C = foo.Foo +` + WithOptions( + ProxyFiles(proxy), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + NoDiagnostics(), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/misspelling_test.go b/contribs/gnopls/internal/test/integration/workspace/misspelling_test.go new file mode 100644 index 00000000000..ddca05c860e --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/misspelling_test.go @@ -0,0 +1,80 @@ +// 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 workspace + +import ( + "runtime" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/compare" +) + +// Test for golang/go#57081. +func TestFormattingMisspelledURI(t *testing.T) { + if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { + t.Skip("golang/go#57081 only reproduces on case-insensitive filesystems.") + } + const files = ` +-- go.mod -- +module mod.test + +go 1.19 +-- foo.go -- +package foo + +const C = 2 // extra space is intentional +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("Foo.go") + env.FormatBuffer("Foo.go") + want := env.BufferText("Foo.go") + + if want == "" { + t.Fatalf("Foo.go is empty") + } + + // In golang/go#57081, we observed that if overlay cases don't match, gopls + // will find (and format) the on-disk contents rather than the overlay, + // resulting in invalid edits. + // + // Verify that this doesn't happen, by confirming that formatting is + // idempotent. + env.FormatBuffer("Foo.go") + got := env.BufferText("Foo.go") + if diff := compare.Text(want, got); diff != "" { + t.Errorf("invalid content after second formatting:\n%s", diff) + } + }) +} + +// Test that we can find packages for open files with different spelling on +// case-insensitive file systems. +func TestPackageForMisspelledURI(t *testing.T) { + t.Skip("golang/go#57081: this test fails because the Go command does not load Foo.go correctly") + if runtime.GOOS != "windows" && runtime.GOOS != "darwin" { + t.Skip("golang/go#57081 only reproduces on case-insensitive filesystems.") + } + const files = ` +-- go.mod -- +module mod.test + +go 1.19 +-- foo.go -- +package foo + +const C = D +-- bar.go -- +package foo + +const D = 2 +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("Foo.go") + env.AfterChange(NoDiagnostics()) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/modules_test.go b/contribs/gnopls/internal/test/integration/workspace/modules_test.go new file mode 100644 index 00000000000..7eedcff688a --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/modules_test.go @@ -0,0 +1,161 @@ +// 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 workspace + +import ( + "sort" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestModulesCmd(t *testing.T) { + const goModView = ` +-- go.mod -- +module foo + +-- pkg/pkg.go -- +package pkg +func Pkg() + +-- bar/bar.go -- +package bar +func Bar() + +-- bar/baz/go.mod -- +module baz + +-- bar/baz/baz.go -- +package baz +func Baz() +` + + const goWorkView = ` +-- go.work -- +use ./foo +use ./bar + +-- foo/go.mod -- +module foo + +-- foo/foo.go -- +package foo +func Foo() + +-- bar/go.mod -- +module bar + +-- bar/bar.go -- +package bar +func Bar() +` + + t.Run("go.mod view", func(t *testing.T) { + // If baz isn't loaded, it will not be included + t.Run("unloaded", func(t *testing.T) { + Run(t, goModView, func(t *testing.T, env *Env) { + checkModules(t, env, env.Editor.DocumentURI(""), -1, []command.Module{ + { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }) + }) + }) + + // With baz loaded and recursion enabled, baz will be included + t.Run("recurse", func(t *testing.T) { + Run(t, goModView, func(t *testing.T, env *Env) { + env.OpenFile("bar/baz/baz.go") + checkModules(t, env, env.Editor.DocumentURI(""), -1, []command.Module{ + { + Path: "baz", + GoMod: env.Editor.DocumentURI("bar/baz/go.mod"), + }, + { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }) + }) + }) + + // With recursion=1, baz will not be included + t.Run("depth", func(t *testing.T) { + Run(t, goModView, func(t *testing.T, env *Env) { + env.OpenFile("bar/baz/baz.go") + checkModules(t, env, env.Editor.DocumentURI(""), 1, []command.Module{ + { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }) + }) + }) + + // Baz will be included if it is requested specifically + t.Run("nested", func(t *testing.T) { + Run(t, goModView, func(t *testing.T, env *Env) { + env.OpenFile("bar/baz/baz.go") + checkModules(t, env, env.Editor.DocumentURI("bar/baz"), 0, []command.Module{ + { + Path: "baz", + GoMod: env.Editor.DocumentURI("bar/baz/go.mod"), + }, + }) + }) + }) + }) + + t.Run("go.work view", func(t *testing.T) { + t.Run("base", func(t *testing.T) { + Run(t, goWorkView, func(t *testing.T, env *Env) { + checkModules(t, env, env.Editor.DocumentURI(""), 0, nil) + }) + }) + + t.Run("recursive", func(t *testing.T) { + Run(t, goWorkView, func(t *testing.T, env *Env) { + checkModules(t, env, env.Editor.DocumentURI(""), -1, []command.Module{ + { + Path: "bar", + GoMod: env.Editor.DocumentURI("bar/go.mod"), + }, + { + Path: "foo", + GoMod: env.Editor.DocumentURI("foo/go.mod"), + }, + }) + }) + }) + }) +} + +func checkModules(t testing.TB, env *Env, dir protocol.DocumentURI, maxDepth int, want []command.Module) { + t.Helper() + + cmd := command.NewModulesCommand("Modules", command.ModulesArgs{Dir: dir, MaxDepth: maxDepth}) + var result command.ModulesResult + env.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: command.Modules.String(), + Arguments: cmd.Arguments, + }, &result) + + // The ordering of results is undefined and modules from a go.work view are + // retrieved from a map, so sort the results to ensure consistency + sort.Slice(result.Modules, func(i, j int) bool { + a, b := result.Modules[i], result.Modules[j] + return strings.Compare(a.Path, b.Path) < 0 + }) + + diff := cmp.Diff(want, result.Modules) + if diff != "" { + t.Errorf("Modules(%v) returned unexpected diff (-want +got):\n%s", dir, diff) + } +} diff --git a/contribs/gnopls/internal/test/integration/workspace/multi_folder_test.go b/contribs/gnopls/internal/test/integration/workspace/multi_folder_test.go new file mode 100644 index 00000000000..6adc1f8d5ce --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/multi_folder_test.go @@ -0,0 +1,128 @@ +// 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 workspace + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +// TODO(rfindley): update the marker tests to support the concept of multiple +// workspace folders, and move this there. +func TestMultiView_Diagnostics(t *testing.T) { + // In the past, gopls would only diagnose one View at a time + // (the last to have changed). + // + // This test verifies that gopls can maintain diagnostics for multiple Views. + const files = ` + +-- a/go.mod -- +module golang.org/lsptests/a + +go 1.20 +-- a/a.go -- +package a + +func _() { + x := 1 // unused +} +-- b/go.mod -- +module golang.org/lsptests/b + +go 1.20 +-- b/b.go -- +package b + +func _() { + y := 2 // unused +} +` + + WithOptions( + WorkspaceFolders("a", "b"), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("a/a.go", "x")), + Diagnostics(env.AtRegexp("b/b.go", "y")), + ) + }) +} + +func TestMultiView_LocalReplace(t *testing.T) { + // This is a regression test for #66145, where gopls attempted to load a + // package in a locally replaced module as a workspace package, resulting in + // spurious import diagnostics because the module graph had been pruned. + + const proxy = ` +-- example.com/c@v1.2.3/go.mod -- +module example.com/c + +go 1.20 + +-- example.com/c@v1.2.3/c.go -- +package c + +const C = 3 + +` + // In the past, gopls would only diagnose one View at a time + // (the last to have changed). + // + // This test verifies that gopls can maintain diagnostics for multiple Views. + const files = ` +-- a/go.mod -- +module golang.org/lsptests/a + +go 1.20 + +require golang.org/lsptests/b v1.2.3 + +replace golang.org/lsptests/b => ../b + +-- a/a.go -- +package a + +import "golang.org/lsptests/b" + +const A = b.B - 1 + +-- b/go.mod -- +module golang.org/lsptests/b + +go 1.20 + +require example.com/c v1.2.3 + +-- b/go.sum -- +example.com/c v1.2.3 h1:hsOPhoHQLZPEn7l3kNya3fR3SfqW0/rafZMP8ave6fg= +example.com/c v1.2.3/go.mod h1:4uG6Y5qX88LrEd4KfRoiguHZIbdLKUEHD1wXqPyrHcA= +-- b/b.go -- +package b + +const B = 2 + +-- b/unrelated/u.go -- +package unrelated + +import "example.com/c" + +const U = c.C +` + + WithOptions( + WorkspaceFolders("a", "b"), + ProxyFiles(proxy), + ).Run(t, files, func(t *testing.T, env *Env) { + // Opening unrelated first ensures that when we compute workspace packages + // for the "a" workspace, it includes the unrelated package, which will be + // unloadable from a as there is no a/go.sum. + env.OpenFile("b/unrelated/u.go") + env.AfterChange() + env.OpenFile("a/a.go") + env.AfterChange(NoDiagnostics()) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/packages_test.go b/contribs/gnopls/internal/test/integration/workspace/packages_test.go new file mode 100644 index 00000000000..106734a1864 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/packages_test.go @@ -0,0 +1,482 @@ +// 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 workspace + +import ( + "sort" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestPackages(t *testing.T) { + const files = ` +-- go.mod -- +module foo + +-- foo.go -- +package foo +func Foo() + +-- bar/bar.go -- +package bar +func Bar() + +-- baz/go.mod -- +module baz + +-- baz/baz.go -- +package baz +func Baz() +` + + t.Run("file", func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("foo.go")}, false, 0, []command.Package{ + { + Path: "foo", + ModulePath: "foo", + }, + }, map[string]command.Module{ + "foo": { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }, []string{}) + }) + }) + + t.Run("package", func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("bar")}, false, 0, []command.Package{ + { + Path: "foo/bar", + ModulePath: "foo", + }, + }, map[string]command.Module{ + "foo": { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }, []string{}) + }) + }) + + t.Run("workspace", func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("")}, true, 0, []command.Package{ + { + Path: "foo", + ModulePath: "foo", + }, + { + Path: "foo/bar", + ModulePath: "foo", + }, + }, map[string]command.Module{ + "foo": { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }, []string{}) + }) + }) + + t.Run("nested module", func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + // Load the nested module + env.OpenFile("baz/baz.go") + + // Request packages using the URI of the nested module _directory_ + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("baz")}, true, 0, []command.Package{ + { + Path: "baz", + ModulePath: "baz", + }, + }, map[string]command.Module{ + "baz": { + Path: "baz", + GoMod: env.Editor.DocumentURI("baz/go.mod"), + }, + }, []string{}) + }) + }) +} + +func TestPackagesWithTests(t *testing.T) { + const files = ` +-- go.mod -- +module foo + +-- foo.go -- +package foo +import "testing" +func Foo() +func TestFoo2(t *testing.T) + +-- foo_test.go -- +package foo +import "testing" +func TestFoo(t *testing.T) + +-- foo2_test.go -- +package foo_test +import "testing" +func TestBar(t *testing.T) {} + +-- baz/baz_test.go -- +package baz +import "testing" +func TestBaz(*testing.T) +func BenchmarkBaz(*testing.B) +func FuzzBaz(*testing.F) +func ExampleBaz() + +-- bat/go.mod -- +module bat + +-- bat/bat_test.go -- +package bat +import "testing" +func Test(*testing.T) +` + + t.Run("file", func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("foo_test.go")}, false, command.NeedTests, []command.Package{ + { + Path: "foo", + ModulePath: "foo", + }, + { + Path: "foo", + ForTest: "foo", + ModulePath: "foo", + TestFiles: []command.TestFile{ + { + URI: env.Editor.DocumentURI("foo_test.go"), + Tests: []command.TestCase{ + {Name: "TestFoo"}, + }, + }, + }, + }, + { + Path: "foo_test", + ForTest: "foo", + ModulePath: "foo", + TestFiles: []command.TestFile{ + { + URI: env.Editor.DocumentURI("foo2_test.go"), + Tests: []command.TestCase{ + {Name: "TestBar"}, + }, + }, + }, + }, + }, map[string]command.Module{ + "foo": { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }, []string{ + "func TestFoo(t *testing.T)", + "func TestBar(t *testing.T) {}", + }) + }) + }) + + t.Run("package", func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("baz")}, false, command.NeedTests, []command.Package{ + { + Path: "foo/baz", + ForTest: "foo/baz", + ModulePath: "foo", + TestFiles: []command.TestFile{ + { + URI: env.Editor.DocumentURI("baz/baz_test.go"), + Tests: []command.TestCase{ + {Name: "TestBaz"}, + {Name: "BenchmarkBaz"}, + {Name: "FuzzBaz"}, + {Name: "ExampleBaz"}, + }, + }, + }, + }, + }, map[string]command.Module{ + "foo": { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }, []string{ + "func TestBaz(*testing.T)", + "func BenchmarkBaz(*testing.B)", + "func FuzzBaz(*testing.F)", + "func ExampleBaz()", + }) + }) + }) + + t.Run("workspace", func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI(".")}, true, command.NeedTests, []command.Package{ + { + Path: "foo", + ModulePath: "foo", + }, + { + Path: "foo", + ForTest: "foo", + ModulePath: "foo", + TestFiles: []command.TestFile{ + { + URI: env.Editor.DocumentURI("foo_test.go"), + Tests: []command.TestCase{ + {Name: "TestFoo"}, + }, + }, + }, + }, + { + Path: "foo/baz", + ForTest: "foo/baz", + ModulePath: "foo", + TestFiles: []command.TestFile{ + { + URI: env.Editor.DocumentURI("baz/baz_test.go"), + Tests: []command.TestCase{ + {Name: "TestBaz"}, + {Name: "BenchmarkBaz"}, + {Name: "FuzzBaz"}, + {Name: "ExampleBaz"}, + }, + }, + }, + }, + { + Path: "foo_test", + ForTest: "foo", + ModulePath: "foo", + TestFiles: []command.TestFile{ + { + URI: env.Editor.DocumentURI("foo2_test.go"), + Tests: []command.TestCase{ + {Name: "TestBar"}, + }, + }, + }, + }, + }, map[string]command.Module{ + "foo": { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }, []string{ + "func TestFoo(t *testing.T)", + "func TestBaz(*testing.T)", + "func BenchmarkBaz(*testing.B)", + "func FuzzBaz(*testing.F)", + "func ExampleBaz()", + "func TestBar(t *testing.T) {}", + }) + }) + }) + + t.Run("nested module", func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + // Load the nested module + env.OpenFile("bat/bat_test.go") + + // Request packages using the URI of the nested module _directory_ + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("bat")}, true, command.NeedTests, []command.Package{ + { + Path: "bat", + ForTest: "bat", + ModulePath: "bat", + TestFiles: []command.TestFile{ + { + URI: env.Editor.DocumentURI("bat/bat_test.go"), + Tests: []command.TestCase{ + {Name: "Test"}, + }, + }, + }, + }, + }, map[string]command.Module{ + "bat": { + Path: "bat", + GoMod: env.Editor.DocumentURI("bat/go.mod"), + }, + }, []string{ + "func Test(*testing.T)", + }) + }) + }) +} + +func TestPackagesWithSubtests(t *testing.T) { + const files = ` +-- go.mod -- +module foo + +-- foo_test.go -- +package foo + +import "testing" + +// Verify that examples don't break subtest detection +func ExampleFoo() {} + +func TestFoo(t *testing.T) { + t.Run("Bar", func(t *testing.T) { + t.Run("Baz", func(t *testing.T) {}) + }) + t.Run("Bar", func(t *testing.T) {}) + t.Run("Bar", func(t *testing.T) {}) + t.Run("with space", func(t *testing.T) {}) + + var x X + y := func(t *testing.T) { + t.Run("VarSub", func(t *testing.T) {}) + } + t.Run("SubtestFunc", SubtestFunc) + t.Run("SubtestMethod", x.SubtestMethod) + t.Run("SubtestVar", y) +} + +func SubtestFunc(t *testing.T) { + t.Run("FuncSub", func(t *testing.T) {}) +} + +type X int +func (X) SubtestMethod(t *testing.T) { + t.Run("MethodSub", func(t *testing.T) {}) +} +` + + Run(t, files, func(t *testing.T, env *Env) { + checkPackages(t, env, []protocol.DocumentURI{env.Editor.DocumentURI("foo_test.go")}, false, command.NeedTests, []command.Package{ + { + Path: "foo", + ForTest: "foo", + ModulePath: "foo", + TestFiles: []command.TestFile{ + { + URI: env.Editor.DocumentURI("foo_test.go"), + Tests: []command.TestCase{ + {Name: "ExampleFoo"}, + {Name: "TestFoo"}, + {Name: "TestFoo/Bar"}, + {Name: "TestFoo/Bar/Baz"}, + {Name: "TestFoo/Bar#01"}, + {Name: "TestFoo/Bar#02"}, + {Name: "TestFoo/with_space"}, + {Name: "TestFoo/SubtestFunc"}, + {Name: "TestFoo/SubtestFunc/FuncSub"}, + {Name: "TestFoo/SubtestMethod"}, + {Name: "TestFoo/SubtestMethod/MethodSub"}, + {Name: "TestFoo/SubtestVar"}, + // {Name: "TestFoo/SubtestVar/VarSub"}, // TODO + }, + }, + }, + }, + }, map[string]command.Module{ + "foo": { + Path: "foo", + GoMod: env.Editor.DocumentURI("go.mod"), + }, + }, []string{ + "func ExampleFoo() {}", + `func TestFoo(t *testing.T) { + t.Run("Bar", func(t *testing.T) { + t.Run("Baz", func(t *testing.T) {}) + }) + t.Run("Bar", func(t *testing.T) {}) + t.Run("Bar", func(t *testing.T) {}) + t.Run("with space", func(t *testing.T) {}) + + var x X + y := func(t *testing.T) { + t.Run("VarSub", func(t *testing.T) {}) + } + t.Run("SubtestFunc", SubtestFunc) + t.Run("SubtestMethod", x.SubtestMethod) + t.Run("SubtestVar", y) +}`, + "t.Run(\"Bar\", func(t *testing.T) {\n\t\tt.Run(\"Baz\", func(t *testing.T) {})\n\t})", + `t.Run("Baz", func(t *testing.T) {})`, + `t.Run("Bar", func(t *testing.T) {})`, + `t.Run("Bar", func(t *testing.T) {})`, + `t.Run("with space", func(t *testing.T) {})`, + `t.Run("SubtestFunc", SubtestFunc)`, + `t.Run("FuncSub", func(t *testing.T) {})`, + `t.Run("SubtestMethod", x.SubtestMethod)`, + `t.Run("MethodSub", func(t *testing.T) {})`, + `t.Run("SubtestVar", y)`, + }) + }) +} + +func checkPackages(t testing.TB, env *Env, files []protocol.DocumentURI, recursive bool, mode command.PackagesMode, wantPkg []command.Package, wantModule map[string]command.Module, wantSource []string) { + t.Helper() + + cmd := command.NewPackagesCommand("Packages", command.PackagesArgs{Files: files, Recursive: recursive, Mode: mode}) + var result command.PackagesResult + env.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: command.Packages.String(), + Arguments: cmd.Arguments, + }, &result) + + // The ordering of packages is undefined so sort the results to ensure + // consistency + sort.Slice(result.Packages, func(i, j int) bool { + a, b := result.Packages[i], result.Packages[j] + c := strings.Compare(a.Path, b.Path) + if c != 0 { + return c < 0 + } + return strings.Compare(a.ForTest, b.ForTest) < 0 + }) + + // Instead of testing the exact values of the test locations (which would + // make these tests significantly more trouble to maintain), verify the + // source range they refer to. + gotSource := []string{} // avoid issues with comparing null to [] + for i := range result.Packages { + pkg := &result.Packages[i] + for i := range pkg.TestFiles { + file := &pkg.TestFiles[i] + env.OpenFile(file.URI.Path()) + + for i := range file.Tests { + test := &file.Tests[i] + gotSource = append(gotSource, env.FileContentAt(test.Loc)) + test.Loc = protocol.Location{} + } + } + } + + if diff := cmp.Diff(wantPkg, result.Packages); diff != "" { + t.Errorf("Packages(%v) returned unexpected packages (-want +got):\n%s", files, diff) + } + + if diff := cmp.Diff(wantModule, result.Module); diff != "" { + t.Errorf("Packages(%v) returned unexpected modules (-want +got):\n%s", files, diff) + } + + // Don't check the source if the response is incorrect + if !t.Failed() { + if diff := cmp.Diff(wantSource, gotSource); diff != "" { + t.Errorf("Packages(%v) returned unexpected test case ranges (-want +got):\n%s", files, diff) + } + } +} diff --git a/contribs/gnopls/internal/test/integration/workspace/quickfix_test.go b/contribs/gnopls/internal/test/integration/workspace/quickfix_test.go new file mode 100644 index 00000000000..6f7c8e854d0 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/quickfix_test.go @@ -0,0 +1,458 @@ +// 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 workspace + +import ( + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/compare" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestQuickFix_UseModule(t *testing.T) { + t.Skip("temporary skip for golang/go#57979: with zero-config gopls these files are no longer orphaned") + + const files = ` +-- go.work -- +go 1.20 + +use ( + ./a +) +-- a/go.mod -- +module mod.com/a + +go 1.18 + +-- a/main.go -- +package main + +import "mod.com/a/lib" + +func main() { + _ = lib.C +} + +-- a/lib/lib.go -- +package lib + +const C = "b" +-- b/go.mod -- +module mod.com/b + +go 1.18 + +-- b/main.go -- +package main + +import "mod.com/b/lib" + +func main() { + _ = lib.C +} + +-- b/lib/lib.go -- +package lib + +const C = "b" +` + + for _, title := range []string{ + "Use this module", + "Use all modules", + } { + t.Run(title, func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("b/main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange(ReadDiagnostics("b/main.go", &d)) + fixes := env.GetQuickFixes("b/main.go", d.Diagnostics) + var toApply []protocol.CodeAction + for _, fix := range fixes { + if strings.Contains(fix.Title, title) { + toApply = append(toApply, fix) + } + } + if len(toApply) != 1 { + t.Fatalf("codeAction: got %d quick fixes matching %q, want 1; got: %v", len(toApply), title, toApply) + } + env.ApplyCodeAction(toApply[0]) + env.AfterChange(NoDiagnostics()) + want := `go 1.20 + +use ( + ./a + ./b +) +` + got := env.ReadWorkspaceFile("go.work") + if diff := compare.Text(want, got); diff != "" { + t.Errorf("unexpeced go.work content:\n%s", diff) + } + }) + }) + } +} + +func TestQuickFix_AddGoWork(t *testing.T) { + t.Skip("temporary skip for golang/go#57979: with zero-config gopls these files are no longer orphaned") + + const files = ` +-- a/go.mod -- +module mod.com/a + +go 1.18 + +-- a/main.go -- +package main + +import "mod.com/a/lib" + +func main() { + _ = lib.C +} + +-- a/lib/lib.go -- +package lib + +const C = "b" +-- b/go.mod -- +module mod.com/b + +go 1.18 + +-- b/main.go -- +package main + +import "mod.com/b/lib" + +func main() { + _ = lib.C +} + +-- b/lib/lib.go -- +package lib + +const C = "b" +` + + tests := []struct { + name string + file string + title string + want string // expected go.work content, excluding go directive line + }{ + { + "use b", + "b/main.go", + "Add a go.work file using this module", + ` +use ./b +`, + }, + { + "use a", + "a/main.go", + "Add a go.work file using this module", + ` +use ./a +`, + }, + { + "use all", + "a/main.go", + "Add a go.work file using all modules", + ` +use ( + ./a + ./b +) +`, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile(test.file) + var d protocol.PublishDiagnosticsParams + env.AfterChange(ReadDiagnostics(test.file, &d)) + fixes := env.GetQuickFixes(test.file, d.Diagnostics) + var toApply []protocol.CodeAction + for _, fix := range fixes { + if strings.Contains(fix.Title, test.title) { + toApply = append(toApply, fix) + } + } + if len(toApply) != 1 { + t.Fatalf("codeAction: got %d quick fixes matching %q, want 1; got: %v", len(toApply), test.title, toApply) + } + env.ApplyCodeAction(toApply[0]) + env.AfterChange( + NoDiagnostics(ForFile(test.file)), + ) + + got := env.ReadWorkspaceFile("go.work") + // Ignore the `go` directive, which we assume is on the first line of + // the go.work file. This allows the test to be independent of go version. + got = strings.Join(strings.Split(got, "\n")[1:], "\n") + if diff := compare.Text(test.want, got); diff != "" { + t.Errorf("unexpected go.work content:\n%s", diff) + } + }) + }) + } +} + +func TestQuickFix_UnsavedGoWork(t *testing.T) { + t.Skip("temporary skip for golang/go#57979: with zero-config gopls these files are no longer orphaned") + + const files = ` +-- go.work -- +go 1.21 + +use ( + ./a +) +-- a/go.mod -- +module mod.com/a + +go 1.18 + +-- a/main.go -- +package main + +func main() {} +-- b/go.mod -- +module mod.com/b + +go 1.18 + +-- b/main.go -- +package main + +func main() {} +` + + for _, title := range []string{ + "Use this module", + "Use all modules", + } { + t.Run(title, func(t *testing.T) { + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + env.OpenFile("b/main.go") + env.RegexpReplace("go.work", "go 1.21", "go 1.21 // arbitrary comment") + var d protocol.PublishDiagnosticsParams + env.AfterChange(ReadDiagnostics("b/main.go", &d)) + fixes := env.GetQuickFixes("b/main.go", d.Diagnostics) + var toApply []protocol.CodeAction + for _, fix := range fixes { + if strings.Contains(fix.Title, title) { + toApply = append(toApply, fix) + } + } + if len(toApply) != 1 { + t.Fatalf("codeAction: got %d quick fixes matching %q, want 1; got: %v", len(toApply), title, toApply) + } + fix := toApply[0] + err := env.Editor.ApplyCodeAction(env.Ctx, fix) + if err == nil { + t.Fatalf("codeAction(%q) succeeded unexpectedly", fix.Title) + } + + if got := err.Error(); !strings.Contains(got, "must save") { + t.Errorf("codeAction(%q) returned error %q, want containing \"must save\"", fix.Title, err) + } + }) + }) + } +} + +func TestQuickFix_GOWORKOff(t *testing.T) { + t.Skip("temporary skip for golang/go#57979: with zero-config gopls these files are no longer orphaned") + + const files = ` +-- go.work -- +go 1.21 + +use ( + ./a +) +-- a/go.mod -- +module mod.com/a + +go 1.18 + +-- a/main.go -- +package main + +func main() {} +-- b/go.mod -- +module mod.com/b + +go 1.18 + +-- b/main.go -- +package main + +func main() {} +` + + for _, title := range []string{ + "Use this module", + "Use all modules", + } { + t.Run(title, func(t *testing.T) { + WithOptions( + EnvVars{"GOWORK": "off"}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + env.OpenFile("b/main.go") + var d protocol.PublishDiagnosticsParams + env.AfterChange(ReadDiagnostics("b/main.go", &d)) + fixes := env.GetQuickFixes("b/main.go", d.Diagnostics) + var toApply []protocol.CodeAction + for _, fix := range fixes { + if strings.Contains(fix.Title, title) { + toApply = append(toApply, fix) + } + } + if len(toApply) != 1 { + t.Fatalf("codeAction: got %d quick fixes matching %q, want 1; got: %v", len(toApply), title, toApply) + } + fix := toApply[0] + err := env.Editor.ApplyCodeAction(env.Ctx, fix) + if err == nil { + t.Fatalf("codeAction(%q) succeeded unexpectedly", fix.Title) + } + + if got := err.Error(); !strings.Contains(got, "GOWORK=off") { + t.Errorf("codeAction(%q) returned error %q, want containing \"GOWORK=off\"", fix.Title, err) + } + }) + }) + } +} + +func TestStubMethods64087(t *testing.T) { + // We can't use the @fix or @suggestedfixerr or @codeactionerr + // because the error now reported by the corrected logic + // is internal and silently causes no fix to be offered. + // + // See also the similar TestStubMethods64545 below. + + const files = ` +This is a regression test for a panic (issue #64087) in stub methods. + +The illegal expression int("") caused a "cannot convert" error that +spuriously triggered the "stub methods" in a function whose return +statement had too many operands, leading to an out-of-bounds index. + +-- go.mod -- +module mod.com +go 1.18 + +-- a.go -- +package a + +func f() error { + return nil, myerror{int("")} +} + +type myerror struct{any} +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + + // Expect a "wrong result count" diagnostic. + var d protocol.PublishDiagnosticsParams + env.AfterChange(ReadDiagnostics("a.go", &d)) + + // In no particular order, we expect: + // "...too many return values..." (compiler) + // "...cannot convert..." (compiler) + // and possibly: + // "...too many return values..." (fillreturns) + // We check only for the first of these. + found := false + for i, diag := range d.Diagnostics { + t.Logf("Diagnostics[%d] = %q (%s)", i, diag.Message, diag.Source) + if strings.Contains(diag.Message, "too many return") { + found = true + } + } + if !found { + t.Fatalf("Expected WrongResultCount diagnostic not found.") + } + + // GetQuickFixes should not panic (the original bug). + fixes := env.GetQuickFixes("a.go", d.Diagnostics) + + // We should not be offered a "stub methods" fix. + for _, fix := range fixes { + if strings.Contains(fix.Title, "Implement error") { + t.Errorf("unexpected 'stub methods' fix: %#v", fix) + } + } + }) +} + +func TestStubMethods64545(t *testing.T) { + // We can't use the @fix or @suggestedfixerr or @codeactionerr + // because the error now reported by the corrected logic + // is internal and silently causes no fix to be offered. + // + // TODO(adonovan): we may need to generalize this test and + // TestStubMethods64087 if this happens a lot. + + const files = ` +This is a regression test for a panic (issue #64545) in stub methods. + +The illegal expression int("") caused a "cannot convert" error that +spuriously triggered the "stub methods" in a function whose var +spec had no RHS values, leading to an out-of-bounds index. + +-- go.mod -- +module mod.com +go 1.18 + +-- a.go -- +package a + +var _ [int("")]byte +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + + // Expect a "cannot convert" diagnostic, and perhaps others. + var d protocol.PublishDiagnosticsParams + env.AfterChange(ReadDiagnostics("a.go", &d)) + + found := false + for i, diag := range d.Diagnostics { + t.Logf("Diagnostics[%d] = %q (%s)", i, diag.Message, diag.Source) + if strings.Contains(diag.Message, "cannot convert") { + found = true + } + } + if !found { + t.Fatalf("Expected 'cannot convert' diagnostic not found.") + } + + // GetQuickFixes should not panic (the original bug). + fixes := env.GetQuickFixes("a.go", d.Diagnostics) + + // We should not be offered a "stub methods" fix. + for _, fix := range fixes { + if strings.Contains(fix.Title, "Implement error") { + t.Errorf("unexpected 'stub methods' fix: %#v", fix) + } + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/standalone_test.go b/contribs/gnopls/internal/test/integration/workspace/standalone_test.go new file mode 100644 index 00000000000..d837899f7fb --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/standalone_test.go @@ -0,0 +1,206 @@ +// 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 workspace + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestStandaloneFiles(t *testing.T) { + const files = ` +-- go.mod -- +module mod.test + +go 1.16 +-- lib/lib.go -- +package lib + +const K = 0 + +type I interface { + M() +} +-- lib/ignore.go -- +//go:build ignore +// +build ignore + +package main + +import ( + "mod.test/lib" +) + +const K = 1 + +type Mer struct{} +func (Mer) M() + +func main() { + println(lib.K + K) +} +` + WithOptions( + // On Go 1.17 and earlier, this test fails with + // experimentalWorkspaceModule. Not investigated, as + // experimentalWorkspaceModule will be removed. + Modes(Default), + ).Run(t, files, func(t *testing.T, env *Env) { + // Initially, gopls should not know about the standalone file as it hasn't + // been opened. Therefore, we should only find one symbol 'K'. + // + // (The choice of "K" is a little sleazy: it was originally "C" until + // we started adding "unsafe" to the workspace unconditionally, which + // caused a spurious match of "unsafe.Slice". But in practice every + // workspace depends on unsafe.) + syms := env.Symbol("K") + if got, want := len(syms), 1; got != want { + t.Errorf("got %d symbols, want %d (%+v)", got, want, syms) + } + + // Similarly, we should only find one reference to "K", and no + // implementations of I. + checkLocations := func(method string, gotLocations []protocol.Location, wantFiles ...string) { + var gotFiles []string + for _, l := range gotLocations { + gotFiles = append(gotFiles, env.Sandbox.Workdir.URIToPath(l.URI)) + } + sort.Strings(gotFiles) + sort.Strings(wantFiles) + if diff := cmp.Diff(wantFiles, gotFiles); diff != "" { + t.Errorf("%s(...): unexpected locations (-want +got):\n%s", method, diff) + } + } + + env.OpenFile("lib/lib.go") + env.AfterChange(NoDiagnostics()) + + // Replacing K with D should not cause any workspace diagnostics, since we + // haven't yet opened the standalone file. + env.RegexpReplace("lib/lib.go", "K", "D") + env.AfterChange(NoDiagnostics()) + env.RegexpReplace("lib/lib.go", "D", "K") + env.AfterChange(NoDiagnostics()) + + refs := env.References(env.RegexpSearch("lib/lib.go", "K")) + checkLocations("References", refs, "lib/lib.go") + + impls := env.Implementations(env.RegexpSearch("lib/lib.go", "I")) + checkLocations("Implementations", impls) // no implementations + + // Opening the standalone file should not result in any diagnostics. + env.OpenFile("lib/ignore.go") + env.AfterChange(NoDiagnostics()) + + // Having opened the standalone file, we should find its symbols in the + // workspace. + syms = env.Symbol("K") + if got, want := len(syms), 2; got != want { + t.Fatalf("got %d symbols, want %d", got, want) + } + + foundMainK := false + var symNames []string + for _, sym := range syms { + symNames = append(symNames, sym.Name) + if sym.Name == "main.K" { + foundMainK = true + } + } + if !foundMainK { + t.Errorf("WorkspaceSymbol(\"K\") = %v, want containing main.K", symNames) + } + + // We should resolve workspace definitions in the standalone file. + fileLoc := env.GoToDefinition(env.RegexpSearch("lib/ignore.go", "lib.(K)")) + file := env.Sandbox.Workdir.URIToPath(fileLoc.URI) + if got, want := file, "lib/lib.go"; got != want { + t.Errorf("GoToDefinition(lib.K) = %v, want %v", got, want) + } + + // ...as well as intra-file definitions + loc := env.GoToDefinition(env.RegexpSearch("lib/ignore.go", "\\+ (K)")) + wantLoc := env.RegexpSearch("lib/ignore.go", "const (K)") + if loc != wantLoc { + t.Errorf("GoToDefinition(K) = %v, want %v", loc, wantLoc) + } + + // Renaming "lib.K" to "lib.D" should cause a diagnostic in the standalone + // file. + env.RegexpReplace("lib/lib.go", "K", "D") + env.AfterChange(Diagnostics(env.AtRegexp("lib/ignore.go", "lib.(K)"))) + + // Undoing the replacement should fix diagnostics + env.RegexpReplace("lib/lib.go", "D", "K") + env.AfterChange(NoDiagnostics()) + + // Now that our workspace has no errors, we should be able to find + // references and rename. + refs = env.References(env.RegexpSearch("lib/lib.go", "K")) + checkLocations("References", refs, "lib/lib.go", "lib/ignore.go") + + impls = env.Implementations(env.RegexpSearch("lib/lib.go", "I")) + checkLocations("Implementations", impls, "lib/ignore.go") + + // Renaming should rename in the standalone package. + env.Rename(env.RegexpSearch("lib/lib.go", "K"), "D") + env.RegexpSearch("lib/ignore.go", "lib.D") + }) +} + +func TestStandaloneFiles_Configuration(t *testing.T) { + const files = ` +-- go.mod -- +module mod.test + +go 1.18 +-- lib.go -- +package lib // without this package, files are loaded as command-line-arguments +-- ignore.go -- +//go:build ignore +// +build ignore + +package main + +// An arbitrary comment. + +func main() {} +-- standalone.go -- +//go:build standalone +// +build standalone + +package main + +func main() {} +` + + WithOptions( + Settings{ + "standaloneTags": []string{"standalone", "script"}, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("ignore.go") + env.OpenFile("standalone.go") + + env.AfterChange( + Diagnostics(env.AtRegexp("ignore.go", "package (main)")), + NoDiagnostics(ForFile("standalone.go")), + ) + + cfg := env.Editor.Config() + cfg.Settings = map[string]interface{}{ + "standaloneTags": []string{"ignore"}, + } + env.ChangeConfiguration(cfg) + env.AfterChange( + NoDiagnostics(ForFile("ignore.go")), + Diagnostics(env.AtRegexp("standalone.go", "package (main)")), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/std_test.go b/contribs/gnopls/internal/test/integration/workspace/std_test.go new file mode 100644 index 00000000000..9c021fef4f3 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/std_test.go @@ -0,0 +1,73 @@ +// 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 workspace + +import ( + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestStdWorkspace(t *testing.T) { + // This test checks that we actually load workspace packages when opening + // GOROOT. + // + // In golang/go#65801, we failed to do this because go/packages returns nil + // Module for std and cmd. + // + // Because this test loads std as a workspace, it may be slow on smaller + // builders. + if testing.Short() { + t.Skip("skipping with -short: loads GOROOT") + } + + // The test also fails on Windows because an absolute path does not match + // (likely a misspelling due to slashes). + // TODO(rfindley): investigate and fix this on windows. + if runtime.GOOS == "windows" { + t.Skip("skipping on windows: fails to misspelled paths") + } + + // Query GOROOT. This is slightly more precise than e.g. runtime.GOROOT, as + // it queries the go command in the environment. + goroot, err := exec.Command("go", "env", "GOROOT").Output() + if err != nil { + t.Fatal(err) + } + stdDir := filepath.Join(strings.TrimSpace(string(goroot)), "src") + WithOptions( + Modes(Default), // This test may be slow. No reason to run it multiple times. + WorkspaceFolders(stdDir), + ).Run(t, "", func(t *testing.T, env *Env) { + // Find parser.ParseFile. Query with `'` to get an exact match. + syms := env.Symbol("'go/parser.ParseFile") + if len(syms) != 1 { + t.Fatalf("got %d symbols, want exactly 1. Symbols:\n%v", len(syms), syms) + } + parserPath := syms[0].Location.URI.Path() + env.OpenFile(parserPath) + + // Find the reference to ast.File from the signature of ParseFile. This + // helps guard against matching a comment. + astFile := env.RegexpSearch(parserPath, `func ParseFile\(.*ast\.(File)`) + refs := env.References(astFile) + + // If we've successfully loaded workspace packages for std, we should find + // a reference in go/types. + foundGoTypesReference := false + for _, ref := range refs { + if strings.Contains(string(ref.URI), "go/types") { + foundGoTypesReference = true + } + } + if !foundGoTypesReference { + t.Errorf("references(ast.File) did not return a go/types reference. Refs:\n%v", refs) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/vendor_test.go b/contribs/gnopls/internal/test/integration/workspace/vendor_test.go new file mode 100644 index 00000000000..f14cf539de0 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/vendor_test.go @@ -0,0 +1,67 @@ +// 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 workspace + +import ( + "testing" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestWorkspacePackagesExcludesVendor(t *testing.T) { + // This test verifies that packages in the vendor directory are not workspace + // packages. This would be an easy mistake for gopls to make, since mod + // vendoring excludes go.mod files, and therefore the nearest go.mod file for + // vendored packages is often the workspace mod file. + const proxy = ` +-- other.com/b@v1.0.0/go.mod -- +module other.com/b + +go 1.18 + +-- other.com/b@v1.0.0/b.go -- +package b + +type B int + +func _() { + var V int // unused +} +` + const src = ` +-- go.mod -- +module example.com/a +go 1.14 +require other.com/b v1.0.0 + +-- go.sum -- +other.com/b v1.0.0 h1:ct1+0RPozzMvA2rSYnVvIfr/GDHcd7oVnw147okdi3g= +other.com/b v1.0.0/go.mod h1:bfTSZo/4ZtAQJWBYScopwW6n9Ctfsl2mi8nXsqjDXR8= + +-- a.go -- +package a + +import "other.com/b" + +var _ b.B + +` + WithOptions( + ProxyFiles(proxy), + Modes(Default), + ).Run(t, src, func(t *testing.T, env *Env) { + env.RunGoCommand("mod", "vendor") + // Uncomment for updated go.sum contents. + // env.DumpGoSum(".") + env.OpenFile("a.go") + env.AfterChange( + NoDiagnostics(), // as b is not a workspace package + ) + env.GoToDefinition(env.RegexpSearch("a.go", `b\.(B)`)) + env.AfterChange( + Diagnostics(env.AtRegexp("vendor/other.com/b/b.go", "V"), WithMessage("not used")), + ) + }) +} diff --git a/contribs/gnopls/internal/test/integration/workspace/workspace_test.go b/contribs/gnopls/internal/test/integration/workspace/workspace_test.go new file mode 100644 index 00000000000..ac74e6deed5 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/workspace_test.go @@ -0,0 +1,1468 @@ +// 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 workspace + +import ( + "context" + "fmt" + "os" + "sort" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/goversion" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/testenv" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + os.Exit(Main(m)) +} + +const workspaceProxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +import "fmt" + +func SaySomething() { + fmt.Println("something") +} +-- random.org@v1.2.3/go.mod -- +module random.org + +go 1.12 +-- random.org@v1.2.3/bye/bye.go -- +package bye + +func Goodbye() { + println("Bye") +} +` + +// TODO: Add a replace directive. +const workspaceModule = ` +-- pkg/go.mod -- +module mod.com + +go 1.14 + +require ( + example.com v1.2.3 + random.org v1.2.3 +) +-- pkg/go.sum -- +example.com v1.2.3 h1:veRD4tUnatQRgsULqULZPjeoBGFr2qBhevSCZllD2Ds= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +random.org v1.2.3 h1:+JE2Fkp7gS0zsHXGEQJ7hraom3pNTlkxC4b2qPfA+/Q= +random.org v1.2.3/go.mod h1:E9KM6+bBX2g5ykHZ9H27w16sWo3QwgonyjM44Dnej3I= +-- pkg/main.go -- +package main + +import ( + "example.com/blah" + "mod.com/inner" + "random.org/bye" +) + +func main() { + blah.SaySomething() + inner.Hi() + bye.Goodbye() +} +-- pkg/main2.go -- +package main + +import "fmt" + +func _() { + fmt.Print("%s") +} +-- pkg/inner/inner.go -- +package inner + +import "example.com/blah" + +func Hi() { + blah.SaySomething() +} +-- goodbye/bye/bye.go -- +package bye + +func Bye() {} +-- goodbye/go.mod -- +module random.org + +go 1.12 +` + +// Confirm that find references returns all of the references in the module, +// regardless of what the workspace root is. +func TestReferences(t *testing.T) { + for _, tt := range []struct { + name, rootPath string + }{ + { + name: "module root", + rootPath: "pkg", + }, + { + name: "subdirectory", + rootPath: "pkg/inner", + }, + } { + t.Run(tt.name, func(t *testing.T) { + opts := []RunOption{ProxyFiles(workspaceProxy)} + if tt.rootPath != "" { + opts = append(opts, WorkspaceFolders(tt.rootPath)) + } + WithOptions(opts...).Run(t, workspaceModule, func(t *testing.T, env *Env) { + f := "pkg/inner/inner.go" + env.OpenFile(f) + locations := env.References(env.RegexpSearch(f, `SaySomething`)) + want := 3 + if got := len(locations); got != want { + t.Fatalf("expected %v locations, got %v", want, got) + } + }) + }) + } +} + +// Make sure that analysis diagnostics are cleared for the whole package when +// the only opened file is closed. This test was inspired by the experience in +// VS Code, where clicking on a reference result triggers a +// textDocument/didOpen without a corresponding textDocument/didClose. +func TestClearAnalysisDiagnostics(t *testing.T) { + WithOptions( + ProxyFiles(workspaceProxy), + WorkspaceFolders("pkg/inner"), + ).Run(t, workspaceModule, func(t *testing.T, env *Env) { + env.OpenFile("pkg/main.go") + env.AfterChange( + Diagnostics(env.AtRegexp("pkg/main2.go", "fmt.Print")), + ) + env.CloseBuffer("pkg/main.go") + env.AfterChange( + NoDiagnostics(ForFile("pkg/main2.go")), + ) + }) +} + +// TestReloadOnlyOnce checks that changes to the go.mod file do not result in +// redundant package loads (golang/go#54473). +// +// Note that this test may be fragile, as it depends on specific structure to +// log messages around reinitialization. Nevertheless, it is important for +// guarding against accidentally duplicate reloading. +func TestReloadOnlyOnce(t *testing.T) { + WithOptions( + ProxyFiles(workspaceProxy), + WorkspaceFolders("pkg"), + ).Run(t, workspaceModule, func(t *testing.T, env *Env) { + dir := env.Sandbox.Workdir.URI("goodbye").Path() + goModWithReplace := fmt.Sprintf(`%s +replace random.org => %s +`, env.ReadWorkspaceFile("pkg/go.mod"), dir) + env.WriteWorkspaceFile("pkg/go.mod", goModWithReplace) + env.Await( + LogMatching(protocol.Info, `packages\.Load #\d+\n`, 2, false), + ) + }) +} + +const workspaceModuleProxy = ` +-- example.com@v1.2.3/go.mod -- +module example.com + +go 1.12 +-- example.com@v1.2.3/blah/blah.go -- +package blah + +import "fmt" + +func SaySomething() { + fmt.Println("something") +} +-- b.com@v1.2.3/go.mod -- +module b.com + +go 1.12 +-- b.com@v1.2.3/b/b.go -- +package b + +func Hello() {} +` + +const multiModule = ` +-- moda/a/go.mod -- +module a.com + +require b.com v1.2.3 +-- moda/a/go.sum -- +b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= +b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= +-- moda/a/a.go -- +package a + +import ( + "b.com/b" +) + +func main() { + var x int + _ = b.Hello() +} +-- modb/go.mod -- +module b.com + +-- modb/b/b.go -- +package b + +func Hello() int { + var x int +} +` + +func TestAutomaticWorkspaceModule_Interdependent(t *testing.T) { + WithOptions( + ProxyFiles(workspaceModuleProxy), + ).Run(t, multiModule, func(t *testing.T, env *Env) { + env.RunGoCommand("work", "init") + env.RunGoCommand("work", "use", "-r", ".") + env.AfterChange( + Diagnostics(env.AtRegexp("moda/a/a.go", "x")), + Diagnostics(env.AtRegexp("modb/b/b.go", "x")), + NoDiagnostics(env.AtRegexp("moda/a/a.go", `"b.com/b"`)), + ) + }) +} + +func TestWorkspaceVendoring(t *testing.T) { + testenv.NeedsGo1Point(t, 22) + WithOptions( + ProxyFiles(workspaceModuleProxy), + ).Run(t, multiModule, func(t *testing.T, env *Env) { + env.RunGoCommand("work", "init") + env.RunGoCommand("work", "use", "moda/a") + env.AfterChange() + env.OpenFile("moda/a/a.go") + env.RunGoCommand("work", "vendor") + env.AfterChange() + loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "b.(Hello)")) + const want = "vendor/b.com/b/b.go" + if got := env.Sandbox.Workdir.URIToPath(loc.URI); got != want { + t.Errorf("Definition: got location %q, want %q", got, want) + } + }) +} + +func TestModuleWithExclude(t *testing.T) { + const proxy = ` +-- c.com@v1.2.3/go.mod -- +module c.com + +go 1.12 + +require b.com v1.2.3 +-- c.com@v1.2.3/blah/blah.go -- +package blah + +import "fmt" + +func SaySomething() { + fmt.Println("something") +} +-- b.com@v1.2.3/go.mod -- +module b.com + +go 1.12 +-- b.com@v1.2.4/b/b.go -- +package b + +func Hello() {} +-- b.com@v1.2.4/go.mod -- +module b.com + +go 1.12 +` + const files = ` +-- go.mod -- +module a.com + +require c.com v1.2.3 + +exclude b.com v1.2.3 +-- go.sum -- +c.com v1.2.3 h1:n07Dz9fYmpNqvZMwZi5NEqFcSHbvLa9lacMX+/g25tw= +c.com v1.2.3/go.mod h1:/4TyYgU9Nu5tA4NymP5xyqE8R2VMzGD3TbJCwCOvHAg= +-- main.go -- +package a + +func main() { + var x int +} +` + WithOptions( + ProxyFiles(proxy), + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + Diagnostics(env.AtRegexp("main.go", "x")), + ) + }) +} + +// This change tests that the version of the module used changes after it has +// been deleted from the workspace. +// +// TODO(golang/go#55331): delete this placeholder along with experimental +// workspace module. +func TestDeleteModule_Interdependent(t *testing.T) { + const multiModule = ` +-- go.work -- +go 1.18 + +use ( + moda/a + modb +) +-- moda/a/go.mod -- +module a.com + +require b.com v1.2.3 +-- moda/a/go.sum -- +b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= +b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= +-- moda/a/a.go -- +package a + +import ( + "b.com/b" +) + +func main() { + var x int + _ = b.Hello() +} +-- modb/go.mod -- +module b.com + +-- modb/b/b.go -- +package b + +func Hello() int { + var x int +} +` + WithOptions( + ProxyFiles(workspaceModuleProxy), + ).Run(t, multiModule, func(t *testing.T, env *Env) { + env.OpenFile("moda/a/a.go") + env.Await(env.DoneWithOpen()) + + originalLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) + original := env.Sandbox.Workdir.URIToPath(originalLoc.URI) + if want := "modb/b/b.go"; !strings.HasSuffix(original, want) { + t.Errorf("expected %s, got %v", want, original) + } + env.CloseBuffer(original) + env.AfterChange() + + env.RemoveWorkspaceFile("modb/b/b.go") + env.RemoveWorkspaceFile("modb/go.mod") + env.WriteWorkspaceFile("go.work", "go 1.18\nuse moda/a") + env.AfterChange() + + gotLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) + got := env.Sandbox.Workdir.URIToPath(gotLoc.URI) + if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(got, want) { + t.Errorf("expected %s, got %v", want, got) + } + }) +} + +// Tests that the version of the module used changes after it has been added +// to the workspace. +func TestCreateModule_Interdependent(t *testing.T) { + const multiModule = ` +-- go.work -- +go 1.18 + +use ( + moda/a +) +-- moda/a/go.mod -- +module a.com + +require b.com v1.2.3 +-- moda/a/go.sum -- +b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= +b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= +-- moda/a/a.go -- +package a + +import ( + "b.com/b" +) + +func main() { + var x int + _ = b.Hello() +} +` + WithOptions( + ProxyFiles(workspaceModuleProxy), + ).Run(t, multiModule, func(t *testing.T, env *Env) { + env.OpenFile("moda/a/a.go") + loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) + original := env.Sandbox.Workdir.URIToPath(loc.URI) + if want := "b.com@v1.2.3/b/b.go"; !strings.HasSuffix(original, want) { + t.Errorf("expected %s, got %v", want, original) + } + env.CloseBuffer(original) + env.WriteWorkspaceFiles(map[string]string{ + "go.work": `go 1.18 + +use ( + moda/a + modb +) +`, + "modb/go.mod": "module b.com", + "modb/b/b.go": `package b + +func Hello() int { + var x int +} +`, + }) + env.AfterChange(Diagnostics(env.AtRegexp("modb/b/b.go", "x"))) + gotLoc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) + got := env.Sandbox.Workdir.URIToPath(gotLoc.URI) + if want := "modb/b/b.go"; !strings.HasSuffix(got, want) { + t.Errorf("expected %s, got %v", want, original) + } + }) +} + +// This test confirms that a gopls workspace can recover from initialization +// with one invalid module. +func TestOneBrokenModule(t *testing.T) { + const multiModule = ` +-- go.work -- +go 1.18 + +use ( + moda/a + modb +) +-- moda/a/go.mod -- +module a.com + +require b.com v1.2.3 + +-- moda/a/a.go -- +package a + +import ( + "b.com/b" +) + +func main() { + var x int + _ = b.Hello() +} +-- modb/go.mod -- +modul b.com // typo here + +-- modb/b/b.go -- +package b + +func Hello() int { + var x int +} +` + WithOptions( + ProxyFiles(workspaceModuleProxy), + ).Run(t, multiModule, func(t *testing.T, env *Env) { + env.OpenFile("modb/go.mod") + env.AfterChange( + Diagnostics(AtPosition("modb/go.mod", 0, 0)), + ) + env.RegexpReplace("modb/go.mod", "modul", "module") + env.SaveBufferWithoutActions("modb/go.mod") + env.AfterChange( + Diagnostics(env.AtRegexp("modb/b/b.go", "x")), + ) + }) +} + +// TestBadGoWork exercises the panic from golang/vscode-go#2121. +func TestBadGoWork(t *testing.T) { + const files = ` +-- go.work -- +use ./bar +-- bar/go.mod -- +module example.com/bar +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + }) +} + +func TestUseGoWork(t *testing.T) { + // This test validates certain functionality related to using a go.work + // file to specify workspace modules. + const multiModule = ` +-- moda/a/go.mod -- +module a.com + +require b.com v1.2.3 +-- moda/a/go.sum -- +b.com v1.2.3 h1:tXrlXP0rnjRpKNmkbLYoWBdq0ikb3C3bKK9//moAWBI= +b.com v1.2.3/go.mod h1:D+J7pfFBZK5vdIdZEFquR586vKKIkqG7Qjw9AxG5BQ8= +-- moda/a/a.go -- +package a + +import ( + "b.com/b" +) + +func main() { + var x int + _ = b.Hello() +} +-- modb/go.mod -- +module b.com + +require example.com v1.2.3 +-- modb/go.sum -- +example.com v1.2.3 h1:Yryq11hF02fEf2JlOS2eph+ICE2/ceevGV3C9dl5V/c= +example.com v1.2.3/go.mod h1:Y2Rc5rVWjWur0h3pd9aEvK5Pof8YKDANh9gHA2Maujo= +-- modb/b/b.go -- +package b + +func Hello() int { + var x int +} +-- go.work -- +go 1.17 + +use ( + ./moda/a +) +` + WithOptions( + ProxyFiles(workspaceModuleProxy), + Settings{ + "subdirWatchPatterns": "on", + }, + ).Run(t, multiModule, func(t *testing.T, env *Env) { + // Initially, the go.work should cause only the a.com module to be loaded, + // so we shouldn't get any file watches for modb. Further validate this by + // jumping to a definition in b.com and ensuring that we go to the module + // cache. + env.OnceMet( + InitialWorkspaceLoad, + NoFileWatchMatching("modb"), + ) + env.OpenFile("moda/a/a.go") + env.Await(env.DoneWithOpen()) + + // To verify which modules are loaded, we'll jump to the definition of + // b.Hello. + checkHelloLocation := func(want string) error { + loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) + file := env.Sandbox.Workdir.URIToPath(loc.URI) + if !strings.HasSuffix(file, want) { + return fmt.Errorf("expected %s, got %v", want, file) + } + return nil + } + + // Initially this should be in the module cache, as b.com is not replaced. + if err := checkHelloLocation("b.com@v1.2.3/b/b.go"); err != nil { + t.Fatal(err) + } + + // Now, modify the go.work file on disk to activate the b.com module in + // the workspace. + env.WriteWorkspaceFile("go.work", ` +go 1.17 + +use ( + ./moda/a + ./modb +) +`) + + // As of golang/go#54069, writing go.work to the workspace triggers a + // workspace reload, and new file watches. + env.AfterChange( + Diagnostics(env.AtRegexp("modb/b/b.go", "x")), + // TODO(golang/go#60340): we don't get a file watch yet, because + // updateWatchedDirectories runs before snapshot.load. Instead, we get it + // after the next change (the didOpen below). + // FileWatchMatching("modb"), + ) + + // Jumping to definition should now go to b.com in the workspace. + if err := checkHelloLocation("modb/b/b.go"); err != nil { + t.Fatal(err) + } + + // Now, let's modify the go.work *overlay* (not on disk), and verify that + // this change is only picked up once it is saved. + env.OpenFile("go.work") + env.AfterChange( + // TODO(golang/go#60340): delete this expectation in favor of + // the commented-out expectation above, once we fix the evaluation order + // of file watches. We should not have to wait for a second change to get + // the correct watches. + FileWatchMatching("modb"), + ) + env.SetBufferContent("go.work", `go 1.17 + +use ( + ./moda/a +)`) + + // Simply modifying the go.work file does not cause a reload, so we should + // still jump within the workspace. + // + // TODO: should editing the go.work above cause modb diagnostics to be + // suppressed? + env.Await(env.DoneWithChange()) + if err := checkHelloLocation("modb/b/b.go"); err != nil { + t.Fatal(err) + } + + // Saving should reload the workspace. + env.SaveBufferWithoutActions("go.work") + if err := checkHelloLocation("b.com@v1.2.3/b/b.go"); err != nil { + t.Fatal(err) + } + + // This fails if guarded with a OnceMet(DoneWithSave(), ...), because it is + // delayed (and therefore not synchronous with the change). + // + // Note: this check used to assert on NoDiagnostics, but with zero-config + // gopls we still have diagnostics. + env.Await(Diagnostics(ForFile("modb/go.mod"), WithMessage("example.com is not used"))) + + // Test Formatting. + env.SetBufferContent("go.work", `go 1.18 + use ( + + + + ./moda/a +) +`) // TODO(matloob): For some reason there's a "start position 7:0 is out of bounds" error when the ")" is on the last character/line in the file. Rob probably knows what's going on. + env.SaveBuffer("go.work") + env.Await(env.DoneWithSave()) + gotWorkContents := env.ReadWorkspaceFile("go.work") + wantWorkContents := `go 1.18 + +use ( + ./moda/a +) +` + if gotWorkContents != wantWorkContents { + t.Fatalf("formatted contents of workspace: got %q; want %q", gotWorkContents, wantWorkContents) + } + }) +} + +func TestUseGoWorkDiagnosticMissingModule(t *testing.T) { + const files = ` +-- go.work -- +go 1.18 + +use ./foo +-- bar/go.mod -- +module example.com/bar +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + env.AfterChange( + Diagnostics(env.AtRegexp("go.work", "use"), WithMessage("directory ./foo does not contain a module")), + ) + // The following tests is a regression test against an issue where we weren't + // copying the workFile struct field on workspace when a new one was created in + // (*workspace).invalidate. Set the buffer content to a working file so that + // invalidate recognizes the workspace to be change and copies over the workspace + // struct, and then set the content back to the old contents to make sure + // the diagnostic still shows up. + env.SetBufferContent("go.work", "go 1.18 \n\n use ./bar\n") + env.AfterChange( + NoDiagnostics(env.AtRegexp("go.work", "use")), + ) + env.SetBufferContent("go.work", "go 1.18 \n\n use ./foo\n") + env.AfterChange( + Diagnostics(env.AtRegexp("go.work", "use"), WithMessage("directory ./foo does not contain a module")), + ) + }) +} + +func TestUseGoWorkDiagnosticSyntaxError(t *testing.T) { + const files = ` +-- go.work -- +go 1.18 + +usa ./foo +replace +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + env.AfterChange( + Diagnostics(env.AtRegexp("go.work", "usa"), WithMessage("unknown directive: usa")), + Diagnostics(env.AtRegexp("go.work", "replace"), WithMessage("usage: replace")), + ) + }) +} + +func TestUseGoWorkHover(t *testing.T) { + const files = ` +-- go.work -- +go 1.18 + +use ./foo +use ( + ./bar + ./bar/baz +) +-- foo/go.mod -- +module example.com/foo +-- bar/go.mod -- +module example.com/bar +-- bar/baz/go.mod -- +module example.com/bar/baz +` + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("go.work") + + tcs := map[string]string{ + `\./foo`: "example.com/foo", + `(?m)\./bar$`: "example.com/bar", + `\./bar/baz`: "example.com/bar/baz", + } + + for hoverRE, want := range tcs { + got, _ := env.Hover(env.RegexpSearch("go.work", hoverRE)) + if got.Value != want { + t.Errorf(`hover on %q: got %q, want %q`, hoverRE, got, want) + } + } + }) +} + +func TestExpandToGoWork(t *testing.T) { + const workspace = ` +-- moda/a/go.mod -- +module a.com + +require b.com v1.2.3 +-- moda/a/a.go -- +package a + +import ( + "b.com/b" +) + +func main() { + var x int + _ = b.Hello() +} +-- modb/go.mod -- +module b.com + +require example.com v1.2.3 +-- modb/b/b.go -- +package b + +func Hello() int { + var x int +} +-- go.work -- +go 1.17 + +use ( + ./moda/a + ./modb +) +` + WithOptions( + WorkspaceFolders("moda/a"), + ).Run(t, workspace, func(t *testing.T, env *Env) { + env.OpenFile("moda/a/a.go") + env.Await(env.DoneWithOpen()) + loc := env.GoToDefinition(env.RegexpSearch("moda/a/a.go", "Hello")) + file := env.Sandbox.Workdir.URIToPath(loc.URI) + want := "modb/b/b.go" + if !strings.HasSuffix(file, want) { + t.Errorf("expected %s, got %v", want, file) + } + }) +} + +func TestInnerGoWork(t *testing.T) { + // This test checks that gopls honors a go.work file defined + // inside a go module (golang/go#63917). + const workspace = ` +-- go.mod -- +module a.com + +require b.com v1.2.3 +-- a/go.work -- +go 1.18 + +use ( + .. + ../b +) +-- a/a.go -- +package a + +import "b.com/b" + +var _ = b.B +-- b/go.mod -- +module b.com/b + +-- b/b.go -- +package b + +const B = 0 +` + WithOptions( + // This doesn't work if we open the outer module. I'm not sure it should, + // since the go.work file does not apply to the entire module, just a + // subdirectory. + WorkspaceFolders("a"), + ).Run(t, workspace, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + loc := env.GoToDefinition(env.RegexpSearch("a/a.go", "b.(B)")) + got := env.Sandbox.Workdir.URIToPath(loc.URI) + want := "b/b.go" + if got != want { + t.Errorf("Definition(b.B): got %q, want %q", got, want) + } + }) +} + +func TestNonWorkspaceFileCreation(t *testing.T) { + const files = ` +-- work/go.mod -- +module mod.com + +go 1.12 +-- work/x.go -- +package x +` + + const code = ` +package foo +import "fmt" +var _ = fmt.Printf +` + WithOptions( + WorkspaceFolders("work"), // so that outside/... is outside the workspace + ).Run(t, files, func(t *testing.T, env *Env) { + env.CreateBuffer("outside/foo.go", "") + env.EditBuffer("outside/foo.go", fake.NewEdit(0, 0, 0, 0, code)) + env.GoToDefinition(env.RegexpSearch("outside/foo.go", `Printf`)) + }) +} + +func TestGoWork_V2Module(t *testing.T) { + // When using a go.work, we must have proxy content even if it is replaced. + const proxy = ` +-- b.com/v2@v2.1.9/go.mod -- +module b.com/v2 + +go 1.12 +-- b.com/v2@v2.1.9/b/b.go -- +package b + +func Ciao()() int { + return 0 +} +` + + const multiModule = ` +-- go.work -- +go 1.18 + +use ( + moda/a + modb + modb/v2 + modc +) +-- moda/a/go.mod -- +module a.com + +require b.com/v2 v2.1.9 +-- moda/a/a.go -- +package a + +import ( + "b.com/v2/b" +) + +func main() { + var x int + _ = b.Hi() +} +-- modb/go.mod -- +module b.com + +-- modb/b/b.go -- +package b + +func Hello() int { + var x int +} +-- modb/v2/go.mod -- +module b.com/v2 + +-- modb/v2/b/b.go -- +package b + +func Hi() int { + var x int +} +-- modc/go.mod -- +module gopkg.in/yaml.v1 // test gopkg.in versions +-- modc/main.go -- +package main + +func main() { + var x int +} +` + + WithOptions( + ProxyFiles(proxy), + ).Run(t, multiModule, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + // TODO(rfindley): assert on the full set of diagnostics here. We + // should ensure that we don't have a diagnostic at b.Hi in a.go. + Diagnostics(env.AtRegexp("moda/a/a.go", "x")), + Diagnostics(env.AtRegexp("modb/b/b.go", "x")), + Diagnostics(env.AtRegexp("modb/v2/b/b.go", "x")), + Diagnostics(env.AtRegexp("modc/main.go", "x")), + ) + }) +} + +// Confirm that a fix for a tidy module will correct all modules in the +// workspace. +func TestMultiModule_OneBrokenModule(t *testing.T) { + // In the earlier 'experimental workspace mode', gopls would aggregate go.sum + // entries for the workspace module, allowing it to correctly associate + // missing go.sum with diagnostics. With go.work files, this doesn't work: + // the go.command will happily write go.work.sum. + t.Skip("golang/go#57509: go.mod diagnostics do not work in go.work mode") + const files = ` +-- go.work -- +go 1.18 + +use ( + a + b +) +-- go.work.sum -- +-- a/go.mod -- +module a.com + +go 1.12 +-- a/main.go -- +package main +-- b/go.mod -- +module b.com + +go 1.12 + +require ( + example.com v1.2.3 +) +-- b/go.sum -- +-- b/main.go -- +package b + +import "example.com/blah" + +func main() { + blah.Hello() +} +` + WithOptions( + ProxyFiles(workspaceProxy), + ).Run(t, files, func(t *testing.T, env *Env) { + params := &protocol.PublishDiagnosticsParams{} + env.OpenFile("b/go.mod") + env.AfterChange( + Diagnostics( + env.AtRegexp("go.mod", `example.com v1.2.3`), + WithMessage("go.sum is out of sync"), + ), + ReadDiagnostics("b/go.mod", params), + ) + for _, d := range params.Diagnostics { + if !strings.Contains(d.Message, "go.sum is out of sync") { + continue + } + actions := env.GetQuickFixes("b/go.mod", []protocol.Diagnostic{d}) + if len(actions) != 2 { + t.Fatalf("expected 2 code actions, got %v", len(actions)) + } + env.ApplyQuickFixes("b/go.mod", []protocol.Diagnostic{d}) + } + env.AfterChange( + NoDiagnostics(ForFile("b/go.mod")), + ) + }) +} + +// Tests the fix for golang/go#52500. +func TestChangeTestVariant_Issue52500(t *testing.T) { + const src = ` +-- go.mod -- +module mod.test + +go 1.12 +-- main_test.go -- +package main_test + +type Server struct{} + +const mainConst = otherConst +-- other_test.go -- +package main_test + +const otherConst = 0 + +func (Server) Foo() {} +` + + Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("other_test.go") + env.RegexpReplace("other_test.go", "main_test", "main") + + // For this test to function, it is necessary to wait on both of the + // expectations below: the bug is that when switching the package name in + // other_test.go from main->main_test, metadata for main_test is not marked + // as invalid. So we need to wait for the metadata of main_test.go to be + // updated before moving other_test.go back to the main_test package. + env.Await( + Diagnostics(env.AtRegexp("other_test.go", "Server")), + Diagnostics(env.AtRegexp("main_test.go", "otherConst")), + ) + env.RegexpReplace("other_test.go", "main", "main_test") + env.AfterChange( + NoDiagnostics(ForFile("other_test.go")), + NoDiagnostics(ForFile("main_test.go")), + ) + + // This will cause a test failure if other_test.go is not in any package. + _ = env.GoToDefinition(env.RegexpSearch("other_test.go", "Server")) + }) +} + +// Test for golang/go#48929. +func TestClearNonWorkspaceDiagnostics(t *testing.T) { + const ws = ` +-- go.work -- +go 1.18 + +use ( + ./b +) +-- a/go.mod -- +module a + +go 1.17 +-- a/main.go -- +package main + +func main() { + var V string +} +-- b/go.mod -- +module b + +go 1.17 +-- b/main.go -- +package b + +import ( + _ "fmt" +) +` + Run(t, ws, func(t *testing.T, env *Env) { + env.OpenFile("b/main.go") + env.AfterChange( + NoDiagnostics(ForFile("a/main.go")), + ) + env.OpenFile("a/main.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/main.go", "V"), WithMessage("not used")), + ) + // Here, diagnostics are added because of zero-config gopls. + // In the past, they were added simply due to diagnosing changed files. + // (see TestClearNonWorkspaceDiagnostics_NoView below for a + // reimplementation of that test). + if got, want := len(env.Views()), 2; got != want { + t.Errorf("after opening a/main.go, got %d views, want %d", got, want) + } + env.CloseBuffer("a/main.go") + env.AfterChange( + NoDiagnostics(ForFile("a/main.go")), + ) + if got, want := len(env.Views()), 1; got != want { + t.Errorf("after closing a/main.go, got %d views, want %d", got, want) + } + }) +} + +// This test is like TestClearNonWorkspaceDiagnostics, but bypasses the +// zero-config algorithm by opening a nested workspace folder. +// +// We should still compute diagnostics correctly for open packages. +func TestClearNonWorkspaceDiagnostics_NoView(t *testing.T) { + const ws = ` +-- a/go.mod -- +module example.com/a + +go 1.18 + +require example.com/b v1.2.3 + +replace example.com/b => ../b + +-- a/a.go -- +package a + +import "example.com/b" + +func _() { + V := b.B // unused +} + +-- b/go.mod -- +module b + +go 1.18 + +-- b/b.go -- +package b + +const B = 2 + +func _() { + var V int // unused +} + +-- b/b2.go -- +package b + +const B2 = B + +-- c/c.go -- +package main + +func main() { + var V int // unused +} +` + WithOptions( + WorkspaceFolders("a"), + ).Run(t, ws, func(t *testing.T, env *Env) { + env.OpenFile("a/a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), + NoDiagnostics(ForFile("b/b.go")), + NoDiagnostics(ForFile("c/c.go")), + ) + env.OpenFile("b/b.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), + Diagnostics(env.AtRegexp("b/b.go", "V"), WithMessage("not used")), + NoDiagnostics(ForFile("c/c.go")), + ) + + // Opening b/b.go should not result in a new view, because b is not + // contained in a workspace folder. + // + // Yet we should get diagnostics for b, because it is open. + if got, want := len(env.Views()), 1; got != want { + t.Errorf("after opening b/b.go, got %d views, want %d", got, want) + } + env.CloseBuffer("b/b.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), + NoDiagnostics(ForFile("b/b.go")), + NoDiagnostics(ForFile("c/c.go")), + ) + + // We should get references in the b package. + bUse := env.RegexpSearch("a/a.go", `b\.(B)`) + refs := env.References(bUse) + wantRefs := []string{"a/a.go", "b/b.go", "b/b2.go"} + var gotRefs []string + for _, ref := range refs { + gotRefs = append(gotRefs, env.Sandbox.Workdir.URIToPath(ref.URI)) + } + sort.Strings(gotRefs) + if diff := cmp.Diff(wantRefs, gotRefs); diff != "" { + t.Errorf("references(b.B) mismatch (-want +got)\n%s", diff) + } + + // Opening c/c.go should also not result in a new view, yet we should get + // orphaned file diagnostics. + env.OpenFile("c/c.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), + NoDiagnostics(ForFile("b/b.go")), + Diagnostics(env.AtRegexp("c/c.go", "V"), WithMessage("not used")), + ) + if got, want := len(env.Views()), 1; got != want { + t.Errorf("after opening b/b.go, got %d views, want %d", got, want) + } + + env.CloseBuffer("c/c.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), + NoDiagnostics(ForFile("b/b.go")), + NoDiagnostics(ForFile("c/c.go")), + ) + env.CloseBuffer("a/a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("a/a.go", "V"), WithMessage("not used")), + NoDiagnostics(ForFile("b/b.go")), + NoDiagnostics(ForFile("c/c.go")), + ) + }) +} + +// Test that we don't get a version warning when the Go version in PATH is +// supported. +func TestOldGoNotification_SupportedVersion(t *testing.T) { + v := goVersion(t) + if v < goversion.OldestSupported() { + t.Skipf("go version 1.%d is unsupported", v) + } + + Run(t, "", func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + NoShownMessage("upgrade"), + ) + }) +} + +// Test that we do get a version warning when the Go version in PATH is +// unsupported, though this test may never execute if we stop running CI at +// legacy Go versions (see also TestOldGoNotification_Fake) +func TestOldGoNotification_UnsupportedVersion(t *testing.T) { + v := goVersion(t) + if v >= goversion.OldestSupported() { + t.Skipf("go version 1.%d is supported", v) + } + + Run(t, "", func(t *testing.T, env *Env) { + env.Await( + // Note: cannot use OnceMet(InitialWorkspaceLoad, ...) here, as the + // upgrade message may race with the IWL. + ShownMessage("Please upgrade"), + ) + }) +} + +func TestOldGoNotification_Fake(t *testing.T) { + // Get the Go version from path, and make sure it's unsupported. + // + // In the future we'll stop running CI on legacy Go versions. By mutating the + // oldest supported Go version here, we can at least ensure that the + // ShowMessage pop-up works. + ctx := context.Background() + version, err := gocommand.GoVersion(ctx, gocommand.Invocation{}, &gocommand.Runner{}) + if err != nil { + t.Fatal(err) + } + defer func(t []goversion.Support) { + goversion.Supported = t + }(goversion.Supported) + goversion.Supported = []goversion.Support{ + {GoVersion: version, InstallGoplsVersion: "v1.0.0"}, + } + + Run(t, "", func(t *testing.T, env *Env) { + env.Await( + // Note: cannot use OnceMet(InitialWorkspaceLoad, ...) here, as the + // upgrade message may race with the IWL. + ShownMessage("Please upgrade"), + ) + }) +} + +// goVersion returns the version of the Go command in PATH. +func goVersion(t *testing.T) int { + t.Helper() + ctx := context.Background() + goversion, err := gocommand.GoVersion(ctx, gocommand.Invocation{}, &gocommand.Runner{}) + if err != nil { + t.Fatal(err) + } + return goversion +} + +func TestGoworkMutation(t *testing.T) { + WithOptions( + ProxyFiles(workspaceModuleProxy), + ).Run(t, multiModule, func(t *testing.T, env *Env) { + env.RunGoCommand("work", "init") + env.RunGoCommand("work", "use", "-r", ".") + env.AfterChange( + Diagnostics(env.AtRegexp("moda/a/a.go", "x")), + Diagnostics(env.AtRegexp("modb/b/b.go", "x")), + NoDiagnostics(env.AtRegexp("moda/a/a.go", `b\.Hello`)), + ) + env.RunGoCommand("work", "edit", "-dropuse", "modb") + env.Await( + Diagnostics(env.AtRegexp("moda/a/a.go", "x")), + NoDiagnostics(env.AtRegexp("modb/b/b.go", "x")), + Diagnostics(env.AtRegexp("moda/a/a.go", `b\.Hello`)), + ) + }) +} + +func TestInitializeWithNonFileWorkspaceFolders(t *testing.T) { + for _, tt := range []struct { + name string + folders []string + wantViewRoots []string + }{ + { + name: "real,virtual", + folders: []string{"modb", "virtual:///virtualpath"}, + wantViewRoots: []string{"./modb"}, + }, + { + name: "virtual,real", + folders: []string{"virtual:///virtualpath", "modb"}, + wantViewRoots: []string{"./modb"}, + }, + { + name: "real,virtual,real", + folders: []string{"moda/a", "virtual:///virtualpath", "modb"}, + wantViewRoots: []string{"./moda/a", "./modb"}, + }, + { + name: "virtual", + folders: []string{"virtual:///virtualpath"}, + wantViewRoots: nil, + }, + } { + + t.Run(tt.name, func(t *testing.T) { + opts := []RunOption{ProxyFiles(workspaceProxy), WorkspaceFolders(tt.folders...)} + WithOptions(opts...).Run(t, multiModule, func(t *testing.T, env *Env) { + summary := func(typ cache.ViewType, root, folder string) command.View { + return command.View{ + Type: typ.String(), + Root: env.Sandbox.Workdir.URI(root), + Folder: env.Sandbox.Workdir.URI(folder), + } + } + checkViews := func(want ...command.View) { + got := env.Views() + if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(command.View{}, "ID")); diff != "" { + t.Errorf("SummarizeViews() mismatch (-want +got):\n%s", diff) + } + } + var wantViews []command.View + for _, root := range tt.wantViewRoots { + wantViews = append(wantViews, summary(cache.GoModView, root, root)) + } + env.Await( + LogMatching(protocol.Warning, "skip adding virtual folder", 1, false), + ) + checkViews(wantViews...) + }) + }) + } +} + +// Test that non-file scheme Document URIs in ChangeWorkspaceFolders +// notification does not produce errors. +func TestChangeNonFileWorkspaceFolders(t *testing.T) { + for _, tt := range []struct { + name string + before []string + after []string + wantViewRoots []string + }{ + { + name: "add", + before: []string{"modb"}, + after: []string{"modb", "moda/a", "virtual:///virtualpath"}, + wantViewRoots: []string{"./modb", "moda/a"}, + }, + { + name: "remove", + before: []string{"modb", "virtual:///virtualpath", "moda/a"}, + after: []string{"modb"}, + wantViewRoots: []string{"./modb"}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + opts := []RunOption{ProxyFiles(workspaceProxy), WorkspaceFolders(tt.before...)} + WithOptions(opts...).Run(t, multiModule, func(t *testing.T, env *Env) { + summary := func(typ cache.ViewType, root, folder string) command.View { + return command.View{ + Type: typ.String(), + Root: env.Sandbox.Workdir.URI(root), + Folder: env.Sandbox.Workdir.URI(folder), + } + } + checkViews := func(want ...command.View) { + got := env.Views() + if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(command.View{}, "ID")); diff != "" { + t.Errorf("SummarizeViews() mismatch (-want +got):\n%s", diff) + } + } + var wantViews []command.View + for _, root := range tt.wantViewRoots { + wantViews = append(wantViews, summary(cache.GoModView, root, root)) + } + env.ChangeWorkspaceFolders(tt.after...) + env.Await( + LogMatching(protocol.Warning, "skip adding virtual folder", 1, false), + NoOutstandingWork(IgnoreTelemetryPromptWork), + ) + checkViews(wantViews...) + }) + }) + } +} diff --git a/contribs/gnopls/internal/test/integration/workspace/zero_config_test.go b/contribs/gnopls/internal/test/integration/workspace/zero_config_test.go new file mode 100644 index 00000000000..95906274b93 --- /dev/null +++ b/contribs/gnopls/internal/test/integration/workspace/zero_config_test.go @@ -0,0 +1,327 @@ +// 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 workspace + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/protocol/command" + + . "golang.org/x/tools/gopls/internal/test/integration" +) + +func TestAddAndRemoveGoWork(t *testing.T) { + // Use a workspace with a module in the root directory to exercise the case + // where a go.work is added to the existing root directory. This verifies + // that we're detecting changes to the module source, not just the root + // directory. + const nomod = ` +-- go.mod -- +module a.com + +go 1.16 +-- main.go -- +package main + +func main() {} +-- b/go.mod -- +module b.com + +go 1.16 +-- b/main.go -- +package main + +func main() {} +` + WithOptions( + Modes(Default), + ).Run(t, nomod, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + env.OpenFile("b/main.go") + + summary := func(typ cache.ViewType, root, folder string) command.View { + return command.View{ + Type: typ.String(), + Root: env.Sandbox.Workdir.URI(root), + Folder: env.Sandbox.Workdir.URI(folder), + } + } + checkViews := func(want ...command.View) { + got := env.Views() + if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(command.View{}, "ID")); diff != "" { + t.Errorf("SummarizeViews() mismatch (-want +got):\n%s", diff) + } + } + + // Zero-config gopls makes this work. + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + NoDiagnostics(env.AtRegexp("b/main.go", "package (main)")), + ) + checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", ".")) + + env.WriteWorkspaceFile("go.work", `go 1.16 + +use ( + . + b +) +`) + env.AfterChange(NoDiagnostics()) + checkViews(summary(cache.GoWorkView, ".", ".")) + + // Removing the go.work file should put us back where we started. + env.RemoveWorkspaceFile("go.work") + + // Again, zero-config gopls makes this work. + env.AfterChange( + NoDiagnostics(ForFile("main.go")), + NoDiagnostics(env.AtRegexp("b/main.go", "package (main)")), + ) + checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", ".")) + + // Close and reopen b, to ensure the views are adjusted accordingly. + env.CloseBuffer("b/main.go") + env.AfterChange() + checkViews(summary(cache.GoModView, ".", ".")) + + env.OpenFile("b/main.go") + env.AfterChange() + checkViews(summary(cache.GoModView, ".", "."), summary(cache.GoModView, "b", ".")) + }) +} + +func TestOpenAndClosePorts(t *testing.T) { + // This test checks that as we open and close files requiring a different + // port, the set of Views is adjusted accordingly. + const files = ` +-- go.mod -- +module a.com/a + +go 1.20 + +-- a_linux.go -- +package a + +-- a_darwin.go -- +package a + +-- a_windows.go -- +package a +` + + WithOptions( + EnvVars{ + "GOOS": "linux", // assume that linux is the default GOOS + }, + ).Run(t, files, func(t *testing.T, env *Env) { + summary := func(envOverlay ...string) command.View { + return command.View{ + Type: cache.GoModView.String(), + Root: env.Sandbox.Workdir.URI("."), + Folder: env.Sandbox.Workdir.URI("."), + EnvOverlay: envOverlay, + } + } + checkViews := func(want ...command.View) { + got := env.Views() + if diff := cmp.Diff(want, got, cmpopts.IgnoreFields(command.View{}, "ID")); diff != "" { + t.Errorf("SummarizeViews() mismatch (-want +got):\n%s", diff) + } + } + checkViews(summary()) + env.OpenFile("a_linux.go") + checkViews(summary()) + env.OpenFile("a_darwin.go") + checkViews( + summary(), + summary("GOARCH=amd64", "GOOS=darwin"), + ) + env.OpenFile("a_windows.go") + checkViews( + summary(), + summary("GOARCH=amd64", "GOOS=darwin"), + summary("GOARCH=amd64", "GOOS=windows"), + ) + env.CloseBuffer("a_darwin.go") + checkViews( + summary(), + summary("GOARCH=amd64", "GOOS=windows"), + ) + env.CloseBuffer("a_linux.go") + checkViews( + summary(), + summary("GOARCH=amd64", "GOOS=windows"), + ) + env.CloseBuffer("a_windows.go") + checkViews(summary()) + }) +} + +func TestCriticalErrorsInOrphanedFiles(t *testing.T) { + // This test checks that as we open and close files requiring a different + // port, the set of Views is adjusted accordingly. + const files = ` +-- go.mod -- +modul golang.org/lsptests/broken + +go 1.20 + +-- a.go -- +package broken + +const C = 0 +` + + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.AfterChange( + Diagnostics(env.AtRegexp("go.mod", "modul")), + Diagnostics(env.AtRegexp("a.go", "broken"), WithMessage("initialization failed")), + ) + }) +} + +func TestGoModReplace(t *testing.T) { + // This test checks that we treat locally replaced modules as workspace + // modules, according to the "includeReplaceInWorkspace" setting. + const files = ` +-- moda/go.mod -- +module golang.org/a + +require golang.org/b v1.2.3 + +replace golang.org/b => ../modb + +go 1.20 + +-- moda/a.go -- +package a + +import "golang.org/b" + +const A = b.B + +-- modb/go.mod -- +module golang.org/b + +go 1.20 + +-- modb/b.go -- +package b + +const B = 1 +` + + for useReplace, expectation := range map[bool]Expectation{ + true: FileWatchMatching("modb"), + false: NoFileWatchMatching("modb"), + } { + WithOptions( + WorkspaceFolders("moda"), + Settings{ + "includeReplaceInWorkspace": useReplace, + }, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OnceMet( + InitialWorkspaceLoad, + expectation, + ) + }) + } +} + +func TestDisableZeroConfig(t *testing.T) { + // This test checks that we treat locally replaced modules as workspace + // modules, according to the "includeReplaceInWorkspace" setting. + const files = ` +-- moda/go.mod -- +module golang.org/a + +go 1.20 + +-- moda/a.go -- +package a + +-- modb/go.mod -- +module golang.org/b + +go 1.20 + +-- modb/b.go -- +package b + +` + + WithOptions( + Settings{"zeroConfig": false}, + ).Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("moda/a.go") + env.OpenFile("modb/b.go") + env.AfterChange() + if got := env.Views(); len(got) != 1 || got[0].Type != cache.AdHocView.String() { + t.Errorf("Views: got %v, want one adhoc view", got) + } + }) +} + +func TestVendorExcluded(t *testing.T) { + // Test that we don't create Views for vendored modules. + // + // We construct the vendor directory manually here, as `go mod vendor` will + // omit the go.mod file. This synthesizes the setup of Kubernetes, where the + // entire module is vendored through a symlinked directory. + const src = ` +-- go.mod -- +module example.com/a + +go 1.18 + +require other.com/b v1.0.0 + +-- a.go -- +package a +import "other.com/b" +var _ b.B + +-- vendor/modules.txt -- +# other.com/b v1.0.0 +## explicit; go 1.14 +other.com/b + +-- vendor/other.com/b/go.mod -- +module other.com/b +go 1.14 + +-- vendor/other.com/b/b.go -- +package b +type B int + +func _() { + var V int // unused +} +` + WithOptions( + Modes(Default), + ).Run(t, src, func(t *testing.T, env *Env) { + env.OpenFile("a.go") + env.AfterChange(NoDiagnostics()) + loc := env.GoToDefinition(env.RegexpSearch("a.go", `b\.(B)`)) + if !strings.Contains(string(loc.URI), "/vendor/") { + t.Fatalf("Definition(b.B) = %v, want vendored location", loc.URI) + } + env.AfterChange( + Diagnostics(env.AtRegexp("vendor/other.com/b/b.go", "V"), WithMessage("not used")), + ) + + if views := env.Views(); len(views) != 1 { + t.Errorf("After opening /vendor/, got %d views, want 1. Views:\n%v", len(views), views) + } + }) +} diff --git a/contribs/gnopls/internal/test/integration/wrappers.go b/contribs/gnopls/internal/test/integration/wrappers.go new file mode 100644 index 00000000000..ddff4da979b --- /dev/null +++ b/contribs/gnopls/internal/test/integration/wrappers.go @@ -0,0 +1,625 @@ +// 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 integration + +import ( + "encoding/json" + "errors" + "os" + "path" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/protocol/command" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/internal/xcontext" +) + +// RemoveWorkspaceFile deletes a file on disk but does nothing in the +// editor. It calls t.Fatal on any error. +func (e *Env) RemoveWorkspaceFile(name string) { + e.T.Helper() + if err := e.Sandbox.Workdir.RemoveFile(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// ReadWorkspaceFile reads a file from the workspace, calling t.Fatal on any +// error. +func (e *Env) ReadWorkspaceFile(name string) string { + e.T.Helper() + content, err := e.Sandbox.Workdir.ReadFile(name) + if err != nil { + e.T.Fatal(err) + } + return string(content) +} + +// WriteWorkspaceFile writes a file to disk but does nothing in the editor. +// It calls t.Fatal on any error. +func (e *Env) WriteWorkspaceFile(name, content string) { + e.T.Helper() + if err := e.Sandbox.Workdir.WriteFile(e.Ctx, name, content); err != nil { + e.T.Fatal(err) + } +} + +// WriteWorkspaceFiles deletes a file on disk but does nothing in the +// editor. It calls t.Fatal on any error. +func (e *Env) WriteWorkspaceFiles(files map[string]string) { + e.T.Helper() + if err := e.Sandbox.Workdir.WriteFiles(e.Ctx, files); err != nil { + e.T.Fatal(err) + } +} + +// ListFiles lists relative paths to files in the given directory. +// It calls t.Fatal on any error. +func (e *Env) ListFiles(dir string) []string { + e.T.Helper() + paths, err := e.Sandbox.Workdir.ListFiles(dir) + if err != nil { + e.T.Fatal(err) + } + return paths +} + +// OpenFile opens a file in the editor, calling t.Fatal on any error. +func (e *Env) OpenFile(name string) { + e.T.Helper() + if err := e.Editor.OpenFile(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// CreateBuffer creates a buffer in the editor, calling t.Fatal on any error. +func (e *Env) CreateBuffer(name string, content string) { + e.T.Helper() + if err := e.Editor.CreateBuffer(e.Ctx, name, content); err != nil { + e.T.Fatal(err) + } +} + +// BufferText returns the current buffer contents for the file with the given +// relative path, calling t.Fatal if the file is not open in a buffer. +func (e *Env) BufferText(name string) string { + e.T.Helper() + text, ok := e.Editor.BufferText(name) + if !ok { + e.T.Fatalf("buffer %q is not open", name) + } + return text +} + +// CloseBuffer closes an editor buffer without saving, calling t.Fatal on any +// error. +func (e *Env) CloseBuffer(name string) { + e.T.Helper() + if err := e.Editor.CloseBuffer(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// EditBuffer applies edits to an editor buffer, calling t.Fatal on any error. +func (e *Env) EditBuffer(name string, edits ...protocol.TextEdit) { + e.T.Helper() + if err := e.Editor.EditBuffer(e.Ctx, name, edits); err != nil { + e.T.Fatal(err) + } +} + +func (e *Env) SetBufferContent(name string, content string) { + e.T.Helper() + if err := e.Editor.SetBufferContent(e.Ctx, name, content); err != nil { + e.T.Fatal(err) + } +} + +// FileContent returns the file content for name that applies to the current +// editing session: it returns the buffer content for an open file, the +// on-disk content for an unopened file, or "" for a non-existent file. +func (e *Env) FileContent(name string) string { + e.T.Helper() + text, ok := e.Editor.BufferText(name) + if ok { + return text + } + content, err := e.Sandbox.Workdir.ReadFile(name) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return "" + } else { + e.T.Fatal(err) + } + } + return string(content) +} + +// FileContentAt returns the file content at the given location, using the +// file's mapper. +func (e *Env) FileContentAt(location protocol.Location) string { + e.T.Helper() + mapper, err := e.Editor.Mapper(location.URI.Path()) + if err != nil { + e.T.Fatal(err) + } + start, end, err := mapper.RangeOffsets(location.Range) + if err != nil { + e.T.Fatal(err) + } + return string(mapper.Content[start:end]) +} + +// RegexpSearch returns the starting position of the first match for re in the +// buffer specified by name, calling t.Fatal on any error. It first searches +// for the position in open buffers, then in workspace files. +func (e *Env) RegexpSearch(name, re string) protocol.Location { + e.T.Helper() + loc, err := e.Editor.RegexpSearch(name, re) + if err == fake.ErrUnknownBuffer { + loc, err = e.Sandbox.Workdir.RegexpSearch(name, re) + } + if err != nil { + e.T.Fatalf("RegexpSearch: %v, %v for %q", name, err, re) + } + return loc +} + +// RegexpReplace replaces the first group in the first match of regexpStr with +// the replace text, calling t.Fatal on any error. +func (e *Env) RegexpReplace(name, regexpStr, replace string) { + e.T.Helper() + if err := e.Editor.RegexpReplace(e.Ctx, name, regexpStr, replace); err != nil { + e.T.Fatalf("RegexpReplace: %v", err) + } +} + +// SaveBuffer saves an editor buffer, calling t.Fatal on any error. +func (e *Env) SaveBuffer(name string) { + e.T.Helper() + if err := e.Editor.SaveBuffer(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +func (e *Env) SaveBufferWithoutActions(name string) { + e.T.Helper() + if err := e.Editor.SaveBufferWithoutActions(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// GoToDefinition goes to definition in the editor, calling t.Fatal on any +// error. It returns the path and position of the resulting jump. +// +// TODO(rfindley): rename this to just 'Definition'. +func (e *Env) GoToDefinition(loc protocol.Location) protocol.Location { + e.T.Helper() + loc, err := e.Editor.Definition(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return loc +} + +func (e *Env) TypeDefinition(loc protocol.Location) protocol.Location { + e.T.Helper() + loc, err := e.Editor.TypeDefinition(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return loc +} + +// FormatBuffer formats the editor buffer, calling t.Fatal on any error. +func (e *Env) FormatBuffer(name string) { + e.T.Helper() + if err := e.Editor.FormatBuffer(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// OrganizeImports processes the source.organizeImports codeAction, calling +// t.Fatal on any error. +func (e *Env) OrganizeImports(name string) { + e.T.Helper() + if err := e.Editor.OrganizeImports(e.Ctx, name); err != nil { + e.T.Fatal(err) + } +} + +// ApplyQuickFixes processes the quickfix codeAction, calling t.Fatal on any error. +func (e *Env) ApplyQuickFixes(path string, diagnostics []protocol.Diagnostic) { + e.T.Helper() + loc := e.Sandbox.Workdir.EntireFile(path) + if err := e.Editor.ApplyQuickFixes(e.Ctx, loc, diagnostics); err != nil { + e.T.Fatal(err) + } +} + +// ApplyCodeAction applies the given code action. +func (e *Env) ApplyCodeAction(action protocol.CodeAction) { + e.T.Helper() + if err := e.Editor.ApplyCodeAction(e.Ctx, action); err != nil { + e.T.Fatal(err) + } +} + +// GetQuickFixes returns the available quick fix code actions. +func (e *Env) GetQuickFixes(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction { + e.T.Helper() + loc := e.Sandbox.Workdir.EntireFile(path) + actions, err := e.Editor.GetQuickFixes(e.Ctx, loc, diagnostics) + if err != nil { + e.T.Fatal(err) + } + return actions +} + +// Hover in the editor, calling t.Fatal on any error. +// It may return (nil, zero) even on success. +func (e *Env) Hover(loc protocol.Location) (*protocol.MarkupContent, protocol.Location) { + e.T.Helper() + c, loc, err := e.Editor.Hover(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return c, loc +} + +func (e *Env) DocumentLink(name string) []protocol.DocumentLink { + e.T.Helper() + links, err := e.Editor.DocumentLink(e.Ctx, name) + if err != nil { + e.T.Fatal(err) + } + return links +} + +func (e *Env) DocumentHighlight(loc protocol.Location) []protocol.DocumentHighlight { + e.T.Helper() + highlights, err := e.Editor.DocumentHighlight(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return highlights +} + +// RunGenerate runs "go generate" in the given dir, calling t.Fatal on any error. +// It waits for the generate command to complete and checks for file changes +// before returning. +func (e *Env) RunGenerate(dir string) { + e.T.Helper() + if err := e.Editor.RunGenerate(e.Ctx, dir); err != nil { + e.T.Fatal(err) + } + e.Await(NoOutstandingWork(IgnoreTelemetryPromptWork)) + // Ideally the editor.Workspace would handle all synthetic file watching, but + // we help it out here as we need to wait for the generate command to + // complete before checking the filesystem. + e.CheckForFileChanges() +} + +// RunGoCommand runs the given command in the sandbox's default working +// directory. +func (e *Env) RunGoCommand(verb string, args ...string) { + e.T.Helper() + if err := e.Sandbox.RunGoCommand(e.Ctx, "", verb, args, nil, true); err != nil { + e.T.Fatal(err) + } +} + +// RunGoCommandInDir is like RunGoCommand, but executes in the given +// relative directory of the sandbox. +func (e *Env) RunGoCommandInDir(dir, verb string, args ...string) { + e.T.Helper() + if err := e.Sandbox.RunGoCommand(e.Ctx, dir, verb, args, nil, true); err != nil { + e.T.Fatal(err) + } +} + +// RunGoCommandInDirWithEnv is like RunGoCommand, but executes in the given +// relative directory of the sandbox with the given additional environment variables. +func (e *Env) RunGoCommandInDirWithEnv(dir string, env []string, verb string, args ...string) { + e.T.Helper() + if err := e.Sandbox.RunGoCommand(e.Ctx, dir, verb, args, env, true); err != nil { + e.T.Fatal(err) + } +} + +// GoVersion checks the version of the go command. +// It returns the X in Go 1.X. +func (e *Env) GoVersion() int { + e.T.Helper() + v, err := e.Sandbox.GoVersion(e.Ctx) + if err != nil { + e.T.Fatal(err) + } + return v +} + +// DumpGoSum prints the correct go.sum contents for dir in txtar format, +// for use in creating integration tests. +func (e *Env) DumpGoSum(dir string) { + e.T.Helper() + + if err := e.Sandbox.RunGoCommand(e.Ctx, dir, "list", []string{"-mod=mod", "./..."}, nil, true); err != nil { + e.T.Fatal(err) + } + sumFile := path.Join(dir, "go.sum") + e.T.Log("\n\n-- " + sumFile + " --\n" + e.ReadWorkspaceFile(sumFile)) + e.T.Fatal("see contents above") +} + +// CheckForFileChanges triggers a manual poll of the workspace for any file +// changes since creation, or since last polling. It is a workaround for the +// lack of true file watching support in the fake workspace. +func (e *Env) CheckForFileChanges() { + e.T.Helper() + if err := e.Sandbox.Workdir.CheckForFileChanges(e.Ctx); err != nil { + e.T.Fatal(err) + } +} + +// CodeLens calls textDocument/codeLens for the given path, calling t.Fatal on +// any error. +func (e *Env) CodeLens(path string) []protocol.CodeLens { + e.T.Helper() + lens, err := e.Editor.CodeLens(e.Ctx, path) + if err != nil { + e.T.Fatal(err) + } + return lens +} + +// ExecuteCodeLensCommand executes the command for the code lens matching the +// given command name. +func (e *Env) ExecuteCodeLensCommand(path string, cmd command.Command, result interface{}) { + e.T.Helper() + lenses := e.CodeLens(path) + var lens protocol.CodeLens + var found bool + for _, l := range lenses { + if l.Command.Command == cmd.String() { + lens = l + found = true + } + } + if !found { + e.T.Fatalf("found no command with the ID %s", cmd) + } + e.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: lens.Command.Command, + Arguments: lens.Command.Arguments, + }, result) +} + +func (e *Env) ExecuteCommand(params *protocol.ExecuteCommandParams, result interface{}) { + e.T.Helper() + response, err := e.Editor.ExecuteCommand(e.Ctx, params) + if err != nil { + e.T.Fatal(err) + } + if result == nil { + return + } + // Hack: The result of an executeCommand request will be unmarshaled into + // maps. Re-marshal and unmarshal into the type we expect. + // + // This could be improved by generating a jsonrpc2 command client from the + // command.Interface, but that should only be done if we're consolidating + // this part of the tsprotocol generation. + data, err := json.Marshal(response) + if err != nil { + e.T.Fatal(err) + } + if err := json.Unmarshal(data, result); err != nil { + e.T.Fatal(err) + } +} + +// Views returns the server's views. +func (e *Env) Views() []command.View { + var summaries []command.View + cmd := command.NewViewsCommand("") + e.ExecuteCommand(&protocol.ExecuteCommandParams{ + Command: cmd.Command, + Arguments: cmd.Arguments, + }, &summaries) + return summaries +} + +// StartProfile starts a CPU profile with the given name, using the +// gopls.start_profile custom command. It calls t.Fatal on any error. +// +// The resulting stop function must be called to stop profiling (using the +// gopls.stop_profile custom command). +func (e *Env) StartProfile() (stop func() string) { + // TODO(golang/go#61217): revisit the ergonomics of these command APIs. + // + // This would be a lot simpler if we generated params constructors. + args, err := command.MarshalArgs(command.StartProfileArgs{}) + if err != nil { + e.T.Fatal(err) + } + params := &protocol.ExecuteCommandParams{ + Command: command.StartProfile.String(), + Arguments: args, + } + var result command.StartProfileResult + e.ExecuteCommand(params, &result) + + return func() string { + stopArgs, err := command.MarshalArgs(command.StopProfileArgs{}) + if err != nil { + e.T.Fatal(err) + } + stopParams := &protocol.ExecuteCommandParams{ + Command: command.StopProfile.String(), + Arguments: stopArgs, + } + var result command.StopProfileResult + e.ExecuteCommand(stopParams, &result) + return result.File + } +} + +// InlayHints calls textDocument/inlayHints for the given path, calling t.Fatal on +// any error. +func (e *Env) InlayHints(path string) []protocol.InlayHint { + e.T.Helper() + hints, err := e.Editor.InlayHint(e.Ctx, path) + if err != nil { + e.T.Fatal(err) + } + return hints +} + +// Symbol calls workspace/symbol +func (e *Env) Symbol(query string) []protocol.SymbolInformation { + e.T.Helper() + ans, err := e.Editor.Symbols(e.Ctx, query) + if err != nil { + e.T.Fatal(err) + } + return ans +} + +// References wraps Editor.References, calling t.Fatal on any error. +func (e *Env) References(loc protocol.Location) []protocol.Location { + e.T.Helper() + locations, err := e.Editor.References(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return locations +} + +// Rename wraps Editor.Rename, calling t.Fatal on any error. +func (e *Env) Rename(loc protocol.Location, newName string) { + e.T.Helper() + if err := e.Editor.Rename(e.Ctx, loc, newName); err != nil { + e.T.Fatal(err) + } +} + +// Implementations wraps Editor.Implementations, calling t.Fatal on any error. +func (e *Env) Implementations(loc protocol.Location) []protocol.Location { + e.T.Helper() + locations, err := e.Editor.Implementations(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return locations +} + +// RenameFile wraps Editor.RenameFile, calling t.Fatal on any error. +func (e *Env) RenameFile(oldPath, newPath string) { + e.T.Helper() + if err := e.Editor.RenameFile(e.Ctx, oldPath, newPath); err != nil { + e.T.Fatal(err) + } +} + +// SignatureHelp wraps Editor.SignatureHelp, calling t.Fatal on error +func (e *Env) SignatureHelp(loc protocol.Location) *protocol.SignatureHelp { + e.T.Helper() + sighelp, err := e.Editor.SignatureHelp(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return sighelp +} + +// Completion executes a completion request on the server. +func (e *Env) Completion(loc protocol.Location) *protocol.CompletionList { + e.T.Helper() + completions, err := e.Editor.Completion(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return completions +} + +func (e *Env) SetSuggestionInsertReplaceMode(useReplaceMode bool) { + e.T.Helper() + e.Editor.SetSuggestionInsertReplaceMode(e.Ctx, useReplaceMode) +} + +// AcceptCompletion accepts a completion for the given item at the given +// position. +func (e *Env) AcceptCompletion(loc protocol.Location, item protocol.CompletionItem) { + e.T.Helper() + if err := e.Editor.AcceptCompletion(e.Ctx, loc, item); err != nil { + e.T.Fatal(err) + } +} + +// CodeActionForFile calls textDocument/codeAction for the entire +// file, and calls t.Fatal if there were errors. +func (e *Env) CodeActionForFile(path string, diagnostics []protocol.Diagnostic) []protocol.CodeAction { + return e.CodeAction(e.Sandbox.Workdir.EntireFile(path), diagnostics, protocol.CodeActionUnknownTrigger) +} + +// CodeAction calls textDocument/codeAction for a selection, +// and calls t.Fatal if there were errors. +func (e *Env) CodeAction(loc protocol.Location, diagnostics []protocol.Diagnostic, trigger protocol.CodeActionTriggerKind) []protocol.CodeAction { + e.T.Helper() + actions, err := e.Editor.CodeAction(e.Ctx, loc, diagnostics, trigger) + if err != nil { + e.T.Fatal(err) + } + return actions +} + +// ChangeConfiguration updates the editor config, calling t.Fatal on any error. +func (e *Env) ChangeConfiguration(newConfig fake.EditorConfig) { + e.T.Helper() + if err := e.Editor.ChangeConfiguration(e.Ctx, newConfig); err != nil { + e.T.Fatal(err) + } +} + +// ChangeWorkspaceFolders updates the editor workspace folders, calling t.Fatal +// on any error. +func (e *Env) ChangeWorkspaceFolders(newFolders ...string) { + e.T.Helper() + if err := e.Editor.ChangeWorkspaceFolders(e.Ctx, newFolders); err != nil { + e.T.Fatal(err) + } +} + +// SemanticTokensFull invokes textDocument/semanticTokens/full, calling t.Fatal +// on any error. +func (e *Env) SemanticTokensFull(path string) []fake.SemanticToken { + e.T.Helper() + toks, err := e.Editor.SemanticTokensFull(e.Ctx, path) + if err != nil { + e.T.Fatal(err) + } + return toks +} + +// SemanticTokensRange invokes textDocument/semanticTokens/range, calling t.Fatal +// on any error. +func (e *Env) SemanticTokensRange(loc protocol.Location) []fake.SemanticToken { + e.T.Helper() + toks, err := e.Editor.SemanticTokensRange(e.Ctx, loc) + if err != nil { + e.T.Fatal(err) + } + return toks +} + +// Close shuts down the editor session and cleans up the sandbox directory, +// calling t.Error on any error. +func (e *Env) Close() { + ctx := xcontext.Detach(e.Ctx) + if err := e.Editor.Close(ctx); err != nil { + e.T.Errorf("closing editor: %v", err) + } + if err := e.Sandbox.Close(); err != nil { + e.T.Errorf("cleaning up sandbox: %v", err) + } +} diff --git a/contribs/gnopls/internal/test/marker/doc.go b/contribs/gnopls/internal/test/marker/doc.go new file mode 100644 index 00000000000..5bcb31b46de --- /dev/null +++ b/contribs/gnopls/internal/test/marker/doc.go @@ -0,0 +1,377 @@ +// 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 marker defines a framework for running "marker" tests, each +defined by a file in the testdata subdirectory. + +Use this command to run the tests: + + $ go test ./gopls/internal/test/marker [-update] + +A marker test uses the '//@' marker syntax of the x/tools/go/expect package +to annotate source code with various information such as locations and +arguments of LSP operations to be executed by the test. The syntax following +'@' is parsed as a comma-separated list of ordinary Go function calls, for +example + + //@foo(a, "b", 3),bar(0) + +and delegates to a corresponding function to perform LSP-related operations. +See the Marker types documentation below for a list of supported markers. + +Each call argument is converted to the type of the corresponding parameter of +the designated function. The conversion logic may use the surrounding context, +such as the position or nearby text. See the Argument conversion section below +for the full set of special conversions. As a special case, the blank +identifier '_' is treated as the zero value of the parameter type. + +The test runner collects test cases by searching the given directory for +files with the .txt extension. Each file is interpreted as a txtar archive, +which is extracted to a temporary directory. The relative path to the .txt +file is used as the subtest name. The preliminary section of the file +(before the first archive entry) is a free-form comment. + +# Special files + +There are several types of file within the test archive that are given special +treatment by the test runner: + + - "skip": the presence of this file causes the test to be skipped, with + the file content used as the skip message. + + - "flags": this file is treated as a whitespace-separated list of flags + that configure the MarkerTest instance. Supported flags: + -{min,max}_go=go1.20 sets the {min,max}imum Go version for the test + (inclusive) + -cgo requires that CGO_ENABLED is set and the cgo tool is available + -write_sumfile=a,b,c instructs the test runner to generate go.sum files + in these directories before running the test. + -skip_goos=a,b,c instructs the test runner to skip the test for the + listed GOOS values. + -skip_goarch=a,b,c does the same for GOARCH. + -ignore_extra_diags suppresses errors for unmatched diagnostics + TODO(rfindley): using build constraint expressions for -skip_go{os,arch} would + be clearer. + -filter_builtins=false disables the filtering of builtins from + completion results. + -filter_keywords=false disables the filtering of keywords from + completion results. + -errors_ok=true suppresses errors for Error level log entries. + TODO(rfindley): support flag values containing whitespace. + + - "settings.json": this file is parsed as JSON, and used as the + session configuration (see gopls/doc/settings.md) + + - "capabilities.json": this file is parsed as JSON client capabilities, + and applied as an overlay over the default editor client capabilities. + see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#clientCapabilities + for more details. + + - "env": this file is parsed as a list of VAR=VALUE fields specifying the + editor environment. + + - Golden files: Within the archive, file names starting with '@' are + treated as "golden" content, and are not written to disk, but instead are + made available to test methods expecting an argument of type *Golden, + using the identifier following '@'. For example, if the first parameter of + Foo were of type *Golden, the test runner would convert the identifier a + in the call @foo(a, "b", 3) into a *Golden by collecting golden file + data starting with "@a/". As a special case, for tests that only need one + golden file, the data contained in the file "@a" is indexed in the *Golden + value by the empty string "". + + - proxy files: any file starting with proxy/ is treated as a Go proxy + file. If present, these files are written to a separate temporary + directory and GOPROXY is set to file://<proxy directory>. + +# Marker types + +Markers are of two kinds. A few are "value markers" (e.g. @item), which are +processed in a first pass and each computes a value that may be referred to +by name later. Most are "action markers", which are processed in a second +pass and take some action such as testing an LSP operation; they may refer +to values computed by value markers. + +The following markers are supported within marker tests: + + - acceptcompletion(location, label, golden): specifies that accepting the + completion candidate produced at the given location with provided label + results in the given golden state. + + - codeaction(start, end, kind, golden): specifies a code action + to request for the given range. To support multi-line ranges, the range + is defined to be between start.Start and end.End. The golden directory + contains changed file content after the code action is applied. + + TODO(rfindley): now that 'location' supports multi-line matches, replace + uses of 'codeaction' with codeactionedit. + + - codeactionedit(location, kind, golden): a shorter form of + codeaction. Invokes a code action of the given kind for the given + in-line range, and compares the resulting formatted unified *edits* + (notably, not the full file content) with the golden directory. + + - codeactionerr(start, end, kind, wantError): specifies a codeaction that + fails with an error that matches the expectation. + + - codelens(location, title): specifies that a codelens is expected at the + given location, with given title. Must be used in conjunction with + @codelenses. + + - codelenses(): specifies that textDocument/codeLens should be run for the + current document, with results compared to the @codelens annotations in + the current document. + + - complete(location, ...items): specifies expected completion results at + the given location. Must be used in conjunction with @item. + + - diag(location, regexp): specifies an expected diagnostic matching the + given regexp at the given location. The test runner requires + a 1:1 correspondence between observed diagnostics and diag annotations. + The diagnostics source and kind fields are ignored, to reduce fuss. + + The specified location must match the start position of the diagnostic, + but end positions are ignored. + + TODO(adonovan): in the older marker framework, the annotation asserted + two additional fields (source="compiler", kind="error"). Restore them? + + - def(src, dst location): performs a textDocument/definition request at + the src location, and check the result points to the dst location. + + - documentLink(golden): asserts that textDocument/documentLink returns + links as described by the golden file. + + - foldingrange(golden): performs a textDocument/foldingRange for the + current document, and compare with the golden content, which is the + original source annotated with numbered tags delimiting the resulting + ranges (e.g. <1 kind="..."> ... </1>). + + - format(golden): performs a textDocument/format request for the enclosing + file, and compare against the named golden file. If the formatting + request succeeds, the golden file must contain the resulting formatted + source. If the formatting request fails, the golden file must contain + the error message. + + - highlightall(all ...documentHighlight): makes a textDocument/highlight + request at each location in "all" and checks that the result is "all". + In other words, given highlightall(X1, X2, ..., Xn), it checks that + highlight(X1) = highlight(X2) = ... = highlight(Xn) = {X1, X2, ..., Xn}. + In general, highlight sets are not equivalence classes; for asymmetric + cases, use @highlight instead. + Each element of "all" is the label of a @hiloc marker. + + - highlight(src location, dsts ...documentHighlight): makes a + textDocument/highlight request at the given src location, which should + highlight the provided dst locations and kinds. + + - hiloc(label, location, kind): defines a documentHighlight value of the + given location and kind. Use its label in a @highlightall marker to + indicate the expected result of a highlight query. + + - hover(src, dst location, sm stringMatcher): performs a textDocument/hover + at the src location, and checks that the result is the dst location, with + matching hover content. + + - hovererr(src, sm stringMatcher): performs a textDocument/hover at the src + location, and checks that the error matches the given stringMatcher. + + - implementations(src location, want ...location): makes a + textDocument/implementation query at the src location and + checks that the resulting set of locations matches want. + + - incomingcalls(src location, want ...location): makes a + callHierarchy/incomingCalls query at the src location, and checks that + the set of call.From locations matches want. + (These locations are the declarations of the functions enclosing + the calls, not the calls themselves.) + + - item(label, details, kind): defines a completionItem with the provided + fields. This information is not positional, and therefore @item markers + may occur anywhere in the source. Used in conjunction with @complete, + @snippet, or @rank. + + TODO(rfindley): rethink whether floating @item annotations are the best + way to specify completion results. + + - loc(name, location): specifies the name for a location in the source. These + locations may be referenced by other markers. + + - outgoingcalls(src location, want ...location): makes a + callHierarchy/outgoingCalls query at the src location, and checks that + the set of call.To locations matches want. + + - preparerename(src, spn, placeholder): asserts that a textDocument/prepareRename + request at the src location expands to the spn location, with given + placeholder. If placeholder is "", this is treated as a negative + assertion and prepareRename should return nil. + + - rename(location, new, golden): specifies a renaming of the + identifier at the specified location to the new name. + The golden directory contains the transformed files. + + - renameerr(location, new, wantError): specifies a renaming that + fails with an error that matches the expectation. + + - signature(location, label, active): specifies that + signatureHelp at the given location should match the provided string, with + the active parameter (an index) highlighted. + + - suggestedfix(location, regexp, golden): like diag, the location and + regexp identify an expected diagnostic. This diagnostic must + to have exactly one associated code action of the specified kind. + This action is executed for its editing effects on the source files. + Like rename, the golden directory contains the expected transformed files. + + - suggestedfixerr(location, regexp, kind, wantError): specifies that the + suggestedfix operation should fail with an error that matches the expectation. + (Failures in the computation to offer a fix do not generally result + in LSP errors, so this marker is not appropriate for testing them.) + + - rank(location, ...string OR completionItem): executes a + textDocument/completion request at the given location, and verifies that + each expected completion item occurs in the results, in the expected order. + Items may be specified as string literal completion labels, or as + references to a completion item created with the @item marker. + Other unexpected completion items are allowed to occur in the results, and + are ignored. A "!" prefix on a label asserts that the symbol is not a + completion candidate. + + - refs(location, want ...location): executes a textDocument/references + request at the first location and asserts that the result is the set of + 'want' locations. The first want location must be the declaration + (assumedly unique). + + - snippet(location, string OR completionItem, snippet): executes a + textDocument/completion request at the location, and searches for a result + with label matching that its second argument, which may be a string literal + or a reference to a completion item created by the @item marker (in which + case the item's label is used). It checks that the resulting snippet + matches the provided snippet. + + - symbol(golden): makes a textDocument/documentSymbol request + for the enclosing file, formats the response with one symbol + per line, sorts it, and compares against the named golden file. + Each line is of the form: + + dotted.symbol.name kind "detail" +n lines + + where the "+n lines" part indicates that the declaration spans + several lines. The test otherwise makes no attempt to check + location information. There is no point to using more than one + @symbol marker in a given file. + + - token(location, tokenType, mod): makes a textDocument/semanticTokens/range + request at the given location, and asserts that the result includes + exactly one token with the given token type and modifier string. + + - workspacesymbol(query, golden): makes a workspace/symbol request for the + given query, formats the response with one symbol per line, and compares + against the named golden file. As workspace symbols are by definition a + workspace-wide request, the location of the workspace symbol marker does + not matter. Each line is of the form: + + location name kind + +# Argument conversion + +Marker arguments are first parsed by the go/expect package, which accepts +the following tokens as defined by the Go spec: + - string, int64, float64, and rune literals + - true and false + - nil + - identifiers (type expect.Identifier) + - regular expressions, denoted the two tokens re"abc" (type *regexp.Regexp) + +These values are passed as arguments to the corresponding parameter of the +test function. Additional value conversions may occur for these argument -> +parameter type pairs: + - string->regexp: the argument is parsed as a regular expressions. + - string->location: the argument is converted to the location of the first + instance of the argument in the file content starting from the beginning of + the line containing the note. Multi-line matches are permitted, but the + match must begin before the note. + - regexp->location: the argument is converted to the location of the first + match for the argument in the file content starting from the beginning of + the line containing the note. Multi-line matches are permitted, but the + match must begin before the note. If the regular expression contains + exactly one subgroup, the position of the subgroup is used rather than the + position of the submatch. + - name->location: the argument is replaced by the named location. + - name->Golden: the argument is used to look up golden content prefixed by + @<argument>. + - {string,regexp,identifier}->stringMatcher: a stringMatcher type + specifies an expected string, either in the form of a substring + that must be present, a regular expression that it must match, or an + identifier (e.g. foo) such that the archive entry @foo exists and + contains the exact expected string. + stringMatchers are used by some markers to match positive results + (outputs) and by other markers to match error messages. + +# Example + +Here is a complete example: + + This test checks hovering over constants. + + -- a.go -- + package a + + const abc = 0x2a //@hover("b", "abc", abc),hover(" =", "abc", abc) + + -- @abc -- + ```go + const abc untyped int = 42 + ``` + + @hover("b", "abc", abc),hover(" =", "abc", abc) + +In this example, the @hover annotation tells the test runner to run the +hoverMarker function, which has parameters: + + (mark marker, src, dsc protocol.Location, g *Golden). + +The first argument holds the test context, including fake editor with open +files, and sandboxed directory. + +Argument converters translate the "b" and "abc" arguments into locations by +interpreting each one as a substring (or as a regular expression, if of the +form re"a|b") and finding the location of its first occurrence starting on the +preceding portion of the line, and the abc identifier into a the golden content +contained in the file @abc. Then the hoverMarker method executes a +textDocument/hover LSP request at the src position, and ensures the result +spans "abc", with the markdown content from @abc. (Note that the markdown +content includes the expect annotation as the doc comment.) + +The next hover on the same line asserts the same result, but initiates the +hover immediately after "abc" in the source. This tests that we find the +preceding identifier when hovering. + +# Updating golden files + +To update golden content in the test archive, it is easier to regenerate +content automatically rather than edit it by hand. To do this, run the +tests with the -update flag. Only tests that actually run will be updated. + +In some cases, golden content will vary by Go version (for example, gopls +produces different markdown at Go versions before the 1.19 go/doc update). +By convention, the golden content in test archives should match the output +at Go tip. Each test function can normalize golden content for older Go +versions. + +Note that -update does not cause missing @diag or @loc markers to be added. + +# TODO + + - Rename the files .txtar. + - Provide some means by which locations in the standard library + (or builtin.go) can be named, so that, for example, we can we + can assert that MyError implements the built-in error type. + - If possible, improve handling for optional arguments. Rather than have + multiple variations of a marker, it would be nice to support a more + flexible signature: can codeaction, codeactionedit, codeactionerr, and + suggestedfix be consolidated? +*/ +package marker diff --git a/contribs/gnopls/internal/test/marker/marker_test.go b/contribs/gnopls/internal/test/marker/marker_test.go new file mode 100644 index 00000000000..1896d8fb19f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/marker_test.go @@ -0,0 +1,2497 @@ +// 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 marker + +// This file defines the marker test framework. +// See doc.go for extensive documentation. + +import ( + "bytes" + "context" + "encoding/json" + "flag" + "fmt" + "go/token" + "go/types" + "io/fs" + "log" + "os" + "path" + "path/filepath" + "reflect" + "regexp" + "runtime" + "slices" + "sort" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "golang.org/x/tools/go/expect" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/debug" + "golang.org/x/tools/gopls/internal/lsprpc" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/test/compare" + "golang.org/x/tools/gopls/internal/test/integration" + "golang.org/x/tools/gopls/internal/test/integration/fake" + "golang.org/x/tools/gopls/internal/util/bug" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/diff/myers" + "golang.org/x/tools/internal/jsonrpc2" + "golang.org/x/tools/internal/jsonrpc2/servertest" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/txtar" +) + +var update = flag.Bool("update", false, "if set, update test data during marker tests") + +func TestMain(m *testing.M) { + bug.PanicOnBugs = true + testenv.ExitIfSmallMachine() + // Disable GOPACKAGESDRIVER, as it can cause spurious test failures. + os.Setenv("GOPACKAGESDRIVER", "off") + os.Exit(m.Run()) +} + +// Test runs the marker tests from the testdata directory. +// +// See package documentation for details on how marker tests work. +// +// These tests were inspired by (and in many places copied from) a previous +// iteration of the marker tests built on top of the packagestest framework. +// Key design decisions motivating this reimplementation are as follows: +// - The old tests had a single global session, causing interaction at a +// distance and several awkward workarounds. +// - The old tests could not be safely parallelized, because certain tests +// manipulated the server options +// - Relatedly, the old tests did not have a logic grouping of assertions into +// a single unit, resulting in clusters of files serving clusters of +// entangled assertions. +// - The old tests used locations in the source as test names and as the +// identity of golden content, meaning that a single edit could change the +// name of an arbitrary number of subtests, and making it difficult to +// manually edit golden content. +// - The old tests did not hew closely to LSP concepts, resulting in, for +// example, each marker implementation doing its own position +// transformations, and inventing its own mechanism for configuration. +// - The old tests had an ad-hoc session initialization process. The integration +// test environment has had more time devoted to its initialization, and has a +// more convenient API. +// - The old tests lacked documentation, and often had failures that were hard +// to understand. By starting from scratch, we can revisit these aspects. +func Test(t *testing.T) { + if testing.Short() { + builder := os.Getenv("GO_BUILDER_NAME") + // Note that HasPrefix(builder, "darwin-" only matches legacy builders. + // LUCI builder names start with x_tools-goN.NN. + // We want to exclude solaris on both legacy and LUCI builders, as + // it is timing out. + if strings.HasPrefix(builder, "darwin-") || strings.Contains(builder, "solaris") { + t.Skip("golang/go#64473: skipping with -short: this test is too slow on darwin and solaris builders") + } + } + // The marker tests must be able to run go/packages.Load. + testenv.NeedsGoPackages(t) + + const dir = "testdata" + tests, err := loadMarkerTests(dir) + if err != nil { + t.Fatal(err) + } + + // Opt: use a shared cache. + cache := cache.New(nil) + + // Opt: seed the cache and file cache by type-checking and analyzing common + // standard library packages. + seedCache(t, cache) + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + if test.skipReason != "" { + t.Skip(test.skipReason) + } + if slices.Contains(test.skipGOOS, runtime.GOOS) { + t.Skipf("skipping on %s due to -skip_goos", runtime.GOOS) + } + if slices.Contains(test.skipGOARCH, runtime.GOARCH) { + t.Skipf("skipping on %s due to -skip_goarch", runtime.GOARCH) + } + + // TODO(rfindley): it may be more useful to have full support for build + // constraints. + if test.minGoVersion != "" { + var go1point int + if _, err := fmt.Sscanf(test.minGoVersion, "go1.%d", &go1point); err != nil { + t.Fatalf("parsing -min_go version: %v", err) + } + testenv.NeedsGo1Point(t, go1point) + } + if test.maxGoVersion != "" { + var go1point int + if _, err := fmt.Sscanf(test.maxGoVersion, "go1.%d", &go1point); err != nil { + t.Fatalf("parsing -max_go version: %v", err) + } + testenv.SkipAfterGo1Point(t, go1point) + } + if test.cgo { + testenv.NeedsTool(t, "cgo") + } + config := fake.EditorConfig{ + Settings: test.settings, + CapabilitiesJSON: test.capabilities, + Env: test.env, + } + if _, ok := config.Settings["diagnosticsDelay"]; !ok { + if config.Settings == nil { + config.Settings = make(map[string]any) + } + config.Settings["diagnosticsDelay"] = "10ms" + } + // inv: config.Settings != nil + + run := &markerTestRun{ + test: test, + env: newEnv(t, cache, test.files, test.proxyFiles, test.writeGoSum, config), + settings: config.Settings, + values: make(map[expect.Identifier]any), + diags: make(map[protocol.Location][]protocol.Diagnostic), + extraNotes: make(map[protocol.DocumentURI]map[string][]*expect.Note), + } + // TODO(rfindley): make it easier to clean up the integration test environment. + defer run.env.Editor.Shutdown(context.Background()) // ignore error + defer run.env.Sandbox.Close() // ignore error + + // Open all files so that we operate consistently with LSP clients, and + // (pragmatically) so that we have a Mapper available via the fake + // editor. + // + // This also allows avoiding mutating the editor state in tests. + for file := range test.files { + run.env.OpenFile(file) + } + + // Wait for the didOpen notifications to be processed, then collect + // diagnostics. + var diags map[string]*protocol.PublishDiagnosticsParams + run.env.AfterChange(integration.ReadAllDiagnostics(&diags)) + for path, params := range diags { + uri := run.env.Sandbox.Workdir.URI(path) + for _, diag := range params.Diagnostics { + loc := protocol.Location{ + URI: uri, + Range: protocol.Range{ + Start: diag.Range.Start, + End: diag.Range.Start, // ignore end positions + }, + } + run.diags[loc] = append(run.diags[loc], diag) + } + } + + var markers []marker + for _, note := range test.notes { + mark := marker{run: run, note: note} + if fn, ok := valueMarkerFuncs[note.Name]; ok { + fn(mark) + } else if _, ok := actionMarkerFuncs[note.Name]; ok { + markers = append(markers, mark) // save for later + } else { + uri := mark.uri() + if run.extraNotes[uri] == nil { + run.extraNotes[uri] = make(map[string][]*expect.Note) + } + run.extraNotes[uri][note.Name] = append(run.extraNotes[uri][note.Name], note) + } + } + + // Invoke each remaining marker in the test. + for _, mark := range markers { + actionMarkerFuncs[mark.note.Name](mark) + } + + // Any remaining (un-eliminated) diagnostics are an error. + if !test.ignoreExtraDiags { + for loc, diags := range run.diags { + for _, diag := range diags { + t.Errorf("%s: unexpected diagnostic: %q", run.fmtLoc(loc), diag.Message) + } + } + } + + // TODO(rfindley): use these for whole-file marker tests. + for uri, extras := range run.extraNotes { + for name, extra := range extras { + if len(extra) > 0 { + t.Errorf("%s: %d unused %q markers", run.env.Sandbox.Workdir.URIToPath(uri), len(extra), name) + } + } + } + + // Now that all markers have executed, check whether there where any + // unexpected error logs. + // This guards against noisiness: see golang/go#66746) + if !test.errorsOK { + run.env.AfterChange(integration.NoErrorLogs()) + } + + formatted, err := formatTest(test) + if err != nil { + t.Errorf("formatTest: %v", err) + } else if *update { + filename := filepath.Join(dir, test.name) + if err := os.WriteFile(filename, formatted, 0644); err != nil { + t.Error(err) + } + } else if !t.Failed() { + // Verify that the testdata has not changed. + // + // Only check this if the test hasn't already failed, otherwise we'd + // report duplicate mismatches of golden data. + // Otherwise, verify that formatted content matches. + if diff := compare.NamedText("formatted", "on-disk", string(formatted), string(test.content)); diff != "" { + t.Errorf("formatted test does not match on-disk content:\n%s", diff) + } + } + }) + } + + if abs, err := filepath.Abs(dir); err == nil && t.Failed() { + t.Logf("(Filenames are relative to %s.)", abs) + } +} + +// seedCache populates the file cache by type checking and analyzing standard +// library packages that are reachable from tests. +// +// Most tests are themselves small codebases, and yet may reference large +// amounts of standard library code. Since tests are heavily parallelized, they +// naively end up type checking and analyzing many of the same standard library +// packages. By seeding the cache, we ensure cache hits for these standard +// library packages, significantly reducing the amount of work done by each +// test. +// +// The following command was used to determine the set of packages to import +// below: +// +// rm -rf ~/.cache/gopls && \ +// go test -count=1 ./internal/test/marker -cpuprofile=prof -v +// +// Look through the individual test timings to see which tests are slow, then +// look through the imports of slow tests to see which standard library +// packages are imported. Choose high level packages such as go/types that +// import others such as fmt or go/ast. After doing so, re-run the command and +// verify that the total samples in the collected profile decreased. +func seedCache(t *testing.T, cache *cache.Cache) { + start := time.Now() + + // The the doc string for details on how this seed was produced. + seed := `package p +import ( + _ "net/http" + _ "sort" + _ "go/types" + _ "testing" +) +` + + // Create a test environment for the seed file. + env := newEnv(t, cache, map[string][]byte{"p.go": []byte(seed)}, nil, nil, fake.EditorConfig{}) + // See other TODO: this cleanup logic is too messy. + defer env.Editor.Shutdown(context.Background()) // ignore error + defer env.Sandbox.Close() // ignore error + env.Awaiter.Await(context.Background(), integration.InitialWorkspaceLoad) + + // Opening the file is necessary to trigger analysis. + env.OpenFile("p.go") + + // As a checksum, verify that the file has no errors after analysis. + // This isn't strictly necessary, but helps avoid incorrect seeding due to + // typos. + env.AfterChange(integration.NoDiagnostics()) + + t.Logf("warming the cache took %s", time.Since(start)) +} + +// A marker holds state for the execution of a single @marker +// annotation in the source. +type marker struct { + run *markerTestRun + note *expect.Note +} + +// ctx returns the mark context. +func (m marker) ctx() context.Context { return m.run.env.Ctx } + +// T returns the testing.TB for this mark. +func (m marker) T() testing.TB { return m.run.env.T } + +// server returns the LSP server for the marker test run. +func (m marker) editor() *fake.Editor { return m.run.env.Editor } + +// server returns the LSP server for the marker test run. +func (m marker) server() protocol.Server { return m.run.env.Editor.Server } + +// uri returns the URI of the file containing the marker. +func (mark marker) uri() protocol.DocumentURI { + return mark.run.env.Sandbox.Workdir.URI(mark.run.test.fset.File(mark.note.Pos).Name()) +} + +// document returns a protocol.TextDocumentIdentifier for the current file. +func (mark marker) document() protocol.TextDocumentIdentifier { + return protocol.TextDocumentIdentifier{URI: mark.uri()} +} + +// path returns the relative path to the file containing the marker. +func (mark marker) path() string { + return mark.run.env.Sandbox.Workdir.RelPath(mark.run.test.fset.File(mark.note.Pos).Name()) +} + +// mapper returns a *protocol.Mapper for the current file. +func (mark marker) mapper() *protocol.Mapper { + mapper, err := mark.editor().Mapper(mark.path()) + if err != nil { + mark.T().Fatalf("failed to get mapper for current mark: %v", err) + } + return mapper +} + +// errorf reports an error with a prefix indicating the position of the marker note. +// +// It formats the error message using mark.sprintf. +func (mark marker) errorf(format string, args ...any) { + mark.T().Helper() + msg := mark.sprintf(format, args...) + // TODO(adonovan): consider using fmt.Fprintf(os.Stderr)+t.Fail instead of + // t.Errorf to avoid reporting uninteresting positions in the Go source of + // the driver. However, this loses the order of stderr wrt "FAIL: TestFoo" + // subtest dividers. + mark.T().Errorf("%s: %s", mark.run.fmtPos(mark.note.Pos), msg) +} + +// valueMarkerFunc returns a wrapper around a function that allows it to be +// called during the processing of value markers (e.g. @value(v, 123)) with marker +// arguments converted to function parameters. The provided function's first +// parameter must be of type 'marker', and it must return a value. +// +// Unlike action markers, which are executed for actions such as test +// assertions, value markers are all evaluated first, and each computes +// a value that is recorded by its identifier, which is the marker's first +// argument. These values may be referred to from an action marker by +// this identifier, e.g. @action(... , v, ...). +// +// For example, given a fn with signature +// +// func(mark marker, label, details, kind string) CompletionItem +// +// The result of valueMarkerFunc can associated with @item notes, and invoked +// as follows: +// +// //@item(FooCompletion, "Foo", "func() int", "func") +// +// The provided fn should not mutate the test environment. +func valueMarkerFunc(fn any) func(marker) { + ftype := reflect.TypeOf(fn) + if ftype.NumIn() == 0 || ftype.In(0) != markerType { + panic(fmt.Sprintf("value marker function %#v must accept marker as its first argument", ftype)) + } + if ftype.NumOut() != 1 { + panic(fmt.Sprintf("value marker function %#v must have exactly 1 result", ftype)) + } + + return func(mark marker) { + if len(mark.note.Args) == 0 || !is[expect.Identifier](mark.note.Args[0]) { + mark.errorf("first argument to a value marker function must be an identifier") + return + } + id := mark.note.Args[0].(expect.Identifier) + if alt, ok := mark.run.values[id]; ok { + mark.errorf("%s already declared as %T", id, alt) + return + } + args := append([]any{mark}, mark.note.Args[1:]...) + argValues, err := convertArgs(mark, ftype, args) + if err != nil { + mark.errorf("converting args: %v", err) + return + } + results := reflect.ValueOf(fn).Call(argValues) + mark.run.values[id] = results[0].Interface() + } +} + +// actionMarkerFunc returns a wrapper around a function that allows it to be +// called during the processing of action markers (e.g. @action("abc", 123)) +// with marker arguments converted to function parameters. The provided +// function's first parameter must be of type 'marker', and it must not return +// any values. +// +// The provided fn should not mutate the test environment. +func actionMarkerFunc(fn any) func(marker) { + ftype := reflect.TypeOf(fn) + if ftype.NumIn() == 0 || ftype.In(0) != markerType { + panic(fmt.Sprintf("action marker function %#v must accept marker as its first argument", ftype)) + } + if ftype.NumOut() != 0 { + panic(fmt.Sprintf("action marker function %#v cannot have results", ftype)) + } + + return func(mark marker) { + args := append([]any{mark}, mark.note.Args...) + argValues, err := convertArgs(mark, ftype, args) + if err != nil { + mark.errorf("converting args: %v", err) + return + } + reflect.ValueOf(fn).Call(argValues) + } +} + +func convertArgs(mark marker, ftype reflect.Type, args []any) ([]reflect.Value, error) { + var ( + argValues []reflect.Value + pnext int // next param index + p reflect.Type // current param + ) + for i, arg := range args { + if i < ftype.NumIn() { + p = ftype.In(pnext) + pnext++ + } else if p == nil || !ftype.IsVariadic() { + // The actual number of arguments expected by the mark varies, depending + // on whether this is a value marker or an action marker. + // + // Since this error indicates a bug, probably OK to have an imprecise + // error message here. + return nil, fmt.Errorf("too many arguments to %s", mark.note.Name) + } + elemType := p + if ftype.IsVariadic() && pnext == ftype.NumIn() { + elemType = p.Elem() + } + var v reflect.Value + if id, ok := arg.(expect.Identifier); ok && id == "_" { + v = reflect.Zero(elemType) + } else { + a, err := convert(mark, arg, elemType) + if err != nil { + return nil, err + } + v = reflect.ValueOf(a) + } + argValues = append(argValues, v) + } + // Check that we have sufficient arguments. If the function is variadic, we + // do not need arguments for the final parameter. + if pnext < ftype.NumIn()-1 || pnext == ftype.NumIn()-1 && !ftype.IsVariadic() { + // Same comment as above: OK to be vague here. + return nil, fmt.Errorf("not enough arguments to %s", mark.note.Name) + } + return argValues, nil +} + +// is reports whether arg is a T. +func is[T any](arg any) bool { + _, ok := arg.(T) + return ok +} + +// Supported value marker functions. See [valueMarkerFunc] for more details. +var valueMarkerFuncs = map[string]func(marker){ + "loc": valueMarkerFunc(locMarker), + "item": valueMarkerFunc(completionItemMarker), + "hiloc": valueMarkerFunc(highlightLocationMarker), +} + +// Supported action marker functions. See [actionMarkerFunc] for more details. +var actionMarkerFuncs = map[string]func(marker){ + "acceptcompletion": actionMarkerFunc(acceptCompletionMarker), + "codeaction": actionMarkerFunc(codeActionMarker), + "codeactionedit": actionMarkerFunc(codeActionEditMarker), + "codeactionerr": actionMarkerFunc(codeActionErrMarker), + "codelenses": actionMarkerFunc(codeLensesMarker), + "complete": actionMarkerFunc(completeMarker), + "def": actionMarkerFunc(defMarker), + "diag": actionMarkerFunc(diagMarker), + "documentlink": actionMarkerFunc(documentLinkMarker), + "foldingrange": actionMarkerFunc(foldingRangeMarker), + "format": actionMarkerFunc(formatMarker), + "highlight": actionMarkerFunc(highlightMarker), + "highlightall": actionMarkerFunc(highlightAllMarker), + "hover": actionMarkerFunc(hoverMarker), + "hovererr": actionMarkerFunc(hoverErrMarker), + "implementation": actionMarkerFunc(implementationMarker), + "incomingcalls": actionMarkerFunc(incomingCallsMarker), + "inlayhints": actionMarkerFunc(inlayhintsMarker), + "outgoingcalls": actionMarkerFunc(outgoingCallsMarker), + "preparerename": actionMarkerFunc(prepareRenameMarker), + "rank": actionMarkerFunc(rankMarker), + "refs": actionMarkerFunc(refsMarker), + "rename": actionMarkerFunc(renameMarker), + "renameerr": actionMarkerFunc(renameErrMarker), + "selectionrange": actionMarkerFunc(selectionRangeMarker), + "signature": actionMarkerFunc(signatureMarker), + "snippet": actionMarkerFunc(snippetMarker), + "suggestedfix": actionMarkerFunc(suggestedfixMarker), + "suggestedfixerr": actionMarkerFunc(suggestedfixErrMarker), + "symbol": actionMarkerFunc(symbolMarker), + "token": actionMarkerFunc(tokenMarker), + "typedef": actionMarkerFunc(typedefMarker), + "workspacesymbol": actionMarkerFunc(workspaceSymbolMarker), +} + +// markerTest holds all the test data extracted from a test txtar archive. +// +// See the documentation for RunMarkerTests for more information on the archive +// format. +type markerTest struct { + name string // relative path to the txtar file in the testdata dir + fset *token.FileSet // fileset used for parsing notes + content []byte // raw test content + archive *txtar.Archive // original test archive + settings map[string]any // gopls settings + capabilities []byte // content of capabilities.json file + env map[string]string // editor environment + proxyFiles map[string][]byte // proxy content + files map[string][]byte // data files from the archive (excluding special files) + notes []*expect.Note // extracted notes from data files + golden map[expect.Identifier]*Golden // extracted golden content, by identifier name + + skipReason string // the skip reason extracted from the "skip" archive file + flags []string // flags extracted from the special "flags" archive file. + + // Parsed flags values. See the flag definitions below for documentation. + minGoVersion string + maxGoVersion string + cgo bool + writeGoSum []string + skipGOOS []string + skipGOARCH []string + ignoreExtraDiags bool + filterBuiltins bool + filterKeywords bool + errorsOK bool +} + +// flagSet returns the flagset used for parsing the special "flags" file in the +// test archive. +func (t *markerTest) flagSet() *flag.FlagSet { + flags := flag.NewFlagSet(t.name, flag.ContinueOnError) + flags.StringVar(&t.minGoVersion, "min_go", "", "if set, the minimum go1.X version required for this test") + flags.StringVar(&t.maxGoVersion, "max_go", "", "if set, the maximum go1.X version required for this test") + flags.BoolVar(&t.cgo, "cgo", false, "if set, requires cgo (both the cgo tool and CGO_ENABLED=1)") + flags.Var((*stringListValue)(&t.writeGoSum), "write_sumfile", "if set, write the sumfile for these directories") + flags.Var((*stringListValue)(&t.skipGOOS), "skip_goos", "if set, skip this test on these GOOS values") + flags.Var((*stringListValue)(&t.skipGOARCH), "skip_goarch", "if set, skip this test on these GOARCH values") + flags.BoolVar(&t.ignoreExtraDiags, "ignore_extra_diags", false, "if set, suppress errors for unmatched diagnostics") + flags.BoolVar(&t.filterBuiltins, "filter_builtins", true, "if set, filter builtins from completion results") + flags.BoolVar(&t.filterKeywords, "filter_keywords", true, "if set, filter keywords from completion results") + flags.BoolVar(&t.errorsOK, "errors_ok", false, "if set, Error level log messages are acceptable in this test") + return flags +} + +// stringListValue implements flag.Value. +type stringListValue []string + +func (l *stringListValue) Set(s string) error { + if s != "" { + for _, d := range strings.Split(s, ",") { + *l = append(*l, strings.TrimSpace(d)) + } + } + return nil +} + +func (l stringListValue) String() string { + return strings.Join([]string(l), ",") +} + +func (t *markerTest) getGolden(id expect.Identifier) *Golden { + golden, ok := t.golden[id] + // If there was no golden content for this identifier, we must create one + // to handle the case where -update is set: we need a place to store + // the updated content. + if !ok { + golden = &Golden{id: id} + + // TODO(adonovan): the separation of markerTest (the + // static aspects) from markerTestRun (the dynamic + // ones) is evidently bogus because here we modify + // markerTest during execution. Let's merge the two. + t.golden[id] = golden + } + return golden +} + +// Golden holds extracted golden content for a single @<name> prefix. +// +// When -update is set, golden captures the updated golden contents for later +// writing. +type Golden struct { + id expect.Identifier + data map[string][]byte // key "" => @id itself + updated map[string][]byte +} + +// Get returns golden content for the given name, which corresponds to the +// relative path following the golden prefix @<name>/. For example, to access +// the content of @foo/path/to/result.json from the Golden associated with +// @foo, name should be "path/to/result.json". +// +// If -update is set, the given update function will be called to get the +// updated golden content that should be written back to testdata. +// +// Marker functions must use this method instead of accessing data entries +// directly otherwise the -update operation will delete those entries. +// +// TODO(rfindley): rethink the logic here. We may want to separate Get and Set, +// and not delete golden content that isn't set. +func (g *Golden) Get(t testing.TB, name string, updated []byte) ([]byte, bool) { + if existing, ok := g.updated[name]; ok { + // Multiple tests may reference the same golden data, but if they do they + // must agree about its expected content. + if diff := compare.NamedText("existing", "updated", string(existing), string(updated)); diff != "" { + t.Errorf("conflicting updates for golden data %s/%s:\n%s", g.id, name, diff) + } + } + if g.updated == nil { + g.updated = make(map[string][]byte) + } + g.updated[name] = updated + if *update { + return updated, true + } + + res, ok := g.data[name] + return res, ok +} + +// loadMarkerTests walks the given dir looking for .txt files, which it +// interprets as a txtar archive. +// +// See the documentation for RunMarkerTests for more details on the test data +// archive. +func loadMarkerTests(dir string) ([]*markerTest, error) { + var tests []*markerTest + err := filepath.WalkDir(dir, func(path string, _ fs.DirEntry, err error) error { + if strings.HasSuffix(path, ".txt") { + content, err := os.ReadFile(path) + if err != nil { + return err + } + + name := strings.TrimPrefix(path, dir+string(filepath.Separator)) + test, err := loadMarkerTest(name, content) + if err != nil { + return fmt.Errorf("%s: %v", path, err) + } + tests = append(tests, test) + } + return err + }) + return tests, err +} + +func loadMarkerTest(name string, content []byte) (*markerTest, error) { + archive := txtar.Parse(content) + if len(archive.Files) == 0 { + return nil, fmt.Errorf("txtar file has no '-- filename --' sections") + } + if bytes.Contains(archive.Comment, []byte("\n-- ")) { + // This check is conservative, but the comment is only a comment. + return nil, fmt.Errorf("ill-formed '-- filename --' header in comment") + } + test := &markerTest{ + name: name, + fset: token.NewFileSet(), + content: content, + archive: archive, + files: make(map[string][]byte), + golden: make(map[expect.Identifier]*Golden), + } + for _, file := range archive.Files { + switch { + case file.Name == "skip": + reason := strings.ReplaceAll(string(file.Data), "\n", " ") + reason = strings.TrimSpace(reason) + test.skipReason = reason + + case file.Name == "flags": + test.flags = strings.Fields(string(file.Data)) + + case file.Name == "settings.json": + if err := json.Unmarshal(file.Data, &test.settings); err != nil { + return nil, err + } + + case file.Name == "capabilities.json": + test.capabilities = file.Data // lazily unmarshalled by the editor + + case file.Name == "env": + test.env = make(map[string]string) + fields := strings.Fields(string(file.Data)) + for _, field := range fields { + key, value, ok := strings.Cut(field, "=") + if !ok { + return nil, fmt.Errorf("env vars must be formatted as var=value, got %q", field) + } + test.env[key] = value + } + + case strings.HasPrefix(file.Name, "@"): // golden content + idstring, name, _ := strings.Cut(file.Name[len("@"):], "/") + id := expect.Identifier(idstring) + // Note that a file.Name of just "@id" gives (id, name) = ("id", ""). + if _, ok := test.golden[id]; !ok { + test.golden[id] = &Golden{ + id: id, + data: make(map[string][]byte), + } + } + test.golden[id].data[name] = file.Data + + case strings.HasPrefix(file.Name, "proxy/"): + name := file.Name[len("proxy/"):] + if test.proxyFiles == nil { + test.proxyFiles = make(map[string][]byte) + } + test.proxyFiles[name] = file.Data + + default: // ordinary file content + notes, err := expect.Parse(test.fset, file.Name, file.Data) + if err != nil { + return nil, fmt.Errorf("parsing notes in %q: %v", file.Name, err) + } + + // Reject common misspelling: "// @mark". + // TODO(adonovan): permit "// @" within a string. Detect multiple spaces. + if i := bytes.Index(file.Data, []byte("// @")); i >= 0 { + line := 1 + bytes.Count(file.Data[:i], []byte("\n")) + return nil, fmt.Errorf("%s:%d: unwanted space before marker (// @)", file.Name, line) + } + + // The 'go list' command doesn't work correct with modules named + // testdata", so don't allow it as a module name (golang/go#65406). + // (Otherwise files within it will end up in an ad hoc + // package, "command-line-arguments/$TMPDIR/...".) + if filepath.Base(file.Name) == "go.mod" && + bytes.Contains(file.Data, []byte("module testdata")) { + return nil, fmt.Errorf("'testdata' is not a valid module name") + } + + test.notes = append(test.notes, notes...) + test.files[file.Name] = file.Data + } + + // Print a warning if we see what looks like "-- filename --" + // without the second "--". It's not necessarily wrong, + // but it should almost never appear in our test inputs. + if bytes.Contains(file.Data, []byte("\n-- ")) { + log.Printf("ill-formed '-- filename --' header in %s?", file.Name) + } + } + + // Parse flags after loading files, as they may have been set by the "flags" + // file. + if err := test.flagSet().Parse(test.flags); err != nil { + return nil, fmt.Errorf("parsing flags: %v", err) + } + + return test, nil +} + +// formatTest formats the test as a txtar archive. +func formatTest(test *markerTest) ([]byte, error) { + arch := &txtar.Archive{ + Comment: test.archive.Comment, + } + + updatedGolden := make(map[string][]byte) + for id, g := range test.golden { + for name, data := range g.updated { + filename := "@" + path.Join(string(id), name) // name may be "" + updatedGolden[filename] = data + } + } + + // Preserve the original ordering of archive files. + for _, file := range test.archive.Files { + switch file.Name { + // Preserve configuration files exactly as they were. They must have parsed + // if we got this far. + case "skip", "flags", "settings.json", "capabilities.json", "env": + arch.Files = append(arch.Files, file) + default: + if _, ok := test.files[file.Name]; ok { // ordinary file + arch.Files = append(arch.Files, file) + } else if strings.HasPrefix(file.Name, "proxy/") { // proxy file + arch.Files = append(arch.Files, file) + } else if data, ok := updatedGolden[file.Name]; ok { // golden file + arch.Files = append(arch.Files, txtar.File{Name: file.Name, Data: data}) + delete(updatedGolden, file.Name) + } + } + } + + // ...followed by any new golden files. + var newGoldenFiles []txtar.File + for filename, data := range updatedGolden { + // TODO(rfindley): it looks like this implicitly removes trailing newlines + // from golden content. Is there any way to fix that? Perhaps we should + // just make the diff tolerant of missing newlines? + newGoldenFiles = append(newGoldenFiles, txtar.File{Name: filename, Data: data}) + } + // Sort new golden files lexically. + sort.Slice(newGoldenFiles, func(i, j int) bool { + return newGoldenFiles[i].Name < newGoldenFiles[j].Name + }) + arch.Files = append(arch.Files, newGoldenFiles...) + + return txtar.Format(arch), nil +} + +// newEnv creates a new environment for a marker test. +// +// TODO(rfindley): simplify and refactor the construction of testing +// environments across integration tests, marker tests, and benchmarks. +func newEnv(t *testing.T, cache *cache.Cache, files, proxyFiles map[string][]byte, writeGoSum []string, config fake.EditorConfig) *integration.Env { + sandbox, err := fake.NewSandbox(&fake.SandboxConfig{ + RootDir: t.TempDir(), + Files: files, + ProxyFiles: proxyFiles, + }) + if err != nil { + t.Fatal(err) + } + + for _, dir := range writeGoSum { + if err := sandbox.RunGoCommand(context.Background(), dir, "list", []string{"-mod=mod", "..."}, []string{"GOWORK=off"}, true); err != nil { + t.Fatal(err) + } + } + + // Put a debug instance in the context to prevent logging to stderr. + // See associated TODO in runner.go: we should revisit this pattern. + ctx := context.Background() + ctx = debug.WithInstance(ctx, "off") + + awaiter := integration.NewAwaiter(sandbox.Workdir) + ss := lsprpc.NewStreamServer(cache, false, nil) + server := servertest.NewPipeServer(ss, jsonrpc2.NewRawStream) + editor, err := fake.NewEditor(sandbox, config).Connect(ctx, server, awaiter.Hooks()) + if err != nil { + sandbox.Close() // ignore error + t.Fatal(err) + } + if err := awaiter.Await(ctx, integration.InitialWorkspaceLoad); err != nil { + sandbox.Close() // ignore error + t.Fatal(err) + } + return &integration.Env{ + T: t, + Ctx: ctx, + Editor: editor, + Sandbox: sandbox, + Awaiter: awaiter, + } +} + +// A markerTestRun holds the state of one run of a marker test archive. +type markerTestRun struct { + test *markerTest + env *integration.Env + settings map[string]any + + // Collected information. + // Each @diag/@suggestedfix marker eliminates an entry from diags. + values map[expect.Identifier]any + diags map[protocol.Location][]protocol.Diagnostic // diagnostics by position; location end == start + + // Notes that weren't associated with a top-level marker func. They may be + // consumed by another marker (e.g. @codelenses collects @codelens markers). + // Any notes that aren't consumed are flagged as an error. + extraNotes map[protocol.DocumentURI]map[string][]*expect.Note +} + +// sprintf returns a formatted string after applying pre-processing to +// arguments of the following types: +// - token.Pos: formatted using (*markerTestRun).fmtPos +// - protocol.Location: formatted using (*markerTestRun).fmtLoc +func (c *marker) sprintf(format string, args ...any) string { + if false { + _ = fmt.Sprintf(format, args...) // enable vet printf checker + } + var args2 []any + for _, arg := range args { + switch arg := arg.(type) { + case token.Pos: + args2 = append(args2, c.run.fmtPos(arg)) + case protocol.Location: + args2 = append(args2, c.run.fmtLoc(arg)) + default: + args2 = append(args2, arg) + } + } + return fmt.Sprintf(format, args2...) +} + +// fmtPos formats the given pos in the context of the test, using +// archive-relative paths for files and including the line number in the full +// archive file. +func (run *markerTestRun) fmtPos(pos token.Pos) string { + file := run.test.fset.File(pos) + if file == nil { + run.env.T.Errorf("position %d not in test fileset", pos) + return "<invalid location>" + } + m, err := run.env.Editor.Mapper(file.Name()) + if err != nil { + run.env.T.Errorf("%s", err) + return "<invalid location>" + } + loc, err := m.PosLocation(file, pos, pos) + if err != nil { + run.env.T.Errorf("Mapper(%s).PosLocation failed: %v", file.Name(), err) + } + return run.fmtLoc(loc) +} + +// fmtLoc formats the given location in the context of the test, using +// archive-relative paths for files and including the line number in the full +// archive file. +func (run *markerTestRun) fmtLoc(loc protocol.Location) string { + formatted := run.fmtLocDetails(loc, true) + if formatted == "" { + run.env.T.Errorf("unable to find %s in test archive", loc) + return "<invalid location>" + } + return formatted +} + +// See fmtLoc. If includeTxtPos is not set, the position in the full archive +// file is omitted. +// +// If the location cannot be found within the archive, fmtLocDetails returns "". +func (run *markerTestRun) fmtLocDetails(loc protocol.Location, includeTxtPos bool) string { + if loc == (protocol.Location{}) { + return "" + } + lines := bytes.Count(run.test.archive.Comment, []byte("\n")) + var name string + for _, f := range run.test.archive.Files { + lines++ // -- separator -- + uri := run.env.Sandbox.Workdir.URI(f.Name) + if uri == loc.URI { + name = f.Name + break + } + lines += bytes.Count(f.Data, []byte("\n")) + } + if name == "" { + return "" + } + m, err := run.env.Editor.Mapper(name) + if err != nil { + run.env.T.Errorf("internal error: %v", err) + return "<invalid location>" + } + start, end, err := m.RangeOffsets(loc.Range) + if err != nil { + run.env.T.Errorf("error formatting location %s: %v", loc, err) + return "<invalid location>" + } + var ( + startLine, startCol8 = m.OffsetLineCol8(start) + endLine, endCol8 = m.OffsetLineCol8(end) + ) + innerSpan := fmt.Sprintf("%d:%d", startLine, startCol8) // relative to the embedded file + outerSpan := fmt.Sprintf("%d:%d", lines+startLine, startCol8) // relative to the archive file + if start != end { + if endLine == startLine { + innerSpan += fmt.Sprintf("-%d", endCol8) + outerSpan += fmt.Sprintf("-%d", endCol8) + } else { + innerSpan += fmt.Sprintf("-%d:%d", endLine, endCol8) + outerSpan += fmt.Sprintf("-%d:%d", lines+endLine, endCol8) + } + } + + if includeTxtPos { + return fmt.Sprintf("%s:%s (%s:%s)", name, innerSpan, run.test.name, outerSpan) + } else { + return fmt.Sprintf("%s:%s", name, innerSpan) + } +} + +// ---- converters ---- + +// Types with special handling. +var ( + goldenType = reflect.TypeOf(&Golden{}) + markerType = reflect.TypeOf(marker{}) + stringMatcherType = reflect.TypeOf(stringMatcher{}) +) + +// Custom conversions. +// +// These functions are called after valueMarkerFuncs have run to convert +// arguments into the desired parameter types. +// +// Converters should return an error rather than calling marker.errorf(). +var customConverters = map[reflect.Type]func(marker, any) (any, error){ + reflect.TypeOf(protocol.Location{}): converter(convertLocation), + reflect.TypeOf(completionLabel("")): converter(convertCompletionLabel), +} + +// converter transforms a typed argument conversion function to an untyped +// conversion function. +func converter[T any](f func(marker, any) (T, error)) func(marker, any) (any, error) { + return func(m marker, arg any) (any, error) { + return f(m, arg) + } +} + +func convert(mark marker, arg any, paramType reflect.Type) (any, error) { + // Handle stringMatcher and golden parameters before resolving identifiers, + // because golden content lives in a separate namespace from other + // identifiers. + switch paramType { + case stringMatcherType: + return convertStringMatcher(mark, arg) + case goldenType: + id, ok := arg.(expect.Identifier) + if !ok { + return nil, fmt.Errorf("invalid input type %T: golden key must be an identifier", arg) + } + return mark.run.test.getGolden(id), nil + } + if id, ok := arg.(expect.Identifier); ok { + if arg2, ok := mark.run.values[id]; ok { + arg = arg2 + } + } + if converter, ok := customConverters[paramType]; ok { + arg2, err := converter(mark, arg) + if err != nil { + return nil, fmt.Errorf("converting for input type %T to %v: %v", arg, paramType, err) + } + arg = arg2 + } + if reflect.TypeOf(arg).AssignableTo(paramType) { + return arg, nil // no conversion required + } + return nil, fmt.Errorf("cannot convert %v (%T) to %s", arg, arg, paramType) +} + +// convertLocation converts a string or regexp argument into the protocol +// location corresponding to the first position of the string (or first match +// of the regexp) in the line preceding the note. +func convertLocation(mark marker, arg any) (protocol.Location, error) { + // matchContent is used to match the given argument against the file content + // starting at the marker line. + var matchContent func([]byte) (int, int, error) + + switch arg := arg.(type) { + case protocol.Location: + return arg, nil // nothing to do + case string: + matchContent = func(content []byte) (int, int, error) { + idx := bytes.Index(content, []byte(arg)) + if idx < 0 { + return 0, 0, fmt.Errorf("substring %q not found", arg) + } + return idx, idx + len(arg), nil + } + case *regexp.Regexp: + matchContent = func(content []byte) (int, int, error) { + matches := arg.FindSubmatchIndex(content) + if len(matches) == 0 { + return 0, 0, fmt.Errorf("no match for regexp %q", arg) + } + switch len(matches) { + case 2: + // no subgroups: return the range of the regexp expression + return matches[0], matches[1], nil + case 4: + // one subgroup: return its range + return matches[2], matches[3], nil + default: + return 0, 0, fmt.Errorf("invalid location regexp %q: expect either 0 or 1 subgroups, got %d", arg, len(matches)/2-1) + } + } + default: + return protocol.Location{}, fmt.Errorf("cannot convert argument type %T to location (must be a string or regexp to match the preceding line)", arg) + } + + // Now use matchFunc to match a range starting on the marker line. + + file := mark.run.test.fset.File(mark.note.Pos) + posn := safetoken.Position(file, mark.note.Pos) + lineStart := file.LineStart(posn.Line) + lineStartOff, lineEndOff, err := safetoken.Offsets(file, lineStart, mark.note.Pos) + if err != nil { + return protocol.Location{}, err + } + m := mark.mapper() + start, end, err := matchContent(m.Content[lineStartOff:]) + if err != nil { + return protocol.Location{}, err + } + startOff, endOff := lineStartOff+start, lineStartOff+end + if startOff > lineEndOff { + // The start of the match must be between the start of the line and the + // marker position (inclusive). + return protocol.Location{}, fmt.Errorf("no matching range found starting on the current line") + } + return m.OffsetLocation(startOff, endOff) +} + +// completionLabel is a special parameter type that may be converted from a +// string literal, or extracted from a completion item. +// +// See [convertCompletionLabel]. +type completionLabel string + +// convertCompletionLabel coerces an argument to a [completionLabel] parameter +// type. +// +// If the arg is a string, it is trivially converted. If the arg is a +// completionItem, its label is extracted. +// +// This allows us to stage a migration of the "snippet" marker to a simpler +// model where the completion label can just be listed explicitly. +func convertCompletionLabel(mark marker, arg any) (completionLabel, error) { + switch arg := arg.(type) { + case string: + return completionLabel(arg), nil + case completionItem: + return completionLabel(arg.Label), nil + default: + return "", fmt.Errorf("cannot convert argument type %T to completion label (must be a string or completion item)", arg) + } +} + +// convertStringMatcher converts a string, regexp, or identifier +// argument into a stringMatcher. The string is a substring of the +// expected error, the regexp is a pattern than matches the expected +// error, and the identifier is a golden file containing the expected +// error. +func convertStringMatcher(mark marker, arg any) (stringMatcher, error) { + switch arg := arg.(type) { + case string: + return stringMatcher{substr: arg}, nil + case *regexp.Regexp: + return stringMatcher{pattern: arg}, nil + case expect.Identifier: + golden := mark.run.test.getGolden(arg) + return stringMatcher{golden: golden}, nil + default: + return stringMatcher{}, fmt.Errorf("cannot convert %T to wantError (want: string, regexp, or identifier)", arg) + } +} + +// A stringMatcher represents an expectation of a specific string value. +// +// It may be indicated in one of three ways, in 'expect' notation: +// - an identifier 'foo', to compare (exactly) with the contents of the golden +// section @foo; +// - a pattern expression re"ab.*c", to match against a regular expression; +// - a string literal "abc", to check for a substring. +type stringMatcher struct { + golden *Golden + pattern *regexp.Regexp + substr string +} + +func (sc stringMatcher) String() string { + if sc.golden != nil { + return fmt.Sprintf("content from @%s entry", sc.golden.id) + } else if sc.pattern != nil { + return fmt.Sprintf("content matching %#q", sc.pattern) + } else { + return fmt.Sprintf("content with substring %q", sc.substr) + } +} + +// checkErr asserts that the given error matches the stringMatcher's expectations. +func (sc stringMatcher) checkErr(mark marker, err error) { + if err == nil { + mark.errorf("@%s succeeded unexpectedly, want %v", mark.note.Name, sc) + return + } + sc.check(mark, err.Error()) +} + +// check asserts that the given content matches the stringMatcher's expectations. +func (sc stringMatcher) check(mark marker, got string) { + if sc.golden != nil { + compareGolden(mark, []byte(got), sc.golden) + } else if sc.pattern != nil { + // Content must match the regular expression pattern. + if !sc.pattern.MatchString(got) { + mark.errorf("got %q, does not match pattern %#q", got, sc.pattern) + } + + } else if !strings.Contains(got, sc.substr) { + // Content must contain the expected substring. + mark.errorf("got %q, want substring %q", got, sc.substr) + } +} + +// checkChangedFiles compares the files changed by an operation with their expected (golden) state. +func checkChangedFiles(mark marker, changed map[string][]byte, golden *Golden) { + // Check changed files match expectations. + for filename, got := range changed { + if want, ok := golden.Get(mark.T(), filename, got); !ok { + mark.errorf("%s: unexpected change to file %s; got:\n%s", + mark.note.Name, filename, got) + + } else if string(got) != string(want) { + mark.errorf("%s: wrong file content for %s: got:\n%s\nwant:\n%s\ndiff:\n%s", + mark.note.Name, filename, got, want, + compare.Bytes(want, got)) + } + } + + // Report unmet expectations. + for filename := range golden.data { + if _, ok := changed[filename]; !ok { + want, _ := golden.Get(mark.T(), filename, nil) + mark.errorf("%s: missing change to file %s; want:\n%s", + mark.note.Name, filename, want) + } + } +} + +// checkDiffs computes unified diffs for each changed file, and compares with +// the diff content stored in the given golden directory. +func checkDiffs(mark marker, changed map[string][]byte, golden *Golden) { + diffs := make(map[string]string) + for name, after := range changed { + before := mark.run.env.FileContent(name) + // TODO(golang/go#64023): switch back to diff.Strings. + // The attached issue is only one obstacle to switching. + // Another is that different diff algorithms produce + // different results, so if we commit diffs in test + // expectations, then we need to either (1) state + // which diff implementation they use and never change + // it, or (2) don't compare diffs, but instead apply + // the "want" diff and check that it produces the + // "got" output. Option 2 is more robust, as it allows + // the test expectation to use any valid diff. + edits := myers.ComputeEdits(before, string(after)) + d, err := diff.ToUnified("before", "after", before, edits, 0) + if err != nil { + // Can't happen: edits are consistent. + log.Fatalf("internal error in diff.ToUnified: %v", err) + } + // Trim the unified header from diffs, as it is unnecessary and repetitive. + difflines := strings.Split(d, "\n") + if len(difflines) >= 2 && strings.HasPrefix(difflines[1], "+++") { + diffs[name] = strings.Join(difflines[2:], "\n") + } else { + diffs[name] = d + } + } + // Check changed files match expectations. + for filename, got := range diffs { + if want, ok := golden.Get(mark.T(), filename, []byte(got)); !ok { + mark.errorf("%s: unexpected change to file %s; got diff:\n%s", + mark.note.Name, filename, got) + + } else if got != string(want) { + mark.errorf("%s: wrong diff for %s:\n\ngot:\n%s\n\nwant:\n%s\n", + mark.note.Name, filename, got, want) + } + } + // Report unmet expectations. + for filename := range golden.data { + if _, ok := changed[filename]; !ok { + want, _ := golden.Get(mark.T(), filename, nil) + mark.errorf("%s: missing change to file %s; want:\n%s", + mark.note.Name, filename, want) + } + } +} + +// ---- marker functions ---- + +// TODO(rfindley): consolidate documentation of these markers. They are already +// documented above, so much of the documentation here is redundant. + +// completionItem is a simplified summary of a completion item. +type completionItem struct { + Label, Detail, Kind, Documentation string +} + +func completionItemMarker(mark marker, label string, other ...string) completionItem { + if len(other) > 3 { + mark.errorf("too many arguments to @item: expect at most 4") + } + item := completionItem{ + Label: label, + } + if len(other) > 0 { + item.Detail = other[0] + } + if len(other) > 1 { + item.Kind = other[1] + } + if len(other) > 2 { + item.Documentation = other[2] + } + return item +} + +func rankMarker(mark marker, src protocol.Location, items ...completionLabel) { + // Separate positive and negative items (expectations). + var pos, neg []completionLabel + for _, item := range items { + if strings.HasPrefix(string(item), "!") { + neg = append(neg, item) + } else { + pos = append(pos, item) + } + } + + // Collect results that are present in items, preserving their order. + list := mark.run.env.Completion(src) + var got []string + for _, g := range list.Items { + for _, w := range pos { + if g.Label == string(w) { + got = append(got, g.Label) + break + } + } + for _, w := range neg { + if g.Label == string(w[len("!"):]) { + mark.errorf("got unwanted completion: %s", g.Label) + break + } + } + } + var want []string + for _, w := range pos { + want = append(want, string(w)) + } + if diff := cmp.Diff(want, got); diff != "" { + mark.errorf("completion rankings do not match (-want +got):\n%s", diff) + } +} + +func snippetMarker(mark marker, src protocol.Location, label completionLabel, want string) { + list := mark.run.env.Completion(src) + var ( + found bool + got string + all []string // for errors + ) + items := filterBuiltinsAndKeywords(mark, list.Items) + for _, i := range items { + all = append(all, i.Label) + if i.Label == string(label) { + found = true + if i.TextEdit != nil { + if edit, err := protocol.SelectCompletionTextEdit(i, false); err == nil { + got = edit.NewText + } + } + break + } + } + if !found { + mark.errorf("no completion item found matching %s (got: %v)", label, all) + return + } + if got != want { + mark.errorf("snippets do not match: got:\n%q\nwant:\n%q", got, want) + } +} + +// completeMarker implements the @complete marker, running +// textDocument/completion at the given src location and asserting that the +// results match the expected results. +func completeMarker(mark marker, src protocol.Location, want ...completionItem) { + list := mark.run.env.Completion(src) + items := filterBuiltinsAndKeywords(mark, list.Items) + var got []completionItem + for i, item := range items { + simplified := completionItem{ + Label: item.Label, + Detail: item.Detail, + Kind: fmt.Sprint(item.Kind), + } + if item.Documentation != nil { + switch v := item.Documentation.Value.(type) { + case string: + simplified.Documentation = v + case protocol.MarkupContent: + simplified.Documentation = strings.TrimSpace(v.Value) // trim newlines + } + } + // Support short-hand notation: if Detail, Kind, or Documentation are omitted from the + // item, don't match them. + if i < len(want) { + if want[i].Detail == "" { + simplified.Detail = "" + } + if want[i].Kind == "" { + simplified.Kind = "" + } + if want[i].Documentation == "" { + simplified.Documentation = "" + } + } + got = append(got, simplified) + } + if len(want) == 0 { + want = nil // got is nil if empty + } + if diff := cmp.Diff(want, got); diff != "" { + mark.errorf("Completion(...) returned unexpect results (-want +got):\n%s", diff) + } +} + +// filterBuiltinsAndKeywords filters out builtins and keywords from completion +// results. +// +// It over-approximates, and does not detect if builtins are shadowed. +func filterBuiltinsAndKeywords(mark marker, items []protocol.CompletionItem) []protocol.CompletionItem { + keep := 0 + for _, item := range items { + if mark.run.test.filterKeywords && item.Kind == protocol.KeywordCompletion { + continue + } + if mark.run.test.filterBuiltins && types.Universe.Lookup(item.Label) != nil { + continue + } + items[keep] = item + keep++ + } + return items[:keep] +} + +// acceptCompletionMarker implements the @acceptCompletion marker, running +// textDocument/completion at the given src location and accepting the +// candidate with the given label. The resulting source must match the provided +// golden content. +func acceptCompletionMarker(mark marker, src protocol.Location, label string, golden *Golden) { + list := mark.run.env.Completion(src) + var selected *protocol.CompletionItem + for _, item := range list.Items { + if item.Label == label { + selected = &item + break + } + } + if selected == nil { + mark.errorf("Completion(...) did not return an item labeled %q", label) + return + } + edit, err := protocol.SelectCompletionTextEdit(*selected, false) + if err != nil { + mark.errorf("Completion(...) did not return a valid edit: %v", err) + return + } + filename := mark.path() + mapper := mark.mapper() + patched, _, err := protocol.ApplyEdits(mapper, append([]protocol.TextEdit{edit}, selected.AdditionalTextEdits...)) + + if err != nil { + mark.errorf("ApplyProtocolEdits failed: %v", err) + return + } + changes := map[string][]byte{filename: patched} + // Check the file state. + checkChangedFiles(mark, changes, golden) +} + +// defMarker implements the @def marker, running textDocument/definition at +// the given src location and asserting that there is exactly one resulting +// location, matching dst. +// +// TODO(rfindley): support a variadic destination set. +func defMarker(mark marker, src, dst protocol.Location) { + got := mark.run.env.GoToDefinition(src) + if got != dst { + mark.errorf("definition location does not match:\n\tgot: %s\n\twant %s", + mark.run.fmtLoc(got), mark.run.fmtLoc(dst)) + } +} + +func typedefMarker(mark marker, src, dst protocol.Location) { + got := mark.run.env.TypeDefinition(src) + if got != dst { + mark.errorf("type definition location does not match:\n\tgot: %s\n\twant %s", + mark.run.fmtLoc(got), mark.run.fmtLoc(dst)) + } +} + +func foldingRangeMarker(mark marker, g *Golden) { + env := mark.run.env + ranges, err := mark.server().FoldingRange(env.Ctx, &protocol.FoldingRangeParams{ + TextDocument: mark.document(), + }) + if err != nil { + mark.errorf("foldingRange failed: %v", err) + return + } + var edits []protocol.TextEdit + insert := func(line, char uint32, text string) { + pos := protocol.Position{Line: line, Character: char} + edits = append(edits, protocol.TextEdit{ + Range: protocol.Range{ + Start: pos, + End: pos, + }, + NewText: text, + }) + } + for i, rng := range ranges { + insert(rng.StartLine, rng.StartCharacter, fmt.Sprintf("<%d kind=%q>", i, rng.Kind)) + insert(rng.EndLine, rng.EndCharacter, fmt.Sprintf("</%d>", i)) + } + filename := mark.path() + mapper, err := env.Editor.Mapper(filename) + if err != nil { + mark.errorf("Editor.Mapper(%s) failed: %v", filename, err) + return + } + got, _, err := protocol.ApplyEdits(mapper, edits) + if err != nil { + mark.errorf("ApplyProtocolEdits failed: %v", err) + return + } + want, _ := g.Get(mark.T(), "", got) + if diff := compare.Bytes(want, got); diff != "" { + mark.errorf("foldingRange mismatch:\n%s", diff) + } +} + +// formatMarker implements the @format marker. +func formatMarker(mark marker, golden *Golden) { + edits, err := mark.server().Formatting(mark.ctx(), &protocol.DocumentFormattingParams{ + TextDocument: mark.document(), + }) + var got []byte + if err != nil { + got = []byte(err.Error() + "\n") // all golden content is newline terminated + } else { + env := mark.run.env + filename := mark.path() + mapper, err := env.Editor.Mapper(filename) + if err != nil { + mark.errorf("Editor.Mapper(%s) failed: %v", filename, err) + } + + got, _, err = protocol.ApplyEdits(mapper, edits) + if err != nil { + mark.errorf("ApplyProtocolEdits failed: %v", err) + return + } + } + + compareGolden(mark, got, golden) +} + +func highlightLocationMarker(mark marker, loc protocol.Location, kindName expect.Identifier) protocol.DocumentHighlight { + var kind protocol.DocumentHighlightKind + switch kindName { + case "read": + kind = protocol.Read + case "write": + kind = protocol.Write + case "text": + kind = protocol.Text + default: + mark.errorf("invalid highlight kind: %q", kindName) + } + + return protocol.DocumentHighlight{ + Range: loc.Range, + Kind: kind, + } +} +func sortDocumentHighlights(s []protocol.DocumentHighlight) { + sort.Slice(s, func(i, j int) bool { + return protocol.CompareRange(s[i].Range, s[j].Range) < 0 + }) +} + +// highlightAllMarker makes textDocument/highlight +// requests at locations of equivalence classes. Given input +// highlightall(X1, X2, ..., Xn), the marker checks +// highlight(X1) = highlight(X2) = ... = highlight(Xn) = {X1, X2, ..., Xn}. +// It is not the general rule for all highlighting, and use @highlight +// for asymmetric cases. +// +// TODO(b/288111111): this is a bit of a hack. We should probably +// have a more general way of testing that a function is idempotent. +func highlightAllMarker(mark marker, all ...protocol.DocumentHighlight) { + sortDocumentHighlights(all) + for _, src := range all { + loc := protocol.Location{URI: mark.uri(), Range: src.Range} + got := mark.run.env.DocumentHighlight(loc) + sortDocumentHighlights(got) + + if d := cmp.Diff(all, got); d != "" { + mark.errorf("DocumentHighlight(%v) mismatch (-want +got):\n%s", loc, d) + } + } +} + +func highlightMarker(mark marker, src protocol.DocumentHighlight, dsts ...protocol.DocumentHighlight) { + loc := protocol.Location{URI: mark.uri(), Range: src.Range} + got := mark.run.env.DocumentHighlight(loc) + + sortDocumentHighlights(got) + sortDocumentHighlights(dsts) + + if diff := cmp.Diff(dsts, got, cmpopts.EquateEmpty()); diff != "" { + mark.errorf("DocumentHighlight(%v) mismatch (-want +got):\n%s", src, diff) + } +} + +func hoverMarker(mark marker, src, dst protocol.Location, sc stringMatcher) { + content, gotDst := mark.run.env.Hover(src) + if gotDst != dst { + mark.errorf("hover location does not match:\n\tgot: %s\n\twant %s)", mark.run.fmtLoc(gotDst), mark.run.fmtLoc(dst)) + } + gotMD := "" + if content != nil { + gotMD = content.Value + } + sc.check(mark, gotMD) +} + +func hoverErrMarker(mark marker, src protocol.Location, em stringMatcher) { + _, _, err := mark.editor().Hover(mark.ctx(), src) + em.checkErr(mark, err) +} + +// locMarker implements the @loc marker. It is executed before other +// markers, so that locations are available. +func locMarker(mark marker, loc protocol.Location) protocol.Location { return loc } + +// diagMarker implements the @diag marker. It eliminates diagnostics from +// the observed set in mark.test. +func diagMarker(mark marker, loc protocol.Location, re *regexp.Regexp) { + if _, ok := removeDiagnostic(mark, loc, re); !ok { + mark.errorf("no diagnostic at %v matches %q", loc, re) + } +} + +// removeDiagnostic looks for a diagnostic matching loc at the given position. +// +// If found, it returns (diag, true), and eliminates the matched diagnostic +// from the unmatched set. +// +// If not found, it returns (protocol.Diagnostic{}, false). +func removeDiagnostic(mark marker, loc protocol.Location, re *regexp.Regexp) (protocol.Diagnostic, bool) { + loc.Range.End = loc.Range.Start // diagnostics ignore end position. + diags := mark.run.diags[loc] + for i, diag := range diags { + if re.MatchString(diag.Message) { + mark.run.diags[loc] = append(diags[:i], diags[i+1:]...) + return diag, true + } + } + return protocol.Diagnostic{}, false +} + +// renameMarker implements the @rename(location, new, golden) marker. +func renameMarker(mark marker, loc protocol.Location, newName string, golden *Golden) { + changed, err := rename(mark.run.env, loc, newName) + if err != nil { + mark.errorf("rename failed: %v. (Use @renameerr for expected errors.)", err) + return + } + checkDiffs(mark, changed, golden) +} + +// renameErrMarker implements the @renamererr(location, new, error) marker. +func renameErrMarker(mark marker, loc protocol.Location, newName string, wantErr stringMatcher) { + _, err := rename(mark.run.env, loc, newName) + wantErr.checkErr(mark, err) +} + +func selectionRangeMarker(mark marker, loc protocol.Location, g *Golden) { + ranges, err := mark.server().SelectionRange(mark.ctx(), &protocol.SelectionRangeParams{ + TextDocument: mark.document(), + Positions: []protocol.Position{loc.Range.Start}, + }) + if err != nil { + mark.errorf("SelectionRange failed: %v", err) + return + } + var buf bytes.Buffer + m := mark.mapper() + for i, path := range ranges { + fmt.Fprintf(&buf, "Ranges %d:", i) + rng := path + for { + s, e, err := m.RangeOffsets(rng.Range) + if err != nil { + mark.errorf("RangeOffsets failed: %v", err) + return + } + + var snippet string + if e-s < 30 { + snippet = string(m.Content[s:e]) + } else { + snippet = string(m.Content[s:s+15]) + "..." + string(m.Content[e-15:e]) + } + + fmt.Fprintf(&buf, "\n\t%v %q", rng.Range, strings.ReplaceAll(snippet, "\n", "\\n")) + + if rng.Parent == nil { + break + } + rng = *rng.Parent + } + buf.WriteRune('\n') + } + compareGolden(mark, buf.Bytes(), g) +} + +func tokenMarker(mark marker, loc protocol.Location, tokenType, mod string) { + tokens := mark.run.env.SemanticTokensRange(loc) + if len(tokens) != 1 { + mark.errorf("got %d tokens, want 1", len(tokens)) + return + } + tok := tokens[0] + if tok.TokenType != tokenType { + mark.errorf("token type = %q, want %q", tok.TokenType, tokenType) + } + if tok.Mod != mod { + mark.errorf("token mod = %q, want %q", tok.Mod, mod) + } +} + +func signatureMarker(mark marker, src protocol.Location, label string, active int64) { + got := mark.run.env.SignatureHelp(src) + if label == "" { + // A null result is expected. + // (There's no point having a @signatureerr marker + // because the server handler suppresses all errors.) + if got != nil && len(got.Signatures) > 0 { + mark.errorf("signatureHelp = %v, want 0 signatures", got) + } + return + } + if got == nil || len(got.Signatures) != 1 { + mark.errorf("signatureHelp = %v, want exactly 1 signature", got) + return + } + if got := got.Signatures[0].Label; got != label { + mark.errorf("signatureHelp: got label %q, want %q", got, label) + } + if got := int64(got.ActiveParameter); got != active { + mark.errorf("signatureHelp: got active parameter %d, want %d", got, active) + } +} + +// rename returns the new contents of the files that would be modified +// by renaming the identifier at loc to newName. +func rename(env *integration.Env, loc protocol.Location, newName string) (map[string][]byte, error) { + // We call Server.Rename directly, instead of + // env.Editor.Rename(env.Ctx, loc, newName) + // to isolate Rename from PrepareRename, and because we don't + // want to modify the file system in a scenario with multiple + // @rename markers. + + wsedit, err := env.Editor.Server.Rename(env.Ctx, &protocol.RenameParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + NewName: newName, + }) + if err != nil { + return nil, err + } + return changedFiles(env, wsedit.DocumentChanges) +} + +// changedFiles applies the given sequence of document changes to the +// editor buffer content, recording the final contents in the returned map. +// The actual editor state is not changed. +// Deleted files are indicated by a content of []byte(nil). +// +// See also: +// - Editor.applyWorkspaceEdit ../integration/fake/editor.go for the +// implementation of this operation used in normal testing. +// - cmdClient.applyWorkspaceEdit in ../../../cmd/cmd.go for the +// CLI variant. +func changedFiles(env *integration.Env, changes []protocol.DocumentChange) (map[string][]byte, error) { + uriToPath := env.Sandbox.Workdir.URIToPath + + // latest maps each updated file name to a mapper holding its + // current contents, or nil if the file has been deleted. + latest := make(map[protocol.DocumentURI]*protocol.Mapper) + + // read reads a file. It returns an error if the file never + // existed or was deleted. + read := func(uri protocol.DocumentURI) (*protocol.Mapper, error) { + if m, ok := latest[uri]; ok { + if m == nil { + return nil, fmt.Errorf("read: file %s was deleted", uri) + } + return m, nil + } + return env.Editor.Mapper(uriToPath(uri)) + } + + // write (over)writes a file. A nil content indicates a deletion. + write := func(uri protocol.DocumentURI, content []byte) { + var m *protocol.Mapper + if content != nil { + m = protocol.NewMapper(uri, content) + } + latest[uri] = m + } + + // Process the sequence of changes. + for _, change := range changes { + switch { + case change.TextDocumentEdit != nil: + uri := change.TextDocumentEdit.TextDocument.URI + m, err := read(uri) + if err != nil { + return nil, err // missing + } + patched, _, err := protocol.ApplyEdits(m, protocol.AsTextEdits(change.TextDocumentEdit.Edits)) + if err != nil { + return nil, err // bad edit + } + write(uri, patched) + + case change.RenameFile != nil: + old := change.RenameFile.OldURI + m, err := read(old) + if err != nil { + return nil, err // missing + } + write(old, nil) + + new := change.RenameFile.NewURI + if _, err := read(old); err == nil { + return nil, fmt.Errorf("RenameFile: destination %s exists", new) + } + write(new, m.Content) + + case change.CreateFile != nil: + uri := change.CreateFile.URI + if _, err := read(uri); err == nil { + return nil, fmt.Errorf("CreateFile %s: file exists", uri) + } + write(uri, []byte("")) // initially empty + + case change.DeleteFile != nil: + uri := change.DeleteFile.URI + if _, err := read(uri); err != nil { + return nil, fmt.Errorf("DeleteFile %s: file does not exist", uri) + } + write(uri, nil) + + default: + return nil, fmt.Errorf("invalid DocumentChange") + } + } + + // Convert into result form. + result := make(map[string][]byte) + for uri, mapper := range latest { + var content []byte + if mapper != nil { + content = mapper.Content + } + result[uriToPath(uri)] = content + } + + return result, nil +} + +func codeActionMarker(mark marker, start, end protocol.Location, actionKind string, g *Golden) { + // Request the range from start.Start to end.End. + loc := start + loc.Range.End = end.Range.End + + // Apply the fix it suggests. + changed, err := codeAction(mark.run.env, loc.URI, loc.Range, actionKind, nil) + if err != nil { + mark.errorf("codeAction failed: %v", err) + return + } + + // Check the file state. + checkChangedFiles(mark, changed, g) +} + +func codeActionEditMarker(mark marker, loc protocol.Location, actionKind string, g *Golden) { + changed, err := codeAction(mark.run.env, loc.URI, loc.Range, actionKind, nil) + if err != nil { + mark.errorf("codeAction failed: %v", err) + return + } + + checkDiffs(mark, changed, g) +} + +func codeActionErrMarker(mark marker, start, end protocol.Location, actionKind string, wantErr stringMatcher) { + loc := start + loc.Range.End = end.Range.End + _, err := codeAction(mark.run.env, loc.URI, loc.Range, actionKind, nil) + wantErr.checkErr(mark, err) +} + +// codeLensesMarker runs the @codelenses() marker, collecting @codelens marks +// in the current file and comparing with the result of the +// textDocument/codeLens RPC. +func codeLensesMarker(mark marker) { + type codeLens struct { + Range protocol.Range + Title string + } + + lenses := mark.run.env.CodeLens(mark.path()) + var got []codeLens + for _, lens := range lenses { + title := "" + if lens.Command != nil { + title = lens.Command.Title + } + got = append(got, codeLens{lens.Range, title}) + } + + var want []codeLens + mark.consumeExtraNotes("codelens", actionMarkerFunc(func(_ marker, loc protocol.Location, title string) { + want = append(want, codeLens{loc.Range, title}) + })) + + for _, s := range [][]codeLens{got, want} { + sort.Slice(s, func(i, j int) bool { + li, lj := s[i], s[j] + if c := protocol.CompareRange(li.Range, lj.Range); c != 0 { + return c < 0 + } + return li.Title < lj.Title + }) + } + + if diff := cmp.Diff(want, got); diff != "" { + mark.errorf("codelenses: unexpected diff (-want +got):\n%s", diff) + } +} + +func documentLinkMarker(mark marker, g *Golden) { + var b bytes.Buffer + links := mark.run.env.DocumentLink(mark.path()) + for _, l := range links { + if l.Target == nil { + mark.errorf("%s: nil link target", l.Range) + continue + } + loc := protocol.Location{URI: mark.uri(), Range: l.Range} + fmt.Fprintln(&b, mark.run.fmtLocDetails(loc, false), *l.Target) + } + + compareGolden(mark, b.Bytes(), g) +} + +// consumeExtraNotes runs the provided func for each extra note with the given +// name, and deletes all matching notes. +func (mark marker) consumeExtraNotes(name string, f func(marker)) { + uri := mark.uri() + notes := mark.run.extraNotes[uri][name] + delete(mark.run.extraNotes[uri], name) + + for _, note := range notes { + f(marker{run: mark.run, note: note}) + } +} + +// suggestedfixMarker implements the @suggestedfix(location, regexp, +// kind, golden) marker. It acts like @diag(location, regexp), to set +// the expectation of a diagnostic, but then it applies the first code +// action of the specified kind suggested by the matched diagnostic. +func suggestedfixMarker(mark marker, loc protocol.Location, re *regexp.Regexp, golden *Golden) { + loc.Range.End = loc.Range.Start // diagnostics ignore end position. + // Find and remove the matching diagnostic. + diag, ok := removeDiagnostic(mark, loc, re) + if !ok { + mark.errorf("no diagnostic at %v matches %q", loc, re) + return + } + + // Apply the fix it suggests. + changed, err := codeAction(mark.run.env, loc.URI, diag.Range, "quickfix", &diag) + if err != nil { + mark.errorf("suggestedfix failed: %v. (Use @suggestedfixerr for expected errors.)", err) + return + } + + // Check the file state. + checkDiffs(mark, changed, golden) +} + +func suggestedfixErrMarker(mark marker, loc protocol.Location, re *regexp.Regexp, wantErr stringMatcher) { + loc.Range.End = loc.Range.Start // diagnostics ignore end position. + // Find and remove the matching diagnostic. + diag, ok := removeDiagnostic(mark, loc, re) + if !ok { + mark.errorf("no diagnostic at %v matches %q", loc, re) + return + } + + // Apply the fix it suggests. + _, err := codeAction(mark.run.env, loc.URI, diag.Range, "quickfix", &diag) + wantErr.checkErr(mark, err) +} + +// codeAction executes a textDocument/codeAction request for the specified +// location and kind. If diag is non-nil, it is used as the code action +// context. +// +// The resulting map contains resulting file contents after the code action is +// applied. Currently, this function does not support code actions that return +// edits directly; it only supports code action commands. +func codeAction(env *integration.Env, uri protocol.DocumentURI, rng protocol.Range, actionKind string, diag *protocol.Diagnostic) (map[string][]byte, error) { + changes, err := codeActionChanges(env, uri, rng, actionKind, diag) + if err != nil { + return nil, err + } + return changedFiles(env, changes) +} + +// codeActionChanges executes a textDocument/codeAction request for the +// specified location and kind, and captures the resulting document changes. +// If diag is non-nil, it is used as the code action context. +func codeActionChanges(env *integration.Env, uri protocol.DocumentURI, rng protocol.Range, actionKind string, diag *protocol.Diagnostic) ([]protocol.DocumentChange, error) { + // Request all code actions that apply to the diagnostic. + // (The protocol supports filtering using Context.Only={actionKind} + // but we can give a better error if we don't filter.) + params := &protocol.CodeActionParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: uri}, + Range: rng, + Context: protocol.CodeActionContext{ + Only: nil, // => all kinds + }, + } + if diag != nil { + params.Context.Diagnostics = []protocol.Diagnostic{*diag} + } + + actions, err := env.Editor.Server.CodeAction(env.Ctx, params) + if err != nil { + return nil, err + } + + // Find the sole candidate CodeAction of exactly the specified kind + // (e.g. refactor.inline.call). + var candidates []protocol.CodeAction + for _, act := range actions { + if act.Kind == protocol.CodeActionKind(actionKind) { + candidates = append(candidates, act) + } + } + if len(candidates) != 1 { + for _, act := range actions { + env.T.Logf("found CodeAction Kind=%s Title=%q", act.Kind, act.Title) + } + return nil, fmt.Errorf("found %d CodeActions of kind %s for this diagnostic, want 1", len(candidates), actionKind) + } + action := candidates[0] + + // Apply the codeAction. + // + // Spec: + // "If a code action provides an edit and a command, first the edit is + // executed and then the command." + // An action may specify an edit and/or a command, to be + // applied in that order. But since applyDocumentChanges(env, + // action.Edit.DocumentChanges) doesn't compose, for now we + // assert that actions return one or the other. + + // Resolve code action edits first if the client has resolve support + // and the code action has no edits. + if action.Edit == nil { + editSupport, err := env.Editor.EditResolveSupport() + if err != nil { + return nil, err + } + if editSupport { + resolved, err := env.Editor.Server.ResolveCodeAction(env.Ctx, &action) + if err != nil { + return nil, err + } + action.Edit = resolved.Edit + } + } + + if action.Edit != nil { + if len(action.Edit.Changes) > 0 { + env.T.Errorf("internal error: discarding unexpected CodeAction{Kind=%s, Title=%q}.Edit.Changes", action.Kind, action.Title) + } + if action.Edit.DocumentChanges != nil { + if action.Command != nil { + env.T.Errorf("internal error: discarding unexpected CodeAction{Kind=%s, Title=%q}.Command", action.Kind, action.Title) + } + return action.Edit.DocumentChanges, nil + } + } + + if action.Command != nil { + // This is a typical CodeAction command: + // + // Title: "Implement error" + // Command: gopls.apply_fix + // Arguments: [{"Fix":"stub_methods","URI":".../a.go","Range":...}}] + // + // The client makes an ExecuteCommand RPC to the server, + // which dispatches it to the ApplyFix handler. + // ApplyFix dispatches to the "stub_methods" suggestedfix hook (the meat). + // The server then makes an ApplyEdit RPC to the client, + // whose WorkspaceEditFunc hook temporarily gathers the edits + // instead of applying them. + + var changes []protocol.DocumentChange + cli := env.Editor.Client() + restore := cli.SetApplyEditHandler(func(ctx context.Context, wsedit *protocol.WorkspaceEdit) error { + changes = append(changes, wsedit.DocumentChanges...) + return nil + }) + defer restore() + + if _, err := env.Editor.Server.ExecuteCommand(env.Ctx, &protocol.ExecuteCommandParams{ + Command: action.Command.Command, + Arguments: action.Command.Arguments, + }); err != nil { + return nil, err + } + return changes, nil // populated as a side effect of ExecuteCommand + } + + return nil, nil +} + +// refsMarker implements the @refs marker. +func refsMarker(mark marker, src protocol.Location, want ...protocol.Location) { + refs := func(includeDeclaration bool, want []protocol.Location) error { + got, err := mark.server().References(mark.ctx(), &protocol.ReferenceParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(src), + Context: protocol.ReferenceContext{ + IncludeDeclaration: includeDeclaration, + }, + }) + if err != nil { + return err + } + + return compareLocations(mark, got, want) + } + + for _, includeDeclaration := range []bool{false, true} { + // Ignore first 'want' location if we didn't request the declaration. + // TODO(adonovan): don't assume a single declaration: + // there may be >1 if corresponding methods are considered. + want := want + if !includeDeclaration && len(want) > 0 { + want = want[1:] + } + if err := refs(includeDeclaration, want); err != nil { + mark.errorf("refs(includeDeclaration=%t) failed: %v", + includeDeclaration, err) + } + } +} + +// implementationMarker implements the @implementation marker. +func implementationMarker(mark marker, src protocol.Location, want ...protocol.Location) { + got, err := mark.server().Implementation(mark.ctx(), &protocol.ImplementationParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(src), + }) + if err != nil { + mark.errorf("implementation at %s failed: %v", src, err) + return + } + if err := compareLocations(mark, got, want); err != nil { + mark.errorf("implementation: %v", err) + } +} + +func itemLocation(item protocol.CallHierarchyItem) protocol.Location { + return protocol.Location{ + URI: item.URI, + Range: item.Range, + } +} + +func incomingCallsMarker(mark marker, src protocol.Location, want ...protocol.Location) { + getCalls := func(item protocol.CallHierarchyItem) ([]protocol.Location, error) { + calls, err := mark.server().IncomingCalls(mark.ctx(), &protocol.CallHierarchyIncomingCallsParams{Item: item}) + if err != nil { + return nil, err + } + var locs []protocol.Location + for _, call := range calls { + locs = append(locs, itemLocation(call.From)) + } + return locs, nil + } + callHierarchy(mark, src, getCalls, want) +} + +func outgoingCallsMarker(mark marker, src protocol.Location, want ...protocol.Location) { + getCalls := func(item protocol.CallHierarchyItem) ([]protocol.Location, error) { + calls, err := mark.server().OutgoingCalls(mark.ctx(), &protocol.CallHierarchyOutgoingCallsParams{Item: item}) + if err != nil { + return nil, err + } + var locs []protocol.Location + for _, call := range calls { + locs = append(locs, itemLocation(call.To)) + } + return locs, nil + } + callHierarchy(mark, src, getCalls, want) +} + +type callHierarchyFunc = func(protocol.CallHierarchyItem) ([]protocol.Location, error) + +func callHierarchy(mark marker, src protocol.Location, getCalls callHierarchyFunc, want []protocol.Location) { + items, err := mark.server().PrepareCallHierarchy(mark.ctx(), &protocol.CallHierarchyPrepareParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(src), + }) + if err != nil { + mark.errorf("PrepareCallHierarchy failed: %v", err) + return + } + if nitems := len(items); nitems != 1 { + mark.errorf("PrepareCallHierarchy returned %d items, want exactly 1", nitems) + return + } + if loc := itemLocation(items[0]); loc != src { + mark.errorf("PrepareCallHierarchy found call %v, want %v", loc, src) + return + } + calls, err := getCalls(items[0]) + if err != nil { + mark.errorf("call hierarchy failed: %v", err) + return + } + if calls == nil { + calls = []protocol.Location{} + } + // TODO(rfindley): why aren't call hierarchy results stable? + sortLocs := func(locs []protocol.Location) { + sort.Slice(locs, func(i, j int) bool { + return protocol.CompareLocation(locs[i], locs[j]) < 0 + }) + } + sortLocs(want) + sortLocs(calls) + if d := cmp.Diff(want, calls); d != "" { + mark.errorf("call hierarchy: unexpected results (-want +got):\n%s", d) + } +} + +func inlayhintsMarker(mark marker, g *Golden) { + hints := mark.run.env.InlayHints(mark.path()) + + // Map inlay hints to text edits. + edits := make([]protocol.TextEdit, len(hints)) + for i, hint := range hints { + var paddingLeft, paddingRight string + if hint.PaddingLeft { + paddingLeft = " " + } + if hint.PaddingRight { + paddingRight = " " + } + edits[i] = protocol.TextEdit{ + Range: protocol.Range{Start: hint.Position, End: hint.Position}, + NewText: fmt.Sprintf("<%s%s%s>", paddingLeft, hint.Label[0].Value, paddingRight), + } + } + + m := mark.mapper() + got, _, err := protocol.ApplyEdits(m, edits) + if err != nil { + mark.errorf("ApplyProtocolEdits: %v", err) + return + } + + compareGolden(mark, got, g) +} + +func prepareRenameMarker(mark marker, src, spn protocol.Location, placeholder string) { + params := &protocol.PrepareRenameParams{ + TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(src), + } + got, err := mark.server().PrepareRename(mark.ctx(), params) + if err != nil { + mark.T().Fatal(err) + } + if placeholder == "" { + if got != nil { + mark.errorf("PrepareRename(...) = %v, want nil", got) + } + return + } + want := &protocol.PrepareRenameResult{Range: spn.Range, Placeholder: placeholder} + if diff := cmp.Diff(want, got); diff != "" { + mark.errorf("mismatching PrepareRename result:\n%s", diff) + } +} + +// symbolMarker implements the @symbol marker. +func symbolMarker(mark marker, golden *Golden) { + // Retrieve information about all symbols in this file. + symbols, err := mark.server().DocumentSymbol(mark.ctx(), &protocol.DocumentSymbolParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: mark.uri()}, + }) + if err != nil { + mark.errorf("DocumentSymbol request failed: %v", err) + return + } + + // Format symbols one per line, sorted (in effect) by first column, a dotted name. + var lines []string + for _, symbol := range symbols { + // Each result element is a union of (legacy) + // SymbolInformation and (new) DocumentSymbol, + // so we ascertain which one and then transcode. + data, err := json.Marshal(symbol) + if err != nil { + mark.T().Fatal(err) + } + if _, ok := symbol.(map[string]any)["location"]; ok { + // This case is not reached because Editor initialization + // enables HierarchicalDocumentSymbolSupport. + // TODO(adonovan): test this too. + var sym protocol.SymbolInformation + if err := json.Unmarshal(data, &sym); err != nil { + mark.T().Fatal(err) + } + mark.errorf("fake Editor doesn't support SymbolInformation") + + } else { + var sym protocol.DocumentSymbol // new hierarchical hotness + if err := json.Unmarshal(data, &sym); err != nil { + mark.T().Fatal(err) + } + + // Print each symbol in the response tree. + var visit func(sym protocol.DocumentSymbol, prefix []string) + visit = func(sym protocol.DocumentSymbol, prefix []string) { + var out strings.Builder + out.WriteString(strings.Join(prefix, ".")) + fmt.Fprintf(&out, " %q", sym.Detail) + if delta := sym.Range.End.Line - sym.Range.Start.Line; delta > 0 { + fmt.Fprintf(&out, " +%d lines", delta) + } + lines = append(lines, out.String()) + + for _, child := range sym.Children { + visit(child, append(prefix, child.Name)) + } + } + visit(sym, []string{sym.Name}) + } + } + sort.Strings(lines) + lines = append(lines, "") // match trailing newline in .txtar file + got := []byte(strings.Join(lines, "\n")) + + // Compare with golden. + want, ok := golden.Get(mark.T(), "", got) + if !ok { + mark.errorf("%s: missing golden file @%s", mark.note.Name, golden.id) + } else if diff := cmp.Diff(string(got), string(want)); diff != "" { + mark.errorf("%s: unexpected output: got:\n%s\nwant:\n%s\ndiff:\n%s", + mark.note.Name, got, want, diff) + } +} + +// compareLocations returns an error message if got and want are not +// the same set of locations. The marker is used only for fmtLoc. +func compareLocations(mark marker, got, want []protocol.Location) error { + toStrings := func(locs []protocol.Location) []string { + strs := make([]string, len(locs)) + for i, loc := range locs { + strs[i] = mark.run.fmtLoc(loc) + } + sort.Strings(strs) + return strs + } + if diff := cmp.Diff(toStrings(want), toStrings(got)); diff != "" { + return fmt.Errorf("incorrect result locations: (got %d, want %d):\n%s", + len(got), len(want), diff) + } + return nil +} + +func workspaceSymbolMarker(mark marker, query string, golden *Golden) { + params := &protocol.WorkspaceSymbolParams{ + Query: query, + } + + gotSymbols, err := mark.server().Symbol(mark.ctx(), params) + if err != nil { + mark.errorf("Symbol(%q) failed: %v", query, err) + return + } + var got bytes.Buffer + for _, s := range gotSymbols { + // Omit the txtar position of the symbol location; otherwise edits to the + // txtar archive lead to unexpected failures. + loc := mark.run.fmtLocDetails(s.Location, false) + // TODO(rfindley): can we do better here, by detecting if the location is + // relative to GOROOT? + if loc == "" { + loc = "<unknown>" + } + fmt.Fprintf(&got, "%s %s %s\n", loc, s.Name, s.Kind) + } + + compareGolden(mark, got.Bytes(), golden) +} + +// compareGolden compares the content of got with that of g.Get(""), reporting +// errors on any mismatch. +// +// TODO(rfindley): use this helper in more places. +func compareGolden(mark marker, got []byte, g *Golden) { + want, ok := g.Get(mark.T(), "", got) + if !ok { + mark.errorf("missing golden file @%s", g.id) + return + } + // Normalize newline termination: archive files (i.e. Golden content) can't + // contain non-newline terminated files, except in the special case where the + // file is completely empty. + // + // Note that txtar partitions a contiguous byte slice, so we must copy before + // appending. + normalize := func(s []byte) []byte { + if n := len(s); n > 0 && s[n-1] != '\n' { + s = append(s[:n:n], '\n') // don't mutate array + } + return s + } + got = normalize(got) + want = normalize(want) + if diff := compare.Bytes(want, got); diff != "" { + mark.errorf("%s does not match @%s:\n%s", mark.note.Name, g.id, diff) + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/callhierarchy/callhierarchy.txt b/contribs/gnopls/internal/test/marker/testdata/callhierarchy/callhierarchy.txt new file mode 100644 index 00000000000..43fbdd68281 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/callhierarchy/callhierarchy.txt @@ -0,0 +1,92 @@ +This test checks call hierarchy queries. + +-ignore_extra_diags due to the initialization cycle. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/callhierarchy + +-- incoming/incoming.go -- +package incoming + +import "golang.org/lsptests/callhierarchy" + +// A is exported to test incoming calls across packages +func A() { //@loc(incomingA, "A") + callhierarchy.D() +} + +-- outgoing/outgoing.go -- +package outgoing + +// B is exported to test outgoing calls across packages +func B() { //@loc(outgoingB, "B") +} + +-- hierarchy.go -- +package callhierarchy //@loc(hPkg, "callhierarchy") + +import "golang.org/lsptests/callhierarchy/outgoing" + +func a() { //@loc(hA, "a") + D() +} + +func b() { //@loc(hB, "b") + D() +} + +// C is an exported function +func C() { //@loc(hC, "C") + D() + D() +} + +// To test hierarchy across function literals +var x = func() { D() } //@loc(hX, "x"),loc(hXGlobal, "x") + +// D is exported to test incoming/outgoing calls across packages +func D() { //@loc(hD, "D"),incomingcalls(hD, hA, hB, hC, hXGlobal, incomingA),outgoingcalls(hD, hE, hF, hG, hX, outgoingB, hFoo, hH, hI, hJ, hK) + e() + x() + F() + outgoing.B() + foo := func() {} //@loc(hFoo, "foo"),incomingcalls(hFoo, hD),outgoingcalls(hFoo) + foo() + + func() { + g() + }() + + var i Interface = impl{} + i.H() + i.I() + + s := Struct{} + s.J() + s.K() +} + +func e() {} //@loc(hE, "e") + +// F is an exported function +func F() {} //@loc(hF, "F") + +func g() {} //@loc(hG, "g") + +type Interface interface { + H() //@loc(hH, "H") + I() //@loc(hI, "I") +} + +type impl struct{} + +func (i impl) H() {} +func (i impl) I() {} + +type Struct struct { + J func() //@loc(hJ, "J") + K func() //@loc(hK, "K") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/callhierarchy/issue64451.txt b/contribs/gnopls/internal/test/marker/testdata/callhierarchy/issue64451.txt new file mode 100644 index 00000000000..618d6ed6e34 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/callhierarchy/issue64451.txt @@ -0,0 +1,51 @@ +This test checks call hierarchy queries involving lambdas, which are +treated as mere statements of their enclosing name function, since +we can't track calls to them. + +Calls from a global var decl are reported at the ValueSpec.Names. + +See golang/go#64451. + +-- go.mod -- +module example.com +go 1.0 + +-- a/a.go -- +package a + +func foo() { //@ loc(foo, "foo") + bar() +} + +func bar() { //@ loc(bar, "bar") + go func() { baz() }() +} + +func baz() { //@ loc(baz, "baz") + bluh() +} + +func bluh() { //@ loc(bluh, "bluh") + print() +} + +var _ = func() int { //@ loc(global, "_") + baz() + return 0 +}() + +func init() { //@ loc(init, "init") + baz() +} + +//@ outgoingcalls(foo, bar) +//@ outgoingcalls(bar, baz) +//@ outgoingcalls(baz, bluh) +//@ outgoingcalls(bluh) +//@ outgoingcalls(init, baz) + +//@ incomingcalls(foo) +//@ incomingcalls(bar, foo) +//@ incomingcalls(baz, bar, global, init) +//@ incomingcalls(bluh, baz) +//@ incomingcalls(init) diff --git a/contribs/gnopls/internal/test/marker/testdata/callhierarchy/issue66923.txt b/contribs/gnopls/internal/test/marker/testdata/callhierarchy/issue66923.txt new file mode 100644 index 00000000000..4a5e59f9f9a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/callhierarchy/issue66923.txt @@ -0,0 +1,15 @@ +Regression test for a crash (#66923) in outgoing calls +to a built-in function (unsafe.Slice). + +-- go.mod -- +module example.com +go 1.17 + +-- a/a.go -- +package a + +import "unsafe" + +func A() []int { //@ loc(A, "A") + return unsafe.Slice(new(int), 1) //@ outgoingcalls(A) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/change_quote.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/change_quote.txt new file mode 100644 index 00000000000..a3b4f8d4c83 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/change_quote.txt @@ -0,0 +1,69 @@ +This test checks the behavior of the 'change quote' code action. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/changequote + +go 1.18 + +-- a.go -- +package changequote + +import ( + "fmt" +) + +func foo() { + var s string + s = "hello" //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a1) + s = `hello` //@codeactionedit("`", "refactor.rewrite.changeQuote", a2) + s = "hello\tworld" //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a3) + s = `hello world` //@codeactionedit("`", "refactor.rewrite.changeQuote", a4) + s = "hello\nworld" //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a5) + // add a comment to avoid affect diff compute + s = `hello +world` //@codeactionedit("`", "refactor.rewrite.changeQuote", a6) + s = "hello\"world" //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a7) + s = `hello"world` //@codeactionedit("`", "refactor.rewrite.changeQuote", a8) + s = "hello\x1bworld" //@codeactionerr(`"`, "", "refactor.rewrite.changeQuote", re"found 0 CodeActions") + s = "hello`world" //@codeactionerr(`"`, "", "refactor.rewrite.changeQuote", re"found 0 CodeActions") + s = "hello\x7fworld" //@codeactionerr(`"`, "", "refactor.rewrite.changeQuote", re"found 0 CodeActions") + fmt.Println(s) +} + +-- @a1/a.go -- +@@ -9 +9 @@ +- s = "hello" //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a1) ++ s = `hello` //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a1) +-- @a2/a.go -- +@@ -10 +10 @@ +- s = `hello` //@codeactionedit("`", "refactor.rewrite.changeQuote", a2) ++ s = "hello" //@codeactionedit("`", "refactor.rewrite.changeQuote", a2) +-- @a3/a.go -- +@@ -11 +11 @@ +- s = "hello\tworld" //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a3) ++ s = `hello world` //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a3) +-- @a4/a.go -- +@@ -12 +12 @@ +- s = `hello world` //@codeactionedit("`", "refactor.rewrite.changeQuote", a4) ++ s = "hello\tworld" //@codeactionedit("`", "refactor.rewrite.changeQuote", a4) +-- @a5/a.go -- +@@ -13 +13,2 @@ +- s = "hello\nworld" //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a5) ++ s = `hello ++world` //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a5) +-- @a6/a.go -- +@@ -15,2 +15 @@ +- s = `hello +-world` //@codeactionedit("`", "refactor.rewrite.changeQuote", a6) ++ s = "hello\nworld" //@codeactionedit("`", "refactor.rewrite.changeQuote", a6) +-- @a7/a.go -- +@@ -17 +17 @@ +- s = "hello\"world" //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a7) ++ s = `hello"world` //@codeactionedit(`"`, "refactor.rewrite.changeQuote", a7) +-- @a8/a.go -- +@@ -18 +18 @@ +- s = `hello"world` //@codeactionedit("`", "refactor.rewrite.changeQuote", a8) ++ s = "hello\"world" //@codeactionedit("`", "refactor.rewrite.changeQuote", a8) diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/extract-variadic-63287.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract-variadic-63287.txt new file mode 100644 index 00000000000..d035119bc3a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract-variadic-63287.txt @@ -0,0 +1,28 @@ +This test exercises extract on a variadic function. +It is a regression test for bug #63287 in which +the final paramater's "..." would go missing. + +-- go.mod -- +module example.com +go 1.18 + +-- a/a.go -- +package a + +//@codeactionedit(block, "refactor.extract.function", out) + +func _() { + var logf func(string, ...any) + { println(logf) } //@loc(block, re`{[^}]*}`) +} + +-- @out/a/a.go -- +@@ -7 +7 @@ +- { println(logf) } //@loc(block, re`{[^}]*}`) ++ { newFunction(logf) } //@loc(block, re`{[^}]*}`) +@@ -10 +10,4 @@ ++func newFunction(logf func( string, ...any)) { ++ println(logf) ++} ++ +-- end -- diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_method.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_method.txt new file mode 100644 index 00000000000..7cb22d1577d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_method.txt @@ -0,0 +1,256 @@ +This test exercises function and method extraction. + +-- flags -- +-ignore_extra_diags + +-- basic.go -- +package extract + +//@codeactionedit(A_XLessThanYP, "refactor.extract.method", meth1) +//@codeactionedit(A_XLessThanYP, "refactor.extract.function", func1) +//@codeactionedit(A_AddP1, "refactor.extract.method", meth2) +//@codeactionedit(A_AddP1, "refactor.extract.function", func2) +//@codeactionedit(A_AddP2, "refactor.extract.method", meth3) +//@codeactionedit(A_AddP2, "refactor.extract.function", func3) +//@codeactionedit(A_XLessThanY, "refactor.extract.method", meth4) +//@codeactionedit(A_XLessThanY, "refactor.extract.function", func4) +//@codeactionedit(A_Add1, "refactor.extract.method", meth5) +//@codeactionedit(A_Add1, "refactor.extract.function", func5) +//@codeactionedit(A_Add2, "refactor.extract.method", meth6) +//@codeactionedit(A_Add2, "refactor.extract.function", func6) + +type A struct { + x int + y int +} + +func (a *A) XLessThanYP() bool { + return a.x < a.y //@loc(A_XLessThanYP, re`return.*a\.y`) +} + +func (a *A) AddP() int { + sum := a.x + a.y //@loc(A_AddP1, re`sum.*a\.y`) + return sum //@loc(A_AddP2, re`return.*?sum`) +} + +func (a A) XLessThanY() bool { + return a.x < a.y //@loc(A_XLessThanY, re`return.*a\.y`) +} + +func (a A) Add() int { + sum := a.x + a.y //@loc(A_Add1, re`sum.*a\.y`) + return sum //@loc(A_Add2, re`return.*?sum`) +} + +-- @func1/basic.go -- +@@ -22 +22 @@ +- return a.x < a.y //@loc(A_XLessThanYP, re`return.*a\.y`) ++ return newFunction(a) //@loc(A_XLessThanYP, re`return.*a\.y`) +@@ -25 +25,4 @@ ++func newFunction(a *A) bool { ++ return a.x < a.y ++} ++ +-- @func2/basic.go -- +@@ -26 +26 @@ +- sum := a.x + a.y //@loc(A_AddP1, re`sum.*a\.y`) ++ sum := newFunction(a) //@loc(A_AddP1, re`sum.*a\.y`) +@@ -30 +30,5 @@ ++func newFunction(a *A) int { ++ sum := a.x + a.y ++ return sum ++} ++ +-- @func3/basic.go -- +@@ -27 +27 @@ +- return sum //@loc(A_AddP2, re`return.*?sum`) ++ return newFunction(sum) //@loc(A_AddP2, re`return.*?sum`) +@@ -30 +30,4 @@ ++func newFunction(sum int) int { ++ return sum ++} ++ +-- @func4/basic.go -- +@@ -31 +31 @@ +- return a.x < a.y //@loc(A_XLessThanY, re`return.*a\.y`) ++ return newFunction(a) //@loc(A_XLessThanY, re`return.*a\.y`) +@@ -34 +34,4 @@ ++func newFunction(a A) bool { ++ return a.x < a.y ++} ++ +-- @func5/basic.go -- +@@ -35 +35 @@ +- sum := a.x + a.y //@loc(A_Add1, re`sum.*a\.y`) ++ sum := newFunction(a) //@loc(A_Add1, re`sum.*a\.y`) +@@ -39 +39,5 @@ ++func newFunction(a A) int { ++ sum := a.x + a.y ++ return sum ++} ++ +-- @func6/basic.go -- +@@ -36 +36 @@ +- return sum //@loc(A_Add2, re`return.*?sum`) ++ return newFunction(sum) //@loc(A_Add2, re`return.*?sum`) +@@ -39 +39,4 @@ ++func newFunction(sum int) int { ++ return sum ++} ++ +-- @meth1/basic.go -- +@@ -22 +22 @@ +- return a.x < a.y //@loc(A_XLessThanYP, re`return.*a\.y`) ++ return a.newMethod() //@loc(A_XLessThanYP, re`return.*a\.y`) +@@ -25 +25,4 @@ ++func (a *A) newMethod() bool { ++ return a.x < a.y ++} ++ +-- @meth2/basic.go -- +@@ -26 +26 @@ +- sum := a.x + a.y //@loc(A_AddP1, re`sum.*a\.y`) ++ sum := a.newMethod() //@loc(A_AddP1, re`sum.*a\.y`) +@@ -30 +30,5 @@ ++func (a *A) newMethod() int { ++ sum := a.x + a.y ++ return sum ++} ++ +-- @meth3/basic.go -- +@@ -27 +27 @@ +- return sum //@loc(A_AddP2, re`return.*?sum`) ++ return a.newMethod(sum) //@loc(A_AddP2, re`return.*?sum`) +@@ -30 +30,4 @@ ++func (*A) newMethod(sum int) int { ++ return sum ++} ++ +-- @meth4/basic.go -- +@@ -31 +31 @@ +- return a.x < a.y //@loc(A_XLessThanY, re`return.*a\.y`) ++ return a.newMethod() //@loc(A_XLessThanY, re`return.*a\.y`) +@@ -34 +34,4 @@ ++func (a A) newMethod() bool { ++ return a.x < a.y ++} ++ +-- @meth5/basic.go -- +@@ -35 +35 @@ +- sum := a.x + a.y //@loc(A_Add1, re`sum.*a\.y`) ++ sum := a.newMethod() //@loc(A_Add1, re`sum.*a\.y`) +@@ -39 +39,5 @@ ++func (a A) newMethod() int { ++ sum := a.x + a.y ++ return sum ++} ++ +-- @meth6/basic.go -- +@@ -36 +36 @@ +- return sum //@loc(A_Add2, re`return.*?sum`) ++ return a.newMethod(sum) //@loc(A_Add2, re`return.*?sum`) +@@ -39 +39,4 @@ ++func (A) newMethod(sum int) int { ++ return sum ++} ++ +-- context.go -- +package extract + +import ( + "context" + "testing" +) + +//@codeactionedit(B_AddP, "refactor.extract.method", contextMeth1) +//@codeactionedit(B_AddP, "refactor.extract.function", contextFunc1) +//@codeactionedit(B_LongList, "refactor.extract.method", contextMeth2) +//@codeactionedit(B_LongList, "refactor.extract.function", contextFunc2) +//@codeactionedit(B_AddPWithB, "refactor.extract.function", contextFuncB) +//@codeactionedit(B_LongListWithT, "refactor.extract.function", contextFuncT) + +type B struct { + x int + y int +} + +func (b *B) AddP(ctx context.Context) (int, error) { + sum := b.x + b.y + return sum, ctx.Err() //@loc(B_AddP, re`return.*ctx\.Err\(\)`) +} + +func (b *B) LongList(ctx context.Context) (int, error) { + p1 := 1 + p2 := 1 + p3 := 1 + return p1 + p2 + p3, ctx.Err() //@loc(B_LongList, re`return.*ctx\.Err\(\)`) +} + +func (b *B) AddPWithB(ctx context.Context, tB *testing.B) (int, error) { + sum := b.x + b.y //@loc(B_AddPWithB, re`(?s:^.*?Err\(\))`) + tB.Skip() + return sum, ctx.Err() +} + +func (b *B) LongListWithT(ctx context.Context, t *testing.T) (int, error) { + p1 := 1 + p2 := 1 + p3 := 1 + p4 := p1 + p2 //@loc(B_LongListWithT, re`(?s:^.*?Err\(\))`) + t.Skip() + return p4 + p3, ctx.Err() +} +-- @contextMeth1/context.go -- +@@ -22 +22 @@ +- return sum, ctx.Err() //@loc(B_AddP, re`return.*ctx\.Err\(\)`) ++ return b.newMethod(ctx, sum) //@loc(B_AddP, re`return.*ctx\.Err\(\)`) +@@ -25 +25,4 @@ ++func (*B) newMethod(ctx context.Context, sum int) (int, error) { ++ return sum, ctx.Err() ++} ++ +-- @contextMeth2/context.go -- +@@ -29 +29 @@ +- return p1 + p2 + p3, ctx.Err() //@loc(B_LongList, re`return.*ctx\.Err\(\)`) ++ return b.newMethod(ctx, p1, p2, p3) //@loc(B_LongList, re`return.*ctx\.Err\(\)`) +@@ -32 +32,4 @@ ++func (*B) newMethod(ctx context.Context, p1 int, p2 int, p3 int) (int, error) { ++ return p1 + p2 + p3, ctx.Err() ++} ++ +-- @contextFunc2/context.go -- +@@ -29 +29 @@ +- return p1 + p2 + p3, ctx.Err() //@loc(B_LongList, re`return.*ctx\.Err\(\)`) ++ return newFunction(ctx, p1, p2, p3) //@loc(B_LongList, re`return.*ctx\.Err\(\)`) +@@ -32 +32,4 @@ ++func newFunction(ctx context.Context, p1 int, p2 int, p3 int) (int, error) { ++ return p1 + p2 + p3, ctx.Err() ++} ++ +-- @contextFunc1/context.go -- +@@ -22 +22 @@ +- return sum, ctx.Err() //@loc(B_AddP, re`return.*ctx\.Err\(\)`) ++ return newFunction(ctx, sum) //@loc(B_AddP, re`return.*ctx\.Err\(\)`) +@@ -25 +25,4 @@ ++func newFunction(ctx context.Context, sum int) (int, error) { ++ return sum, ctx.Err() ++} ++ +-- @contextFuncB/context.go -- +@@ -33 +33,6 @@ +- sum := b.x + b.y //@loc(B_AddPWithB, re`(?s:^.*?Err\(\))`) ++ //@loc(B_AddPWithB, re`(?s:^.*?Err\(\))`) ++ return newFunction(ctx, tB, b) ++} ++ ++func newFunction(ctx context.Context, tB *testing.B, b *B) (int, error) { ++ sum := b.x + b.y +-- @contextFuncT/context.go -- +@@ -42 +42,6 @@ +- p4 := p1 + p2 //@loc(B_LongListWithT, re`(?s:^.*?Err\(\))`) ++ //@loc(B_LongListWithT, re`(?s:^.*?Err\(\))`) ++ return newFunction(ctx, t, p1, p2, p3) ++} ++ ++func newFunction(ctx context.Context, t *testing.T, p1 int, p2 int, p3 int) (int, error) { ++ p4 := p1 + p2 diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable-67905.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable-67905.txt new file mode 100644 index 00000000000..259b84a09a3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable-67905.txt @@ -0,0 +1,29 @@ +This test verifies the fix for golang/go#67905: Extract variable from type +switch produces invalid code + +-- go.mod -- +module mod.test/extract + +go 1.18 + +-- extract_switch.go -- +package extract + +import ( + "io" +) + +func f() io.Reader + +func main() { + switch r := f().(type) { //@codeactionedit("f()", "refactor.extract.variable", type_switch_func_call) + default: + _ = r + } +} + +-- @type_switch_func_call/extract_switch.go -- +@@ -10 +10,2 @@ +- switch r := f().(type) { //@codeactionedit("f()", "refactor.extract.variable", type_switch_func_call) ++ x := f() ++ switch r := x.(type) { //@codeactionedit("f()", "refactor.extract.variable", type_switch_func_call) diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable.txt new file mode 100644 index 00000000000..8c500d02c1e --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable.txt @@ -0,0 +1,70 @@ +This test checks the behavior of the 'extract variable' code action. +See extract_variable_resolve.txt for the same test with resolve support. + +-- flags -- +-ignore_extra_diags + +-- basic_lit.go -- +package extract + +func _() { + var _ = 1 + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) + var _ = 3 + 4 //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) +} + +-- @basic_lit1/basic_lit.go -- +@@ -4 +4,2 @@ +- var _ = 1 + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) ++ x := 1 ++ var _ = x + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) +-- @basic_lit2/basic_lit.go -- +@@ -5 +5,2 @@ +- var _ = 3 + 4 //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) ++ x := 3 + 4 ++ var _ = x //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) +-- func_call.go -- +package extract + +import "strconv" + +func _() { + x0 := append([]int{}, 1) //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) + str := "1" + b, err := strconv.Atoi(str) //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) +} + +-- @func_call1/func_call.go -- +@@ -6 +6,2 @@ +- x0 := append([]int{}, 1) //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) ++ x := append([]int{}, 1) ++ x0 := x //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) +-- @func_call2/func_call.go -- +@@ -8 +8,2 @@ +- b, err := strconv.Atoi(str) //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) ++ x, x1 := strconv.Atoi(str) ++ b, err := x, x1 //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) +-- scope.go -- +package extract + +import "go/ast" + +func _() { + x0 := 0 + if true { + y := ast.CompositeLit{} //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) + } + if true { + x1 := !false //@codeactionedit("!false", "refactor.extract.variable", scope2) + } +} + +-- @scope1/scope.go -- +@@ -8 +8,2 @@ +- y := ast.CompositeLit{} //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) ++ x := ast.CompositeLit{} ++ y := x //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) +-- @scope2/scope.go -- +@@ -11 +11,2 @@ +- x1 := !false //@codeactionedit("!false", "refactor.extract.variable", scope2) ++ x := !false ++ x1 := x //@codeactionedit("!false", "refactor.extract.variable", scope2) diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt new file mode 100644 index 00000000000..b3a9a67059f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/extract_variable_resolve.txt @@ -0,0 +1,81 @@ +This test checks the behavior of the 'extract variable' code action, with resolve support. +See extract_variable.txt for the same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- flags -- +-ignore_extra_diags + +-- basic_lit.go -- +package extract + +func _() { + var _ = 1 + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) + var _ = 3 + 4 //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) +} + +-- @basic_lit1/basic_lit.go -- +@@ -4 +4,2 @@ +- var _ = 1 + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) ++ x := 1 ++ var _ = x + 2 //@codeactionedit("1", "refactor.extract.variable", basic_lit1) +-- @basic_lit2/basic_lit.go -- +@@ -5 +5,2 @@ +- var _ = 3 + 4 //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) ++ x := 3 + 4 ++ var _ = x //@codeactionedit("3 + 4", "refactor.extract.variable", basic_lit2) +-- func_call.go -- +package extract + +import "strconv" + +func _() { + x0 := append([]int{}, 1) //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) + str := "1" + b, err := strconv.Atoi(str) //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) +} + +-- @func_call1/func_call.go -- +@@ -6 +6,2 @@ +- x0 := append([]int{}, 1) //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) ++ x := append([]int{}, 1) ++ x0 := x //@codeactionedit("append([]int{}, 1)", "refactor.extract.variable", func_call1) +-- @func_call2/func_call.go -- +@@ -8 +8,2 @@ +- b, err := strconv.Atoi(str) //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) ++ x, x1 := strconv.Atoi(str) ++ b, err := x, x1 //@codeactionedit("strconv.Atoi(str)", "refactor.extract.variable", func_call2) +-- scope.go -- +package extract + +import "go/ast" + +func _() { + x0 := 0 + if true { + y := ast.CompositeLit{} //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) + } + if true { + x1 := !false //@codeactionedit("!false", "refactor.extract.variable", scope2) + } +} + +-- @scope1/scope.go -- +@@ -8 +8,2 @@ +- y := ast.CompositeLit{} //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) ++ x := ast.CompositeLit{} ++ y := x //@codeactionedit("ast.CompositeLit{}", "refactor.extract.variable", scope1) +-- @scope2/scope.go -- +@@ -11 +11,2 @@ +- x1 := !false //@codeactionedit("!false", "refactor.extract.variable", scope2) ++ x := !false ++ x1 := x //@codeactionedit("!false", "refactor.extract.variable", scope2) diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/extracttofile.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/extracttofile.txt new file mode 100644 index 00000000000..158a9f9a22c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/extracttofile.txt @@ -0,0 +1,279 @@ +This test checks the behavior of the 'extract to a new file' code action. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/extracttofile + +go 1.18 + +-- a.go -- +package main + +// docs +func fn() {} //@codeactionedit("func", "refactor.extract.toNewFile", function_declaration) + +func fn2() {} //@codeactionedit("fn2", "refactor.extract.toNewFile", only_select_func_name) + +func fn3() {} //@codeactionedit(re`()fn3`, "refactor.extract.toNewFile", zero_width_selection_on_func_name) + +// docs +type T int //@codeactionedit("type", "refactor.extract.toNewFile", type_declaration) + +// docs +var V int //@codeactionedit("var", "refactor.extract.toNewFile", var_declaration) + +// docs +const K = "" //@codeactionedit("const", "refactor.extract.toNewFile", const_declaration) + +const ( //@codeactionedit("const", "refactor.extract.toNewFile", const_declaration_multiple_specs) + P = iota + Q + R +) + +func fnA () {} //@codeaction("func", mdEnd, "refactor.extract.toNewFile", multiple_declarations) + +// unattached comment + +func fnB () {} //@loc(mdEnd, "}") + + +-- existing.go -- +-- existing2.go -- +-- existing2.1.go -- +-- b.go -- +package main +func existing() {} //@codeactionedit("func", "refactor.extract.toNewFile", file_name_conflict) +func existing2() {} //@codeactionedit("func", "refactor.extract.toNewFile", file_name_conflict_again) + +-- single_import.go -- +package main +import "fmt" +func F() { //@codeactionedit("func", "refactor.extract.toNewFile", single_import) + fmt.Println() +} + +-- multiple_imports.go -- +package main +import ( + "fmt" + "log" + time1 "time" +) +func init(){ + log.Println() +} +func F() { //@codeactionedit("func", "refactor.extract.toNewFile", multiple_imports) + fmt.Println() +} +func g() string{ //@codeactionedit("func", "refactor.extract.toNewFile", renamed_import) + return time1.Now().string() +} + +-- blank_import.go -- +package main +import _ "fmt" +func F() {} //@codeactionedit("func", "refactor.extract.toNewFile", blank_import) + + + +-- @blank_import/blank_import.go -- +@@ -3 +3 @@ +-func F() {} //@codeactionedit("func", "refactor.extract.toNewFile", blank_import) ++//@codeactionedit("func", "refactor.extract.toNewFile", blank_import) +-- @blank_import/f.go -- +@@ -0,0 +1,3 @@ ++package main ++ ++func F() {} +-- @const_declaration/a.go -- +@@ -16,2 +16 @@ +-// docs +-const K = "" //@codeactionedit("const", "refactor.extract.toNewFile", const_declaration) ++//@codeactionedit("const", "refactor.extract.toNewFile", const_declaration) +-- @const_declaration/k.go -- +@@ -0,0 +1,4 @@ ++package main ++ ++// docs ++const K = "" +-- @const_declaration_multiple_specs/a.go -- +@@ -19,6 +19 @@ +-const ( //@codeactionedit("const", "refactor.extract.toNewFile", const_declaration_multiple_specs) +- P = iota +- Q +- R +-) +- +-- @const_declaration_multiple_specs/p.go -- +@@ -0,0 +1,7 @@ ++package main ++ ++const ( //@codeactionedit("const", "refactor.extract.toNewFile", const_declaration_multiple_specs) ++ P = iota ++ Q ++ R ++) +-- @file_name_conflict/b.go -- +@@ -2 +2 @@ +-func existing() {} //@codeactionedit("func", "refactor.extract.toNewFile", file_name_conflict) ++//@codeactionedit("func", "refactor.extract.toNewFile", file_name_conflict) +-- @file_name_conflict/existing.1.go -- +@@ -0,0 +1,3 @@ ++package main ++ ++func existing() {} +-- @file_name_conflict_again/b.go -- +@@ -3 +3 @@ +-func existing2() {} //@codeactionedit("func", "refactor.extract.toNewFile", file_name_conflict_again) ++//@codeactionedit("func", "refactor.extract.toNewFile", file_name_conflict_again) +-- @file_name_conflict_again/existing2.2.go -- +@@ -0,0 +1,3 @@ ++package main ++ ++func existing2() {} +-- @function_declaration/a.go -- +@@ -3,2 +3 @@ +-// docs +-func fn() {} //@codeactionedit("func", "refactor.extract.toNewFile", function_declaration) ++//@codeactionedit("func", "refactor.extract.toNewFile", function_declaration) +-- @function_declaration/fn.go -- +@@ -0,0 +1,4 @@ ++package main ++ ++// docs ++func fn() {} +-- @multiple_declarations/a.go -- +package main + +// docs +func fn() {} //@codeactionedit("func", "refactor.extract.toNewFile", function_declaration) + +func fn2() {} //@codeactionedit("fn2", "refactor.extract.toNewFile", only_select_func_name) + +func fn3() {} //@codeactionedit(re`()fn3`, "refactor.extract.toNewFile", zero_width_selection_on_func_name) + +// docs +type T int //@codeactionedit("type", "refactor.extract.toNewFile", type_declaration) + +// docs +var V int //@codeactionedit("var", "refactor.extract.toNewFile", var_declaration) + +// docs +const K = "" //@codeactionedit("const", "refactor.extract.toNewFile", const_declaration) + +const ( //@codeactionedit("const", "refactor.extract.toNewFile", const_declaration_multiple_specs) + P = iota + Q + R +) + +//@loc(mdEnd, "}") + + +-- @multiple_declarations/fna.go -- +package main + +func fnA() {} //@codeaction("func", mdEnd, "refactor.extract.toNewFile", multiple_declarations) + +// unattached comment + +func fnB() {} +-- @multiple_imports/f.go -- +@@ -0,0 +1,9 @@ ++package main ++ ++import ( ++ "fmt" ++) ++ ++func F() { //@codeactionedit("func", "refactor.extract.toNewFile", multiple_imports) ++ fmt.Println() ++} +-- @multiple_imports/multiple_imports.go -- +@@ -3 +3 @@ +- "fmt" ++ +@@ -10,3 +10 @@ +-func F() { //@codeactionedit("func", "refactor.extract.toNewFile", multiple_imports) +- fmt.Println() +-} +-- @only_select_func_name/a.go -- +@@ -6 +6 @@ +-func fn2() {} //@codeactionedit("fn2", "refactor.extract.toNewFile", only_select_func_name) ++//@codeactionedit("fn2", "refactor.extract.toNewFile", only_select_func_name) +-- @only_select_func_name/fn2.go -- +@@ -0,0 +1,3 @@ ++package main ++ ++func fn2() {} +-- @single_import/f.go -- +@@ -0,0 +1,9 @@ ++package main ++ ++import ( ++ "fmt" ++) ++ ++func F() { //@codeactionedit("func", "refactor.extract.toNewFile", single_import) ++ fmt.Println() ++} +-- @single_import/single_import.go -- +@@ -2,4 +2 @@ +-import "fmt" +-func F() { //@codeactionedit("func", "refactor.extract.toNewFile", single_import) +- fmt.Println() +-} +-- @type_declaration/a.go -- +@@ -10,2 +10 @@ +-// docs +-type T int //@codeactionedit("type", "refactor.extract.toNewFile", type_declaration) ++//@codeactionedit("type", "refactor.extract.toNewFile", type_declaration) +-- @type_declaration/t.go -- +@@ -0,0 +1,4 @@ ++package main ++ ++// docs ++type T int +-- @var_declaration/a.go -- +@@ -13,2 +13 @@ +-// docs +-var V int //@codeactionedit("var", "refactor.extract.toNewFile", var_declaration) ++//@codeactionedit("var", "refactor.extract.toNewFile", var_declaration) +-- @var_declaration/v.go -- +@@ -0,0 +1,4 @@ ++package main ++ ++// docs ++var V int +-- @zero_width_selection_on_func_name/a.go -- +@@ -8 +8 @@ +-func fn3() {} //@codeactionedit(re`()fn3`, "refactor.extract.toNewFile", zero_width_selection_on_func_name) ++//@codeactionedit(re`()fn3`, "refactor.extract.toNewFile", zero_width_selection_on_func_name) +-- @zero_width_selection_on_func_name/fn3.go -- +@@ -0,0 +1,3 @@ ++package main ++ ++func fn3() {} +-- @renamed_import/g.go -- +@@ -0,0 +1,9 @@ ++package main ++ ++import ( ++ time1 "time" ++) ++ ++func g() string { //@codeactionedit("func", "refactor.extract.toNewFile", renamed_import) ++ return time1.Now().string() ++} +-- @renamed_import/multiple_imports.go -- +@@ -5 +5 @@ +- time1 "time" ++ +@@ -13,4 +13 @@ +-func g() string{ //@codeactionedit("func", "refactor.extract.toNewFile", renamed_import) +- return time1.Now().string() +-} +- diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_struct.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_struct.txt new file mode 100644 index 00000000000..2b947bf8bbc --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_struct.txt @@ -0,0 +1,575 @@ +This test checks the behavior of the 'fill struct' code action. +See fill_struct_resolve.txt for same test with resolve support. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/fillstruct + +go 1.18 + +-- data/data.go -- +package data + +type B struct { + ExportedInt int + unexportedInt int +} + +-- a.go -- +package fillstruct + +import ( + "golang.org/lsptests/fillstruct/data" +) + +type basicStruct struct { + foo int +} + +var _ = basicStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a1) + +type twoArgStruct struct { + foo int + bar string +} + +var _ = twoArgStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a2) + +type nestedStruct struct { + bar string + basic basicStruct +} + +var _ = nestedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a3) + +var _ = data.B{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a4) +-- @a1/a.go -- +@@ -11 +11,3 @@ +-var _ = basicStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a1) ++var _ = basicStruct{ ++ foo: 0, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a1) +-- @a2/a.go -- +@@ -18 +18,4 @@ +-var _ = twoArgStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a2) ++var _ = twoArgStruct{ ++ foo: 0, ++ bar: "", ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a2) +-- @a3/a.go -- +@@ -25 +25,4 @@ +-var _ = nestedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a3) ++var _ = nestedStruct{ ++ bar: "", ++ basic: basicStruct{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a3) +-- @a4/a.go -- +@@ -27 +27,3 @@ +-var _ = data.B{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a4) ++var _ = data.B{ ++ ExportedInt: 0, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a4) +-- a2.go -- +package fillstruct + +type typedStruct struct { + m map[string]int + s []int + c chan int + c1 <-chan int + a [2]string +} + +var _ = typedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a21) + +type funStruct struct { + fn func(i int) int +} + +var _ = funStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a22) + +type funStructComplex struct { + fn func(i int, s string) (string, int) +} + +var _ = funStructComplex{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a23) + +type funStructEmpty struct { + fn func() +} + +var _ = funStructEmpty{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a24) + +-- @a21/a2.go -- +@@ -11 +11,7 @@ +-var _ = typedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a21) ++var _ = typedStruct{ ++ m: map[string]int{}, ++ s: []int{}, ++ c: make(chan int), ++ c1: make(<-chan int), ++ a: [2]string{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a21) +-- @a22/a2.go -- +@@ -17 +17,4 @@ +-var _ = funStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a22) ++var _ = funStruct{ ++ fn: func(i int) int { ++ }, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a22) +-- @a23/a2.go -- +@@ -23 +23,4 @@ +-var _ = funStructComplex{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a23) ++var _ = funStructComplex{ ++ fn: func(i int, s string) (string, int) { ++ }, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a23) +-- @a24/a2.go -- +@@ -29 +29,4 @@ +-var _ = funStructEmpty{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a24) ++var _ = funStructEmpty{ ++ fn: func() { ++ }, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a24) +-- a3.go -- +package fillstruct + +import ( + "go/ast" + "go/token" +) + +type Foo struct { + A int +} + +type Bar struct { + X *Foo + Y *Foo +} + +var _ = Bar{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a31) + +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{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a32) + +type pointerBuiltinStruct struct { + b *bool + s *string + i *int +} + +var _ = pointerBuiltinStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a33) + +var _ = []ast.BasicLit{ + {}, //@codeactionedit("}", "refactor.rewrite.fillStruct", a34) +} + +var _ = []ast.BasicLit{{}} //@codeactionedit("}", "refactor.rewrite.fillStruct", a35) +-- @a31/a3.go -- +@@ -17 +17,4 @@ +-var _ = Bar{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a31) ++var _ = Bar{ ++ X: &Foo{}, ++ Y: &Foo{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a31) +-- @a32/a3.go -- +@@ -28 +28,9 @@ +-var _ = importedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a32) ++var _ = importedStruct{ ++ m: map[*ast.CompositeLit]ast.Field{}, ++ s: []ast.BadExpr{}, ++ a: [3]token.Token{}, ++ c: make(chan ast.EmptyStmt), ++ fn: func(ast_decl ast.DeclStmt) ast.Ellipsis { ++ }, ++ st: ast.CompositeLit{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a32) +-- @a33/a3.go -- +@@ -36 +36,5 @@ +-var _ = pointerBuiltinStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a33) ++var _ = pointerBuiltinStruct{ ++ b: new(bool), ++ s: new(string), ++ i: new(int), ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a33) +-- @a34/a3.go -- +@@ -39 +39,5 @@ +- {}, //@codeactionedit("}", "refactor.rewrite.fillStruct", a34) ++ { ++ ValuePos: 0, ++ Kind: 0, ++ Value: "", ++ }, //@codeactionedit("}", "refactor.rewrite.fillStruct", a34) +-- @a35/a3.go -- +@@ -42 +42,5 @@ +-var _ = []ast.BasicLit{{}} //@codeactionedit("}", "refactor.rewrite.fillStruct", a35) ++var _ = []ast.BasicLit{{ ++ ValuePos: 0, ++ Kind: 0, ++ Value: "", ++}} //@codeactionedit("}", "refactor.rewrite.fillStruct", a35) +-- a4.go -- +package fillstruct + +import "go/ast" + +type iStruct struct { + X int +} + +type sStruct struct { + str string +} + +type multiFill struct { + num int + strin string + arr []int +} + +type assignStruct struct { + n ast.Node +} + +func fill() { + var x int + var _ = iStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a41) + + var s string + var _ = sStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a42) + + var n int + _ = []int{} + if true { + arr := []int{1, 2} + } + var _ = multiFill{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a43) + + var node *ast.CompositeLit + var _ = assignStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a45) +} + +-- @a41/a4.go -- +@@ -25 +25,3 @@ +- var _ = iStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a41) ++ var _ = iStruct{ ++ X: x, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", a41) +-- @a42/a4.go -- +@@ -28 +28,3 @@ +- var _ = sStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a42) ++ var _ = sStruct{ ++ str: s, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", a42) +-- @a43/a4.go -- +@@ -35 +35,5 @@ +- var _ = multiFill{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a43) ++ var _ = multiFill{ ++ num: n, ++ strin: s, ++ arr: []int{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", a43) +-- @a45/a4.go -- +@@ -38 +38,3 @@ +- var _ = assignStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a45) ++ var _ = assignStruct{ ++ n: node, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", a45) +-- fillStruct.go -- +package fillstruct + +type StructA struct { + unexportedIntField int + ExportedIntField int + MapA map[int]string + Array []int + StructB +} + +type StructA2 struct { + B *StructB +} + +type StructA3 struct { + B StructB +} + +func fill() { + a := StructA{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct1) + b := StructA2{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct2) + c := StructA3{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct3) + if true { + _ = StructA3{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct4) + } +} + +-- @fillStruct1/fillStruct.go -- +@@ -20 +20,7 @@ +- a := StructA{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct1) ++ a := StructA{ ++ unexportedIntField: 0, ++ ExportedIntField: 0, ++ MapA: map[int]string{}, ++ Array: []int{}, ++ StructB: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct1) +-- @fillStruct2/fillStruct.go -- +@@ -21 +21,3 @@ +- b := StructA2{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct2) ++ b := StructA2{ ++ B: &StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct2) +-- @fillStruct3/fillStruct.go -- +@@ -22 +22,3 @@ +- c := StructA3{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct3) ++ c := StructA3{ ++ B: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct3) +-- @fillStruct4/fillStruct.go -- +@@ -24 +24,3 @@ +- _ = StructA3{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct4) ++ _ = StructA3{ ++ B: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct4) +-- fillStruct_anon.go -- +package fillstruct + +type StructAnon struct { + a struct{} + b map[string]interface{} + c map[string]struct { + d int + e bool + } +} + +func fill() { + _ := StructAnon{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_anon) +} +-- @fillStruct_anon/fillStruct_anon.go -- +@@ -13 +13,5 @@ +- _ := StructAnon{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_anon) ++ _ := StructAnon{ ++ a: struct{}{}, ++ b: map[string]interface{}{}, ++ c: map[string]struct{d int; e bool}{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_anon) +-- fillStruct_nested.go -- +package fillstruct + +type StructB struct { + StructC +} + +type StructC struct { + unexportedInt int +} + +func nested() { + c := StructB{ + StructC: StructC{}, //@codeactionedit("}", "refactor.rewrite.fillStruct", fill_nested) + } +} + +-- @fill_nested/fillStruct_nested.go -- +@@ -13 +13,3 @@ +- StructC: StructC{}, //@codeactionedit("}", "refactor.rewrite.fillStruct", fill_nested) ++ StructC: StructC{ ++ unexportedInt: 0, ++ }, //@codeactionedit("}", "refactor.rewrite.fillStruct", fill_nested) +-- fillStruct_package.go -- +package fillstruct + +import ( + h2 "net/http" + + "golang.org/lsptests/fillstruct/data" +) + +func unexported() { + a := data.B{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package1) + _ = h2.Client{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package2) +} +-- @fillStruct_package1/fillStruct_package.go -- +@@ -10 +10,3 @@ +- a := data.B{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package1) ++ a := data.B{ ++ ExportedInt: 0, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package1) +-- @fillStruct_package2/fillStruct_package.go -- +@@ -11 +11,7 @@ +- _ = h2.Client{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package2) ++ _ = h2.Client{ ++ Transport: nil, ++ CheckRedirect: func(req *h2.Request, via []*h2.Request) error { ++ }, ++ Jar: nil, ++ Timeout: 0, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package2) +-- fillStruct_partial.go -- +package fillstruct + +type StructPartialA struct { + PrefilledInt int + UnfilledInt int + StructPartialB +} + +type StructPartialB struct { + PrefilledInt int + UnfilledInt int +} + +func fill() { + a := StructPartialA{ + PrefilledInt: 5, + } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_partial1) + b := StructPartialB{ + /* this comment should disappear */ + PrefilledInt: 7, // This comment should be blown away. + /* As should + this one */ + } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_partial2) +} + +-- @fillStruct_partial1/fillStruct_partial.go -- +@@ -16 +16,3 @@ +- PrefilledInt: 5, ++ PrefilledInt: 5, ++ UnfilledInt: 0, ++ StructPartialB: StructPartialB{}, +-- @fillStruct_partial2/fillStruct_partial.go -- +@@ -19,4 +19,2 @@ +- /* this comment should disappear */ +- PrefilledInt: 7, // This comment should be blown away. +- /* As should +- this one */ ++ PrefilledInt: 7, ++ UnfilledInt: 0, +-- fillStruct_spaces.go -- +package fillstruct + +type StructD struct { + ExportedIntField int +} + +func spaces() { + d := StructD{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_spaces) +} + +-- @fillStruct_spaces/fillStruct_spaces.go -- +@@ -8 +8,3 @@ +- d := StructD{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_spaces) ++ d := StructD{ ++ ExportedIntField: 0, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_spaces) +-- fillStruct_unsafe.go -- +package fillstruct + +import "unsafe" + +type unsafeStruct struct { + x int + p unsafe.Pointer +} + +func fill() { + _ := unsafeStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_unsafe) +} + +-- @fillStruct_unsafe/fillStruct_unsafe.go -- +@@ -11 +11,4 @@ +- _ := unsafeStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_unsafe) ++ _ := unsafeStruct{ ++ x: 0, ++ p: nil, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_unsafe) +-- typeparams.go -- +package fillstruct + +type emptyStructWithTypeParams[A any] struct{} + +var _ = emptyStructWithTypeParams[int]{} // no suggested fix + +type basicStructWithTypeParams[T any] struct { + foo T +} + +var _ = basicStructWithTypeParams[int]{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams1) + +type twoArgStructWithTypeParams[F, B any] struct { + foo F + bar B +} + +var _ = twoArgStructWithTypeParams[string, int]{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams2) + +var _ = twoArgStructWithTypeParams[int, string]{ + bar: "bar", +} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams3) + +type nestedStructWithTypeParams struct { + bar string + basic basicStructWithTypeParams[int] +} + +var _ = nestedStructWithTypeParams{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams4) + +func _[T any]() { + type S struct{ t T } + _ = S{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams5) +} +-- @typeparams1/typeparams.go -- +@@ -11 +11,3 @@ +-var _ = basicStructWithTypeParams[int]{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams1) ++var _ = basicStructWithTypeParams[int]{ ++ foo: 0, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams1) +-- @typeparams2/typeparams.go -- +@@ -18 +18,4 @@ +-var _ = twoArgStructWithTypeParams[string, int]{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams2) ++var _ = twoArgStructWithTypeParams[string, int]{ ++ foo: "", ++ bar: 0, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams2) +-- @typeparams3/typeparams.go -- +@@ -21 +21 @@ ++ foo: 0, +-- @typeparams4/typeparams.go -- +@@ -29 +29,4 @@ +-var _ = nestedStructWithTypeParams{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams4) ++var _ = nestedStructWithTypeParams{ ++ bar: "", ++ basic: basicStructWithTypeParams{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams4) +-- @typeparams5/typeparams.go -- +@@ -33 +33,3 @@ +- _ = S{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams5) ++ _ = S{ ++ t: *new(T), ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams5) +-- issue63921.go -- +package fillstruct + +// Test for golang/go#63921: fillstruct panicked with invalid fields. +type invalidStruct struct { + F int + Undefined +} + +func _() { + // Note: the golden content for issue63921 is empty: fillstruct produces no + // edits, but does not panic. + invalidStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", issue63921) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_struct_resolve.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_struct_resolve.txt new file mode 100644 index 00000000000..24e7a9126e2 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_struct_resolve.txt @@ -0,0 +1,586 @@ +This test checks the behavior of the 'fill struct' code action, with resolve support. +See fill_struct.txt for same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/fillstruct + +go 1.18 + +-- data/data.go -- +package data + +type B struct { + ExportedInt int + unexportedInt int +} + +-- a.go -- +package fillstruct + +import ( + "golang.org/lsptests/fillstruct/data" +) + +type basicStruct struct { + foo int +} + +var _ = basicStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a1) + +type twoArgStruct struct { + foo int + bar string +} + +var _ = twoArgStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a2) + +type nestedStruct struct { + bar string + basic basicStruct +} + +var _ = nestedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a3) + +var _ = data.B{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a4) +-- @a1/a.go -- +@@ -11 +11,3 @@ +-var _ = basicStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a1) ++var _ = basicStruct{ ++ foo: 0, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a1) +-- @a2/a.go -- +@@ -18 +18,4 @@ +-var _ = twoArgStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a2) ++var _ = twoArgStruct{ ++ foo: 0, ++ bar: "", ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a2) +-- @a3/a.go -- +@@ -25 +25,4 @@ +-var _ = nestedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a3) ++var _ = nestedStruct{ ++ bar: "", ++ basic: basicStruct{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a3) +-- @a4/a.go -- +@@ -27 +27,3 @@ +-var _ = data.B{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a4) ++var _ = data.B{ ++ ExportedInt: 0, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a4) +-- a2.go -- +package fillstruct + +type typedStruct struct { + m map[string]int + s []int + c chan int + c1 <-chan int + a [2]string +} + +var _ = typedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a21) + +type funStruct struct { + fn func(i int) int +} + +var _ = funStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a22) + +type funStructComplex struct { + fn func(i int, s string) (string, int) +} + +var _ = funStructComplex{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a23) + +type funStructEmpty struct { + fn func() +} + +var _ = funStructEmpty{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a24) + +-- @a21/a2.go -- +@@ -11 +11,7 @@ +-var _ = typedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a21) ++var _ = typedStruct{ ++ m: map[string]int{}, ++ s: []int{}, ++ c: make(chan int), ++ c1: make(<-chan int), ++ a: [2]string{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a21) +-- @a22/a2.go -- +@@ -17 +17,4 @@ +-var _ = funStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a22) ++var _ = funStruct{ ++ fn: func(i int) int { ++ }, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a22) +-- @a23/a2.go -- +@@ -23 +23,4 @@ +-var _ = funStructComplex{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a23) ++var _ = funStructComplex{ ++ fn: func(i int, s string) (string, int) { ++ }, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a23) +-- @a24/a2.go -- +@@ -29 +29,4 @@ +-var _ = funStructEmpty{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a24) ++var _ = funStructEmpty{ ++ fn: func() { ++ }, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a24) +-- a3.go -- +package fillstruct + +import ( + "go/ast" + "go/token" +) + +type Foo struct { + A int +} + +type Bar struct { + X *Foo + Y *Foo +} + +var _ = Bar{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a31) + +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{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a32) + +type pointerBuiltinStruct struct { + b *bool + s *string + i *int +} + +var _ = pointerBuiltinStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a33) + +var _ = []ast.BasicLit{ + {}, //@codeactionedit("}", "refactor.rewrite.fillStruct", a34) +} + +var _ = []ast.BasicLit{{}} //@codeactionedit("}", "refactor.rewrite.fillStruct", a35) +-- @a31/a3.go -- +@@ -17 +17,4 @@ +-var _ = Bar{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a31) ++var _ = Bar{ ++ X: &Foo{}, ++ Y: &Foo{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a31) +-- @a32/a3.go -- +@@ -28 +28,9 @@ +-var _ = importedStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a32) ++var _ = importedStruct{ ++ m: map[*ast.CompositeLit]ast.Field{}, ++ s: []ast.BadExpr{}, ++ a: [3]token.Token{}, ++ c: make(chan ast.EmptyStmt), ++ fn: func(ast_decl ast.DeclStmt) ast.Ellipsis { ++ }, ++ st: ast.CompositeLit{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a32) +-- @a33/a3.go -- +@@ -36 +36,5 @@ +-var _ = pointerBuiltinStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a33) ++var _ = pointerBuiltinStruct{ ++ b: new(bool), ++ s: new(string), ++ i: new(int), ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", a33) +-- @a34/a3.go -- +@@ -39 +39,5 @@ +- {}, //@codeactionedit("}", "refactor.rewrite.fillStruct", a34) ++ { ++ ValuePos: 0, ++ Kind: 0, ++ Value: "", ++ }, //@codeactionedit("}", "refactor.rewrite.fillStruct", a34) +-- @a35/a3.go -- +@@ -42 +42,5 @@ +-var _ = []ast.BasicLit{{}} //@codeactionedit("}", "refactor.rewrite.fillStruct", a35) ++var _ = []ast.BasicLit{{ ++ ValuePos: 0, ++ Kind: 0, ++ Value: "", ++}} //@codeactionedit("}", "refactor.rewrite.fillStruct", a35) +-- a4.go -- +package fillstruct + +import "go/ast" + +type iStruct struct { + X int +} + +type sStruct struct { + str string +} + +type multiFill struct { + num int + strin string + arr []int +} + +type assignStruct struct { + n ast.Node +} + +func fill() { + var x int + var _ = iStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a41) + + var s string + var _ = sStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a42) + + var n int + _ = []int{} + if true { + arr := []int{1, 2} + } + var _ = multiFill{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a43) + + var node *ast.CompositeLit + var _ = assignStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a45) +} + +-- @a41/a4.go -- +@@ -25 +25,3 @@ +- var _ = iStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a41) ++ var _ = iStruct{ ++ X: x, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", a41) +-- @a42/a4.go -- +@@ -28 +28,3 @@ +- var _ = sStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a42) ++ var _ = sStruct{ ++ str: s, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", a42) +-- @a43/a4.go -- +@@ -35 +35,5 @@ +- var _ = multiFill{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a43) ++ var _ = multiFill{ ++ num: n, ++ strin: s, ++ arr: []int{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", a43) +-- @a45/a4.go -- +@@ -38 +38,3 @@ +- var _ = assignStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", a45) ++ var _ = assignStruct{ ++ n: node, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", a45) +-- fillStruct.go -- +package fillstruct + +type StructA struct { + unexportedIntField int + ExportedIntField int + MapA map[int]string + Array []int + StructB +} + +type StructA2 struct { + B *StructB +} + +type StructA3 struct { + B StructB +} + +func fill() { + a := StructA{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct1) + b := StructA2{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct2) + c := StructA3{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct3) + if true { + _ = StructA3{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct4) + } +} + +-- @fillStruct1/fillStruct.go -- +@@ -20 +20,7 @@ +- a := StructA{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct1) ++ a := StructA{ ++ unexportedIntField: 0, ++ ExportedIntField: 0, ++ MapA: map[int]string{}, ++ Array: []int{}, ++ StructB: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct1) +-- @fillStruct2/fillStruct.go -- +@@ -21 +21,3 @@ +- b := StructA2{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct2) ++ b := StructA2{ ++ B: &StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct2) +-- @fillStruct3/fillStruct.go -- +@@ -22 +22,3 @@ +- c := StructA3{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct3) ++ c := StructA3{ ++ B: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct3) +-- @fillStruct4/fillStruct.go -- +@@ -24 +24,3 @@ +- _ = StructA3{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct4) ++ _ = StructA3{ ++ B: StructB{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct4) +-- fillStruct_anon.go -- +package fillstruct + +type StructAnon struct { + a struct{} + b map[string]interface{} + c map[string]struct { + d int + e bool + } +} + +func fill() { + _ := StructAnon{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_anon) +} +-- @fillStruct_anon/fillStruct_anon.go -- +@@ -13 +13,5 @@ +- _ := StructAnon{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_anon) ++ _ := StructAnon{ ++ a: struct{}{}, ++ b: map[string]interface{}{}, ++ c: map[string]struct{d int; e bool}{}, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_anon) +-- fillStruct_nested.go -- +package fillstruct + +type StructB struct { + StructC +} + +type StructC struct { + unexportedInt int +} + +func nested() { + c := StructB{ + StructC: StructC{}, //@codeactionedit("}", "refactor.rewrite.fillStruct", fill_nested) + } +} + +-- @fill_nested/fillStruct_nested.go -- +@@ -13 +13,3 @@ +- StructC: StructC{}, //@codeactionedit("}", "refactor.rewrite.fillStruct", fill_nested) ++ StructC: StructC{ ++ unexportedInt: 0, ++ }, //@codeactionedit("}", "refactor.rewrite.fillStruct", fill_nested) +-- fillStruct_package.go -- +package fillstruct + +import ( + h2 "net/http" + + "golang.org/lsptests/fillstruct/data" +) + +func unexported() { + a := data.B{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package1) + _ = h2.Client{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package2) +} +-- @fillStruct_package1/fillStruct_package.go -- +@@ -10 +10,3 @@ +- a := data.B{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package1) ++ a := data.B{ ++ ExportedInt: 0, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package1) +-- @fillStruct_package2/fillStruct_package.go -- +@@ -11 +11,7 @@ +- _ = h2.Client{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package2) ++ _ = h2.Client{ ++ Transport: nil, ++ CheckRedirect: func(req *h2.Request, via []*h2.Request) error { ++ }, ++ Jar: nil, ++ Timeout: 0, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_package2) +-- fillStruct_partial.go -- +package fillstruct + +type StructPartialA struct { + PrefilledInt int + UnfilledInt int + StructPartialB +} + +type StructPartialB struct { + PrefilledInt int + UnfilledInt int +} + +func fill() { + a := StructPartialA{ + PrefilledInt: 5, + } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_partial1) + b := StructPartialB{ + /* this comment should disappear */ + PrefilledInt: 7, // This comment should be blown away. + /* As should + this one */ + } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_partial2) +} + +-- @fillStruct_partial1/fillStruct_partial.go -- +@@ -16 +16,3 @@ +- PrefilledInt: 5, ++ PrefilledInt: 5, ++ UnfilledInt: 0, ++ StructPartialB: StructPartialB{}, +-- @fillStruct_partial2/fillStruct_partial.go -- +@@ -19,4 +19,2 @@ +- /* this comment should disappear */ +- PrefilledInt: 7, // This comment should be blown away. +- /* As should +- this one */ ++ PrefilledInt: 7, ++ UnfilledInt: 0, +-- fillStruct_spaces.go -- +package fillstruct + +type StructD struct { + ExportedIntField int +} + +func spaces() { + d := StructD{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_spaces) +} + +-- @fillStruct_spaces/fillStruct_spaces.go -- +@@ -8 +8,3 @@ +- d := StructD{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_spaces) ++ d := StructD{ ++ ExportedIntField: 0, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_spaces) +-- fillStruct_unsafe.go -- +package fillstruct + +import "unsafe" + +type unsafeStruct struct { + x int + p unsafe.Pointer +} + +func fill() { + _ := unsafeStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_unsafe) +} + +-- @fillStruct_unsafe/fillStruct_unsafe.go -- +@@ -11 +11,4 @@ +- _ := unsafeStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_unsafe) ++ _ := unsafeStruct{ ++ x: 0, ++ p: nil, ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", fillStruct_unsafe) +-- typeparams.go -- +package fillstruct + +type emptyStructWithTypeParams[A any] struct{} + +var _ = emptyStructWithTypeParams[int]{} // no suggested fix + +type basicStructWithTypeParams[T any] struct { + foo T +} + +var _ = basicStructWithTypeParams[int]{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams1) + +type twoArgStructWithTypeParams[F, B any] struct { + foo F + bar B +} + +var _ = twoArgStructWithTypeParams[string, int]{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams2) + +var _ = twoArgStructWithTypeParams[int, string]{ + bar: "bar", +} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams3) + +type nestedStructWithTypeParams struct { + bar string + basic basicStructWithTypeParams[int] +} + +var _ = nestedStructWithTypeParams{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams4) + +func _[T any]() { + type S struct{ t T } + _ = S{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams5) +} +-- @typeparams1/typeparams.go -- +@@ -11 +11,3 @@ +-var _ = basicStructWithTypeParams[int]{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams1) ++var _ = basicStructWithTypeParams[int]{ ++ foo: 0, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams1) +-- @typeparams2/typeparams.go -- +@@ -18 +18,4 @@ +-var _ = twoArgStructWithTypeParams[string, int]{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams2) ++var _ = twoArgStructWithTypeParams[string, int]{ ++ foo: "", ++ bar: 0, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams2) +-- @typeparams3/typeparams.go -- +@@ -21 +21 @@ ++ foo: 0, +-- @typeparams4/typeparams.go -- +@@ -29 +29,4 @@ +-var _ = nestedStructWithTypeParams{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams4) ++var _ = nestedStructWithTypeParams{ ++ bar: "", ++ basic: basicStructWithTypeParams{}, ++} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams4) +-- @typeparams5/typeparams.go -- +@@ -33 +33,3 @@ +- _ = S{} //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams5) ++ _ = S{ ++ t: *new(T), ++ } //@codeactionedit("}", "refactor.rewrite.fillStruct", typeparams5) +-- issue63921.go -- +package fillstruct + +// Test for golang/go#63921: fillstruct panicked with invalid fields. +type invalidStruct struct { + F int + Undefined +} + +func _() { + // Note: the golden content for issue63921 is empty: fillstruct produces no + // edits, but does not panic. + invalidStruct{} //@codeactionedit("}", "refactor.rewrite.fillStruct", issue63921) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_switch.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_switch.txt new file mode 100644 index 00000000000..0d92b05fc41 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_switch.txt @@ -0,0 +1,105 @@ +This test checks the behavior of the 'fill switch' code action. +See fill_switch_resolve.txt for same test with resolve support. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/fillswitch + +go 1.18 + +-- data/data.go -- +package data + +type TypeB int + +const ( + TypeBOne TypeB = iota + TypeBTwo + TypeBThree +) + +-- a.go -- +package fillswitch + +import ( + "golang.org/lsptests/fillswitch/data" +) + +type typeA int + +const ( + typeAOne typeA = iota + typeATwo + typeAThree +) + +type notification interface { + isNotification() +} + +type notificationOne struct{} + +func (notificationOne) isNotification() {} + +type notificationTwo struct{} + +func (notificationTwo) isNotification() {} + +func doSwitch() { + var b data.TypeB + switch b { + case data.TypeBOne: //@codeactionedit(":", "refactor.rewrite.fillSwitch", a1) + } + + var a typeA + switch a { + case typeAThree: //@codeactionedit(":", "refactor.rewrite.fillSwitch", a2) + } + + var n notification + switch n.(type) { //@codeactionedit("{", "refactor.rewrite.fillSwitch", a3) + } + + switch nt := n.(type) { //@codeactionedit("{", "refactor.rewrite.fillSwitch", a4) + } + + var s struct { + a typeA + } + + switch s.a { + case typeAThree: //@codeactionedit(":", "refactor.rewrite.fillSwitch", a5) + } +} +-- @a1/a.go -- +@@ -31 +31,4 @@ ++ case data.TypeBThree: ++ case data.TypeBTwo: ++ default: ++ panic(fmt.Sprintf("unexpected data.TypeB: %#v", b)) +-- @a2/a.go -- +@@ -36 +36,4 @@ ++ case typeAOne: ++ case typeATwo: ++ default: ++ panic(fmt.Sprintf("unexpected fillswitch.typeA: %#v", a)) +-- @a3/a.go -- +@@ -40 +40,4 @@ ++ case notificationOne: ++ case notificationTwo: ++ default: ++ panic(fmt.Sprintf("unexpected fillswitch.notification: %#v", n)) +-- @a4/a.go -- +@@ -43 +43,4 @@ ++ case notificationOne: ++ case notificationTwo: ++ default: ++ panic(fmt.Sprintf("unexpected fillswitch.notification: %#v", nt)) +-- @a5/a.go -- +@@ -51 +51,4 @@ ++ case typeAOne: ++ case typeATwo: ++ default: ++ panic(fmt.Sprintf("unexpected fillswitch.typeA: %#v", s.a)) diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_switch_resolve.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_switch_resolve.txt new file mode 100644 index 00000000000..84464417b81 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/fill_switch_resolve.txt @@ -0,0 +1,116 @@ +This test checks the behavior of the 'fill switch' code action, with resolve support. +See fill_switch.txt for same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/fillswitch + +go 1.18 + +-- data/data.go -- +package data + +type TypeB int + +const ( + TypeBOne TypeB = iota + TypeBTwo + TypeBThree +) + +-- a.go -- +package fillswitch + +import ( + "golang.org/lsptests/fillswitch/data" +) + +type typeA int + +const ( + typeAOne typeA = iota + typeATwo + typeAThree +) + +type notification interface { + isNotification() +} + +type notificationOne struct{} + +func (notificationOne) isNotification() {} + +type notificationTwo struct{} + +func (notificationTwo) isNotification() {} + +func doSwitch() { + var b data.TypeB + switch b { + case data.TypeBOne: //@codeactionedit(":", "refactor.rewrite.fillSwitch", a1) + } + + var a typeA + switch a { + case typeAThree: //@codeactionedit(":", "refactor.rewrite.fillSwitch", a2) + } + + var n notification + switch n.(type) { //@codeactionedit("{", "refactor.rewrite.fillSwitch", a3) + } + + switch nt := n.(type) { //@codeactionedit("{", "refactor.rewrite.fillSwitch", a4) + } + + var s struct { + a typeA + } + + switch s.a { + case typeAThree: //@codeactionedit(":", "refactor.rewrite.fillSwitch", a5) + } +} +-- @a1/a.go -- +@@ -31 +31,4 @@ ++ case data.TypeBThree: ++ case data.TypeBTwo: ++ default: ++ panic(fmt.Sprintf("unexpected data.TypeB: %#v", b)) +-- @a2/a.go -- +@@ -36 +36,4 @@ ++ case typeAOne: ++ case typeATwo: ++ default: ++ panic(fmt.Sprintf("unexpected fillswitch.typeA: %#v", a)) +-- @a3/a.go -- +@@ -40 +40,4 @@ ++ case notificationOne: ++ case notificationTwo: ++ default: ++ panic(fmt.Sprintf("unexpected fillswitch.notification: %#v", n)) +-- @a4/a.go -- +@@ -43 +43,4 @@ ++ case notificationOne: ++ case notificationTwo: ++ default: ++ panic(fmt.Sprintf("unexpected fillswitch.notification: %#v", nt)) +-- @a5/a.go -- +@@ -51 +51,4 @@ ++ case typeAOne: ++ case typeATwo: ++ default: ++ panic(fmt.Sprintf("unexpected fillswitch.typeA: %#v", s.a)) diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/functionextraction.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/functionextraction.txt new file mode 100644 index 00000000000..1c65fcd2329 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/functionextraction.txt @@ -0,0 +1,583 @@ +This test verifies various behaviors of function extraction. + +-- go.mod -- +module mod.test/extract + +go 1.18 + +-- basic.go -- +package extract + +func _() { //@codeaction("{", closeBracket, "refactor.extract.function", outer) + a := 1 //@codeaction("a", end, "refactor.extract.function", inner) + _ = a + 4 //@loc(end, "4") +} //@loc(closeBracket, "}") + +-- @inner/basic.go -- +package extract + +func _() { //@codeaction("{", closeBracket, "refactor.extract.function", outer) + //@codeaction("a", end, "refactor.extract.function", inner) + newFunction() //@loc(end, "4") +} + +func newFunction() { + a := 1 + _ = a + 4 +} //@loc(closeBracket, "}") + +-- @outer/basic.go -- +package extract + +func _() { //@codeaction("{", closeBracket, "refactor.extract.function", outer) + //@codeaction("a", end, "refactor.extract.function", inner) + newFunction() //@loc(end, "4") +} + +func newFunction() { + a := 1 + _ = a + 4 +} //@loc(closeBracket, "}") + +-- return.go -- +package extract + +func _() bool { + x := 1 + if x == 0 { //@codeaction("if", ifend, "refactor.extract.function", return) + return true + } //@loc(ifend, "}") + return false +} + +-- @return/return.go -- +package extract + +func _() bool { + x := 1 + //@codeaction("if", ifend, "refactor.extract.function", return) + shouldReturn, returnValue := newFunction(x) + if shouldReturn { + return returnValue + } //@loc(ifend, "}") + return false +} + +func newFunction(x int) (bool, bool) { + if x == 0 { + return true, true + } + return false, false +} + +-- return_nonnested.go -- +package extract + +func _() bool { + x := 1 //@codeaction("x", rnnEnd, "refactor.extract.function", rnn) + if x == 0 { + return true + } + return false //@loc(rnnEnd, "false") +} + +-- @rnn/return_nonnested.go -- +package extract + +func _() bool { + //@codeaction("x", rnnEnd, "refactor.extract.function", rnn) + return newFunction() //@loc(rnnEnd, "false") +} + +func newFunction() bool { + x := 1 + if x == 0 { + return true + } + return false +} + +-- return_complex.go -- +package extract + +import "fmt" + +func _() (int, string, error) { + x := 1 + y := "hello" + z := "bye" //@codeaction("z", rcEnd, "refactor.extract.function", rc) + if y == z { + return x, y, fmt.Errorf("same") + } else if false { + z = "hi" + return x, z, nil + } //@loc(rcEnd, "}") + return x, z, nil +} + +-- @rc/return_complex.go -- +package extract + +import "fmt" + +func _() (int, string, error) { + x := 1 + y := "hello" + //@codeaction("z", rcEnd, "refactor.extract.function", rc) + z, shouldReturn, returnValue, returnValue1, returnValue2 := newFunction(y, x) + if shouldReturn { + return returnValue, returnValue1, returnValue2 + } //@loc(rcEnd, "}") + return x, z, nil +} + +func newFunction(y string, x int) (string, bool, int, string, error) { + z := "bye" + if y == z { + return "", true, x, y, fmt.Errorf("same") + } else if false { + z = "hi" + return "", true, x, z, nil + } + return z, false, 0, "", nil +} + +-- return_complex_nonnested.go -- +package extract + +import "fmt" + +func _() (int, string, error) { + x := 1 + y := "hello" + z := "bye" //@codeaction("z", rcnnEnd, "refactor.extract.function", rcnn) + if y == z { + return x, y, fmt.Errorf("same") + } else if false { + z = "hi" + return x, z, nil + } + return x, z, nil //@loc(rcnnEnd, "nil") +} + +-- @rcnn/return_complex_nonnested.go -- +package extract + +import "fmt" + +func _() (int, string, error) { + x := 1 + y := "hello" + //@codeaction("z", rcnnEnd, "refactor.extract.function", rcnn) + return newFunction(y, x) //@loc(rcnnEnd, "nil") +} + +func newFunction(y string, x int) (int, string, error) { + z := "bye" + if y == z { + return x, y, fmt.Errorf("same") + } else if false { + z = "hi" + return x, z, nil + } + return x, z, nil +} + +-- return_func_lit.go -- +package extract + +import "go/ast" + +func _() { + ast.Inspect(ast.NewIdent("a"), func(n ast.Node) bool { + if n == nil { //@codeaction("if", rflEnd, "refactor.extract.function", rfl) + return true + } //@loc(rflEnd, "}") + return false + }) +} + +-- @rfl/return_func_lit.go -- +package extract + +import "go/ast" + +func _() { + ast.Inspect(ast.NewIdent("a"), func(n ast.Node) bool { + //@codeaction("if", rflEnd, "refactor.extract.function", rfl) + shouldReturn, returnValue := newFunction(n) + if shouldReturn { + return returnValue + } //@loc(rflEnd, "}") + return false + }) +} + +func newFunction(n ast.Node) (bool, bool) { + if n == nil { + return true, true + } + return false, false +} + +-- return_func_lit_nonnested.go -- +package extract + +import "go/ast" + +func _() { + ast.Inspect(ast.NewIdent("a"), func(n ast.Node) bool { + if n == nil { //@codeaction("if", rflnnEnd, "refactor.extract.function", rflnn) + return true + } + return false //@loc(rflnnEnd, "false") + }) +} + +-- @rflnn/return_func_lit_nonnested.go -- +package extract + +import "go/ast" + +func _() { + ast.Inspect(ast.NewIdent("a"), func(n ast.Node) bool { + //@codeaction("if", rflnnEnd, "refactor.extract.function", rflnn) + return newFunction(n) //@loc(rflnnEnd, "false") + }) +} + +func newFunction(n ast.Node) bool { + if n == nil { + return true + } + return false +} + +-- return_init.go -- +package extract + +func _() string { + x := 1 + if x == 0 { //@codeaction("if", riEnd, "refactor.extract.function", ri) + x = 3 + return "a" + } //@loc(riEnd, "}") + x = 2 + return "b" +} + +-- @ri/return_init.go -- +package extract + +func _() string { + x := 1 + //@codeaction("if", riEnd, "refactor.extract.function", ri) + shouldReturn, returnValue := newFunction(x) + if shouldReturn { + return returnValue + } //@loc(riEnd, "}") + x = 2 + return "b" +} + +func newFunction(x int) (bool, string) { + if x == 0 { + x = 3 + return true, "a" + } + return false, "" +} + +-- return_init_nonnested.go -- +package extract + +func _() string { + x := 1 + if x == 0 { //@codeaction("if", rinnEnd, "refactor.extract.function", rinn) + x = 3 + return "a" + } + x = 2 + return "b" //@loc(rinnEnd, "\"b\"") +} + +-- @rinn/return_init_nonnested.go -- +package extract + +func _() string { + x := 1 + //@codeaction("if", rinnEnd, "refactor.extract.function", rinn) + return newFunction(x) //@loc(rinnEnd, "\"b\"") +} + +func newFunction(x int) string { + if x == 0 { + x = 3 + return "a" + } + x = 2 + return "b" +} + +-- args_returns.go -- +package extract + +func _() { + a := 1 + a = 5 //@codeaction("a", araend, "refactor.extract.function", ara) + a = a + 2 //@loc(araend, "2") + + b := a * 2 //@codeaction("b", arbend, "refactor.extract.function", arb) + _ = b + 4 //@loc(arbend, "4") +} + +-- @ara/args_returns.go -- +package extract + +func _() { + a := 1 + //@codeaction("a", araend, "refactor.extract.function", ara) + a = newFunction(a) //@loc(araend, "2") + + b := a * 2 //@codeaction("b", arbend, "refactor.extract.function", arb) + _ = b + 4 //@loc(arbend, "4") +} + +func newFunction(a int) int { + a = 5 + a = a + 2 + return a +} + +-- @arb/args_returns.go -- +package extract + +func _() { + a := 1 + a = 5 //@codeaction("a", araend, "refactor.extract.function", ara) + a = a + 2 //@loc(araend, "2") + + //@codeaction("b", arbend, "refactor.extract.function", arb) + newFunction(a) //@loc(arbend, "4") +} + +func newFunction(a int) { + b := a * 2 + _ = b + 4 +} + +-- scope.go -- +package extract + +func _() { + newFunction := 1 + a := newFunction //@codeaction("a", "newFunction", "refactor.extract.function", scope) + _ = a // avoid diagnostic +} + +func newFunction1() int { + return 1 +} + +-- @scope/scope.go -- +package extract + +func _() { + newFunction := 1 + a := newFunction2(newFunction) //@codeaction("a", "newFunction", "refactor.extract.function", scope) + _ = a // avoid diagnostic +} + +func newFunction2(newFunction int) int { + a := newFunction + return a +} + +func newFunction1() int { + return 1 +} + +-- smart_initialization.go -- +package extract + +func _() { + var a []int + a = append(a, 2) //@codeaction("a", siEnd, "refactor.extract.function", si) + b := 4 //@loc(siEnd, "4") + a = append(a, b) +} + +-- @si/smart_initialization.go -- +package extract + +func _() { + var a []int + //@codeaction("a", siEnd, "refactor.extract.function", si) + a, b := newFunction(a) //@loc(siEnd, "4") + a = append(a, b) +} + +func newFunction(a []int) ([]int, int) { + a = append(a, 2) + b := 4 + return a, b +} + +-- smart_return.go -- +package extract + +func _() { + var b []int + var a int + a = 2 //@codeaction("a", srEnd, "refactor.extract.function", sr) + b = []int{} + b = append(b, a) //@loc(srEnd, ")") + b[0] = 1 +} + +-- @sr/smart_return.go -- +package extract + +func _() { + var b []int + var a int + //@codeaction("a", srEnd, "refactor.extract.function", sr) + b = newFunction(a, b) //@loc(srEnd, ")") + b[0] = 1 +} + +func newFunction(a int, b []int) []int { + a = 2 + b = []int{} + b = append(b, a) + return b +} + +-- unnecessary_param.go -- +package extract + +func _() { + var b []int + a := 2 //@codeaction("a", upEnd, "refactor.extract.function", up) + b = []int{} + b = append(b, a) //@loc(upEnd, ")") + b[0] = 1 + if a == 2 { + return + } +} + +-- @up/unnecessary_param.go -- +package extract + +func _() { + var b []int + //@codeaction("a", upEnd, "refactor.extract.function", up) + a, b := newFunction(b) //@loc(upEnd, ")") + b[0] = 1 + if a == 2 { + return + } +} + +func newFunction(b []int) (int, []int) { + a := 2 + b = []int{} + b = append(b, a) + return a, b +} + +-- comment.go -- +package extract + +func _() { + a := /* comment in the middle of a line */ 1 //@codeaction("a", commentEnd, "refactor.extract.function", comment1) + // Comment on its own line //@codeaction("Comment", commentEnd, "refactor.extract.function", comment2) + _ = a + 4 //@loc(commentEnd, "4"),codeaction("_", lastComment, "refactor.extract.function", comment3) + // Comment right after 3 + 4 + + // Comment after with space //@loc(lastComment, "Comment") +} + +-- @comment1/comment.go -- +package extract + +func _() { + /* comment in the middle of a line */ + //@codeaction("a", commentEnd, "refactor.extract.function", comment1) + // Comment on its own line //@codeaction("Comment", commentEnd, "refactor.extract.function", comment2) + newFunction() //@loc(commentEnd, "4"),codeaction("_", lastComment, "refactor.extract.function", comment3) + // Comment right after 3 + 4 + + // Comment after with space //@loc(lastComment, "Comment") +} + +func newFunction() { + a := 1 + + _ = a + 4 +} + +-- @comment2/comment.go -- +package extract + +func _() { + a := /* comment in the middle of a line */ 1 //@codeaction("a", commentEnd, "refactor.extract.function", comment1) + // Comment on its own line //@codeaction("Comment", commentEnd, "refactor.extract.function", comment2) + newFunction(a) //@loc(commentEnd, "4"),codeaction("_", lastComment, "refactor.extract.function", comment3) + // Comment right after 3 + 4 + + // Comment after with space //@loc(lastComment, "Comment") +} + +func newFunction(a int) { + _ = a + 4 +} + +-- @comment3/comment.go -- +package extract + +func _() { + a := /* comment in the middle of a line */ 1 //@codeaction("a", commentEnd, "refactor.extract.function", comment1) + // Comment on its own line //@codeaction("Comment", commentEnd, "refactor.extract.function", comment2) + newFunction(a) //@loc(commentEnd, "4"),codeaction("_", lastComment, "refactor.extract.function", comment3) + // Comment right after 3 + 4 + + // Comment after with space //@loc(lastComment, "Comment") +} + +func newFunction(a int) { + _ = a + 4 +} + +-- redefine.go -- +package extract + +import "strconv" + +func _() { + i, err := strconv.Atoi("1") + u, err := strconv.Atoi("2") //@codeaction("u", ")", "refactor.extract.function", redefine) + if i == u || err == nil { + return + } +} + +-- @redefine/redefine.go -- +package extract + +import "strconv" + +func _() { + i, err := strconv.Atoi("1") + u, err := newFunction() //@codeaction("u", ")", "refactor.extract.function", redefine) + if i == u || err == nil { + return + } +} + +func newFunction() (int, error) { + u, err := strconv.Atoi("2") + return u, err +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/functionextraction_issue44813.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/functionextraction_issue44813.txt new file mode 100644 index 00000000000..aaca44d6c7a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/functionextraction_issue44813.txt @@ -0,0 +1,42 @@ +This test verifies the fix for golang/go#44813: extraction failure when there +are blank identifiers. + +-- go.mod -- +module mod.test/extract + +go 1.18 + +-- p.go -- +package extract + +import "fmt" + +func main() { + x := []rune{} //@codeaction("x", end, "refactor.extract.function", ext) + s := "HELLO" + for _, c := range s { + x = append(x, c) + } //@loc(end, "}") + fmt.Printf("%x\n", x) +} + +-- @ext/p.go -- +package extract + +import "fmt" + +func main() { + //@codeaction("x", end, "refactor.extract.function", ext) + x := newFunction() //@loc(end, "}") + fmt.Printf("%x\n", x) +} + +func newFunction() []rune { + x := []rune{} + s := "HELLO" + for _, c := range s { + x = append(x, c) + } + return x +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/grouplines.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/grouplines.txt new file mode 100644 index 00000000000..1f14360d2e9 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/grouplines.txt @@ -0,0 +1,206 @@ +This test exercises the refactoring of putting arguments, return values, and composite literal elements into a +single line. + +-- go.mod -- +module unused.mod + +go 1.18 + +-- func_arg/func_arg.go -- +package func_arg + +func A( + a string, + b, c int64, + x int /*@codeaction("x", "x", "refactor.rewrite.joinLines", func_arg)*/, + y int, +) (r1 string, r2, r3 int64, r4 int, r5 int) { + return a, b, c, x, y +} + +-- @func_arg/func_arg/func_arg.go -- +package func_arg + +func A(a string, b, c int64, x int /*@codeaction("x", "x", "refactor.rewrite.joinLines", func_arg)*/, y int) (r1 string, r2, r3 int64, r4 int, r5 int) { + return a, b, c, x, y +} + +-- func_ret/func_ret.go -- +package func_ret + +func A(a string, b, c int64, x int, y int) ( + r1 string /*@codeaction("r1", "r1", "refactor.rewrite.joinLines", func_ret)*/, + r2, r3 int64, + r4 int, + r5 int, +) { + return a, b, c, x, y +} + +-- @func_ret/func_ret/func_ret.go -- +package func_ret + +func A(a string, b, c int64, x int, y int) (r1 string /*@codeaction("r1", "r1", "refactor.rewrite.joinLines", func_ret)*/, r2, r3 int64, r4 int, r5 int) { + return a, b, c, x, y +} + +-- functype_arg/functype_arg.go -- +package functype_arg + +type A func( + a string, + b, c int64, + x int /*@codeaction("x", "x", "refactor.rewrite.joinLines", functype_arg)*/, + y int, +) (r1 string, r2, r3 int64, r4 int, r5 int) + +-- @functype_arg/functype_arg/functype_arg.go -- +package functype_arg + +type A func(a string, b, c int64, x int /*@codeaction("x", "x", "refactor.rewrite.joinLines", functype_arg)*/, y int) (r1 string, r2, r3 int64, r4 int, r5 int) + +-- functype_ret/functype_ret.go -- +package functype_ret + +type A func(a string, b, c int64, x int, y int) ( + r1 string /*@codeaction("r1", "r1", "refactor.rewrite.joinLines", functype_ret)*/, + r2, r3 int64, + r4 int, + r5 int, +) + +-- @functype_ret/functype_ret/functype_ret.go -- +package functype_ret + +type A func(a string, b, c int64, x int, y int) (r1 string /*@codeaction("r1", "r1", "refactor.rewrite.joinLines", functype_ret)*/, r2, r3 int64, r4 int, r5 int) + +-- func_call/func_call.go -- +package func_call + +import "fmt" + +func a() { + fmt.Println( + 1 /*@codeaction("1", "1", "refactor.rewrite.joinLines", func_call)*/, + 2, + 3, + fmt.Sprintf("hello %d", 4), + ) +} + +-- @func_call/func_call/func_call.go -- +package func_call + +import "fmt" + +func a() { + fmt.Println(1 /*@codeaction("1", "1", "refactor.rewrite.joinLines", func_call)*/, 2, 3, fmt.Sprintf("hello %d", 4)) +} + +-- indent/indent.go -- +package indent + +import "fmt" + +func a() { + fmt.Println( + 1, + 2, + 3, + fmt.Sprintf( + "hello %d" /*@codeaction("hello", "hello", "refactor.rewrite.joinLines", indent)*/, + 4, + )) +} + +-- @indent/indent/indent.go -- +package indent + +import "fmt" + +func a() { + fmt.Println( + 1, + 2, + 3, + fmt.Sprintf("hello %d" /*@codeaction("hello", "hello", "refactor.rewrite.joinLines", indent)*/, 4)) +} + +-- structelts/structelts.go -- +package structelts + +type A struct{ + a int + b int +} + +func a() { + _ = A{ + a: 1, + b: 2 /*@codeaction("b", "b", "refactor.rewrite.joinLines", structelts)*/, + } +} + +-- @structelts/structelts/structelts.go -- +package structelts + +type A struct{ + a int + b int +} + +func a() { + _ = A{a: 1, b: 2 /*@codeaction("b", "b", "refactor.rewrite.joinLines", structelts)*/} +} + +-- sliceelts/sliceelts.go -- +package sliceelts + +func a() { + _ = []int{ + 1 /*@codeaction("1", "1", "refactor.rewrite.joinLines", sliceelts)*/, + 2, + } +} + +-- @sliceelts/sliceelts/sliceelts.go -- +package sliceelts + +func a() { + _ = []int{1 /*@codeaction("1", "1", "refactor.rewrite.joinLines", sliceelts)*/, 2} +} + +-- mapelts/mapelts.go -- +package mapelts + +func a() { + _ = map[string]int{ + "a": 1 /*@codeaction("1", "1", "refactor.rewrite.joinLines", mapelts)*/, + "b": 2, + } +} + +-- @mapelts/mapelts/mapelts.go -- +package mapelts + +func a() { + _ = map[string]int{"a": 1 /*@codeaction("1", "1", "refactor.rewrite.joinLines", mapelts)*/, "b": 2} +} + +-- starcomment/starcomment.go -- +package starcomment + +func A( + /*1*/ x /*2*/ string /*3*/ /*@codeaction("x", "x", "refactor.rewrite.joinLines", starcomment)*/, + /*4*/ y /*5*/ int /*6*/, +) (string, int) { + return x, y +} + +-- @starcomment/starcomment/starcomment.go -- +package starcomment + +func A(/*1*/ x /*2*/ string /*3*/ /*@codeaction("x", "x", "refactor.rewrite.joinLines", starcomment)*/, /*4*/ y /*5*/ int /*6*/) (string, int) { + return x, y +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/import-shadows-builtin.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/import-shadows-builtin.txt new file mode 100644 index 00000000000..aeb86a22686 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/import-shadows-builtin.txt @@ -0,0 +1,55 @@ +This is a regression test for bug #63592 in "organize imports" whereby +the new imports would shadow predeclared names. + +In the original example, the conflict was between predeclared error +type and the unfortunately named package github.com/coreos/etcd/error, +but this example uses a package with the ludicrous name of complex128. + +The new behavior is that we will not attempt to import packages +that shadow predeclared names. (Ideally we would do that only if +the predeclared name is actually referenced in the file, which +complex128 happens to be in this example, but that's a trickier +analysis than the internal/imports package is game for.) + +The name complex127 works as usual. + +-- go.mod -- +module example.com +go 1.18 + +-- complex128/a.go -- +package complex128 + +var V int + +-- complex127/a.go -- +package complex127 + +var V int + +-- main.go -- +package main + +import () //@codeaction("import", "", "source.organizeImports", out) + +func main() { + complex128.V() //@diag("V", re"type complex128 has no field") + complex127.V() //@diag("complex127", re"(undeclared|undefined)") +} + +func _() { + var _ complex128 = 1 + 2i +} +-- @out/main.go -- +package main + +import "example.com/complex127" //@codeaction("import", "", "source.organizeImports", out) + +func main() { + complex128.V() //@diag("V", re"type complex128 has no field") + complex127.V() //@diag("complex127", re"(undeclared|undefined)") +} + +func _() { + var _ complex128 = 1 + 2i +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/imports.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/imports.txt new file mode 100644 index 00000000000..3d058fb36a1 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/imports.txt @@ -0,0 +1,175 @@ +This test verifies the behavior of the 'source.organizeImports' code action. + +-- go.mod -- +module mod.test/imports + +go 1.18 + +-- add.go -- +package imports //@codeaction("imports", "", "source.organizeImports", add) + +import ( + "fmt" +) + +func _() { + fmt.Println("") + bytes.NewBuffer(nil) //@diag("bytes", re"(undeclared|undefined)") +} + +-- @add/add.go -- +package imports //@codeaction("imports", "", "source.organizeImports", add) + +import ( + "bytes" + "fmt" +) + +func _() { + fmt.Println("") + bytes.NewBuffer(nil) //@diag("bytes", re"(undeclared|undefined)") +} + +-- good.go -- +package imports //@codeactionerr("imports", "", "source.organizeImports", re"found 0 CodeActions") + +import "fmt" + +func _() { +fmt.Println("") +} + +-- issue35458.go -- + + + + + +// package doc +package imports //@codeaction("imports", "", "source.organizeImports", issue35458) + + + + + + +func _() { + println("Hello, world!") +} + + + + + + + + +-- @issue35458/issue35458.go -- +// package doc +package imports //@codeaction("imports", "", "source.organizeImports", issue35458) + + + + + + +func _() { + println("Hello, world!") +} + + + + + + + + +-- multi.go -- +package imports //@codeaction("imports", "", "source.organizeImports", multi) + +import "fmt" + +import "bytes" //@diag("\"bytes\"", re"not used") + +func _() { + fmt.Println("") +} + +-- @multi/multi.go -- +package imports //@codeaction("imports", "", "source.organizeImports", multi) + +import "fmt" + +//@diag("\"bytes\"", re"not used") + +func _() { + fmt.Println("") +} + +-- needs.go -- +package imports //@codeaction("package", "", "source.organizeImports", needs) + +func goodbye() { + fmt.Printf("HI") //@diag("fmt", re"(undeclared|undefined)") + log.Printf("byeeeee") //@diag("log", re"(undeclared|undefined)") +} + +-- @needs/needs.go -- +package imports //@codeaction("package", "", "source.organizeImports", needs) + +import ( + "fmt" + "log" +) + +func goodbye() { + fmt.Printf("HI") //@diag("fmt", re"(undeclared|undefined)") + log.Printf("byeeeee") //@diag("log", re"(undeclared|undefined)") +} + +-- remove.go -- +package imports //@codeaction("package", "", "source.organizeImports", remove) + +import ( + "bytes" //@diag("\"bytes\"", re"not used") + "fmt" +) + +func _() { + fmt.Println("") +} + +-- @remove/remove.go -- +package imports //@codeaction("package", "", "source.organizeImports", remove) + +import ( + "fmt" +) + +func _() { + fmt.Println("") +} + +-- removeall.go -- +package imports //@codeaction("package", "", "source.organizeImports", removeall) + +import ( + "bytes" //@diag("\"bytes\"", re"not used") + "fmt" //@diag("\"fmt\"", re"not used") + +) + +func _() { +} + +-- @removeall/removeall.go -- +package imports //@codeaction("package", "", "source.organizeImports", removeall) + +//@diag("\"fmt\"", re"not used") + +func _() { +} + +-- twolines.go -- +package imports +func main() {} //@codeactionerr("main", "", "source.organizeImports", re"found 0") diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/infertypeargs.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/infertypeargs.txt new file mode 100644 index 00000000000..b622efdc358 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/infertypeargs.txt @@ -0,0 +1,25 @@ +This test verifies the infertypeargs refactoring. + +-- go.mod -- +module mod.test/infertypeargs + +go 1.18 + +-- p.go -- +package infertypeargs + +func app[S interface{ ~[]E }, E interface{}](s S, e E) S { + return append(s, e) +} + +func _() { + _ = app[[]int] + _ = app[[]int, int] + _ = app[[]int]([]int{}, 0) //@suggestedfix("[[]int]", re"unnecessary type arguments", infer) + _ = app([]int{}, 0) +} + +-- @infer/p.go -- +@@ -10 +10 @@ +- _ = app[[]int]([]int{}, 0) //@suggestedfix("[[]int]", re"unnecessary type arguments", infer) ++ _ = app([]int{}, 0) //@suggestedfix("[[]int]", re"unnecessary type arguments", infer) diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/inline.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/inline.txt new file mode 100644 index 00000000000..050fe25b8ec --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/inline.txt @@ -0,0 +1,24 @@ +This is a minimal test of the refactor.inline.call code action, without resolve support. +See inline_resolve.txt for same test with resolve support. + +-- go.mod -- +module example.com/codeaction +go 1.18 + +-- a/a.go -- +package a + +func _() { + println(add(1, 2)) //@codeaction("add", ")", "refactor.inline.call", inline) +} + +func add(x, y int) int { return x + y } + +-- @inline/a/a.go -- +package a + +func _() { + println(1 + 2) //@codeaction("add", ")", "refactor.inline.call", inline) +} + +func add(x, y int) int { return x + y } diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/inline_resolve.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/inline_resolve.txt new file mode 100644 index 00000000000..fa8476e91f6 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/inline_resolve.txt @@ -0,0 +1,35 @@ +This is a minimal test of the refactor.inline.call code actions, with resolve support. +See inline.txt for same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- go.mod -- +module example.com/codeaction +go 1.18 + +-- a/a.go -- +package a + +func _() { + println(add(1, 2)) //@codeaction("add", ")", "refactor.inline.call", inline) +} + +func add(x, y int) int { return x + y } + +-- @inline/a/a.go -- +package a + +func _() { + println(1 + 2) //@codeaction("add", ")", "refactor.inline.call", inline) +} + +func add(x, y int) int { return x + y } diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/invertif.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/invertif.txt new file mode 100644 index 00000000000..02f856f6977 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/invertif.txt @@ -0,0 +1,218 @@ +This test exercises the 'invert if condition' code action. + +-- p.go -- +package invertif + +import ( + "fmt" + "os" +) + +func Boolean() { + b := true + if b { //@codeactionedit("if b", "refactor.rewrite.invertIf", boolean) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +func BooleanFn() { + if os.IsPathSeparator('X') { //@codeactionedit("if os.IsPathSeparator('X')", "refactor.rewrite.invertIf", boolean_fn) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +// Note that the comment here jumps to the wrong location. +func DontRemoveParens() { + a := false + b := true + if !(a || + b) { //@codeactionedit("b", "refactor.rewrite.invertIf", dont_remove_parens) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +func ElseIf() { + // No inversion expected when there's not else clause + if len(os.Args) > 2 { + fmt.Println("A") + } + + // No inversion expected for else-if, that would become unreadable + if len(os.Args) > 2 { + fmt.Println("A") + } else if os.Args[0] == "X" { //@codeactionedit(re"if os.Args.0. == .X.", "refactor.rewrite.invertIf", else_if) + fmt.Println("B") + } else { + fmt.Println("C") + } +} + +func GreaterThan() { + if len(os.Args) > 2 { //@codeactionedit("i", "refactor.rewrite.invertIf", greater_than) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +func NotBoolean() { + b := true + if !b { //@codeactionedit("if !b", "refactor.rewrite.invertIf", not_boolean) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +func RemoveElse() { + if true { //@codeactionedit("if true", "refactor.rewrite.invertIf", remove_else) + fmt.Println("A") + } else { + fmt.Println("B") + return + } + + fmt.Println("C") +} + +func RemoveParens() { + b := true + if !(b) { //@codeactionedit("if", "refactor.rewrite.invertIf", remove_parens) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +func Semicolon() { + if _, err := fmt.Println("x"); err != nil { //@codeactionedit("if", "refactor.rewrite.invertIf", semicolon) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +func SemicolonAnd() { + if n, err := fmt.Println("x"); err != nil && n > 0 { //@codeactionedit("f", "refactor.rewrite.invertIf", semicolon_and) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +func SemicolonOr() { + if n, err := fmt.Println("x"); err != nil || n < 5 { //@codeactionedit(re"if n, err := fmt.Println..x..; err != nil .. n < 5", "refactor.rewrite.invertIf", semicolon_or) + fmt.Println("A") + } else { + fmt.Println("B") + } +} + +-- @boolean/p.go -- +@@ -10,3 +10 @@ +- if b { //@codeactionedit("if b", "refactor.rewrite.invertIf", boolean) +- fmt.Println("A") +- } else { ++ if !b { +@@ -14 +12,2 @@ ++ } else { //@codeactionedit("if b", "refactor.rewrite.invertIf", boolean) ++ fmt.Println("A") +-- @boolean_fn/p.go -- +@@ -18,3 +18 @@ +- if os.IsPathSeparator('X') { //@codeactionedit("if os.IsPathSeparator('X')", "refactor.rewrite.invertIf", boolean_fn) +- fmt.Println("A") +- } else { ++ if !os.IsPathSeparator('X') { +@@ -22 +20,2 @@ ++ } else { //@codeactionedit("if os.IsPathSeparator('X')", "refactor.rewrite.invertIf", boolean_fn) ++ fmt.Println("A") +-- @dont_remove_parens/p.go -- +@@ -29,4 +29,2 @@ +- if !(a || +- b) { //@codeactionedit("b", "refactor.rewrite.invertIf", dont_remove_parens) +- fmt.Println("A") +- } else { ++ if (a || ++ b) { +@@ -34 +32,2 @@ ++ } else { //@codeactionedit("b", "refactor.rewrite.invertIf", dont_remove_parens) ++ fmt.Println("A") +-- @else_if/p.go -- +@@ -46,3 +46 @@ +- } else if os.Args[0] == "X" { //@codeactionedit(re"if os.Args.0. == .X.", "refactor.rewrite.invertIf", else_if) +- fmt.Println("B") +- } else { ++ } else if os.Args[0] != "X" { +@@ -50 +48,2 @@ ++ } else { //@codeactionedit(re"if os.Args.0. == .X.", "refactor.rewrite.invertIf", else_if) ++ fmt.Println("B") +-- @greater_than/p.go -- +@@ -54,3 +54 @@ +- if len(os.Args) > 2 { //@codeactionedit("i", "refactor.rewrite.invertIf", greater_than) +- fmt.Println("A") +- } else { ++ if len(os.Args) <= 2 { +@@ -58 +56,2 @@ ++ } else { //@codeactionedit("i", "refactor.rewrite.invertIf", greater_than) ++ fmt.Println("A") +-- @not_boolean/p.go -- +@@ -63,3 +63 @@ +- if !b { //@codeactionedit("if !b", "refactor.rewrite.invertIf", not_boolean) +- fmt.Println("A") +- } else { ++ if b { +@@ -67 +65,2 @@ ++ } else { //@codeactionedit("if !b", "refactor.rewrite.invertIf", not_boolean) ++ fmt.Println("A") +-- @remove_else/p.go -- +@@ -71,3 +71 @@ +- if true { //@codeactionedit("if true", "refactor.rewrite.invertIf", remove_else) +- fmt.Println("A") +- } else { ++ if false { +@@ -78 +76,3 @@ ++ //@codeactionedit("if true", "refactor.rewrite.invertIf", remove_else) ++ fmt.Println("A") ++ +-- @remove_parens/p.go -- +@@ -83,3 +83 @@ +- if !(b) { //@codeactionedit("if", "refactor.rewrite.invertIf", remove_parens) +- fmt.Println("A") +- } else { ++ if b { +@@ -87 +85,2 @@ ++ } else { //@codeactionedit("if", "refactor.rewrite.invertIf", remove_parens) ++ fmt.Println("A") +-- @semicolon/p.go -- +@@ -91,3 +91 @@ +- if _, err := fmt.Println("x"); err != nil { //@codeactionedit("if", "refactor.rewrite.invertIf", semicolon) +- fmt.Println("A") +- } else { ++ if _, err := fmt.Println("x"); err == nil { +@@ -95 +93,2 @@ ++ } else { //@codeactionedit("if", "refactor.rewrite.invertIf", semicolon) ++ fmt.Println("A") +-- @semicolon_and/p.go -- +@@ -99,3 +99 @@ +- if n, err := fmt.Println("x"); err != nil && n > 0 { //@codeactionedit("f", "refactor.rewrite.invertIf", semicolon_and) +- fmt.Println("A") +- } else { ++ if n, err := fmt.Println("x"); err == nil || n <= 0 { +@@ -103 +101,2 @@ ++ } else { //@codeactionedit("f", "refactor.rewrite.invertIf", semicolon_and) ++ fmt.Println("A") +-- @semicolon_or/p.go -- +@@ -107,3 +107 @@ +- if n, err := fmt.Println("x"); err != nil || n < 5 { //@codeactionedit(re"if n, err := fmt.Println..x..; err != nil .. n < 5", "refactor.rewrite.invertIf", semicolon_or) +- fmt.Println("A") +- } else { ++ if n, err := fmt.Println("x"); err == nil && n >= 5 { +@@ -111 +109,2 @@ ++ } else { //@codeactionedit(re"if n, err := fmt.Println..x..; err != nil .. n < 5", "refactor.rewrite.invertIf", semicolon_or) ++ fmt.Println("A") diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/issue64558.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/issue64558.txt new file mode 100644 index 00000000000..7ca661fbf00 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/issue64558.txt @@ -0,0 +1,14 @@ +Test of an inlining failure due to an ill-typed input program (#64558). + +-- go.mod -- +module example.com +go 1.18 + +-- a/a.go -- +package a + +func _() { + f(1, 2) //@ diag("2", re"too many arguments"), codeactionerr("f", ")", "refactor.inline.call", re`inlining failed \("args/params mismatch"\), likely because inputs were ill-typed`) +} + +func f(int) {} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam.txt new file mode 100644 index 00000000000..2b78b882df6 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam.txt @@ -0,0 +1,247 @@ +This test exercises the refactoring to remove unused parameters. +See removeparam_resolve.txt for same test with resolve support. + +-- go.mod -- +module unused.mod + +go 1.18 + +-- a/a.go -- +package a + +func A(x, unused int) int { //@codeaction("unused", "unused", "refactor.rewrite.removeUnusedParam", a) + return x +} + +-- @a/a/a.go -- +package a + +func A(x int) int { //@codeaction("unused", "unused", "refactor.rewrite.removeUnusedParam", a) + return x +} + +-- a/a2.go -- +package a + +func _() { + A(1, 2) +} + +-- a/a_test.go -- +package a + +func _() { + A(1, 2) +} + +-- a/a_x_test.go -- +package a_test + +import "unused.mod/a" + +func _() { + a.A(1, 2) +} + +-- b/b.go -- +package b + +import "unused.mod/a" + +func f() int { + return 1 +} + +func g() int { + return 2 +} + +func _() { + a.A(f(), 1) +} + +-- @a/a/a2.go -- +package a + +func _() { + A(1) +} +-- @a/a/a_test.go -- +package a + +func _() { + A(1) +} +-- @a/a/a_x_test.go -- +package a_test + +import "unused.mod/a" + +func _() { + a.A(1) +} +-- @a/b/b.go -- +package b + +import "unused.mod/a" + +func f() int { + return 1 +} + +func g() int { + return 2 +} + +func _() { + a.A(f()) +} +-- field/field.go -- +package field + +func Field(x int, field int) { //@codeaction("int", "int", "refactor.rewrite.removeUnusedParam", field) +} + +func _() { + Field(1, 2) +} +-- @field/field/field.go -- +package field + +func Field(field int) { //@codeaction("int", "int", "refactor.rewrite.removeUnusedParam", field) +} + +func _() { + Field(2) +} +-- ellipsis/ellipsis.go -- +package ellipsis + +func Ellipsis(...any) { //@codeaction("any", "any", "refactor.rewrite.removeUnusedParam", ellipsis) +} + +func _() { + // TODO(rfindley): investigate the broken formatting resulting from these inlinings. + Ellipsis() + Ellipsis(1) + Ellipsis(1, 2) + Ellipsis(1, f(), g()) + Ellipsis(h()) + Ellipsis(i()...) +} + +func f() int +func g() int +func h() (int, int) +func i() []any + +-- @ellipsis/ellipsis/ellipsis.go -- +package ellipsis + +func Ellipsis() { //@codeaction("any", "any", "refactor.rewrite.removeUnusedParam", ellipsis) +} + +func _() { + // TODO(rfindley): investigate the broken formatting resulting from these inlinings. + Ellipsis() + Ellipsis() + Ellipsis() + var _ []any = []any{1, f(), g()} + Ellipsis() + func(_ ...any) { + Ellipsis() + }(h()) + var _ []any = i() + Ellipsis() +} + +func f() int +func g() int +func h() (int, int) +func i() []any +-- ellipsis2/ellipsis2.go -- +package ellipsis2 + +func Ellipsis2(_, _ int, rest ...int) { //@codeaction("_", "_", "refactor.rewrite.removeUnusedParam", ellipsis2) +} + +func _() { + Ellipsis2(1,2,3) + Ellipsis2(h()) + Ellipsis2(1,2, []int{3, 4}...) +} + +func h() (int, int) + +-- @ellipsis2/ellipsis2/ellipsis2.go -- +package ellipsis2 + +func Ellipsis2(_ int, rest ...int) { //@codeaction("_", "_", "refactor.rewrite.removeUnusedParam", ellipsis2) +} + +func _() { + Ellipsis2(2, []int{3}...) + func(_, blank0 int, rest ...int) { + Ellipsis2(blank0, rest...) + }(h()) + Ellipsis2(2, []int{3, 4}...) +} + +func h() (int, int) +-- overlapping/overlapping.go -- +package overlapping + +func Overlapping(i int) int { //@codeactionerr(re"(i) int", re"(i) int", "refactor.rewrite.removeUnusedParam", re"overlapping") + return 0 +} + +func _() { + x := Overlapping(Overlapping(0)) + _ = x +} + +-- effects/effects.go -- +package effects + +func effects(x, y int) int { //@ diag("y", re"unused"), codeaction("y", "y", "refactor.rewrite.removeUnusedParam", effects) + return x +} + +func f() int +func g() int + +func _() { + effects(f(), g()) + effects(f(), g()) +} +-- @effects/effects/effects.go -- +package effects + +func effects(x int) int { //@ diag("y", re"unused"), codeaction("y", "y", "refactor.rewrite.removeUnusedParam", effects) + return x +} + +func f() int +func g() int + +func _() { + var x, _ int = f(), g() + effects(x) + { + var x, _ int = f(), g() + effects(x) + } +} +-- recursive/recursive.go -- +package recursive + +func Recursive(x int) int { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", recursive) + return Recursive(1) +} + +-- @recursive/recursive/recursive.go -- +package recursive + +func Recursive() int { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", recursive) + return Recursive() +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_formatting.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_formatting.txt new file mode 100644 index 00000000000..b192d79b584 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_formatting.txt @@ -0,0 +1,55 @@ +This test exercises behavior of change signature refactoring with respect to +comments. + +Currently, inline comments around arguments or parameters are dropped, which is +probably acceptable. Fixing this is likely intractible without fixing comment +representation in the AST. + +-- go.mod -- +module unused.mod + +go 1.18 + +-- a/a.go -- +package a + +// A doc comment. +func A(x /* used parameter */, unused int /* unused parameter */ ) int { //@codeaction("unused", "unused", "refactor.rewrite.removeUnusedParam", a) + // about to return + return x // returning + // just returned +} + +// This function makes calls. +func _() { + // about to call + A(one() /* used arg */, 2 /* unused arg */) // calling + // just called +} + +func one() int { + // I should be unaffected! + return 1 +} + +-- @a/a/a.go -- +package a + +// A doc comment. +func A(x int) int { //@codeaction("unused", "unused", "refactor.rewrite.removeUnusedParam", a) + // about to return + return x // returning + // just returned +} + +// This function makes calls. +func _() { + // about to call + A(one()) // calling + // just called +} + +func one() int { + // I should be unaffected! + return 1 +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_funcvalue.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_funcvalue.txt new file mode 100644 index 00000000000..ec8f63c34b3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_funcvalue.txt @@ -0,0 +1,19 @@ +This test exercises change signature refactoring handling of function values. + +TODO(rfindley): use a literalization strategy to allow these references. + +-- go.mod -- +module unused.mod + +go 1.18 + +-- a/a.go -- +package a + +func A(x, unused int) int { //@codeactionerr("unused", "unused", "refactor.rewrite.removeUnusedParam", re"non-call function reference") + return x +} + +func _() { + _ = A +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_imports.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_imports.txt new file mode 100644 index 00000000000..1f96d6b424c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_imports.txt @@ -0,0 +1,163 @@ +This test checks the behavior of removing a parameter with respect to various +import scenarios. + +-- flags -- +-min_go=go1.21 + +-- go.mod -- +module mod.test + +go 1.21 + + +-- a/a1.go -- +package a + +import "mod.test/b" + +func _() { + b.B(<-b.Chan, <-b.Chan) +} + +-- a/a2.go -- +package a + +import "mod.test/b" + +func _() { + b.B(<-b.Chan, <-b.Chan) + b.B(<-b.Chan, <-b.Chan) +} + +-- a/a3.go -- +package a + +import "mod.test/b" + +func _() { + b.B(<-b.Chan, <-b.Chan) +} + +func _() { + b.B(<-b.Chan, <-b.Chan) +} + +-- a/a4.go -- +package a + +// TODO(rfindley/adonovan): inlining here adds an additional import of +// mod.test/b. Can we do better? +import ( + . "mod.test/b" +) + +func _() { + B(<-Chan, <-Chan) +} + +-- b/b.go -- +package b + +import "mod.test/c" + +var Chan chan c.C + +func B(x, y c.C) { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", b) +} + +-- c/c.go -- +package c + +type C int + +-- d/d.go -- +package d + +// Removing the parameter should remove this import. +import "mod.test/c" + +func D(x c.C) { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", d) +} + +func _() { + D(1) +} + +-- @b/a/a1.go -- +package a + +import ( + "mod.test/b" + "mod.test/c" +) + +func _() { + var _ c.C = <-b.Chan + b.B(<-b.Chan) +} +-- @b/a/a2.go -- +package a + +import ( + "mod.test/b" + "mod.test/c" +) + +func _() { + var _ c.C = <-b.Chan + b.B(<-b.Chan) + var _ c.C = <-b.Chan + b.B(<-b.Chan) +} +-- @b/a/a3.go -- +package a + +import ( + "mod.test/b" + "mod.test/c" +) + +func _() { + var _ c.C = <-b.Chan + b.B(<-b.Chan) +} + +func _() { + var _ c.C = <-b.Chan + b.B(<-b.Chan) +} +-- @b/a/a4.go -- +package a + +// TODO(rfindley/adonovan): inlining here adds an additional import of +// mod.test/b. Can we do better? +import ( + "mod.test/b" + . "mod.test/b" + "mod.test/c" +) + +func _() { + var _ c.C = <-Chan + b.B(<-Chan) +} +-- @b/b/b.go -- +package b + +import "mod.test/c" + +var Chan chan c.C + +func B(y c.C) { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", b) +} +-- @d/d/d.go -- +package d + +// Removing the parameter should remove this import. + +func D() { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", d) +} + +func _() { + D() +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_issue65217.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_issue65217.txt new file mode 100644 index 00000000000..f2ecae4ad1c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_issue65217.txt @@ -0,0 +1,58 @@ +This test reproduces condition of golang/go#65217, where the inliner created an +unnecessary eta abstraction. + +-- go.mod -- +module unused.mod + +go 1.18 + +-- a/a.go -- +package a + +type S struct{} + +func (S) Int() int { return 0 } + +func _() { + var s S + _ = f(s, s.Int()) + var j int + j = f(s, s.Int()) + _ = j +} + +func _() { + var s S + i := f(s, s.Int()) + _ = i +} + +func f(unused S, i int) int { //@codeaction("unused", "unused", "refactor.rewrite.removeUnusedParam", rewrite), diag("unused", re`unused`) + return i +} + +-- @rewrite/a/a.go -- +package a + +type S struct{} + +func (S) Int() int { return 0 } + +func _() { + var s S + _ = f(s.Int()) + var j int + j = f(s.Int()) + _ = j +} + +func _() { + var s S + var _ S = s + i := f(s.Int()) + _ = i +} + +func f(i int) int { //@codeaction("unused", "unused", "refactor.rewrite.removeUnusedParam", rewrite), diag("unused", re`unused`) + return i +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_method.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_method.txt new file mode 100644 index 00000000000..174d9061927 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_method.txt @@ -0,0 +1,126 @@ +This test verifies that gopls can remove unused parameters from methods. + +Specifically, check +1. basic removal of unused parameters, when the receiver is named, locally and + across package boundaries +2. handling of unnamed receivers + +-- flags -- +-min_go=go1.20 + +-- go.mod -- +module example.com/rm + +go 1.20 + +-- basic.go -- +package rm + +type Basic int + +func (t Basic) Foo(x int) { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", basic) +} + +func _(b Basic) { + b.Foo(1) + // TODO(rfindley): methodexprs should not get rewritten as methods. + Basic.Foo(1, 2) +} + +-- basicuse/p.go -- +package basicuse + +import "example.com/rm" + +func _() { + x := new(rm.Basic) + x.Foo(sideEffects()) + rm.Basic.Foo(1,2) +} + +func sideEffects() int + +-- @basic/basic.go -- +package rm + +type Basic int + +func (t Basic) Foo() { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", basic) +} + +func _(b Basic) { + b.Foo() + // TODO(rfindley): methodexprs should not get rewritten as methods. + Basic(1).Foo() +} +-- @basic/basicuse/p.go -- +package basicuse + +import "example.com/rm" + +func _() { + x := new(rm.Basic) + var ( + t rm.Basic = *x + _ int = sideEffects() + ) + t.Foo() + rm.Basic(1).Foo() +} + +func sideEffects() int +-- missingrecv.go -- +package rm + +type Missing struct{} + +var r2 int + +func (Missing) M(a, b, c, r0 int) (r1 int) { //@codeaction("b", "b", "refactor.rewrite.removeUnusedParam", missingrecv) + return a + c +} + +func _() { + m := &Missing{} + _ = m.M(1, 2, 3, 4) +} + +-- missingrecvuse/p.go -- +package missingrecvuse + +import "example.com/rm" + +func _() { + x := rm.Missing{} + x.M(1, sideEffects(), 3, 4) +} + +func sideEffects() int + +-- @missingrecv/missingrecv.go -- +package rm + +type Missing struct{} + +var r2 int + +func (Missing) M(a, c, r0 int) (r1 int) { //@codeaction("b", "b", "refactor.rewrite.removeUnusedParam", missingrecv) + return a + c +} + +func _() { + m := &Missing{} + _ = (*m).M(1, 3, 4) +} +-- @missingrecv/missingrecvuse/p.go -- +package missingrecvuse + +import "example.com/rm" + +func _() { + x := rm.Missing{} + var _ int = sideEffects() + x.M(1, 3, 4) +} + +func sideEffects() int diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt new file mode 100644 index 00000000000..92f8d299272 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_resolve.txt @@ -0,0 +1,258 @@ +This test exercises the refactoring to remove unused parameters, with resolve support. +See removeparam.txt for same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- go.mod -- +module unused.mod + +go 1.18 + +-- a/a.go -- +package a + +func A(x, unused int) int { //@codeaction("unused", "unused", "refactor.rewrite.removeUnusedParam", a) + return x +} + +-- @a/a/a.go -- +package a + +func A(x int) int { //@codeaction("unused", "unused", "refactor.rewrite.removeUnusedParam", a) + return x +} + +-- a/a2.go -- +package a + +func _() { + A(1, 2) +} + +-- a/a_test.go -- +package a + +func _() { + A(1, 2) +} + +-- a/a_x_test.go -- +package a_test + +import "unused.mod/a" + +func _() { + a.A(1, 2) +} + +-- b/b.go -- +package b + +import "unused.mod/a" + +func f() int { + return 1 +} + +func g() int { + return 2 +} + +func _() { + a.A(f(), 1) +} + +-- @a/a/a2.go -- +package a + +func _() { + A(1) +} +-- @a/a/a_test.go -- +package a + +func _() { + A(1) +} +-- @a/a/a_x_test.go -- +package a_test + +import "unused.mod/a" + +func _() { + a.A(1) +} +-- @a/b/b.go -- +package b + +import "unused.mod/a" + +func f() int { + return 1 +} + +func g() int { + return 2 +} + +func _() { + a.A(f()) +} +-- field/field.go -- +package field + +func Field(x int, field int) { //@codeaction("int", "int", "refactor.rewrite.removeUnusedParam", field) +} + +func _() { + Field(1, 2) +} +-- @field/field/field.go -- +package field + +func Field(field int) { //@codeaction("int", "int", "refactor.rewrite.removeUnusedParam", field) +} + +func _() { + Field(2) +} +-- ellipsis/ellipsis.go -- +package ellipsis + +func Ellipsis(...any) { //@codeaction("any", "any", "refactor.rewrite.removeUnusedParam", ellipsis) +} + +func _() { + // TODO(rfindley): investigate the broken formatting resulting from these inlinings. + Ellipsis() + Ellipsis(1) + Ellipsis(1, 2) + Ellipsis(1, f(), g()) + Ellipsis(h()) + Ellipsis(i()...) +} + +func f() int +func g() int +func h() (int, int) +func i() []any + +-- @ellipsis/ellipsis/ellipsis.go -- +package ellipsis + +func Ellipsis() { //@codeaction("any", "any", "refactor.rewrite.removeUnusedParam", ellipsis) +} + +func _() { + // TODO(rfindley): investigate the broken formatting resulting from these inlinings. + Ellipsis() + Ellipsis() + Ellipsis() + var _ []any = []any{1, f(), g()} + Ellipsis() + func(_ ...any) { + Ellipsis() + }(h()) + var _ []any = i() + Ellipsis() +} + +func f() int +func g() int +func h() (int, int) +func i() []any +-- ellipsis2/ellipsis2.go -- +package ellipsis2 + +func Ellipsis2(_, _ int, rest ...int) { //@codeaction("_", "_", "refactor.rewrite.removeUnusedParam", ellipsis2) +} + +func _() { + Ellipsis2(1,2,3) + Ellipsis2(h()) + Ellipsis2(1,2, []int{3, 4}...) +} + +func h() (int, int) + +-- @ellipsis2/ellipsis2/ellipsis2.go -- +package ellipsis2 + +func Ellipsis2(_ int, rest ...int) { //@codeaction("_", "_", "refactor.rewrite.removeUnusedParam", ellipsis2) +} + +func _() { + Ellipsis2(2, []int{3}...) + func(_, blank0 int, rest ...int) { + Ellipsis2(blank0, rest...) + }(h()) + Ellipsis2(2, []int{3, 4}...) +} + +func h() (int, int) +-- overlapping/overlapping.go -- +package overlapping + +func Overlapping(i int) int { //@codeactionerr(re"(i) int", re"(i) int", "refactor.rewrite.removeUnusedParam", re"overlapping") + return 0 +} + +func _() { + x := Overlapping(Overlapping(0)) + _ = x +} + +-- effects/effects.go -- +package effects + +func effects(x, y int) int { //@codeaction("y", "y", "refactor.rewrite.removeUnusedParam", effects), diag("y", re"unused") + return x +} + +func f() int +func g() int + +func _() { + effects(f(), g()) + effects(f(), g()) +} +-- @effects/effects/effects.go -- +package effects + +func effects(x int) int { //@codeaction("y", "y", "refactor.rewrite.removeUnusedParam", effects), diag("y", re"unused") + return x +} + +func f() int +func g() int + +func _() { + var x, _ int = f(), g() + effects(x) + { + var x, _ int = f(), g() + effects(x) + } +} +-- recursive/recursive.go -- +package recursive + +func Recursive(x int) int { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", recursive) + return Recursive(1) +} + +-- @recursive/recursive/recursive.go -- +package recursive + +func Recursive() int { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", recursive) + return Recursive() +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_satisfies.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_satisfies.txt new file mode 100644 index 00000000000..f35662e3dad --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_satisfies.txt @@ -0,0 +1,65 @@ +This test verifies that gopls can remove unused parameters from methods, +when that method satisfies an interface. + +For now, we just update static calls. In the future, we should compute the set +of dynamic calls that must change (and therefore, the set of concrete functions +that must be modified), in order to produce the desired outcome for our users. + +Doing so would be more complicated, so for now this test simply records the +current behavior. + +-- flags -- +-min_go=go1.20 + +-- go.mod -- +module example.com/rm + +go 1.20 + +-- p.go -- +package rm + +type T int + +func (t T) Foo(x int) { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", basic) +} + +-- use/use.go -- +package use + +import "example.com/rm" + +type Fooer interface{ + Foo(int) +} + +var _ Fooer = rm.T(0) + +func _() { + var x rm.T + x.Foo(1) +} +-- @basic/p.go -- +package rm + +type T int + +func (t T) Foo() { //@codeaction("x", "x", "refactor.rewrite.removeUnusedParam", basic) +} + +-- @basic/use/use.go -- +package use + +import "example.com/rm" + +type Fooer interface { + Foo(int) +} + +var _ Fooer = rm.T(0) + +func _() { + var x rm.T + var t rm.T = x + t.Foo() +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_witherrs.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_witherrs.txt new file mode 100644 index 00000000000..5b4cd37a51a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/removeparam_witherrs.txt @@ -0,0 +1,11 @@ +This test checks that we can't remove parameters for packages with errors. + +-- p.go -- +package p + +func foo(unused int) { //@codeactionerr("unused", "unused", "refactor.rewrite.removeUnusedParam", re"found 0") +} + +func _() { + foo("") //@diag(`""`, re"cannot use") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/codeaction/splitlines.txt b/contribs/gnopls/internal/test/marker/testdata/codeaction/splitlines.txt new file mode 100644 index 00000000000..5600ccb777a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codeaction/splitlines.txt @@ -0,0 +1,223 @@ +This test exercises the refactoring of putting arguments, results, and composite literal elements +into separate lines. + +-- go.mod -- +module unused.mod + +go 1.18 + +-- func_arg/func_arg.go -- +package func_arg + +func A(a string, b, c int64, x int, y int) (r1 string, r2, r3 int64, r4 int, r5 int) { //@codeaction("x", "x", "refactor.rewrite.splitLines", func_arg) + return a, b, c, x, y +} + +-- @func_arg/func_arg/func_arg.go -- +package func_arg + +func A( + a string, + b, c int64, + x int, + y int, +) (r1 string, r2, r3 int64, r4 int, r5 int) { //@codeaction("x", "x", "refactor.rewrite.splitLines", func_arg) + return a, b, c, x, y +} + +-- func_ret/func_ret.go -- +package func_ret + +func A(a string, b, c int64, x int, y int) (r1 string, r2, r3 int64, r4 int, r5 int) { //@codeaction("r1", "r1", "refactor.rewrite.splitLines", func_ret) + return a, b, c, x, y +} + +-- @func_ret/func_ret/func_ret.go -- +package func_ret + +func A(a string, b, c int64, x int, y int) ( + r1 string, + r2, r3 int64, + r4 int, + r5 int, +) { //@codeaction("r1", "r1", "refactor.rewrite.splitLines", func_ret) + return a, b, c, x, y +} + +-- functype_arg/functype_arg.go -- +package functype_arg + +type A func(a string, b, c int64, x int, y int) (r1 string, r2, r3 int64, r4 int, r5 int) //@codeaction("x", "x", "refactor.rewrite.splitLines", functype_arg) + +-- @functype_arg/functype_arg/functype_arg.go -- +package functype_arg + +type A func( + a string, + b, c int64, + x int, + y int, +) (r1 string, r2, r3 int64, r4 int, r5 int) //@codeaction("x", "x", "refactor.rewrite.splitLines", functype_arg) + +-- functype_ret/functype_ret.go -- +package functype_ret + +type A func(a string, b, c int64, x int, y int) (r1 string, r2, r3 int64, r4 int, r5 int) //@codeaction("r1", "r1", "refactor.rewrite.splitLines", functype_ret) + +-- @functype_ret/functype_ret/functype_ret.go -- +package functype_ret + +type A func(a string, b, c int64, x int, y int) ( + r1 string, + r2, r3 int64, + r4 int, + r5 int, +) //@codeaction("r1", "r1", "refactor.rewrite.splitLines", functype_ret) + +-- func_call/func_call.go -- +package func_call + +import "fmt" + +func a() { + fmt.Println(1, 2, 3, fmt.Sprintf("hello %d", 4)) //@codeaction("1", "1", "refactor.rewrite.splitLines", func_call) +} + +-- @func_call/func_call/func_call.go -- +package func_call + +import "fmt" + +func a() { + fmt.Println( + 1, + 2, + 3, + fmt.Sprintf("hello %d", 4), + ) //@codeaction("1", "1", "refactor.rewrite.splitLines", func_call) +} + +-- indent/indent.go -- +package indent + +import "fmt" + +func a() { + fmt.Println(1, 2, 3, fmt.Sprintf("hello %d", 4)) //@codeaction("hello", "hello", "refactor.rewrite.splitLines", indent) +} + +-- @indent/indent/indent.go -- +package indent + +import "fmt" + +func a() { + fmt.Println(1, 2, 3, fmt.Sprintf( + "hello %d", + 4, + )) //@codeaction("hello", "hello", "refactor.rewrite.splitLines", indent) +} + +-- indent2/indent2.go -- +package indent2 + +import "fmt" + +func a() { + fmt. + Println(1, 2, 3, fmt.Sprintf("hello %d", 4)) //@codeaction("1", "1", "refactor.rewrite.splitLines", indent2) +} + +-- @indent2/indent2/indent2.go -- +package indent2 + +import "fmt" + +func a() { + fmt. + Println( + 1, + 2, + 3, + fmt.Sprintf("hello %d", 4), + ) //@codeaction("1", "1", "refactor.rewrite.splitLines", indent2) +} + +-- structelts/structelts.go -- +package structelts + +type A struct{ + a int + b int +} + +func a() { + _ = A{a: 1, b: 2} //@codeaction("b", "b", "refactor.rewrite.splitLines", structelts) +} + +-- @structelts/structelts/structelts.go -- +package structelts + +type A struct{ + a int + b int +} + +func a() { + _ = A{ + a: 1, + b: 2, + } //@codeaction("b", "b", "refactor.rewrite.splitLines", structelts) +} + +-- sliceelts/sliceelts.go -- +package sliceelts + +func a() { + _ = []int{1, 2} //@codeaction("1", "1", "refactor.rewrite.splitLines", sliceelts) +} + +-- @sliceelts/sliceelts/sliceelts.go -- +package sliceelts + +func a() { + _ = []int{ + 1, + 2, + } //@codeaction("1", "1", "refactor.rewrite.splitLines", sliceelts) +} + +-- mapelts/mapelts.go -- +package mapelts + +func a() { + _ = map[string]int{"a": 1, "b": 2} //@codeaction("1", "1", "refactor.rewrite.splitLines", mapelts) +} + +-- @mapelts/mapelts/mapelts.go -- +package mapelts + +func a() { + _ = map[string]int{ + "a": 1, + "b": 2, + } //@codeaction("1", "1", "refactor.rewrite.splitLines", mapelts) +} + +-- starcomment/starcomment.go -- +package starcomment + +func A(/*1*/ x /*2*/ string /*3*/, /*4*/ y /*5*/ int /*6*/) (string, int) { //@codeaction("x", "x", "refactor.rewrite.splitLines", starcomment) + return x, y +} + +-- @starcomment/starcomment/starcomment.go -- +package starcomment + +func A( + /*1*/ x /*2*/ string /*3*/, + /*4*/ y /*5*/ int /*6*/, +) (string, int) { //@codeaction("x", "x", "refactor.rewrite.splitLines", starcomment) + return x, y +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/codelens/generate.txt b/contribs/gnopls/internal/test/marker/testdata/codelens/generate.txt new file mode 100644 index 00000000000..086c961f07d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codelens/generate.txt @@ -0,0 +1,9 @@ +This test exercises the "generate" codelens. + +-- generate.go -- +//@codelenses() + +package generate + +//go:generate echo Hi //@ codelens("//go:generate", "run go generate"), codelens("//go:generate", "run go generate ./...") +//go:generate echo I shall have no CodeLens diff --git a/contribs/gnopls/internal/test/marker/testdata/codelens/test.txt b/contribs/gnopls/internal/test/marker/testdata/codelens/test.txt new file mode 100644 index 00000000000..ba68cf019df --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/codelens/test.txt @@ -0,0 +1,32 @@ +This file tests codelenses for test functions. + +TODO: for some reason these code lens have zero width. Does that affect their +utility/visibility in various LSP clients? + +-- settings.json -- +{ + "codelenses": { + "test": true + } +} + +-- p_test.go -- +//@codelenses() + +package codelens //@codelens(re"()package codelens", "run file benchmarks") + +import "testing" + +func TestMain(m *testing.M) {} // no code lens for TestMain + +func TestFuncWithCodeLens(t *testing.T) { //@codelens(re"()func", "run test") +} + +func thisShouldNotHaveACodeLens(t *testing.T) { //@diag("t ", re"unused parameter") + println() // nonempty body => "unused parameter" +} + +func BenchmarkFuncWithCodeLens(b *testing.B) { //@codelens(re"()func", "run benchmark") +} + +func helper() {} // expect no code lens diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/address.txt b/contribs/gnopls/internal/test/marker/testdata/completion/address.txt new file mode 100644 index 00000000000..676b9ad9b55 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/address.txt @@ -0,0 +1,92 @@ +This test exercises the reference and dereference completion modifiers. + +TODO: remove the need to set "literalCompletions" here, as this is one of the +few places this setting is needed. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests + +go 1.18 + +-- address/address.go -- +package address + +func wantsPtr(*int) {} +func wantsVariadicPtr(...*int) {} + +func wantsVariadic(...int) {} + +type foo struct{ c int } //@item(addrFieldC, "c", "int", "field") + +func _() { + var ( + a string //@item(addrA, "a", "string", "var") + b int //@item(addrB, "b", "int", "var") + ) + + wantsPtr() //@rank(")", addrB, addrA),snippet(")", addrB, "&b") + wantsPtr(&b) //@snippet(")", addrB, "b") + + wantsVariadicPtr() //@rank(")", addrB, addrA),snippet(")", addrB, "&b") + + var s foo + s.c //@item(addrDeepC, "s.c", "int", "field") + wantsPtr() //@snippet(")", addrDeepC, "&s.c") + wantsPtr(s) //@snippet(")", addrDeepC, "&s.c") + wantsPtr(&s) //@snippet(")", addrDeepC, "s.c") + + // don't add "&" in item (it gets added as an additional edit) + wantsPtr(&s.c) //@snippet(")", addrFieldC, "c") + + // check dereferencing as well + var c *int //@item(addrCPtr, "c", "*int", "var") + var _ int = _ //@rank("_ //", addrCPtr, addrA),snippet("_ //", addrCPtr, "*c") + + wantsVariadic() //@rank(")", addrCPtr, addrA),snippet(")", addrCPtr, "*c") + + var d **int //@item(addrDPtr, "d", "**int", "var") + var _ int = _ //@rank("_ //", addrDPtr, addrA),snippet("_ //", addrDPtr, "**d") + + type namedPtr *int + var np namedPtr //@item(addrNamedPtr, "np", "namedPtr", "var") + + var _ int = _ //@rank("_ //", addrNamedPtr, addrA) + + // don't get tripped up by recursive pointer type + type dontMessUp *dontMessUp //@item(dontMessUp, "dontMessUp", "*dontMessUp", "type") + var dmu *dontMessUp //@item(addrDMU, "dmu", "*dontMessUp", "var") + + var _ int = dmu //@complete(" //", addrDMU, dontMessUp) +} + +func (f foo) ptr() *foo { return &f } + +func _() { + getFoo := func() foo { return foo{} } + + // not addressable + getFoo().c //@item(addrGetFooC, "getFoo().c", "int", "field") + + // addressable + getFoo().ptr().c //@item(addrGetFooPtrC, "getFoo().ptr().c", "int", "field") + + wantsPtr() //@snippet(")", addrGetFooPtrC, "&getFoo().ptr().c") + wantsPtr(&g) //@snippet(")", addrGetFooPtrC, "getFoo().ptr().c") +} + +type nested struct { + f foo +} + +func _() { + getNested := func() nested { return nested{} } + + getNested().f.c //@item(addrNestedC, "getNested().f.c", "int", "field") + getNested().f.ptr().c //@item(addrNestedPtrC, "getNested().f.ptr().c", "int", "field") + + // addrNestedC is not addressable, so rank lower + wantsPtr(getNestedfc) //@complete(")", addrNestedPtrC, addrNestedC) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/anon.txt b/contribs/gnopls/internal/test/marker/testdata/completion/anon.txt new file mode 100644 index 00000000000..37d8cf73b65 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/anon.txt @@ -0,0 +1,37 @@ +This test checks completion related to anonymous structs. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "deepCompletion": false +} + +-- anon.go -- +package anon + +// Literal completion results. +/* int() */ //@item(int, "int()", "int", "var") + +func _() { + for _, _ := range []struct { + i, j int //@item(anonI, "i", "int", "field"),item(anonJ, "j", "int", "field") + }{ + { + i: 1, + //@complete("", anonJ) + }, + { + //@complete("", anonI, anonJ, int) + }, + } { + continue + } + + s := struct{ f int }{ } //@item(anonF, "f", "int", "field"),item(structS, "s", "struct{...}", "var"),complete(" }", anonF, int) + + _ = map[struct{ x int }]int{ //@item(anonX, "x", "int", "field") + struct{ x int }{ }: 1, //@complete(" }", anonX, int, structS) + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/append.txt b/contribs/gnopls/internal/test/marker/testdata/completion/append.txt new file mode 100644 index 00000000000..96c09d7d428 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/append.txt @@ -0,0 +1,61 @@ +This test checks behavior of completion within append expressions. + +It requires go1.23 as the new "structs" package appears as a completion. + +-- flags -- +-ignore_extra_diags +-min_go=go1.23 + +-- go.mod -- +module golang.org/lsptests/append + +go 1.18 + +-- append.go -- +package append + +func foo([]string) {} +func bar(...string) {} + +func _() { + var ( + aInt []int //@item(appendInt, "aInt", "[]int", "var") + aStrings []string //@item(appendStrings, "aStrings", "[]string", "var") + aString string //@item(appendString, "aString", "string", "var") + ) + + append(aStrings, a) //@rank(")", appendString, appendInt) + var _ interface{} = append(aStrings, a) //@rank(")", appendString, appendInt) + var _ []string = append(oops, a) //@rank(")", appendString, appendInt) + + foo(append()) //@rank("))", appendStrings, appendInt),rank("))", appendStrings, appendString) + foo(append([]string{}, a)) //@rank("))", appendStrings, appendInt),rank("))", appendString, appendInt),snippet("))", appendStrings, "aStrings...") + foo(append([]string{}, "", a)) //@rank("))", appendString, appendInt),rank("))", appendString, appendStrings) + + // Don't add "..." to append() argument. + bar(append()) //@snippet("))", appendStrings, "aStrings") + + type baz struct{} + baz{} //@item(appendBazLiteral, "baz{}", "", "var") + var bazzes []baz //@item(appendBazzes, "bazzes", "[]baz", "var") + var bazzy baz //@item(appendBazzy, "bazzy", "baz", "var") + bazzes = append(bazzes, ba) //@rank(")", appendBazzy, appendBazLiteral, appendBazzes) + + var b struct{ b []baz } + b.b //@item(appendNestedBaz, "b.b", "[]baz", "field") + b.b = append(b.b, b) //@rank(")", appendBazzy, appendBazLiteral, appendNestedBaz) + + var aStringsPtr *[]string //@item(appendStringsPtr, "aStringsPtr", "*[]string", "var") + foo(append([]string{}, a)) //@snippet("))", appendStringsPtr, "*aStringsPtr...") + + foo(append([]string{}, *a)) //@snippet("))", appendStringsPtr, "aStringsPtr...") +} + +-- append2.go -- +package append + +func _() { + _ = append(a, struct) //@complete(")", structs) +} + +//@item(structs, "structs", `"structs"`) diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/assign.txt b/contribs/gnopls/internal/test/marker/testdata/completion/assign.txt new file mode 100644 index 00000000000..4f7ea5c72a1 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/assign.txt @@ -0,0 +1,47 @@ +This test checks that completion considers assignability when ranking results. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/assign + +go 1.18 + +-- settings.json -- +{ + "completeUnimported": false +} + +-- assign.go -- +package assign + +import "golang.org/lsptests/assign/internal/secret" + +func _() { + secret.Hello() + var ( + myInt int //@item(assignInt, "myInt", "int", "var") + myStr string //@item(assignStr, "myStr", "string", "var") + ) + + var _ string = my //@rank(" //", assignStr, assignInt) + var _ string = //@rank(" //", assignStr, assignInt) +} + +func _() { + var a string = a //@complete(" //") +} + +func _() { + fooBar := fooBa //@complete(" //"),item(assignFooBar, "fooBar", "", "var") + abc, fooBar := 123, fooBa //@complete(" //", assignFooBar) + { + fooBar := fooBa //@complete(" //", assignFooBar) + } +} + +-- internal/secret/secret.go -- +package secret + +func Hello() {} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/bad.txt b/contribs/gnopls/internal/test/marker/testdata/completion/bad.txt new file mode 100644 index 00000000000..28d8ea22c30 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/bad.txt @@ -0,0 +1,68 @@ +This test exercises completion in the presence of type errors. + +Note: this test was ported from the old marker tests, which did not enable +unimported completion. Enabling it causes matches in e.g. crypto/rand. + +-- settings.json -- +{ + "completeUnimported": false +} + +-- go.mod -- +module bad.test + +go 1.18 + +-- bad/bad0.go -- +package bad + +func stuff() { //@item(stuff, "stuff", "func()", "func") + x := "heeeeyyyy" + random2(x) //@diag("x", re"cannot use x \\(variable of type string\\) as int value in argument to random2") + random2(1) //@complete("dom", random, random2, random3) + y := 3 //@diag("y", re"declared (and|but) not used") +} + +type bob struct { //@item(bob, "bob", "struct{...}", "struct") + x int +} + +func _() { + var q int + _ = &bob{ + f: q, //@diag("f: q", re"unknown field f in struct literal") + } +} + +-- bad/bad1.go -- +package bad + +// See #36637 +type stateFunc func() stateFunc //@item(stateFunc, "stateFunc", "func() stateFunc", "type") + +var a unknown //@item(global_a, "a", "unknown", "var"),diag("unknown", re"(undeclared name|undefined): unknown") + +func random() int { //@item(random, "random", "func() int", "func") + //@complete("", global_a, bob, random, random2, random3, stateFunc, stuff) + return 0 +} + +func random2(y int) int { //@item(random2, "random2", "func(y int) int", "func"),item(bad_y_param, "y", "int", "var") + x := 6 //@item(x, "x", "int", "var"),diag("x", re"declared (and|but) not used") + var q blah //@item(q, "q", "blah", "var"),diag("q", re"declared (and|but) not used"),diag("blah", re"(undeclared name|undefined): blah") + var t **blob //@item(t, "t", "**blob", "var"),diag("t", re"declared (and|but) not used"),diag("blob", re"(undeclared name|undefined): blob") + //@complete("", q, t, x, bad_y_param, global_a, bob, random, random2, random3, stateFunc, stuff) + + return y +} + +func random3(y ...int) { //@item(random3, "random3", "func(y ...int)", "func"),item(y_variadic_param, "y", "[]int", "var") + //@complete("", y_variadic_param, global_a, bob, random, random2, random3, stateFunc, stuff) + + var ch chan (favType1) //@item(ch, "ch", "chan (favType1)", "var"),diag("ch", re"declared (and|but) not used"),diag("favType1", re"(undeclared name|undefined): favType1") + var m map[keyType]int //@item(m, "m", "map[keyType]int", "var"),diag("m", re"declared (and|but) not used"),diag("keyType", re"(undeclared name|undefined): keyType") + var arr []favType2 //@item(arr, "arr", "[]favType2", "var"),diag("arr", re"declared (and|but) not used"),diag("favType2", re"(undeclared name|undefined): favType2") + var fn1 func() badResult //@item(fn1, "fn1", "func() badResult", "var"),diag("fn1", re"declared (and|but) not used"),diag("badResult", re"(undeclared name|undefined): badResult") + var fn2 func(badParam) //@item(fn2, "fn2", "func(badParam)", "var"),diag("fn2", re"declared (and|but) not used"),diag("badParam", re"(undeclared name|undefined): badParam") + //@complete("", arr, ch, fn1, fn2, m, y_variadic_param, global_a, bob, random, random2, random3, stateFunc, stuff) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/basic_lit.txt b/contribs/gnopls/internal/test/marker/testdata/completion/basic_lit.txt new file mode 100644 index 00000000000..aa06326d39b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/basic_lit.txt @@ -0,0 +1,19 @@ +This test checks completion related to basic literals. + +-- flags -- +-ignore_extra_diags + +-- basiclit.go -- +package basiclit + +func _() { + var a int // something for lexical completions + + _ = "hello." //@complete(".") + + _ = 1 //@complete(" //") + + _ = 1. //@complete(".") + + _ = 'a' //@complete("' ") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/builtins.txt b/contribs/gnopls/internal/test/marker/testdata/completion/builtins.txt new file mode 100644 index 00000000000..add694bdb81 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/builtins.txt @@ -0,0 +1,118 @@ +This test checks completion of Go builtins. + +-- flags -- +-ignore_extra_diags +-filter_builtins=false + +-- builtin_args.go -- +package builtins + +func _() { + var ( + aSlice []int //@item(builtinSlice, "aSlice", "[]int", "var") + aMap map[string]int //@item(builtinMap, "aMap", "map[string]int", "var") + aString string //@item(builtinString, "aString", "string", "var") + aArray [0]int //@item(builtinArray, "aArray", "[0]int", "var") + aArrayPtr *[0]int //@item(builtinArrayPtr, "aArrayPtr", "*[0]int", "var") + aChan chan int //@item(builtinChan, "aChan", "chan int", "var") + aPtr *int //@item(builtinPtr, "aPtr", "*int", "var") + aInt int //@item(builtinInt, "aInt", "int", "var") + ) + + type ( + aSliceType []int //@item(builtinSliceType, "aSliceType", "[]int", "type") + aChanType chan int //@item(builtinChanType, "aChanType", "chan int", "type") + aMapType map[string]int //@item(builtinMapType, "aMapType", "map[string]int", "type") + ) + + close() //@rank(")", builtinChan, builtinSlice) + + append() //@rank(")", builtinSlice, builtinChan) + + var _ []byte = append([]byte(nil), ""...) //@rank(") //") + + copy() //@rank(")", builtinSlice, builtinChan) + copy(aSlice, aS) //@rank(")", builtinSlice, builtinString) + copy(aS, aSlice) //@rank(",", builtinSlice, builtinString) + + delete() //@rank(")", builtinMap, builtinChan) + delete(aMap, aS) //@rank(")", builtinString, builtinSlice) + + aMapFunc := func() map[int]int { //@item(builtinMapFunc, "aMapFunc", "func() map[int]int", "var") + return nil + } + delete() //@rank(")", builtinMapFunc, builtinSlice) + + len() //@rank(")", builtinSlice, builtinInt),rank(")", builtinMap, builtinInt),rank(")", builtinString, builtinInt),rank(")", builtinArray, builtinInt),rank(")", builtinArrayPtr, builtinPtr),rank(")", builtinChan, builtinInt) + + cap() //@rank(")", builtinSlice, builtinMap),rank(")", builtinArray, builtinString),rank(")", builtinArrayPtr, builtinPtr),rank(")", builtinChan, builtinInt) + + make() //@rank(")", builtinMapType, int),rank(")", builtinChanType, int),rank(")", builtinSliceType, int),rank(")", builtinMapType, int) + make(aSliceType, a) //@rank(")", builtinInt, builtinSlice) + + type myInt int + var mi myInt //@item(builtinMyInt, "mi", "myInt", "var") + make(aSliceType, m) //@snippet(")", builtinMyInt, "mi") + + var _ []int = make() //@rank(")", builtinSliceType, builtinMapType) + + type myStruct struct{} //@item(builtinStructType, "myStruct", "struct{...}", "struct") + var _ *myStruct = new() //@rank(")", builtinStructType, int) + + for k := range a { //@rank(" {", builtinSlice, builtinInt),rank(" {", builtinString, builtinInt),rank(" {", builtinChan, builtinInt),rank(" {", builtinArray, builtinInt),rank(" {", builtinArrayPtr, builtinInt),rank(" {", builtinMap, builtinInt), + } + + for k, v := range a { //@rank(" {", builtinSlice, builtinChan) + } + + <-a //@rank(" //", builtinChan, builtinInt) +} + +-- builtin_types.go -- +package builtins + +func _() { + var _ []bool //@item(builtinBoolSliceType, "[]bool", "[]bool", "type") + + var _ []bool = make() //@rank(")", builtinBoolSliceType, int) + + var _ []bool = make([], 0) //@rank(",", bool, int) + + var _ [][]bool = make([][], 0) //@rank(",", bool, int) +} + +-- builtins.go -- +package builtins + +// Definitions of builtin completion items that are still used in tests. + +/* bool */ //@item(bool, "bool", "", "type") +/* complex(r float64, i float64) */ //@item(complex, "complex", "func(r float64, i float64) complex128", "func") +/* float32 */ //@item(float32, "float32", "", "type") +/* float64 */ //@item(float64, "float64", "", "type") +/* imag(c complex128) float64 */ //@item(imag, "imag", "func(c complex128) float64", "func") +/* int */ //@item(int, "int", "", "type") +/* iota */ //@item(iota, "iota", "", "const") +/* string */ //@item(string, "string", "", "type") +/* true */ //@item(_true, "true", "", "const") + +-- constants.go -- +package builtins + +func _() { + const ( + foo = iota //@complete(" //", iota) + ) + + iota //@complete(" //") + + var iota int //@item(iotaVar, "iota", "int", "var") + + iota //@complete(" //", iotaVar) +} + +func _() { + var twoRedUpEnd bool //@item(TRUEVar, "twoRedUpEnd", "bool", "var") + + var _ bool = true //@rank(" //", _true, TRUEVar) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/casesensitive.txt b/contribs/gnopls/internal/test/marker/testdata/completion/casesensitive.txt new file mode 100644 index 00000000000..418dcea29e8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/casesensitive.txt @@ -0,0 +1,24 @@ +This test exercises the caseSensitive completion matcher. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false, + "matcher": "caseSensitive" +} + +-- casesensitive.go -- +package casesensitive + +func _() { + var lower int //@item(lower, "lower", "int", "var") + var Upper int //@item(upper, "Upper", "int", "var") + + l //@complete(" //", lower) + U //@complete(" //", upper) + + L //@complete(" //") + u //@complete(" //") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/cast.txt b/contribs/gnopls/internal/test/marker/testdata/completion/cast.txt new file mode 100644 index 00000000000..6c52d5063b5 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/cast.txt @@ -0,0 +1,17 @@ +This test checks completion related to casts. + +-- flags -- +-ignore_extra_diags + +-- cast.go -- +package cast + +func _() { + foo := struct{x int}{x: 1} //@item(x_field, "x", "int", "field") + _ = float64(foo.x) //@complete("x", x_field) +} + +func _() { + foo := struct{x int}{x: 1} + _ = float64(foo. //@complete(" /", x_field) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/channel.txt b/contribs/gnopls/internal/test/marker/testdata/completion/channel.txt new file mode 100644 index 00000000000..e07ae8e9be9 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/channel.txt @@ -0,0 +1,36 @@ +This test checks completion related to channels. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false +} + +-- channel.go -- +package channel + +func _() { + var ( + aa = "123" //@item(channelAA, "aa", "string", "var") + ab = 123 //@item(channelAB, "ab", "int", "var") + ) + + { + type myChan chan int + var mc myChan + mc <- a //@complete(" //", channelAB, channelAA) + } + + { + var ac chan int //@item(channelAC, "ac", "chan int", "var") + a <- a //@complete(" <-", channelAC, channelAA, channelAB) + } + + { + var foo chan int //@item(channelFoo, "foo", "chan int", "var") + wantsInt := func(int) {} //@item(channelWantsInt, "wantsInt", "func(int)", "var") + wantsInt(<-) //@rank(")", channelFoo, channelAB) + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/comment.txt b/contribs/gnopls/internal/test/marker/testdata/completion/comment.txt new file mode 100644 index 00000000000..f66bfdab186 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/comment.txt @@ -0,0 +1,81 @@ +This test checks behavior of completion within comments. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/comment + +go 1.18 + +-- p.go -- +package comment_completion + +var p bool + +//@complete(re"//()") + +func _() { + var a int + + switch a { + case 1: + //@complete(re"//()") + _ = a + } + + var b chan int + select { + case <-b: + //@complete(re"//()") + _ = b + } + + var ( + //@complete(re"//()") + _ = a + ) +} + +// //@complete(" ", variableC) +var C string //@item(variableC, "C", "string", "var") //@complete(" ", variableC) + +// //@complete(" ", constant) +const Constant = "example" //@item(constant, "Constant", "string", "const") //@complete(" ", constant) + +// //@complete(" ", structType, fieldB, fieldA) +type StructType struct { //@item(structType, "StructType", "struct{...}", "struct") //@complete(" ", structType, fieldA, fieldB) + // //@complete(" ", fieldA, structType, fieldB) + A string //@item(fieldA, "A", "string", "field") //@complete(" ", fieldA, structType, fieldB) + b int //@item(fieldB, "b", "int", "field") //@complete(" ", fieldB, structType, fieldA) +} + +// //@complete(" ", method, structRecv, paramX, resultY, fieldB, fieldA) +func (structType *StructType) Method(X int) (Y int) { //@item(structRecv, "structType", "*StructType", "var"),item(method, "Method", "func(X int) (Y int)", "method"),item(paramX, "X", "int", "var"),item(resultY, "Y", "int", "var") + // //@complete(" ", method, structRecv, paramX, resultY, fieldB, fieldA) + return +} + +// //@complete(" ", newType) +type NewType string //@item(newType, "NewType", "string", "type") //@complete(" ", newType) + +// //@complete(" ", testInterface, testA, testB) +type TestInterface interface { //@item(testInterface, "TestInterface", "interface{...}", "interface") + // //@complete(" ", testA, testInterface, testB) + TestA(L string) (M int) //@item(testA, "TestA", "func(L string) (M int)", "method"),item(paramL, "L", "var", "string"),item(resM, "M", "var", "int") //@complete(" ", testA, testInterface, testB) + TestB(N int) bool //@item(testB, "TestB", "func(N int) bool", "method"),item(paramN, "N", "var", "int") //@complete(" ", testB, testInterface, testA) +} + +// //@complete(" ", function) +func Function() int { //@item(function, "Function", "func() int", "func") //@complete(" ", function) + // //@complete(" ", function) + return 0 +} + +// This tests multiline block comments and completion with prefix +// Lorem Ipsum Multili//@complete("Multi", multiline) +// Lorem ipsum dolor sit ametom +func Multiline() int { //@item(multiline, "Multiline", "func() int", "func") + // //@complete(" ", multiline) + return 0 +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/complit.txt b/contribs/gnopls/internal/test/marker/testdata/completion/complit.txt new file mode 100644 index 00000000000..59384893d79 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/complit.txt @@ -0,0 +1,104 @@ +This test checks completion related to composite literals. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false +} + +-- complit.go -- +package complit + +// Literal completion results. +/* int() */ //@item(int, "int()", "int", "var") + +// general completions + +type position struct { //@item(structPosition, "position", "struct{...}", "struct") + X, Y int //@item(fieldX, "X", "int", "field"),item(fieldY, "Y", "int", "field") +} + +func _() { + _ = position{ + //@complete("", fieldX, fieldY, int, structPosition) + } + _ = position{ + X: 1, + //@complete("", fieldY) + } + _ = position{ + //@complete("", fieldX) + Y: 1, + } + _ = []*position{ + { + //@complete("", fieldX, fieldY, int, structPosition) + }, + } +} + +func _() { + var ( + aa string //@item(aaVar, "aa", "string", "var") + ab int //@item(abVar, "ab", "int", "var") + ) + + _ = map[int]int{ + a: a, //@complete(":", abVar, aaVar),complete(",", abVar, aaVar) + } + + _ = map[int]int{ + //@complete("", abVar, int, aaVar, structPosition) + } + + _ = []string{a: ""} //@complete(":", abVar, aaVar) + _ = [1]string{a: ""} //@complete(":", abVar, aaVar) + + _ = position{X: a} //@complete("}", abVar, aaVar) + _ = position{a} //@complete("}", abVar, aaVar) + _ = position{a, } //@complete("}", abVar, int, aaVar, structPosition) + + _ = []int{a} //@complete("}", abVar, aaVar) + _ = [1]int{a} //@complete("}", abVar, aaVar) + + type myStruct struct { + AA int //@item(fieldAA, "AA", "int", "field") + AB string //@item(fieldAB, "AB", "string", "field") + } + + _ = myStruct{ + AB: a, //@complete(",", aaVar, abVar) + } + + var s myStruct + + _ = map[int]string{1: "" + s.A} //@complete("}", fieldAB, fieldAA) + _ = map[int]string{1: (func(i int) string { return "" })(s.A)} //@complete(")}", fieldAA, fieldAB) + _ = map[int]string{1: func() string { s.A }} //@complete(" }", fieldAA, fieldAB) + + _ = position{s.A} //@complete("}", fieldAA, fieldAB) + + var X int //@item(varX, "X", "int", "var") + _ = position{X} //@complete("}", fieldX, varX) +} + +func _() { + type foo struct{} //@item(complitFoo, "foo", "struct{...}", "struct") + + var _ *foo = &fo{} //@snippet("{", complitFoo, "foo") + var _ *foo = fo{} //@snippet("{", complitFoo, "&foo") + + struct { a, b *foo }{ + a: &fo{}, //@rank("{", complitFoo) + b: fo{}, //@snippet("{", complitFoo, "&foo") + } +} + +func _() { + _ := position{ + X: 1, //@complete("X", fieldX),complete(" 1", int, structPosition) + Y: , //@complete(":", fieldY),complete(" ,", int, structPosition) + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/constant.txt b/contribs/gnopls/internal/test/marker/testdata/completion/constant.txt new file mode 100644 index 00000000000..9ac2e43316a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/constant.txt @@ -0,0 +1,20 @@ +This test checks completion related to constants. + +-- flags -- +-ignore_extra_diags + +-- constant.go -- +package constant + +const x = 1 //@item(constX, "x", "int", "const") + +const ( + a int = iota << 2 //@item(constA, "a", "int", "const") + b //@item(constB, "b", "int", "const") + c //@item(constC, "c", "int", "const") +) + +func _() { + const y = "hi" //@item(constY, "y", "string", "const") + //@complete("", constY, constA, constB, constC, constX) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/danglingstmt.txt b/contribs/gnopls/internal/test/marker/testdata/completion/danglingstmt.txt new file mode 100644 index 00000000000..86e79979353 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/danglingstmt.txt @@ -0,0 +1,158 @@ +This test checks that completion works as expected in the presence of +incomplete statements that may affect parser recovery. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/dangling + +go 1.18 + +-- settings.json -- +{ + "completeUnimported": false, + "deepCompletion": false +} + +-- dangling_for.go -- +package danglingstmt + +func _() { + for bar //@rank(" //", danglingBar) +} + +func bar() bool { //@item(danglingBar, "bar", "func() bool", "func") + return true +} + +-- dangling_for_init.go -- +package danglingstmt + +func _() { + for i := bar //@rank(" //", danglingBar2) +} + +func bar2() int { //@item(danglingBar2, "bar2", "func() int", "func") + return 0 +} + +-- dangling_for_init_cond.go -- +package danglingstmt + +func _() { + for i := bar3(); i > bar //@rank(" //", danglingBar3) +} + +func bar3() int { //@item(danglingBar3, "bar3", "func() int", "func") + return 0 +} + +-- dangling_for_init_cond_post.go -- +package danglingstmt + +func _() { + for i := bar4(); i > bar4(); i += bar //@rank(" //", danglingBar4) +} + +func bar4() int { //@item(danglingBar4, "bar4", "func() int", "func") + return 0 +} + +-- dangling_if.go -- +package danglingstmt + +func _() { + if foo //@rank(" //", danglingFoo) +} + +func foo() bool { //@item(danglingFoo, "foo", "func() bool", "func") + return true +} + +-- dangling_if_eof.go -- +package danglingstmt + +func bar5() bool { //@item(danglingBar5, "bar5", "func() bool", "func") + return true +} + +func _() { + if b //@rank(" //", danglingBar5) + +-- dangling_if_init.go -- +package danglingstmt + +func _() { + if i := foo //@rank(" //", danglingFoo2) +} + +func foo2() bool { //@item(danglingFoo2, "foo2", "func() bool", "func") + return true +} + +-- dangling_if_init_cond.go -- +package danglingstmt + +func _() { + if i := 123; foo //@rank(" //", danglingFoo3) +} + +func foo3() bool { //@item(danglingFoo3, "foo3", "func() bool", "func") + return true +} + +-- dangling_multiline_if.go -- +package danglingstmt + +func walrus() bool { //@item(danglingWalrus, "walrus", "func() bool", "func") + return true +} + +func _() { + if true && + walrus //@complete(" //", danglingWalrus) +} + +-- dangling_selector_1.go -- +package danglingstmt + +func _() { + x. //@rank(" //", danglingI) +} + +var x struct { i int } //@item(danglingI, "i", "int", "field") + +-- dangling_selector_2.go -- +package danglingstmt + +// TODO: re-enable this test, which was broken when the foo package was removed. +// (we can replicate the relevant definitions in the new marker test) +// import "golang.org/lsptests/foo" + +func _() { + foo. // rank(" //", Foo) + var _ = []string{foo.} // rank("}", Foo) +} + +-- dangling_switch_init.go -- +package danglingstmt + +func _() { + switch i := baz //@rank(" //", danglingBaz) +} + +func baz() int { //@item(danglingBaz, "baz", "func() int", "func") + return 0 +} + +-- dangling_switch_init_tag.go -- +package danglingstmt + +func _() { + switch i := 0; baz //@rank(" //", danglingBaz2) +} + +func baz2() int { //@item(danglingBaz2, "baz2", "func() int", "func") + return 0 +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/deep.txt b/contribs/gnopls/internal/test/marker/testdata/completion/deep.txt new file mode 100644 index 00000000000..68d306a8c32 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/deep.txt @@ -0,0 +1,110 @@ +This test exercises deep completion. + +-- settings.json -- +{ + "completeUnimported": false, + "matcher": "caseInsensitive" +} + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests + +go 1.18 + +-- deep/deep.go -- +package deep + +import "context" + +type deepA struct { + b deepB //@item(deepBField, "b", "deepB", "field") +} + +type deepB struct { +} + +func wantsDeepB(deepB) {} + +func _() { + var a deepA //@item(deepAVar, "a", "deepA", "var") + a.b //@item(deepABField, "a.b", "deepB", "field") + wantsDeepB(a) //@complete(")", deepABField, deepAVar) + + deepA{a} //@snippet("}", deepABField, "a.b") +} + +func wantsContext(context.Context) {} + +func _() { + context.Background() //@item(ctxBackground, "context.Background", "func() context.Context", "func", "Background returns a non-nil, empty Context.") + context.TODO() //@item(ctxTODO, "context.TODO", "func() context.Context", "func", "TODO returns a non-nil, empty Context.") + + wantsContext(c) //@rank(")", ctxBackground),rank(")", ctxTODO) +} + +func _() { + var cork struct{ err error } + cork.err //@item(deepCorkErr, "cork.err", "error", "field") + context //@item(deepContextPkg, "context", "\"context\"", "package") + var _ error = co // rank(" //", deepCorkErr, deepContextPkg) +} + +func _() { + // deepCircle is circular. + type deepCircle struct { + *deepCircle + } + var circle deepCircle //@item(deepCircle, "circle", "deepCircle", "var") + circle.deepCircle //@item(deepCircleField, "circle.deepCircle", "*deepCircle", "field") + var _ deepCircle = circ //@complete(" //", deepCircle, deepCircleField),snippet(" //", deepCircleField, "*circle.deepCircle") +} + +func _() { + type deepEmbedC struct { + } + type deepEmbedB struct { + deepEmbedC + } + type deepEmbedA struct { + deepEmbedB + } + + wantsC := func(deepEmbedC) {} + + var a deepEmbedA //@item(deepEmbedA, "a", "deepEmbedA", "var") + a.deepEmbedB //@item(deepEmbedB, "a.deepEmbedB", "deepEmbedB", "field") + a.deepEmbedC //@item(deepEmbedC, "a.deepEmbedC", "deepEmbedC", "field") + wantsC(a) //@complete(")", deepEmbedC, deepEmbedA, deepEmbedB) +} + +func _() { + type nested struct { + a int + n *nested //@item(deepNestedField, "n", "*nested", "field") + } + + nested{ + a: 123, //@complete(" //", deepNestedField) + } +} + +func _() { + var a struct { + b struct { + c int + } + d int + } + + a.d //@item(deepAD, "a.d", "int", "field") + a.b.c //@item(deepABC, "a.b.c", "int", "field") + a.b //@item(deepAB, "a.b", "struct{...}", "field") + a //@item(deepA, "a", "struct{...}", "var") + + // "a.d" should be ranked above the deeper "a.b.c" + var i int + i = a //@complete(" //", deepAD, deepABC, deepA, deepAB) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/deep2.txt b/contribs/gnopls/internal/test/marker/testdata/completion/deep2.txt new file mode 100644 index 00000000000..cf343ce4e3f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/deep2.txt @@ -0,0 +1,65 @@ +This test exercises deep completion. + +It was originally bundled with deep.go, but is split into a separate test as +the new marker tests do not permit mutating server options for individual +marks. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests + +go 1.18 + +-- deep/deep2.go -- +package deep + +type foo struct { + b bar +} + +func (f foo) bar() bar { + return f.b +} + +func (f foo) barPtr() *bar { + return &f.b +} + +type bar struct{} + +func (b bar) valueReceiver() int { + return 0 +} + +func (b *bar) ptrReceiver() int { + return 0 +} + +func _() { + var ( + i int + f foo + ) + + f.bar().valueReceiver //@item(deepBarValue, "f.bar().valueReceiver", "func() int", "method") + f.barPtr().ptrReceiver //@item(deepBarPtrPtr, "f.barPtr().ptrReceiver", "func() int", "method") + f.barPtr().valueReceiver //@item(deepBarPtrValue, "f.barPtr().valueReceiver", "func() int", "method") + + i = fbar //@complete(" //", deepBarValue, deepBarPtrPtr, deepBarPtrValue) +} + +func (b baz) Thing() struct{ val int } { + return b.thing +} + +type baz struct { + thing struct{ val int } +} + +func (b baz) _() { + b.Thing().val //@item(deepBazMethVal, "b.Thing().val", "int", "field") + b.thing.val //@item(deepBazFieldVal, "b.thing.val", "int", "field") + var _ int = bval //@rank(" //", deepBazFieldVal, deepBazMethVal) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/errors.txt b/contribs/gnopls/internal/test/marker/testdata/completion/errors.txt new file mode 100644 index 00000000000..87e86ab05e9 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/errors.txt @@ -0,0 +1,33 @@ +This test checks completion related to errors. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "deepCompletion": false +} + +-- go.mod -- +module golang.org/lsptests + +go 1.18 + +-- errors.go -- +package errors + +import ( + "golang.org/lsptests/types" +) + +func _() { + bob.Bob() //@complete(".") + types.b //@complete(" //", Bob_interface) +} + +-- types/types.go -- +package types + +type Bob interface { //@item(Bob_interface, "Bob", "interface{...}", "interface") + Bobby() +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/field_list.txt b/contribs/gnopls/internal/test/marker/testdata/completion/field_list.txt new file mode 100644 index 00000000000..40658f04f4d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/field_list.txt @@ -0,0 +1,38 @@ +This test checks completion related to field lists. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false +} + +-- field_list.go -- +package fieldlist + +var myInt int //@item(flVar, "myInt", "int", "var") +type myType int //@item(flType, "myType", "int", "type") + +func (my) _() {} //@complete(") _", flType) +func (my my) _() {} //@complete(" my)"),complete(") _", flType) + +func (myType) _() {} //@complete(") {", flType) + +func (myType) _(my my) {} //@complete(" my)"),complete(") {", flType) + +func (myType) _() my {} //@complete(" {", flType) + +func (myType) _() (my my) {} //@complete(" my"),complete(") {", flType) + +func _() { + var _ struct { + //@complete("", flType) + m my //@complete(" my"),complete(" //", flType) + } + + var _ interface { + //@complete("", flType) + m() my //@complete("("),complete(" //", flType) + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/foobarbaz.txt b/contribs/gnopls/internal/test/marker/testdata/completion/foobarbaz.txt new file mode 100644 index 00000000000..1da0a405404 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/foobarbaz.txt @@ -0,0 +1,541 @@ +This test ports some arbitrary tests from the old marker framework, that were +*mostly* about completion. + +-- flags -- +-ignore_extra_diags +-min_go=go1.20 + +-- settings.json -- +{ + "completeUnimported": false, + "deepCompletion": false, + "experimentalPostfixCompletions": false +} + +-- go.mod -- +module foobar.test + +go 1.18 + +-- foo/foo.go -- +package foo //@loc(PackageFoo, "foo"),item(PackageFooItem, "foo", "\"foobar.test/foo\"", "package") + +type StructFoo struct { //@loc(StructFooLoc, "StructFoo"), item(StructFoo, "StructFoo", "struct{...}", "struct") + Value int //@item(Value, "Value", "int", "field") +} + +// Pre-set this marker, as we don't have a "source" for it in this package. +/* Error() */ //@item(Error, "Error", "func() string", "method") + +func Foo() { //@item(Foo, "Foo", "func()", "func") + var err error + err.Error() //@complete("E", Error) +} + +func _() { + var sFoo StructFoo //@complete("t", StructFoo) + if x := sFoo; x.Value == 1 { //@complete("V", Value), typedef("sFoo", StructFooLoc) + return + } +} + +func _() { + shadowed := 123 + { + shadowed := "hi" //@item(shadowed, "shadowed", "string", "var") + sha //@complete("a", shadowed), diag("sha", re"(undefined|undeclared)") + _ = shadowed + } +} + +type IntFoo int //@loc(IntFooLoc, "IntFoo"), item(IntFoo, "IntFoo", "int", "type") + +-- bar/bar.go -- +package bar + +import ( + "foobar.test/foo" //@item(foo, "foo", "\"foobar.test/foo\"", "package") +) + +func helper(i foo.IntFoo) {} //@item(helper, "helper", "func(i foo.IntFoo)", "func") + +func _() { + help //@complete("l", helper) + _ = foo.StructFoo{} //@complete("S", IntFoo, StructFoo) +} + +// Bar is a function. +func Bar() { //@item(Bar, "Bar", "func()", "func", "Bar is a function.") + foo.Foo() //@complete("F", Foo, IntFoo, StructFoo) + var _ foo.IntFoo //@complete("I", IntFoo, StructFoo) + foo.() //@complete("(", Foo, IntFoo, StructFoo), diag(")", re"expected type") +} + +// These items weren't present in the old marker tests (due to settings), but +// we may as well include them. +//@item(intConversion, "int()"), item(fooFoo, "foo.Foo") +//@item(fooIntFoo, "foo.IntFoo"), item(fooStructFoo, "foo.StructFoo") + +func _() { + var Valentine int //@item(Valentine, "Valentine", "int", "var") + + _ = foo.StructFoo{ //@diag("foo", re"unkeyed fields") + Valu //@complete(" //", Value) + } + _ = foo.StructFoo{ //@diag("foo", re"unkeyed fields") + Va //@complete("a", Value, Valentine) + + } + _ = foo.StructFoo{ + Value: 5, //@complete("a", Value) + } + _ = foo.StructFoo{ + //@complete("//", Value, Valentine, intConversion, foo, helper, Bar) + } + _ = foo.StructFoo{ + Value: Valen //@complete("le", Valentine) + } + _ = foo.StructFoo{ + Value: //@complete(" //", Valentine, intConversion, foo, helper, Bar) + } + _ = foo.StructFoo{ + Value: //@complete(" ", Valentine, intConversion, foo, helper, Bar) + } +} + +-- baz/baz.go -- +package baz + +import ( + "foobar.test/bar" + + f "foobar.test/foo" +) + +var FooStruct f.StructFoo + +func Baz() { + defer bar.Bar() //@complete("B", Bar) + // TODO: Test completion here. + defer bar.B //@diag(re"bar.B()", re"must be function call") + var x f.IntFoo //@complete("n", IntFoo), typedef("x", IntFooLoc) + bar.Bar() //@complete("B", Bar) +} + +func _() { + bob := f.StructFoo{Value: 5} + if x := bob. //@complete(" //", Value) + switch true == false { + case true: + if x := bob. //@complete(" //", Value) + case false: + } + if x := bob.Va //@complete("a", Value) + switch true == true { + default: + } +} + +-- arraytype/arraytype.go -- +package arraytype + +import ( + "foobar.test/foo" +) + +func _() { + var ( + val string //@item(atVal, "val", "string", "var") + ) + + [] //@complete(" //", atVal, PackageFooItem) + + []val //@complete(" //") + + []foo.StructFoo //@complete(" //", StructFoo) + + []foo.StructFoo(nil) //@complete("(", StructFoo) + + []*foo.StructFoo //@complete(" //", StructFoo) + + [...]foo.StructFoo //@complete(" //", StructFoo) + + [2][][4]foo.StructFoo //@complete(" //", StructFoo) + + []struct { f []foo.StructFoo } //@complete(" }", StructFoo) +} + +func _() { + type myInt int //@item(atMyInt, "myInt", "int", "type") + + var mark []myInt //@item(atMark, "mark", "[]myInt", "var") + + var s []myInt //@item(atS, "s", "[]myInt", "var") + s = []m //@complete(" //", atMyInt) + + var a [1]myInt + a = [1]m //@complete(" //", atMyInt) + + var ds [][]myInt + ds = [][]m //@complete(" //", atMyInt) +} + +func _() { + var b [0]byte //@item(atByte, "b", "[0]byte", "var") + var _ []byte = b //@snippet(" //", atByte, "b[:]") +} + +-- badstmt/badstmt.go -- +package badstmt + +import ( + "foobar.test/foo" +) + +// (The syntax error causes suppression of diagnostics for type errors. +// See issue #59888.) + +func _(x int) { + defer foo.F //@complete(" //", Foo, IntFoo, StructFoo) + defer foo.F //@complete(" //", Foo, IntFoo, StructFoo) +} + +func _() { + switch true { + case true: + go foo.F //@complete(" //", Foo, IntFoo, StructFoo) + } +} + +func _() { + defer func() { + foo.F //@complete(" //", Foo, IntFoo, StructFoo), snippet(" //", Foo, "Foo()") + + foo. //@rank(" //", Foo) + } +} + +-- badstmt/badstmt_2.go -- +package badstmt + +import ( + "foobar.test/foo" +) + +func _() { + defer func() { foo. } //@rank(" }", Foo) +} + +-- badstmt/badstmt_3.go -- +package badstmt + +import ( + "foobar.test/foo" +) + +func _() { + go foo. //@rank(" //", Foo, IntFoo), snippet(" //", Foo, "Foo()") +} + +-- badstmt/badstmt_4.go -- +package badstmt + +import ( + "foobar.test/foo" +) + +func _() { + go func() { + defer foo. //@rank(" //", Foo, IntFoo) + } +} + +-- selector/selector.go -- +package selector + +import ( + "foobar.test/bar" +) + +type S struct { + B, A, C int //@item(Bf, "B", "int", "field"),item(Af, "A", "int", "field"),item(Cf, "C", "int", "field") +} + +func _() { + _ = S{}.; //@complete(";", Af, Bf, Cf) +} + +type bob struct { a int } //@item(a, "a", "int", "field") +type george struct { b int } +type jack struct { c int } //@item(c, "c", "int", "field") +type jill struct { d int } + +func (b *bob) george() *george {} //@item(george, "george", "func() *george", "method") +func (g *george) jack() *jack {} +func (j *jack) jill() *jill {} //@item(jill, "jill", "func() *jill", "method") + +func _() { + b := &bob{} + y := b.george(). + jack(); + y.; //@complete(";", c, jill) +} + +func _() { + bar. //@complete(" /", Bar) + x := 5 + + var b *bob + b. //@complete(" /", a, george) + y, z := 5, 6 + + b. //@complete(" /", a, george) + y, z, a, b, c := 5, 6 +} + +func _() { + bar. //@complete(" /", Bar) + bar.Bar() + + bar. //@complete(" /", Bar) + go f() +} + +func _() { + var b *bob + if y != b. //@complete(" /", a, george) + z := 5 + + if z + y + 1 + b. //@complete(" /", a, george) + r, s, t := 4, 5 + + if y != b. //@complete(" /", a, george) + z = 5 + + if z + y + 1 + b. //@complete(" /", a, george) + r = 4 +} + +-- literal_snippets/literal_snippets.go -- +package literal_snippets + +import ( + "bytes" + "context" + "go/ast" + "net/http" + "sort" + + "golang.org/lsptests/foo" +) + +func _() { + []int{} //@item(litIntSlice, "[]int{}", "", "var") + &[]int{} //@item(litIntSliceAddr, "&[]int{}", "", "var") + make([]int, 0) //@item(makeIntSlice, "make([]int, 0)", "", "func") + + var _ *[]int = in //@snippet(" //", litIntSliceAddr, "&[]int{$0\\}") + var _ **[]int = in //@complete(" //") + + var slice []int + slice = i //@snippet(" //", litIntSlice, "[]int{$0\\}") + slice = m //@snippet(" //", makeIntSlice, "make([]int, ${1:})") +} + +func _() { + type namedInt []int + + namedInt{} //@item(litNamedSlice, "namedInt{}", "", "var") + make(namedInt, 0) //@item(makeNamedSlice, "make(namedInt, 0)", "", "func") + + var namedSlice namedInt + namedSlice = n //@snippet(" //", litNamedSlice, "namedInt{$0\\}") + namedSlice = m //@snippet(" //", makeNamedSlice, "make(namedInt, ${1:})") +} + +func _() { + make(chan int) //@item(makeChan, "make(chan int)", "", "func") + + var ch chan int + ch = m //@snippet(" //", makeChan, "make(chan int)") +} + +func _() { + map[string]struct{}{} //@item(litMap, "map[string]struct{}{}", "", "var") + make(map[string]struct{}) //@item(makeMap, "make(map[string]struct{})", "", "func") + + var m map[string]struct{} + m = m //@snippet(" //", litMap, "map[string]struct{\\}{$0\\}") + m = m //@snippet(" //", makeMap, "make(map[string]struct{\\})") + + struct{}{} //@item(litEmptyStruct, "struct{}{}", "", "var") + + m["hi"] = s //@snippet(" //", litEmptyStruct, "struct{\\}{\\}") +} + +func _() { + type myStruct struct{ i int } //@item(myStructType, "myStruct", "struct{...}", "struct") + + myStruct{} //@item(litStruct, "myStruct{}", "", "var") + &myStruct{} //@item(litStructPtr, "&myStruct{}", "", "var") + + var ms myStruct + ms = m //@snippet(" //", litStruct, "myStruct{$0\\}") + + var msPtr *myStruct + msPtr = m //@snippet(" //", litStructPtr, "&myStruct{$0\\}") + + msPtr = &m //@snippet(" //", litStruct, "myStruct{$0\\}") + + type myStructCopy struct { i int } //@item(myStructCopyType, "myStructCopy", "struct{...}", "struct") + + // Don't offer literal completion for convertible structs. + ms = myStruct //@complete(" //", litStruct, myStructType, myStructCopyType) +} + +type myImpl struct{} + +func (myImpl) foo() {} + +func (*myImpl) bar() {} + +type myBasicImpl string + +func (myBasicImpl) foo() {} + +func _() { + type myIntf interface { + foo() + } + + myImpl{} //@item(litImpl, "myImpl{}", "", "var") + + var mi myIntf + mi = m //@snippet(" //", litImpl, "myImpl{\\}") + + myBasicImpl() //@item(litBasicImpl, "myBasicImpl()", "string", "var") + + mi = m //@snippet(" //", litBasicImpl, "myBasicImpl($0)") + + // only satisfied by pointer to myImpl + type myPtrIntf interface { + bar() + } + + &myImpl{} //@item(litImplPtr, "&myImpl{}", "", "var") + + var mpi myPtrIntf + mpi = m //@snippet(" //", litImplPtr, "&myImpl{\\}") +} + +func _() { + var s struct{ i []int } //@item(litSliceField, "i", "[]int", "field") + var foo []int + // no literal completions after selector + foo = s.i //@complete(" //", litSliceField) +} + +func _() { + type myStruct struct{ i int } //@item(litMyStructType, "myStruct", "struct{...}", "struct") + myStruct{} //@item(litMyStruct, "myStruct{}", "", "var") + + foo := func(s string, args ...myStruct) {} + // Don't give literal slice candidate for variadic arg. + // Do give literal candidates for variadic element. + foo("", myStruct) //@complete(")", litMyStruct, litMyStructType) +} + +func _() { + Buffer{} //@item(litBuffer, "Buffer{}", "", "var") + + var b *bytes.Buffer + b = bytes.Bu //@snippet(" //", litBuffer, "Buffer{\\}") +} + +func _() { + _ = "func(...) {}" //@item(litFunc, "func(...) {}", "", "var") + + // no literal "func" completions + http.Handle("", fun) //@complete(")") + + var namedReturn func(s string) (b bool) + namedReturn = f //@snippet(" //", litFunc, "func(s string) (b bool) {$0\\}") + + var multiReturn func() (bool, int) + multiReturn = f //@snippet(" //", litFunc, "func() (bool, int) {$0\\}") + + var multiNamedReturn func() (b bool, i int) + multiNamedReturn = f //@snippet(" //", litFunc, "func() (b bool, i int) {$0\\}") + + var duplicateParams func(myImpl, int, myImpl) + duplicateParams = f //@snippet(" //", litFunc, "func(mi1 myImpl, i int, mi2 myImpl) {$0\\}") + + type aliasImpl = myImpl + var aliasParams func(aliasImpl) aliasImpl + aliasParams = f //@snippet(" //", litFunc, "func(ai aliasImpl) aliasImpl {$0\\}") + + const two = 2 + var builtinTypes func([]int, [two]bool, map[string]string, struct{ i int }, interface{ foo() }, <-chan int) + builtinTypes = f //@snippet(" //", litFunc, "func(i1 []int, b [2]bool, m map[string]string, s struct{i int\\}, i2 interface{foo()\\}, c <-chan int) {$0\\}") + + var _ func(ast.Node) = f //@snippet(" //", litFunc, "func(n ast.Node) {$0\\}") + var _ func(error) = f //@snippet(" //", litFunc, "func(err error) {$0\\}") + var _ func(context.Context) = f //@snippet(" //", litFunc, "func(ctx context.Context) {$0\\}") + + type context struct {} + var _ func(context) = f //@snippet(" //", litFunc, "func(ctx context) {$0\\}") +} + +func _() { + float64() //@item(litFloat64, "float64()", "float64", "var") + + // don't complete to "&float64()" + var _ *float64 = float64 //@complete(" //") + + var f float64 + f = fl //@complete(" //", litFloat64),snippet(" //", litFloat64, "float64($0)") + + type myInt int + myInt() //@item(litMyInt, "myInt()", "", "var") + + var mi myInt + mi = my //@snippet(" //", litMyInt, "myInt($0)") +} + +func _() { + type ptrStruct struct { + p *ptrStruct + } + + ptrStruct{} //@item(litPtrStruct, "ptrStruct{}", "", "var") + + ptrStruct{ + p: &ptrSt, //@rank(",", litPtrStruct) + } + + &ptrStruct{} //@item(litPtrStructPtr, "&ptrStruct{}", "", "var") + + &ptrStruct{ + p: ptrSt, //@rank(",", litPtrStructPtr) + } +} + +func _() { + f := func(...[]int) {} + f() //@snippet(")", litIntSlice, "[]int{$0\\}") +} + + +func _() { + // don't complete to "untyped int()" + []int{}[untyped] //@complete("] //") +} + +type Tree[T any] struct{} + +func (tree Tree[T]) Do(f func(s T)) {} + +func _() { + var t Tree[string] + t.Do(fun) //@complete(")", litFunc), snippet(")", litFunc, "func(s string) {$0\\}") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/func_rank.txt b/contribs/gnopls/internal/test/marker/testdata/completion/func_rank.txt new file mode 100644 index 00000000000..157361fb62f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/func_rank.txt @@ -0,0 +1,83 @@ +This test checks various ranking of completion results within function call +context. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false, + "deepCompletion": false +} + +-- func_rank.go -- +package func_rank + +import "net/http" + +var stringAVar = "var" //@item(stringAVar, "stringAVar", "string", "var") +func stringBFunc() string { return "str" } //@item(stringBFunc, "stringBFunc", "func() string", "func") +type stringer struct{} //@item(stringer, "stringer", "struct{...}", "struct") + +func _() stringer //@complete("tr", stringer) + +func _(val stringer) {} //@complete("tr", stringer) + +func (stringer) _() {} //@complete("tr", stringer) + +func _() { + var s struct { + AA int //@item(rankAA, "AA", "int", "field") + AB string //@item(rankAB, "AB", "string", "field") + AC int //@item(rankAC, "AC", "int", "field") + } + fnStr := func(string) {} + fnStr(s.A) //@complete(")", rankAB, rankAA, rankAC) + fnStr("" + s.A) //@complete(")", rankAB, rankAA, rankAC) + + fnInt := func(int) {} + fnInt(-s.A) //@complete(")", rankAA, rankAC, rankAB) + + // no expected type + fnInt(func() int { s.A }) //@complete(" }", rankAA, rankAB, rankAC) + fnInt(s.A()) //@complete("()", rankAA, rankAC, rankAB) + fnInt([]int{}[s.A]) //@complete("])", rankAA, rankAC, rankAB) + fnInt([]int{}[:s.A]) //@complete("])", rankAA, rankAC, rankAB) + + fnInt(s.A.(int)) //@complete(".(", rankAA, rankAC, rankAB) + + fnPtr := func(*string) {} + fnPtr(&s.A) //@complete(")", rankAB, rankAA, rankAC) + + var aaPtr *string //@item(rankAAPtr, "aaPtr", "*string", "var") + var abPtr *int //@item(rankABPtr, "abPtr", "*int", "var") + fnInt(*a) //@complete(")", rankABPtr, rankAAPtr, stringAVar) + + _ = func() string { + return s.A //@complete(" //", rankAB, rankAA, rankAC) + } +} + +type foo struct { + fooPrivateField int //@item(rankFooPrivField, "fooPrivateField", "int", "field") + FooPublicField int //@item(rankFooPubField, "FooPublicField", "int", "field") +} + +func (foo) fooPrivateMethod() int { //@item(rankFooPrivMeth, "fooPrivateMethod", "func() int", "method") + return 0 +} + +func (foo) FooPublicMethod() int { //@item(rankFooPubMeth, "FooPublicMethod", "func() int", "method") + return 0 +} + +func _() { + var _ int = foo{}. //@rank(" //", rankFooPrivField, rankFooPubField),rank(" //", rankFooPrivMeth, rankFooPubMeth),rank(" //", rankFooPrivField, rankFooPrivMeth) +} + +func _() { + HandleFunc //@item(httpHandleFunc, "HandleFunc", "func(pattern string, handler func(http.ResponseWriter, *http.Request))", "func") + HandlerFunc //@item(httpHandlerFunc, "HandlerFunc", "func(http.ResponseWriter, *http.Request)", "type") + + http.HandleFunc //@rank(" //", httpHandleFunc, httpHandlerFunc) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/func_sig.txt b/contribs/gnopls/internal/test/marker/testdata/completion/func_sig.txt new file mode 100644 index 00000000000..7b323e23766 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/func_sig.txt @@ -0,0 +1,15 @@ +This test checks completion related to function signatures. + +-- flags -- +-ignore_extra_diags + +-- func_sig.go -- +package funcsig + +type someType int //@item(sigSomeType, "someType", "int", "type") + +// Don't complete "foo" in signature. +func (foo someType) _() { //@item(sigFoo, "foo", "someType", "var"),complete(") {", sigSomeType) + + //@complete("", sigFoo, sigSomeType) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/func_snippets.txt b/contribs/gnopls/internal/test/marker/testdata/completion/func_snippets.txt new file mode 100644 index 00000000000..01316342b7f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/func_snippets.txt @@ -0,0 +1,32 @@ +This test exercises function snippets using generics. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "usePlaceholders": true +} + +-- go.mod -- +module golang.org/lsptests/snippets + +go 1.18 + +-- funcsnippets.go -- +package snippets + +type SyncMap[K comparable, V any] struct{} + +func NewSyncMap[K comparable, V any]() (result *SyncMap[K, V]) { //@item(NewSyncMap, "NewSyncMap", "", "") + return +} + +func Identity[P ~int](p P) P { //@item(Identity, "Identity", "", "") + return p +} + +func _() { + _ = NewSyncM //@snippet(" //", NewSyncMap, "NewSyncMap[${1:K comparable}, ${2:V any}]()") + _ = Identi //@snippet(" //", Identity, "Identity(${1:p P})") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/func_value.txt b/contribs/gnopls/internal/test/marker/testdata/completion/func_value.txt new file mode 100644 index 00000000000..9b1370f129d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/func_value.txt @@ -0,0 +1,33 @@ +This test checks completion related to function values. + +-- flags -- +-ignore_extra_diags + +-- func_value.go -- +package funcvalue + +func fooFunc() int { //@item(fvFooFunc, "fooFunc", "func() int", "func") + return 0 +} + +var _ = fooFunc() //@item(fvFooFuncCall, "fooFunc", "func() int", "func") + +var fooVar = func() int { //@item(fvFooVar, "fooVar", "func() int", "var") + return 0 +} + +var _ = fooVar() //@item(fvFooVarCall, "fooVar", "func() int", "var") + +type myFunc func() int + +var fooType myFunc = fooVar //@item(fvFooType, "fooType", "myFunc", "var") + +var _ = fooType() //@item(fvFooTypeCall, "fooType", "func() int", "var") + +func _() { + var f func() int + f = foo //@complete(" //", fvFooFunc, fvFooType, fvFooVar) + + var i int + i = foo //@complete(" //", fvFooFuncCall, fvFooTypeCall, fvFooVarCall) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/fuzzy.txt b/contribs/gnopls/internal/test/marker/testdata/completion/fuzzy.txt new file mode 100644 index 00000000000..2a94dce7a2d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/fuzzy.txt @@ -0,0 +1,55 @@ +This test exercises fuzzy completion matching. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests + +go 1.18 + +-- fuzzy/fuzzy.go -- +package fuzzy + +func _() { + var a struct { + fabar int + fooBar string + } + + a.fabar //@item(fuzzFabarField, "a.fabar", "int", "field") + a.fooBar //@item(fuzzFooBarField, "a.fooBar", "string", "field") + + afa //@complete(" //", fuzzFabarField, fuzzFooBarField) + afb //@complete(" //", fuzzFooBarField, fuzzFabarField) + + fab //@complete(" //", fuzzFabarField) + + var myString string + myString = af //@complete(" //", fuzzFooBarField, fuzzFabarField) + + var b struct { + c struct { + d struct { + e struct { + abc string + } + abc float32 + } + abc bool + } + abc int + } + + b.abc //@item(fuzzABCInt, "b.abc", "int", "field") + b.c.abc //@item(fuzzABCbool, "b.c.abc", "bool", "field") + b.c.d.abc //@item(fuzzABCfloat, "b.c.d.abc", "float32", "field") + b.c.d.e.abc //@item(fuzzABCstring, "b.c.d.e.abc", "string", "field") + + // in depth order by default + abc //@complete(" //", fuzzABCInt, fuzzABCbool, fuzzABCfloat) + + // deep candidate that matches expected type should still ranked first + var s string + s = abc //@complete(" //", fuzzABCstring, fuzzABCInt, fuzzABCbool) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/imported-std.txt b/contribs/gnopls/internal/test/marker/testdata/completion/imported-std.txt new file mode 100644 index 00000000000..bb17a07d4f8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/imported-std.txt @@ -0,0 +1,61 @@ +Test of imported completions respecting the effective Go version of the file. + +(See "un-" prefixed file for same test of unimported completions.) + +These symbols below were introduced to go/types in go1.22: + + Alias + Info.FileVersions + (Checker).PkgNameOf + +The underlying logic depends on versions.FileVersion, which only +behaves correctly in go1.22. (When go1.22 is assured, we can remove +the min_go flag but leave the test inputs unchanged.) + +-- flags -- +-ignore_extra_diags -min_go=go1.22 + +-- go.mod -- +module example.com + +go 1.21 + +-- a/a.go -- +package a + +import "go/ast" +import "go/token" +import "go/types" + +// package-level decl +var _ = types.Sat //@rank("Sat", "Satisfies") +var _ = types.Ali //@rank("Ali", "!Alias") + +// field +var _ = new(types.Info).Use //@rank("Use", "Uses") +var _ = new(types.Info).Fil //@rank("Fil", "!FileVersions") + +// method +var _ = new(types.Checker).Obje //@rank("Obje", "ObjectOf") +var _ = new(types.Checker).PkgN //@rank("PkgN", "!PkgNameOf") + +-- b/b.go -- +//go:build go1.22 + +package a + +import "go/ast" +import "go/token" +import "go/types" + +// package-level decl +var _ = types.Sat //@rank("Sat", "Satisfies") +var _ = types.Ali //@rank("Ali", "Alias") + +// field +var _ = new(types.Info).Use //@rank("Use", "Uses") +var _ = new(types.Info).Fil //@rank("Fil", "FileVersions") + +// method +var _ = new(types.Checker).Obje //@rank("Obje", "ObjectOf") +var _ = new(types.Checker).PkgN //@rank("PkgN", "PkgNameOf") diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/index.txt b/contribs/gnopls/internal/test/marker/testdata/completion/index.txt new file mode 100644 index 00000000000..b2fc840dffc --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/index.txt @@ -0,0 +1,36 @@ +This test checks completion related to index expressions. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false +} + +-- index.go -- +package index + +func _() { + var ( + aa = "123" //@item(indexAA, "aa", "string", "var") + ab = 123 //@item(indexAB, "ab", "int", "var") + ) + + var foo [1]int + foo[a] //@complete("]", indexAB, indexAA) + foo[:a] //@complete("]", indexAB, indexAA) + a[:a] //@complete("[", indexAA, indexAB) + a[a] //@complete("[", indexAA, indexAB) + + var bar map[string]int + bar[a] //@complete("]", indexAA, indexAB) + + type myMap map[string]int + var baz myMap + baz[a] //@complete("]", indexAA, indexAB) + + type myInt int + var mi myInt //@item(indexMyInt, "mi", "myInt", "var") + foo[m] //@snippet("]", indexMyInt, "mi") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/interfacerank.txt b/contribs/gnopls/internal/test/marker/testdata/completion/interfacerank.txt new file mode 100644 index 00000000000..d1199abebba --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/interfacerank.txt @@ -0,0 +1,36 @@ +This test checks that completion ranking accounts for interface assignability. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false, + "deepCompletion": false +} + +-- p.go -- + +package interfacerank + +type foo interface { + foo() +} + +type fooImpl int + +func (*fooImpl) foo() {} + +func wantsFoo(foo) {} + +func _() { + var ( + aa string //@item(irAA, "aa", "string", "var") + ab *fooImpl //@item(irAB, "ab", "*fooImpl", "var") + ) + + wantsFoo(a) //@complete(")", irAB, irAA) + + var ac fooImpl //@item(irAC, "ac", "fooImpl", "var") + wantsFoo(&a) //@complete(")", irAC, irAA, irAB) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/issue51783.txt b/contribs/gnopls/internal/test/marker/testdata/completion/issue51783.txt new file mode 100644 index 00000000000..074259ca713 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/issue51783.txt @@ -0,0 +1,47 @@ +Regression test for "completion gives unneeded generic type +instantiation snippet", #51783. + +Type parameters that can be inferred from the arguments +are not part of the offered completion snippet. + +-- flags -- +-ignore_extra_diags + +-- a.go -- +package a + +// identity has a single simple type parameter. +// The completion omits the instantiation. +func identity[T any](x T) T + +// clone has a second type parameter that is nonetheless constrained by the parameter. +// The completion omits the instantiation. +func clone[S ~[]E, E any](s S) S + +// unconstrained has a type parameter constrained only by the result. +// The completion suggests instantiation. +func unconstrained[X, Y any](x X) Y + +// partial has three type parameters, +// only the last two of which may be omitted as they +// are constrained by the arguments. +func partial[R any, S ~[]E, E any](s S) R + +//@item(identity, "identity", "details", "kind") +//@item(clone, "clone", "details", "kind") +//@item(unconstrained, "unconstrained", "details", "kind") +//@item(partial, "partial", "details", "kind") + +func _() { + _ = identity //@snippet("identity", identity, "identity(${1:})") + + _ = clone //@snippet("clone", clone, "clone(${1:})") + + _ = unconstrained //@snippet("unconstrained", unconstrained, "unconstrained[${1:}](${2:})") + + _ = partial //@snippet("partial", partial, "partial[${1:}](${2:})") + + // Result-type inference permits us to omit Y in this (rare) case, + // but completion doesn't support that. + var _ int = unconstrained //@snippet("unconstrained", unconstrained, "unconstrained[${1:}](${2:})") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/issue56505.txt b/contribs/gnopls/internal/test/marker/testdata/completion/issue56505.txt new file mode 100644 index 00000000000..f79e69f4925 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/issue56505.txt @@ -0,0 +1,13 @@ +Test for golang/go#56505: completion on variables of type *error should not +panic. + +-- flags -- +-ignore_extra_diags + +-- issue.go -- +package issues + +func _() { + var e *error + e.x //@complete(" //") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/issue59096.txt b/contribs/gnopls/internal/test/marker/testdata/completion/issue59096.txt new file mode 100644 index 00000000000..23d82c4dc9c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/issue59096.txt @@ -0,0 +1,20 @@ +This test exercises the panic in golang/go#59096: completing at a syntactic +type-assert expression was panicking because gopls was translating it into +a (malformed) selector expr. + +-- go.mod -- +module example.com + +-- a/a.go -- +package a + +func _() { + b.(foo) //@complete(re"b.()", B), diag("b", re"(undefined|undeclared name): b") +} + +//@item(B, "B", "const (from \"example.com/b\")", "const") + +-- b/b.go -- +package b + +const B = 0 diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/issue60545.txt b/contribs/gnopls/internal/test/marker/testdata/completion/issue60545.txt new file mode 100644 index 00000000000..4d204979b6a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/issue60545.txt @@ -0,0 +1,28 @@ +This test checks that unimported completion is case-insensitive. + +-- go.mod -- +module mod.test + +go 1.18 + +-- main.go -- +package main + +//@item(Print, "Print", "func (from \"fmt\")", "func") +//@item(Printf, "Printf", "func (from \"fmt\")", "func") +//@item(Println, "Println", "func (from \"fmt\")", "func") + +func main() { + fmt.p //@complete(re"fmt.p()", Print, Printf, Println), diag("fmt", re"(undefined|undeclared)") +} + +-- other.go -- +package main + +// Including another package that imports "fmt" causes completion to use the +// existing metadata, which is the codepath leading to golang/go#60545. +import "fmt" + +func _() { + fmt.Println() +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/issue62141.txt b/contribs/gnopls/internal/test/marker/testdata/completion/issue62141.txt new file mode 100644 index 00000000000..877e59d0b7c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/issue62141.txt @@ -0,0 +1,39 @@ +This test checks that we don't suggest completion to an untyped conversion such +as "untyped float(abcdef)". + +-- main.go -- +package main + +func main() { + abcdef := 32 //@diag("abcdef", re"not used") + x := 1.0 / abcd //@acceptcompletion(re"abcd()", "abcdef", int), diag("x", re"not used"), diag("abcd", re"(undefined|undeclared)") + + // Verify that we don't suggest converting compatible untyped constants. + const untypedConst = 42 + y := 1.1 / untypedC //@acceptcompletion(re"untypedC()", "untypedConst", untyped), diag("y", re"not used"), diag("untypedC", re"(undefined|undeclared)") +} + +-- @int/main.go -- +package main + +func main() { + abcdef := 32 //@diag("abcdef", re"not used") + x := 1.0 / float64(abcdef) //@acceptcompletion(re"abcd()", "abcdef", int), diag("x", re"not used"), diag("abcd", re"(undefined|undeclared)") + + // Verify that we don't suggest converting compatible untyped constants. + const untypedConst = 42 + y := 1.1 / untypedC //@acceptcompletion(re"untypedC()", "untypedConst", untyped), diag("y", re"not used"), diag("untypedC", re"(undefined|undeclared)") +} + +-- @untyped/main.go -- +package main + +func main() { + abcdef := 32 //@diag("abcdef", re"not used") + x := 1.0 / abcd //@acceptcompletion(re"abcd()", "abcdef", int), diag("x", re"not used"), diag("abcd", re"(undefined|undeclared)") + + // Verify that we don't suggest converting compatible untyped constants. + const untypedConst = 42 + y := 1.1 / untypedConst //@acceptcompletion(re"untypedC()", "untypedConst", untyped), diag("y", re"not used"), diag("untypedC", re"(undefined|undeclared)") +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/issue62560.txt b/contribs/gnopls/internal/test/marker/testdata/completion/issue62560.txt new file mode 100644 index 00000000000..b018bd7cdb8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/issue62560.txt @@ -0,0 +1,19 @@ +This test verifies that completion of package members in unimported packages +reflects their fuzzy score, even when those members are present in the +transitive import graph of the main module. (For technical reasons, this was +the nature of the regression in golang/go#62560.) + +-- go.mod -- +module mod.test + +-- foo/foo.go -- +package foo + +func _() { + json.U //@rank(re"U()", "Unmarshal", "InvalidUTF8Error"), diag("json", re"(undefined|undeclared)") +} + +-- bar/bar.go -- +package bar + +import _ "encoding/json" diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/issue62676.txt b/contribs/gnopls/internal/test/marker/testdata/completion/issue62676.txt new file mode 100644 index 00000000000..8f20c5872c2 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/issue62676.txt @@ -0,0 +1,64 @@ +This test verifies that unimported completion respects the usePlaceholders setting. + +-- flags -- +-ignore_extra_diags +-min_go=go1.21 + +-- settings.json -- +{ + "usePlaceholders": false +} + +-- go.mod -- +module mod.test + +go 1.21 + +-- foo/foo.go -- +package foo + +func _() { + // This uses goimports-based completion; TODO: this should insert snippets. + os.Open //@acceptcompletion(re"Open()", "Open", open) +} + +func _() { + // This uses metadata-based completion. + errors.New //@acceptcompletion(re"New()", "New", new) +} + +-- bar/bar.go -- +package bar + +import _ "errors" // important: doesn't transitively import os. + +-- @new/foo/foo.go -- +package foo + +import "errors" + +func _() { + // This uses goimports-based completion; TODO: this should insert snippets. + os.Open //@acceptcompletion(re"Open()", "Open", open) +} + +func _() { + // This uses metadata-based completion. + errors.New(${1:}) //@acceptcompletion(re"New()", "New", new) +} + +-- @open/foo/foo.go -- +package foo + +import "os" + +func _() { + // This uses goimports-based completion; TODO: this should insert snippets. + os.Open //@acceptcompletion(re"Open()", "Open", open) +} + +func _() { + // This uses metadata-based completion. + errors.New //@acceptcompletion(re"New()", "New", new) +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/keywords.txt b/contribs/gnopls/internal/test/marker/testdata/completion/keywords.txt new file mode 100644 index 00000000000..3a43f190553 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/keywords.txt @@ -0,0 +1,166 @@ +This test checks completion of Go keywords. + +-- flags -- +-ignore_extra_diags +-filter_keywords=false + +-- settings.json -- +{ + "completeUnimported": false, + "matcher": "caseInsensitive", + "experimentalPostfixCompletions": false +} + +-- keywords.go -- +package keywords + +//@rank("", type),rank("", func),rank("", var),rank("", const),rank("", import) + +func _() { + var test int //@rank(" //", int, interface) + var tChan chan int + var _ m //@complete(" //", map) + var _ f //@complete(" //", func) + var _ c //@complete(" //", chan) + + var _ str //@rank(" //", string, struct) + + type _ int //@rank(" //", interface, int) + + type _ str //@rank(" //", struct, string) + + switch test { + case 1: // TODO: trying to complete case here will break because the parser won't return *ast.Ident + b //@complete(" //", break) + case 2: + f //@complete(" //", fallthrough, for) + r //@complete(" //", return) + d //@complete(" //", default, defer) + c //@complete(" //", case, const) + } + + switch test.(type) { + case fo: //@complete(":") + case int: + b //@complete(" //", break) + case int32: + f //@complete(" //", for) + d //@complete(" //", default, defer) + r //@complete(" //", return) + c //@complete(" //", case, const) + } + + select { + case <-tChan: + b //@complete(" //", break) + c //@complete(" //", case, const) + } + + for index := 0; index < test; index++ { + c //@complete(" //", const, continue) + b //@complete(" //", break) + } + + for range []int{} { + c //@complete(" //", const, continue) + b //@complete(" //", break) + } + + // Test function level keywords + + //Using 2 characters to test because map output order is random + sw //@complete(" //", switch) + se //@complete(" //", select) + + f //@complete(" //", for) + d //@complete(" //", defer) + g //@rank(" //", go),rank(" //", goto) + r //@complete(" //", return) + i //@complete(" //", if) + e //@complete(" //", else) + v //@complete(" //", var) + c //@complete(" //", const) + + for i := r //@complete(" //", range) +} + +/* package */ //@item(package, "package", "", "keyword") +/* import */ //@item(import, "import", "", "keyword") +/* func */ //@item(func, "func", "", "keyword") +/* type */ //@item(type, "type", "", "keyword") +/* var */ //@item(var, "var", "", "keyword") +/* const */ //@item(const, "const", "", "keyword") +/* break */ //@item(break, "break", "", "keyword") +/* default */ //@item(default, "default", "", "keyword") +/* case */ //@item(case, "case", "", "keyword") +/* defer */ //@item(defer, "defer", "", "keyword") +/* go */ //@item(go, "go", "", "keyword") +/* for */ //@item(for, "for", "", "keyword") +/* if */ //@item(if, "if", "", "keyword") +/* else */ //@item(else, "else", "", "keyword") +/* switch */ //@item(switch, "switch", "", "keyword") +/* select */ //@item(select, "select", "", "keyword") +/* fallthrough */ //@item(fallthrough, "fallthrough", "", "keyword") +/* continue */ //@item(continue, "continue", "", "keyword") +/* return */ //@item(return, "return", "", "keyword") +/* goto */ //@item(goto, "goto", "", "keyword") +/* struct */ //@item(struct, "struct", "", "keyword") +/* interface */ //@item(interface, "interface", "", "keyword") +/* map */ //@item(map, "map", "", "keyword") +/* chan */ //@item(chan, "chan", "", "keyword") +/* range */ //@item(range, "range", "", "keyword") +/* string */ //@item(string, "string", "", "type") +/* int */ //@item(int, "int", "", "type") + +-- accidental_keywords.go -- +package keywords + +// non-matching candidate - shouldn't show up as completion +var apple = "apple" + +func _() { + foo.bar() // insert some extra statements to exercise our AST surgery + variance := 123 //@item(kwVariance, "variance", "int", "var") + foo.bar() + println(var) //@complete(")", kwVariance) +} + +func _() { + foo.bar() + var s struct { variance int } //@item(kwVarianceField, "variance", "int", "field") + foo.bar() + s.var //@complete(" //", kwVarianceField) +} + +func _() { + channel := 123 //@item(kwChannel, "channel", "int", "var") + chan //@complete(" //", kwChannel) + foo.bar() +} + +func _() { + foo.bar() + var typeName string //@item(kwTypeName, "typeName", "string", "var") + foo.bar() + type //@complete(" //", kwTypeName) +} +-- empty_select.go -- +package keywords + +func _() { + select { + c //@complete(" //", case) + } +} +-- empty_switch.go -- +package keywords + +func _() { + switch { + //@complete("", case, default) + } + + switch test.(type) { + d //@complete(" //", default) + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/labels.txt b/contribs/gnopls/internal/test/marker/testdata/completion/labels.txt new file mode 100644 index 00000000000..3caaa5a211a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/labels.txt @@ -0,0 +1,55 @@ +This test checks completion of labels. + +-- flags -- +-ignore_extra_diags + +-- labels.go -- +package labels + +func _() { + goto F //@complete(" //", label1, label5) + +Foo1: //@item(label1, "Foo1", "label", "const") + for a, b := range []int{} { + Foo2: //@item(label2, "Foo2", "label", "const") + switch { + case true: + break F //@complete(" //", label2, label1) + + continue F //@complete(" //", label1) + + { + FooUnjumpable: + } + + goto F //@complete(" //", label1, label2, label4, label5) + + func() { + goto F //@complete(" //", label3) + + break F //@complete(" //") + + continue F //@complete(" //") + + Foo3: //@item(label3, "Foo3", "label", "const") + }() + } + + Foo4: //@item(label4, "Foo4", "label", "const") + switch interface{}(a).(type) { + case int: + break F //@complete(" //", label4, label1) + } + } + + break F //@complete(" //") + + continue F //@complete(" //") + +Foo5: //@item(label5, "Foo5", "label", "const") + for { + break F //@complete(" //", label5) + } + + return +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/lit.txt b/contribs/gnopls/internal/test/marker/testdata/completion/lit.txt new file mode 100644 index 00000000000..7224f42ab77 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/lit.txt @@ -0,0 +1,49 @@ + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module mod.test + +go 1.18 + +-- foo/foo.go -- +package foo + +type StructFoo struct{ F int } + +-- a.go -- +package a + +import "mod.test/foo" + +func _() { + StructFoo{} //@item(litStructFoo, "StructFoo{}", "struct{...}", "struct") + + var sfp *foo.StructFoo + // Don't insert the "&" before "StructFoo{}". + sfp = foo.Str //@snippet(" //", litStructFoo, "StructFoo{$0\\}") + + var sf foo.StructFoo + sf = foo.Str //@snippet(" //", litStructFoo, "StructFoo{$0\\}") + sf = foo. //@snippet(" //", litStructFoo, "StructFoo{$0\\}") +} + +-- http.go -- +package a + +import ( + "net/http" + "sort" +) + +func _() { + sort.Slice(nil, fun) //@snippet(")", litFunc, "func(i, j int) bool {$0\\}") + + http.HandleFunc("", f) //@snippet(")", litFunc, "func(w http.ResponseWriter, r *http.Request) {$0\\}") + + //@item(litFunc, "func(...) {}", "", "var") + http.HandlerFunc() //@item(handlerFunc, "http.HandlerFunc()", "", "var") + http.Handle("", http.HandlerFunc()) //@snippet("))", litFunc, "func(w http.ResponseWriter, r *http.Request) {$0\\}") + http.Handle("", h) //@snippet(")", handlerFunc, "http.HandlerFunc($0)") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/maps.txt b/contribs/gnopls/internal/test/marker/testdata/completion/maps.txt new file mode 100644 index 00000000000..052cc26bd38 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/maps.txt @@ -0,0 +1,29 @@ +This test checks completion of map keys and values. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false +} + +-- maps.go -- +package maps + +func _() { + var aVar int //@item(mapVar, "aVar", "int", "var") + + // not comparabale + type aSlice []int //@item(mapSliceType, "aSlice", "[]int", "type") + + *aSlice //@item(mapSliceTypePtr, "*aSlice", "[]int", "type") + + // comparable + type aStruct struct{} //@item(mapStructType, "aStruct", "struct{...}", "struct") + + map[]a{} //@complete("]", mapSliceType, mapStructType),snippet("]", mapSliceType, "*aSlice") + + map[a]a{} //@complete("]", mapSliceType, mapStructType) + map[a]a{} //@complete("{", mapSliceType, mapStructType) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/multi_return.txt b/contribs/gnopls/internal/test/marker/testdata/completion/multi_return.txt new file mode 100644 index 00000000000..72facfcf6f3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/multi_return.txt @@ -0,0 +1,55 @@ +This test checks various ranking of completion results related to functions +with multiple return values. + +-- flags -- +-ignore_extra_diags + +-- multireturn.go -- +package multireturn + +func f0() {} //@item(multiF0, "f0", "func()", "func") + +func f1(int) int { return 0 } //@item(multiF1, "f1", "func(int) int", "func") + +func f2(int, int) (int, int) { return 0, 0 } //@item(multiF2, "f2", "func(int, int) (int, int)", "func") + +func f2Str(string, string) (string, string) { return "", "" } //@item(multiF2Str, "f2Str", "func(string, string) (string, string)", "func") + +func f3(int, int, int) (int, int, int) { return 0, 0, 0 } //@item(multiF3, "f3", "func(int, int, int) (int, int, int)", "func") + +func _() { + _ := f //@rank(" //", multiF1, multiF2) + + _, _ := f //@rank(" //", multiF2, multiF0),rank(" //", multiF1, multiF0) + + _, _ := _, f //@rank(" //", multiF1, multiF2),rank(" //", multiF1, multiF0) + + _, _ := f, abc //@rank(", abc", multiF1, multiF2) + + f1() //@rank(")", multiF1, multiF0) + f1(f) //@rank(")", multiF1, multiF2) + f2(f) //@rank(")", multiF2, multiF3),rank(")", multiF1, multiF3) + f2(1, f) //@rank(")", multiF1, multiF2),rank(")", multiF1, multiF0) + f2(1, ) //@rank(")", multiF1, multiF2),rank(")", multiF1, multiF0) + f2Str() //@rank(")", multiF2Str, multiF2) + + var i int + i, _ := f //@rank(" //", multiF2, multiF2Str) + + var s string + _, s := f //@rank(" //", multiF2Str, multiF2) + + banana, s = f //@rank(" //", multiF2, multiF3) + + var variadic func(int, ...int) + variadic() //@rank(")", multiF1, multiF0),rank(")", multiF2, multiF0),rank(")", multiF3, multiF0) +} + +func _() { + var baz func(...interface{}) + + var otterNap func() (int, int) //@item(multiTwo, "otterNap", "func() (int, int)", "var") + var one int //@item(multiOne, "one", "int", "var") + + baz(on) //@rank(")", multiOne, multiTwo) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/nested_complit.txt b/contribs/gnopls/internal/test/marker/testdata/completion/nested_complit.txt new file mode 100644 index 00000000000..264ae77eab8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/nested_complit.txt @@ -0,0 +1,26 @@ +This test checks completion of nested composite literals; + +Parser recovery changed in Go 1.20, so this test requires at least that +version for consistency. + +-- flags -- +-ignore_extra_diags +-min_go=go1.20 + +-- nested_complit.go -- +package nested_complit + +type ncFoo struct {} //@item(structNCFoo, "ncFoo", "struct{...}", "struct") + +type ncBar struct { //@item(structNCBar, "ncBar", "struct{...}", "struct") + baz []ncFoo +} + +func _() { + _ = []ncFoo{} //@item(litNCFoo, "[]ncFoo{}", "", "var") + _ = make([]ncFoo, 0) //@item(makeNCFoo, "make([]ncFoo, 0)", "", "func") + + _ := ncBar{ + baz: [] //@complete(" //", litNCFoo, makeNCFoo, structNCBar, structNCFoo) + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/postfix.txt b/contribs/gnopls/internal/test/marker/testdata/completion/postfix.txt new file mode 100644 index 00000000000..9b54b578f4c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/postfix.txt @@ -0,0 +1,131 @@ +These tests check that postfix completions do and do not show up in certain +cases. Tests for the postfix completion contents are implemented as ad-hoc +integration tests. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/snippets + +go 1.18 + +-- postfix.go -- +package snippets + +import ( + "strconv" +) + +func _() { + var foo []int + foo.append //@rank(" //", postfixAppend) + + []int{}.append //@complete(" //") + + []int{}.last //@complete(" //") + + + foo.copy //@rank(" //", postfixCopy) + + var s struct{ i []int } + s.i.copy //@rank(" //", postfixCopy) + + var _ []int = s.i.copy //@complete(" //") + + var blah func() []int + blah().append //@complete(" //") +} + +func _() { + /* append! */ //@item(postfixAppend, "append!", "append and re-assign slice", "snippet") + /* copy! */ //@item(postfixCopy, "copy!", "duplicate slice", "snippet") + /* for! */ //@item(postfixFor, "for!", "range over slice by index", "snippet") + /* forr! */ //@item(postfixForr, "forr!", "range over slice by index and value", "snippet") + /* last! */ //@item(postfixLast, "last!", "s[len(s)-1]", "snippet") + /* len! */ //@item(postfixLen, "len!", "len(s)", "snippet") + /* print! */ //@item(postfixPrint, "print!", "print to stdout", "snippet") + /* range! */ //@item(postfixRange, "range!", "range over slice", "snippet") + /* reverse! */ //@item(postfixReverse, "reverse!", "reverse slice", "snippet") + /* sort! */ //@item(postfixSort, "sort!", "sort.Slice()", "snippet") + /* var! */ //@item(postfixVar, "var!", "assign to variable", "snippet") + /* ifnotnil! */ //@item(postfixIfNotNil, "ifnotnil!", "if expr != nil", "snippet") + + var foo []int + foo. //@complete(" //", postfixAppend, postfixCopy, postfixFor, postfixForr, postfixIfNotNil, postfixLast, postfixLen, postfixPrint, postfixRange, postfixReverse, postfixSort, postfixVar) + foo = nil + + foo.append //@snippet(" //", postfixAppend, "foo = append(foo, $0)") + foo.copy //snippet(" //", postfixCopy, "fooCopy := make([]int, len(foo))\ncopy($fooCopy, foo)\n") + foo.fo //@snippet(" //", postfixFor, "for ${1:} := range foo {\n\t$0\n}") + foo.forr //@snippet(" //", postfixForr, "for ${1:}, ${2:} := range foo {\n\t$0\n}") + foo.last //@snippet(" //", postfixLast, "foo[len(foo)-1]") + foo.len //@snippet(" //", postfixLen, "len(foo)") + foo.print //@snippet(" //", postfixPrint, `fmt.Printf("foo: %v\n", foo)`) + foo.rang //@snippet(" //", postfixRange, "for ${1:}, ${2:} := range foo {\n\t$0\n}") + foo.reverse //@snippet(" //", postfixReverse, "slices.Reverse(foo)") + foo.sort //@snippet(" //", postfixSort, "sort.Slice(foo, func(i, j int) bool {\n\t$0\n})") + foo.va //@snippet(" //", postfixVar, "${1:} := foo") + foo.ifnotnil //@snippet(" //", postfixIfNotNil, "if foo != nil {\n\t$0\n}") +} + +func _() { + /* for! */ //@item(postfixForMap, "for!", "range over map by key", "snippet") + /* forr! */ //@item(postfixForrMap, "forr!", "range over map by key and value", "snippet") + /* range! */ //@item(postfixRangeMap, "range!", "range over map", "snippet") + /* clear! */ //@item(postfixClear, "clear!", "clear map contents", "snippet") + /* keys! */ //@item(postfixKeys, "keys!", "create slice of keys", "snippet") + + var foo map[int]int + foo. //@complete(" //", postfixClear, postfixForMap, postfixForrMap, postfixIfNotNil, postfixKeys, postfixLen, postfixPrint, postfixRangeMap, postfixVar) + + foo = nil + + foo.fo //@snippet(" //", postfixFor, "for ${1:} := range foo {\n\t$0\n}") + foo.forr //@snippet(" //", postfixForr, "for ${1:}, ${2:} := range foo {\n\t$0\n}") + foo.rang //@snippet(" //", postfixRange, "for ${1:}, ${2:} := range foo {\n\t$0\n}") + foo.clear //@snippet(" //", postfixClear, "for k := range foo {\n\tdelete(foo, k)\n}\n") + foo.keys //@snippet(" //", postfixKeys, "keys := make([]int, 0, len(foo))\nfor k := range foo {\n\tkeys = append(keys, k)\n}\n") +} + +func _() { + /* for! */ //@item(postfixForChannel, "for!", "range over channel", "snippet") + /* range! */ //@item(postfixRangeChannel, "range!", "range over channel", "snippet") + + var foo chan int + foo. //@complete(" //", postfixForChannel, postfixIfNotNil, postfixLen, postfixPrint, postfixRangeChannel, postfixVar) + + foo = nil + + foo.fo //@snippet(" //", postfixForChannel, "for ${1:} := range foo {\n\t$0\n}") + foo.rang //@snippet(" //", postfixRangeChannel, "for ${1:} := range foo {\n\t$0\n}") +} + +type T struct { + Name string +} + +func _() (string, T, map[string]string, error) { + /* iferr! */ //@item(postfixIfErr, "iferr!", "check error and return", "snippet") + /* variferr! */ //@item(postfixVarIfErr, "variferr!", "assign variables and check error", "snippet") + /* var! */ //@item(postfixVars, "var!", "assign to variables", "snippet") + + strconv.Atoi("32"). //@complete(" //", postfixIfErr, postfixPrint, postfixVars, postfixVarIfErr) + + var err error + err.iferr //@snippet(" //", postfixIfErr, "if err != nil {\n\treturn \"\", T{}, nil, ${1:}\n}\n") + + strconv.Atoi("32").iferr //@snippet(" //", postfixIfErr, "if _, err := strconv.Atoi(\"32\"); err != nil {\n\treturn \"\", T{}, nil, ${1:}\n}\n") + + strconv.Atoi("32").variferr //@snippet(" //", postfixVarIfErr, "${1:}, ${2:} := strconv.Atoi(\"32\")\nif ${2:} != nil {\n\treturn \"\", T{}, nil, ${3:}\n}\n") + + // test function return multiple errors + var foo func() (error, error) + foo().iferr //@snippet(" //", postfixIfErr, "if _, err := foo(); err != nil {\n\treturn \"\", T{}, nil, ${1:}\n}\n") + foo().variferr //@snippet(" //", postfixVarIfErr, "${1:}, ${2:} := foo()\nif ${2:} != nil {\n\treturn \"\", T{}, nil, ${3:}\n}\n") + + // test function just return error + var bar func() error + bar().iferr //@snippet(" //", postfixIfErr, "if err := bar(); err != nil {\n\treturn \"\", T{}, nil, ${1:}\n}\n") + bar().variferr //@snippet(" //", postfixVarIfErr, "${1:} := bar()\nif ${1:} != nil {\n\treturn \"\", T{}, nil, ${2:}\n}\n") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/postfix_placeholder.txt b/contribs/gnopls/internal/test/marker/testdata/completion/postfix_placeholder.txt new file mode 100644 index 00000000000..7569f130466 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/postfix_placeholder.txt @@ -0,0 +1,83 @@ +These tests check that postfix completions when enable usePlaceholders + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "usePlaceholders": true +} + +-- go.mod -- +module golang.org/lsptests/snippets + +go 1.18 + +-- postfix.go -- +package snippets + +import ( + "strconv" +) + +func _() { + /* for! */ //@item(postfixFor, "for!", "range over slice by index", "snippet") + /* forr! */ //@item(postfixForr, "forr!", "range over slice by index and value", "snippet") + /* range! */ //@item(postfixRange, "range!", "range over slice", "snippet") + /* var! */ //@item(postfixVar, "var!", "assign to variable", "snippet") + + var foo []int + + foo.fo //@snippet(" //", postfixFor, "for ${1:i} := range foo {\n\t$0\n}") + foo.forr //@snippet(" //", postfixForr, "for ${1:i}, ${2:v} := range foo {\n\t$0\n}") + foo.rang //@snippet(" //", postfixRange, "for ${1:i}, ${2:v} := range foo {\n\t$0\n}") + foo.va //@snippet(" //", postfixVar, "${1:i} := foo") +} + +func _() { + /* for! */ //@item(postfixForMap, "for!", "range over map by key", "snippet") + /* forr! */ //@item(postfixForrMap, "forr!", "range over map by key and value", "snippet") + /* range! */ //@item(postfixRangeMap, "range!", "range over map", "snippet") + + var foo map[int]int + + foo.fo //@snippet(" //", postfixFor, "for ${1:k} := range foo {\n\t$0\n}") + foo.forr //@snippet(" //", postfixForr, "for ${1:k}, ${2:v} := range foo {\n\t$0\n}") + foo.rang //@snippet(" //", postfixRange, "for ${1:k}, ${2:v} := range foo {\n\t$0\n}") +} + +func _() { + /* for! */ //@item(postfixForChannel, "for!", "range over channel", "snippet") + /* range! */ //@item(postfixRangeChannel, "range!", "range over channel", "snippet") + + var foo chan int + + foo.fo //@snippet(" //", postfixForChannel, "for ${1:e} := range foo {\n\t$0\n}") + foo.rang //@snippet(" //", postfixRangeChannel, "for ${1:e} := range foo {\n\t$0\n}") +} + +type T struct { + Name string +} + +func _() (string, T, map[string]string, error) { + /* iferr! */ //@item(postfixIfErr, "iferr!", "check error and return", "snippet") + /* variferr! */ //@item(postfixVarIfErr, "variferr!", "assign variables and check error", "snippet") + /* var! */ //@item(postfixVars, "var!", "assign to variables", "snippet") + + + var err error + err.iferr //@snippet(" //", postfixIfErr, "if err != nil {\n\treturn \"\", T{}, nil, ${1:err}\n}\n") + strconv.Atoi("32").iferr //@snippet(" //", postfixIfErr, "if _, err := strconv.Atoi(\"32\"); err != nil {\n\treturn \"\", T{}, nil, ${1:err}\n}\n") + strconv.Atoi("32").variferr //@snippet(" //", postfixVarIfErr, "${1:i}, ${2:err} := strconv.Atoi(\"32\")\nif ${2:err} != nil {\n\treturn \"\", T{}, nil, ${3:${2:err}}\n}\n") + + // test function return multiple errors + var foo func() (error, error) + foo().iferr //@snippet(" //", postfixIfErr, "if _, err := foo(); err != nil {\n\treturn \"\", T{}, nil, ${1:err}\n}\n") + foo().variferr //@snippet(" //", postfixVarIfErr, "${1:err2}, ${2:err} := foo()\nif ${2:err} != nil {\n\treturn \"\", T{}, nil, ${3:${2:err}}\n}\n") + + // test function just return error + var bar func() error + bar().iferr //@snippet(" //", postfixIfErr, "if err := bar(); err != nil {\n\treturn \"\", T{}, nil, ${1:err}\n}\n") + bar().variferr //@snippet(" //", postfixVarIfErr, "${1:err2} := bar()\nif ${1:err2} != nil {\n\treturn \"\", T{}, nil, ${2:${1:err2}}\n}\n") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/printf.txt b/contribs/gnopls/internal/test/marker/testdata/completion/printf.txt new file mode 100644 index 00000000000..270927e8211 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/printf.txt @@ -0,0 +1,42 @@ +This test checks various ranking of completion results related to printf. + +-- flags -- +-ignore_extra_diags + +-- printf.go -- +package printf + +import "fmt" + +func myPrintf(string, ...interface{}) {} + +func _() { + var ( + aInt int //@item(printfInt, "aInt", "int", "var") + aFloat float64 //@item(printfFloat, "aFloat", "float64", "var") + aString string //@item(printfString, "aString", "string", "var") + aBytes []byte //@item(printfBytes, "aBytes", "[]byte", "var") + aStringer fmt.Stringer //@item(printfStringer, "aStringer", "fmt.Stringer", "var") + aError error //@item(printfError, "aError", "error", "var") + aBool bool //@item(printfBool, "aBool", "bool", "var") + ) + + myPrintf("%d", a) //@rank(")", printfInt, printfFloat) + myPrintf("%s", a) //@rank(")", printfString, printfInt),rank(")", printfBytes, printfInt),rank(")", printfStringer, printfInt),rank(")", printfError, printfInt) + myPrintf("%w", a) //@rank(")", printfError, printfInt) + myPrintf("%x %[1]b", a) //@rank(")", printfInt, printfString) + + fmt.Printf("%t", a) //@rank(")", printfBool, printfInt) + + fmt.Fprintf(nil, "%f", a) //@rank(")", printfFloat, printfInt) + + fmt.Sprintf("%[2]q %[1]*.[3]*[4]f", + a, //@rank(",", printfInt, printfFloat) + a, //@rank(",", printfString, printfFloat) + a, //@rank(",", printfInt, printfFloat) + a, //@rank(",", printfFloat, printfInt) + ) + + // Don't insert as "&aStringer" + fmt.Printf("%p", a) //@snippet(")", printfStringer, "aStringer") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/range_func.txt b/contribs/gnopls/internal/test/marker/testdata/completion/range_func.txt new file mode 100644 index 00000000000..638ef9ba1fd --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/range_func.txt @@ -0,0 +1,23 @@ +This test shows we prefer rangeable funcs in range statements. + +-- flags -- +-ignore_extra_diags + +-- range_func.go -- +package rangefunc + +func iterNot(func(int)) {} +func iter0(func() bool) {} +func iter1(func(int) bool) {} +func iter2(func(int, int) bool) + +func _() { + for range i { //@rank(" {", "iter0", "iterNot"),rank(" {", "iter1", "iterNot"),rank(" {", "iter2", "iterNot") + } + + for k := range i { //@rank(" {", "iter1", "iterNot"),rank(" {", "iter1", "iter0"),rank(" {", "iter2", "iter0") + } + + for k, v := range i { //@rank(" {", "iter2", "iterNot"),rank(" {", "iter2", "iter0"),rank(" {", "iter2", "iter1") + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/rank.txt b/contribs/gnopls/internal/test/marker/testdata/completion/rank.txt new file mode 100644 index 00000000000..03ea565a400 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/rank.txt @@ -0,0 +1,212 @@ +This test checks various ranking of completion results. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false, + "deepCompletion": false +} + +-- go.mod -- +module golang.org/lsptests/rank + +go 1.18 + +-- struct/struct_rank.go -- +package struct_rank + +type foo struct { + c int //@item(c_rank, "c", "int", "field") + b int //@item(b_rank, "b", "int", "field") + a int //@item(a_rank, "a", "int", "field") +} + +func f() { + foo := foo{} //@rank("}", c_rank, b_rank, a_rank) +} + +-- assign_rank.go -- +package rank + +// Literal completion results. +/* int() */ //@item(int, "int()", "int", "var") +/* string() */ //@item(string, "string()", "string", "var") + +var ( + apple int = 3 //@item(apple, "apple", "int", "var") + pear string = "hello" //@item(pear, "pear", "string", "var") +) + +func _() { + orange := 1 //@item(orange, "orange", "int", "var") + grape := "hello" //@item(grape, "grape", "string", "var") + orange, grape = 2, "hello" //@complete(" \"", grape, pear, string, orange, apple) +} + +func _() { + var pineapple int //@item(pineapple, "pineapple", "int", "var") + pineapple = 1 //@complete(" 1", pineapple, apple, int, pear) + + y := //@complete(" /", pineapple, apple, pear) +} + +-- binexpr_rank.go -- +package rank + +func _() { + _ = 5 + ; //@complete(" ;", apple, pear) + y := + 5; //@complete(" +", apple, pear) + + if 6 == {} //@complete(" {", apple, pear) +} + +-- boolexpr_rank.go -- +package rank + +func _() { + someRandomBoolFunc := func() bool { //@item(boolExprFunc, "someRandomBoolFunc", "func() bool", "var") + return true + } + + var foo, bar int //@item(boolExprBar, "bar", "int", "var") + if foo == 123 && b { //@rank(" {", boolExprBar, boolExprFunc) + } +} + +-- convert_rank.go -- +package rank + +import "time" + +// Copied from the old builtins.go, which has been ported to the new marker tests. +/* complex(r float64, i float64) */ //@item(complex, "complex", "func(r float64, i float64) complex128", "func") + +func _() { + type strList []string + wantsStrList := func(strList) {} + + var ( + convA string //@item(convertA, "convA", "string", "var") + convB []string //@item(convertB, "convB", "[]string", "var") + ) + wantsStrList(strList(conv)) //@complete("))", convertB, convertA) +} + +func _() { + type myInt int + + const ( + convC = "hi" //@item(convertC, "convC", "string", "const") + convD = 123 //@item(convertD, "convD", "int", "const") + convE int = 123 //@item(convertE, "convE", "int", "const") + convF string = "there" //@item(convertF, "convF", "string", "const") + convG myInt = 123 //@item(convertG, "convG", "myInt", "const") + ) + + var foo int + foo = conv //@rank(" //", convertE, convertD) + + var mi myInt + mi = conv //@rank(" //", convertG, convertD, convertE) + mi + conv //@rank(" //", convertG, convertD, convertE) + + 1 + conv //@rank(" //", convertD, convertC),rank(" //", convertE, convertC),rank(" //", convertG, convertC) + + type myString string + var ms myString + ms = conv //@rank(" //", convertC, convertF) + + type myUint uint32 + var mu myUint + mu = conv //@rank(" //", convertD, convertE) + + // don't downrank constants when assigning to interface{} + var _ interface{} = c //@rank(" //", convertD, complex) + + var _ time.Duration = conv //@rank(" //", convertD, convertE),snippet(" //", convertE, "time.Duration(convE)") + + var convP myInt //@item(convertP, "convP", "myInt", "var") + var _ *int = conv //@snippet(" //", convertP, "(*int)(&convP)") + + var ff float64 //@item(convertFloat, "ff", "float64", "var") + f == convD //@snippet(" =", convertFloat, "ff") +} + +-- switch_rank.go -- +package rank + +import "time" + +func _() { + switch pear { + case _: //@rank("_", pear, apple) + } + + time.Monday //@item(timeMonday, "time.Monday", "time.Weekday", "const"),item(monday ,"Monday", "time.Weekday", "const") + time.Friday //@item(timeFriday, "time.Friday", "time.Weekday", "const"),item(friday ,"Friday", "time.Weekday", "const") + + now := time.Now() + now.Weekday //@item(nowWeekday, "now.Weekday", "func() time.Weekday", "method") + + then := time.Now() + then.Weekday //@item(thenWeekday, "then.Weekday", "func() time.Weekday", "method") + + switch time.Weekday(0) { + case time.Monday, time.Tuesday: + case time.Wednesday, time.Thursday: + case time.Saturday, time.Sunday: + // TODO: these tests were disabled because they require deep completion + // (which would break other tests) + case t: // rank(":", timeFriday, timeMonday) + case time.: //@rank(":", friday, monday) + + case now.Weekday(): + case week: // rank(":", thenWeekday, nowWeekday) + } +} + +-- type_assert_rank.go -- +package rank + +func _() { + type flower int //@item(flower, "flower", "int", "type") + var fig string //@item(fig, "fig", "string", "var") + + _ = interface{}(nil).(f) //@complete(") //", flower) +} + +-- type_switch_rank.go -- +package rank + +import ( + "fmt" + "go/ast" +) + +func _() { + type basket int //@item(basket, "basket", "int", "type") + var banana string //@item(banana, "banana", "string", "var") + + switch interface{}(pear).(type) { + case b: //@complete(":", basket) + b //@complete(" //", banana, basket) + } + + Ident //@item(astIdent, "Ident", "struct{...}", "struct") + IfStmt //@item(astIfStmt, "IfStmt", "struct{...}", "struct") + + switch ast.Node(nil).(type) { + case *ast.Ident: + case *ast.I: //@rank(":", astIfStmt, astIdent) + } + + Stringer //@item(fmtStringer, "Stringer", "interface{...}", "interface") + GoStringer //@item(fmtGoStringer, "GoStringer", "interface{...}", "interface") + + switch interface{}(nil).(type) { + case fmt.Stringer: //@rank(":", fmtStringer, fmtGoStringer) + } +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/snippet.txt b/contribs/gnopls/internal/test/marker/testdata/completion/snippet.txt new file mode 100644 index 00000000000..eb0a4140b90 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/snippet.txt @@ -0,0 +1,77 @@ +This test checks basic completion snippet support. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/snippet + +-- snippet.go -- +package snippets + +// Pre-set this marker, as we don't have a "source" for it in this package. +// The comment is used to create a synthetic completion item. +// +// TODO(rfindley): allow completion markers to refer to ad-hoc items inline, +// without this trick. +/* Error() */ //@item(Error, "Error", "func() string", "method") + +type AliasType = int //@item(sigAliasType, "AliasType", "AliasType", "type") + +func foo(i int, b bool) {} //@item(snipFoo, "foo", "func(i int, b bool)", "func") +func bar(fn func()) func() {} //@item(snipBar, "bar", "func(fn func())", "func") +func baz(at AliasType, b bool) {} //@item(snipBaz, "baz", "func(at AliasType, b bool)", "func") + +type Foo struct { + Bar int //@item(snipFieldBar, "Bar", "int", "field") + Func func(at AliasType) error //@item(snipFieldFunc, "Func", "func(at AliasType) error", "field") +} + +func (Foo) Baz() func() {} //@item(snipMethodBaz, "Baz", "func() func()", "method") +func (Foo) BazBar() func() {} //@item(snipMethodBazBar, "BazBar", "func() func()", "method") +func (Foo) BazBaz(at AliasType) func() {} //@item(snipMethodBazBaz, "BazBaz", "func(at AliasType) func()", "method") + +func _() { + f //@snippet(" //", snipFoo, "foo(${1:})") + + bar //@snippet(" //", snipBar, "bar(${1:})") + + baz //@snippet(" //", snipBaz, "baz(${1:})") + baz() //@signature("(", "baz(at AliasType, b bool)", 0) + + bar(nil) //@snippet("(", snipBar, "bar") + bar(ba) //@snippet(")", snipBar, "bar(${1:})") + var f Foo + bar(f.Ba) //@snippet(")", snipMethodBaz, "Baz()") + (bar)(nil) //@snippet(")", snipBar, "bar(${1:})") + (f.Ba)() //@snippet(")", snipMethodBaz, "Baz()") + + Foo{ + B //@snippet(" //", snipFieldBar, "Bar: ${1:},") + } + + Foo{ + F //@snippet(" //", snipFieldFunc, "Func: ${1:},") + } + + Foo{B} //@snippet("}", snipFieldBar, "Bar: ${1:}") + Foo{} //@snippet("}", snipFieldBar, "Bar: ${1:}") + + Foo{Foo{}.B} //@snippet("} ", snipFieldBar, "Bar") + + var err error + err.Error() //@snippet("E", Error, "Error()") + f.Baz() //@snippet("B", snipMethodBaz, "Baz()") + + f.Baz() //@snippet("(", snipMethodBazBar, "BazBar") + + f.Baz() //@snippet("B", snipMethodBazBaz, "BazBaz(${1:})") +} + +func _() { + type bar struct { + a int + b float64 //@item(snipBarB, "b", "float64") + } + bar{b} //@snippet("}", snipBarB, "b: ${1:}") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/snippet_placeholder.txt b/contribs/gnopls/internal/test/marker/testdata/completion/snippet_placeholder.txt new file mode 100644 index 00000000000..e19ccb06aa2 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/snippet_placeholder.txt @@ -0,0 +1,83 @@ +This test checks basic completion snippet support, using placeholders. + +Unlike the old marker tests, the new marker tests assume static configuration +(as defined by settings.json), and therefore there is duplication between this +test and snippet.txt. This is a price we pay so that we don't have to mutate +the server during testing. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "usePlaceholders": true +} + +-- go.mod -- +module golang.org/lsptests/snippet + +-- snippet.go -- +package snippets + +// Pre-set this marker, as we don't have a "source" for it in this package. +/* Error() */ //@item(Error, "Error", "func() string", "method") + +type AliasType = int //@item(sigAliasType, "AliasType", "AliasType", "type") + +func foo(i int, b bool) {} //@item(snipFoo, "foo", "func(i int, b bool)", "func") +func bar(fn func()) func() {} //@item(snipBar, "bar", "func(fn func())", "func") +func baz(at AliasType, b bool) {} //@item(snipBaz, "baz", "func(at AliasType, b bool)", "func") + +type Foo struct { + Bar int //@item(snipFieldBar, "Bar", "int", "field") + Func func(at AliasType) error //@item(snipFieldFunc, "Func", "func(at AliasType) error", "field") +} + +func (Foo) Baz() func() {} //@item(snipMethodBaz, "Baz", "func() func()", "method") +func (Foo) BazBar() func() {} //@item(snipMethodBazBar, "BazBar", "func() func()", "method") +func (Foo) BazBaz(at AliasType) func() {} //@item(snipMethodBazBaz, "BazBaz", "func(at AliasType) func()", "method") + +func _() { + f //@snippet(" //", snipFoo, "foo(${1:i int}, ${2:b bool})") + + bar //@snippet(" //", snipBar, "bar(${1:fn func()})") + + baz //@snippet(" //", snipBaz, "baz(${1:at AliasType}, ${2:b bool})") + baz() //@signature("(", "baz(at AliasType, b bool)", 0) + + bar(nil) //@snippet("(", snipBar, "bar") + bar(ba) //@snippet(")", snipBar, "bar(${1:fn func()})") + var f Foo + bar(f.Ba) //@snippet(")", snipMethodBaz, "Baz()") + (bar)(nil) //@snippet(")", snipBar, "bar(${1:fn func()})") + (f.Ba)() //@snippet(")", snipMethodBaz, "Baz()") + + Foo{ + B //@snippet(" //", snipFieldBar, "Bar: ${1:int},") + } + + Foo{ + F //@snippet(" //", snipFieldFunc, "Func: ${1:func(at AliasType) error},") + } + + Foo{B} //@snippet("}", snipFieldBar, "Bar: ${1:int}") + Foo{} //@snippet("}", snipFieldBar, "Bar: ${1:int}") + + Foo{Foo{}.B} //@snippet("} ", snipFieldBar, "Bar") + + var err error + err.Error() //@snippet("E", Error, "Error()") + f.Baz() //@snippet("B", snipMethodBaz, "Baz()") + + f.Baz() //@snippet("(", snipMethodBazBar, "BazBar") + + f.Baz() //@snippet("B", snipMethodBazBaz, "BazBaz(${1:at AliasType})") +} + +func _() { + type bar struct { + a int + b float64 //@item(snipBarB, "b", "field") + } + bar{b} //@snippet("}", snipBarB, "b: ${1:float64}") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/statements.txt b/contribs/gnopls/internal/test/marker/testdata/completion/statements.txt new file mode 100644 index 00000000000..9856d938ea3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/statements.txt @@ -0,0 +1,134 @@ +This test exercises completion around various statements. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "usePlaceholders": true +} + +-- go.mod -- +module golang.org/lsptests/statements + +-- append.go -- +package statements + +func _() { + type mySlice []int + + var ( + abc []int //@item(stmtABC, "abc", "[]int", "var") + abcdef mySlice //@item(stmtABCDEF, "abcdef", "mySlice", "var") + ) + + /* abcdef = append(abcdef, ) */ //@item(stmtABCDEFAssignAppend, "abcdef = append(abcdef, )", "", "func") + + // don't offer "abc = append(abc, )" because "abc" isn't necessarily + // better than "abcdef". + abc //@complete(" //", stmtABC, stmtABCDEF) + + abcdef //@complete(" //", stmtABCDEF, stmtABCDEFAssignAppend) + + /* append(abc, ) */ //@item(stmtABCAppend, "append(abc, )", "", "func") + + abc = app //@snippet(" //", stmtABCAppend, "append(abc, ${1:})") +} + +func _() { + var s struct{ xyz []int } + + /* xyz = append(s.xyz, ) */ //@item(stmtXYZAppend, "xyz = append(s.xyz, )", "", "func") + + s.x //@snippet(" //", stmtXYZAppend, "xyz = append(s.xyz, ${1:})") + + /* s.xyz = append(s.xyz, ) */ //@item(stmtDeepXYZAppend, "s.xyz = append(s.xyz, )", "", "func") + + sx //@snippet(" //", stmtDeepXYZAppend, "s.xyz = append(s.xyz, ${1:})") +} + +func _() { + var foo [][]int + + /* append(foo[0], ) */ //@item(stmtFooAppend, "append(foo[0], )", "", "func") + + foo[0] = app //@complete(" //", stmtFooAppend),snippet(" //", stmtFooAppend, "append(foo[0], ${1:})") +} + +-- if_err_check_return.go -- +package statements + +import ( + "bytes" + "io" + "os" +) + +func one() (int, float32, io.Writer, *int, []int, bytes.Buffer, error) { + /* if err != nil { return err } */ //@item(stmtOneIfErrReturn, "if err != nil { return err }", "", "") + /* err != nil { return err } */ //@item(stmtOneErrReturn, "err != nil { return err }", "", "") + + _, err := os.Open("foo") + //@snippet("", stmtOneIfErrReturn, "if err != nil {\n\treturn 0, 0, nil, nil, nil, bytes.Buffer{\\}, ${1:err}\n\\}") + + _, err = os.Open("foo") + i //@snippet(" //", stmtOneIfErrReturn, "if err != nil {\n\treturn 0, 0, nil, nil, nil, bytes.Buffer{\\}, ${1:err}\n\\}") + + _, err = os.Open("foo") + if er //@snippet(" //", stmtOneErrReturn, "err != nil {\n\treturn 0, 0, nil, nil, nil, bytes.Buffer{\\}, ${1:err}\n\\}") + + _, err = os.Open("foo") + if //@snippet(" //", stmtOneIfErrReturn, "if err != nil {\n\treturn 0, 0, nil, nil, nil, bytes.Buffer{\\}, ${1:err}\n\\}") + + _, err = os.Open("foo") + if //@snippet("//", stmtOneIfErrReturn, "if err != nil {\n\treturn 0, 0, nil, nil, nil, bytes.Buffer{\\}, ${1:err}\n\\}") +} + +-- if_err_check_return2.go -- +package statements + +import "os" + +func two() error { + var s struct{ err error } + + /* if s.err != nil { return s.err } */ //@item(stmtTwoIfErrReturn, "if s.err != nil { return s.err }", "", "") + + _, s.err = os.Open("foo") + //@snippet("", stmtTwoIfErrReturn, "if s.err != nil {\n\treturn ${1:s.err}\n\\}") +} + +-- if_err_check_test.go -- +package statements + +import ( + "os" + "testing" +) + +func TestErr(t *testing.T) { + /* if err != nil { t.Fatal(err) } */ //@item(stmtOneIfErrTFatal, "if err != nil { t.Fatal(err) }", "", "") + + _, err := os.Open("foo") + //@snippet("", stmtOneIfErrTFatal, "if err != nil {\n\tt.Fatal(err)\n\\}") +} + +func BenchmarkErr(b *testing.B) { + /* if err != nil { b.Fatal(err) } */ //@item(stmtOneIfErrBFatal, "if err != nil { b.Fatal(err) }", "", "") + + _, err := os.Open("foo") + //@snippet("", stmtOneIfErrBFatal, "if err != nil {\n\tb.Fatal(err)\n\\}") +} + +-- return.go -- +package statements + +//@item(stmtReturnZeroValues, `return 0, "", nil`) + +func foo() (int, string, error) { + ret //@snippet(" ", stmtReturnZeroValues, "return ${1:0}, ${2:\"\"}, ${3:nil}") +} + +func bar() (int, string, error) { + return //@snippet(" ", stmtReturnZeroValues, "return ${1:0}, ${2:\"\"}, ${3:nil}") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/testy.txt b/contribs/gnopls/internal/test/marker/testdata/completion/testy.txt new file mode 100644 index 00000000000..36c98e34acd --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/testy.txt @@ -0,0 +1,61 @@ + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module testy.test + +go 1.18 + +-- types/types.go -- +package types + + +-- signature/signature.go -- +package signature + +type Alias = int + +-- snippets/snippets.go -- +package snippets + +import ( + "testy.test/signature" + t "testy.test/types" +) + +func X(_ map[signature.Alias]t.CoolAlias) (map[signature.Alias]t.CoolAlias) { + return nil +} + +-- testy/testy.go -- +package testy + +func a() { //@item(funcA, "a", "func()", "func") + //@complete("", funcA) +} + + +-- testy/testy_test.go -- +package testy + +import ( + "testing" + + sig "testy.test/signature" + "testy.test/snippets" +) + +func TestSomething(t *testing.T) { //@item(TestSomething, "TestSomething(t *testing.T)", "", "func") + var x int //@loc(testyX, "x"), diag("x", re"declared (and|but) not used") + a() //@loc(testyA, "a") +} + +func _() { + _ = snippets.X(nil) //@signature("nil", "X(_ map[sig.Alias]types.CoolAlias) map[sig.Alias]types.CoolAlias", 0) + var _ sig.Alias +} + +func issue63578(err error) { + err.Error() //@signature(")", "Error()", 0) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/type_assert.txt b/contribs/gnopls/internal/test/marker/testdata/completion/type_assert.txt new file mode 100644 index 00000000000..9cc81cd441f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/type_assert.txt @@ -0,0 +1,30 @@ +This test checks completion related to type assertions. + +-- flags -- +-ignore_extra_diags + +-- type_assert.go -- +package typeassert + +type abc interface { //@item(abcIntf, "abc", "interface{...}", "interface") + abc() +} + +type abcImpl struct{} //@item(abcImpl, "abcImpl", "struct{...}", "struct") +func (abcImpl) abc() + +type abcPtrImpl struct{} //@item(abcPtrImpl, "abcPtrImpl", "struct{...}", "struct") +func (*abcPtrImpl) abc() + +type abcNotImpl struct{} //@item(abcNotImpl, "abcNotImpl", "struct{...}", "struct") + +func _() { + var a abc + switch a.(type) { + case ab: //@complete(":", abcImpl, abcPtrImpl, abcIntf, abcNotImpl) + case *ab: //@complete(":", abcImpl, abcPtrImpl, abcIntf, abcNotImpl) + } + + a.(ab) //@complete(")", abcImpl, abcPtrImpl, abcIntf, abcNotImpl) + a.(*ab) //@complete(")", abcImpl, abcPtrImpl, abcIntf, abcNotImpl) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/type_mods.txt b/contribs/gnopls/internal/test/marker/testdata/completion/type_mods.txt new file mode 100644 index 00000000000..3988a372b57 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/type_mods.txt @@ -0,0 +1,27 @@ +This test check completion snippets with type modifiers. + +-- flags -- +-ignore_extra_diags + +-- typemods.go -- +package typemods + +func fooFunc() func() int { + return func() int { + return 0 + } +} + +func fooPtr() *int { + return nil +} + +func _() { + var _ int = foo //@snippet(" //", "fooFunc", "fooFunc()()"),snippet(" //", "fooPtr", "*fooPtr()") +} + +func _() { + var m map[int][]chan int + + var _ int = m //@snippet(" //", "m", "<-m[${1:}][${2:}]") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/type_params.txt b/contribs/gnopls/internal/test/marker/testdata/completion/type_params.txt new file mode 100644 index 00000000000..8e2f5d7e401 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/type_params.txt @@ -0,0 +1,70 @@ +This test checks various ranking of completion results related to type +parameters. + +-- flags -- +-ignore_extra_diags + +-- type_params.go -- +package typeparams + +// Copied from the old builtins.go, which has been ported to the new marker tests. +/* string */ //@item(string, "string", "", "type") +/* float32 */ //@item(float32, "float32", "", "type") +/* float64 */ //@item(float64, "float64", "", "type") +/* int */ //@item(int, "int", "", "type") + +func one[a int | string]() {} +func two[a int | string, b float64 | int]() {} + +func _() { + one[]() //@rank("]", string, float64) + two[]() //@rank("]", int, float64) + two[int, f]() //@rank("]", float64, float32) +} + +func slices[a []int | []float64]() {} //@item(tpInts, "[]int", "[]int", "type"),item(tpFloats, "[]float64", "[]float64", "type") + +func _() { + slices[]() //@rank("]", tpInts),rank("]", tpFloats) +} + +type s[a int | string] struct{} + +func _() { + s[]{} //@rank("]", int, float64) +} + +func takesGeneric[a int | string](s[a]) { + "s[a]{}" //@item(tpInScopeLit, "s[a]{}", "", "var") + takesGeneric() //@rank(")", tpInScopeLit),snippet(")", tpInScopeLit, "s[a]{\\}") +} + +func _() { + s[int]{} //@item(tpInstLit, "s[int]{}", "", "var") + takesGeneric[int]() //@rank(")", tpInstLit),snippet(")", tpInstLit, "s[int]{\\}") + + "s[...]{}" //@item(tpUninstLit, "s[...]{}", "", "var") + takesGeneric() //@rank(")", tpUninstLit),snippet(")", tpUninstLit, "s[${1:}]{\\}") +} + +func returnTP[A int | float64](a A) A { //@item(returnTP, "returnTP", "something", "func") + return a +} + +func _() { + var _ int = returnTP //@snippet(" //", returnTP, "returnTP(${1:})") + + var aa int //@item(tpInt, "aa", "int", "var") + var ab float64 //@item(tpFloat, "ab", "float64", "var") + returnTP[int](a) //@rank(")", tpInt, tpFloat) +} + +func takesFunc[T any](func(T) T) { + var _ func(t T) T = f //@snippet(" //", tpLitFunc, "func(t T) T {$0\\}") +} + +func _() { + _ = "func(...) {}" //@item(tpLitFunc, "func(...) {}", "", "var") + takesFunc() //@snippet(")", tpLitFunc, "func(${1:}) ${2:} {$0\\}") + takesFunc[int]() //@snippet(")", tpLitFunc, "func(i int) int {$0\\}") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/unimported-std.txt b/contribs/gnopls/internal/test/marker/testdata/completion/unimported-std.txt new file mode 100644 index 00000000000..3bedf6bc5bd --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/unimported-std.txt @@ -0,0 +1,49 @@ +Test of unimported completions respecting the effective Go version of the file. + +(See unprefixed file for same test of imported completions.) + +These symbols below were introduced to go/types in go1.22: + + Alias + Info.FileVersions + (Checker).PkgNameOf + +The underlying logic depends on versions.FileVersion, which only +behaves correctly in go1.22. (When go1.22 is assured, we can remove +the min_go flag but leave the test inputs unchanged.) + +-- flags -- +-ignore_extra_diags -min_go=go1.22 + +-- go.mod -- +module example.com + +go 1.21 + +-- a/a.go -- +package a + +// package-level func +var _ = types.Sat //@rank("Sat", "Satisfies") +var _ = types.Ali //@rank("Ali", "!Alias") + +// (We don't offer completions of methods +// of types from unimported packages, so the fact that +// we don't implement std version filtering isn't evident.) + +// field +var _ = new(types.Info).Use //@rank("Use", "!Uses") +var _ = new(types.Info).Fil //@rank("Fil", "!FileVersions") + +// method +var _ = new(types.Checker).Obje //@rank("Obje", "!ObjectOf") +var _ = new(types.Checker).PkgN //@rank("PkgN", "!PkgNameOf") + +-- b/b.go -- +//go:build go1.22 + +package a + +// package-level decl +var _ = types.Sat //@rank("Sat", "Satisfies") +var _ = types.Ali //@rank("Ali", "Alias") diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/unimported.txt b/contribs/gnopls/internal/test/marker/testdata/completion/unimported.txt new file mode 100644 index 00000000000..7d12269c8ba --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/unimported.txt @@ -0,0 +1,88 @@ + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module unimported.test + +go 1.18 + +-- unimported/export_test.go -- +package unimported + +var TestExport int //@item(testexport, "TestExport", "var (from \"unimported.test/unimported\")", "var") + +-- signature/signature.go -- +package signature + +func Foo() {} + +-- foo/foo.go -- +package foo + +type StructFoo struct{ F int } + +-- baz/baz.go -- +package baz + +import ( + f "unimported.test/foo" +) + +var FooStruct f.StructFoo + +-- unimported/unimported.go -- +package unimported + +func _() { + http //@complete("p", http, httptest, httptrace, httputil) + // container/ring is extremely unlikely to be imported by anything, so shouldn't have type information. + ring.Ring //@complete(re"R()ing", ringring) + signature.Foo //@complete("Foo", signaturefoo) + + context.Bac //@complete(" //", contextBackground) +} + +// Create markers for unimported std lib packages. Only for use by this test. +/* http */ //@item(http, "http", "\"net/http\"", "package") +/* httptest */ //@item(httptest, "httptest", "\"net/http/httptest\"", "package") +/* httptrace */ //@item(httptrace, "httptrace", "\"net/http/httptrace\"", "package") +/* httputil */ //@item(httputil, "httputil", "\"net/http/httputil\"", "package") + +/* ring.Ring */ //@item(ringring, "Ring", "(from \"container/ring\")", "var") + +/* signature.Foo */ //@item(signaturefoo, "Foo", "func (from \"unimported.test/signature\")", "func") + +/* context.Background */ //@item(contextBackground, "Background", "func (from \"context\")", "func") + +// Now that we no longer type-check imported completions, +// we don't expect the context.Background().Err method (see golang/go#58663). +/* context.Background().Err */ //@item(contextBackgroundErr, "Background().Err", "func (from \"context\")", "method") + +-- unimported/unimported_cand_type.go -- +package unimported + +import ( + _ "context" + + "unimported.test/baz" +) + +func _() { + foo.StructFoo{} //@item(litFooStructFoo, "foo.StructFoo{}", "struct{...}", "struct") + + // We get the literal completion for "foo.StructFoo{}" even though we haven't + // imported "foo" yet. + baz.FooStruct = f //@snippet(" //", litFooStructFoo, "foo.StructFoo{$0\\}") +} + +-- unimported/x_test.go -- +package unimported_test + +import ( + "testing" +) + +func TestSomething(t *testing.T) { + _ = unimported.TestExport //@complete("TestExport", testexport) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/unresolved.txt b/contribs/gnopls/internal/test/marker/testdata/completion/unresolved.txt new file mode 100644 index 00000000000..d509b2670c4 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/unresolved.txt @@ -0,0 +1,16 @@ +This test verifies gopls does not crash on fake "resolved" types. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "completeUnimported": false +} + +-- unresolved.go -- +package unresolved + +func foo(interface{}) { + foo(func(i, j f //@complete(" //") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/unsafe.txt b/contribs/gnopls/internal/test/marker/testdata/completion/unsafe.txt new file mode 100644 index 00000000000..0683e3ae1b8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/unsafe.txt @@ -0,0 +1,24 @@ +This test checks completion of symbols in the 'unsafe' package. + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "matcher": "caseinsensitive" +} + +-- unsafe.go -- +package unsafe + +import ( + "unsafe" +) + +// Pre-set this marker, as we don't have a "source" for it in this package. +/* unsafe.Sizeof */ //@item(Sizeof, "Sizeof", "invalid type", "text") + +func _() { + x := struct{}{} + _ = unsafe.Sizeof(x) //@complete("z", Sizeof) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/completion/variadic.txt b/contribs/gnopls/internal/test/marker/testdata/completion/variadic.txt new file mode 100644 index 00000000000..0b2ae8212df --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/completion/variadic.txt @@ -0,0 +1,67 @@ +This test checks completion related to variadic functions. + +-- flags -- +-ignore_extra_diags + +-- variadic.go -- +package variadic + +func foo(i int, strs ...string) {} + +func bar() []string { //@item(vFunc, "bar", "func() []string", "func") + return nil +} + +func _() { + var ( + i int //@item(vInt, "i", "int", "var") + s string //@item(vStr, "s", "string", "var") + ss []string //@item(vStrSlice, "ss", "[]string", "var") + v interface{} //@item(vIntf, "v", "interface{}", "var") + ) + + foo() //@rank(")", vInt, vStr),rank(")", vInt, vStrSlice) + foo(123, ) //@rank(")", vStr, vInt),rank(")", vStrSlice, vInt) + foo(123, "", ) //@rank(")", vStr, vInt),rank(")", vStr, vStrSlice) + foo(123, s, "") //@rank(", \"", vStr, vStrSlice) + + // snippet will add the "..." for you + foo(123, ) //@snippet(")", vStrSlice, "ss..."),snippet(")", vFunc, "bar()..."),snippet(")", vStr, "s") + + // don't add "..." for interface{} + foo(123, ) //@snippet(")", vIntf, "v") +} + +func qux(...func()) {} +func f() {} //@item(vVarArg, "f", "func()", "func") + +func _() { + qux(f) //@snippet(")", vVarArg, "f") +} + +func _() { + foo(0, []string{}...) //@complete(")") +} + +-- variadic_intf.go -- +package variadic + +type baz interface { + baz() +} + +func wantsBaz(...baz) {} + +type bazImpl int + +func (bazImpl) baz() {} + +func _() { + var ( + impls []bazImpl //@item(vImplSlice, "impls", "[]bazImpl", "var") + impl bazImpl //@item(vImpl, "impl", "bazImpl", "var") + bazes []baz //@item(vIntfSlice, "bazes", "[]baz", "var") + ) + + wantsBaz() //@rank(")", vImpl, vImplSlice),rank(")", vIntfSlice, vImplSlice) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/configuration/static.txt b/contribs/gnopls/internal/test/marker/testdata/configuration/static.txt new file mode 100644 index 00000000000..c84b55db117 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/configuration/static.txt @@ -0,0 +1,41 @@ +This test confirms that gopls honors configuration even if the client does not +support dynamic configuration. + +-- capabilities.json -- +{ + "configuration": false +} + +-- settings.json -- +{ + "usePlaceholders": true, + "analyses": { + "composites": false + } +} + +-- go.mod -- +module example.com/config + +go 1.18 + +-- a/a.go -- +package a + +import "example.com/config/b" + +func Identity[P ~int](p P) P { //@item(Identity, "Identity", "", "") + return p +} + +func _() { + _ = b.B{2} + _ = Identi //@snippet(" //", Identity, "Identity(${1:p P})"), diag("Ident", re"(undefined|undeclared)") +} + +-- b/b.go -- +package b + +type B struct { + F int +} diff --git a/contribs/gnopls/internal/test/marker/testdata/definition/cgo.txt b/contribs/gnopls/internal/test/marker/testdata/definition/cgo.txt new file mode 100644 index 00000000000..777285b242b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/definition/cgo.txt @@ -0,0 +1,62 @@ +This test is ported from the old marker tests. +It tests hover and definition for cgo declarations. + +-- flags -- +-cgo + +-- go.mod -- +module cgo.test + +go 1.18 + +-- cgo/cgo.go -- +package cgo + +/* +#include <stdio.h> +#include <stdlib.h> + +void myprint(char* s) { + printf("%s\n", s); +} +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +func Example() { //@loc(cgoexample, "Example"), item(cgoexampleItem, "Example", "func()", "func") + fmt.Println() + cs := C.CString("Hello from stdio\n") + C.myprint(cs) + C.free(unsafe.Pointer(cs)) +} + +func _() { + Example() //@hover("ample", "Example", hoverExample), def("ample", cgoexample), complete("ample", cgoexampleItem) +} + +-- @hoverExample -- +```go +func Example() +``` + +[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/cgo.test/cgo#Example) +-- usecgo/usecgo.go -- +package cgoimport + +import ( + "cgo.test/cgo" +) + +func _() { + cgo.Example() //@hover("ample", "Example", hoverImportedExample), def("ample", cgoexample), complete("ample", cgoexampleItem) +} +-- @hoverImportedExample -- +```go +func cgo.Example() +``` + +[`cgo.Example` on pkg.go.dev](https://pkg.go.dev/cgo.test/cgo#Example) diff --git a/contribs/gnopls/internal/test/marker/testdata/definition/comment.txt b/contribs/gnopls/internal/test/marker/testdata/definition/comment.txt new file mode 100644 index 00000000000..39c860708b8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/definition/comment.txt @@ -0,0 +1,34 @@ +This test executes definition requests over doc links. + +-- go.mod -- +module mod.com + +go 1.19 + +-- path/path.go -- +package path + +func Join() //@loc(Join, "Join") + +-- a.go -- +package p + +import "strconv" //@loc(strconv, `"strconv"`) +import pathpkg "mod.com/path" + +const NumberBase = 10 //@loc(NumberBase, "NumberBase") + +// [Conv] converts s to an int. //@def("Conv", Conv) +func Conv(s string) int { //@loc(Conv, "Conv") + // [strconv.ParseInt] parses s and returns the integer corresponding to it. //@def("strconv", strconv) + // [NumberBase] is the base to use for number parsing. //@def("NumberBase", NumberBase) + i, _ := strconv.ParseInt(s, NumberBase, 64) + return int(i) +} + +// The declared and imported names of the package both work: +// [path.Join] //@ def("Join", Join) +// [pathpkg.Join] //@ def("Join", Join) +func _() { + pathpkg.Join() +} diff --git a/contribs/gnopls/internal/test/marker/testdata/definition/embed.txt b/contribs/gnopls/internal/test/marker/testdata/definition/embed.txt new file mode 100644 index 00000000000..5dc976c8b4d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/definition/embed.txt @@ -0,0 +1,275 @@ +This test checks definition and hover operations over embedded fields and methods. + +Its size expectations assume a 64-bit machine, +and correct sizes information requires go1.21. + +-- flags -- +-skip_goarch=386,arm +-min_go=go1.21 + +-- go.mod -- +module mod.com + +go 1.18 + +-- a/a.go -- +package a + +type A string //@loc(AString, "A") + +func (_ A) Hi() {} //@loc(AHi, "Hi") + +type S struct { + Field int //@loc(SField, "Field") + R // embed a struct + H // embed an interface +} + +type R struct { + Field2 int //@loc(RField2, "Field2") +} + +func (r R) Hey() {} //@loc(RHey, "Hey") + +type H interface { //@loc(H, "H") + Goodbye() //@loc(HGoodbye, "Goodbye") +} + +type I interface { //@loc(I, "I") + B() //@loc(IB, "B") + J +} + +type J interface { //@loc(J, "J") + Hello() //@loc(JHello, "Hello") +} + +-- b/b.go -- +package b + +import "mod.com/a" //@loc(AImport, re"\"[^\"]*\"") + +type embed struct { + F int //@loc(F, "F") +} + +func (embed) M() //@loc(M, "M") + +type Embed struct { + embed + *a.A + a.I + a.S +} + +func _() { + e := Embed{} + e.Hi() //@def("Hi", AHi),hover("Hi", "Hi", AHi) + e.B() //@def("B", IB),hover("B", "B", IB) + _ = e.Field //@def("Field", SField),hover("Field", "Field", SField) + _ = e.Field2 //@def("Field2", RField2),hover("Field2", "Field2", RField2) + e.Hello() //@def("Hello", JHello),hover("Hello", "Hello",JHello) + e.Hey() //@def("Hey", RHey),hover("Hey", "Hey", RHey) + e.Goodbye() //@def("Goodbye", HGoodbye),hover("Goodbye", "Goodbye", HGoodbye) + e.M() //@def("M", M),hover("M", "M", M) + _ = e.F //@def("F", F),hover("F", "F", F) +} + +type aAlias = a.A //@loc(aAlias, "aAlias") + +type S1 struct { //@loc(S1, "S1") + F1 int //@loc(S1F1, "F1") + S2 //@loc(S1S2, "S2"),def("S2", S2),hover("S2", "S2", S2) + a.A //@def("A", AString),hover("A", "A", aA) + aAlias //@def("a", aAlias),hover("a", "aAlias", aAlias) +} + +type S2 struct { //@loc(S2, "S2") + F1 string //@loc(S2F1, "F1") + F2 int //@loc(S2F2, "F2") + *a.A //@def("A", AString),def("a",AImport) +} + +type S3 struct { + F1 struct { + a.A //@def("A", AString) + } +} + +func Bar() { + var x S1 //@def("S1", S1),hover("S1", "S1", S1) + _ = x.S2 //@def("S2", S1S2),hover("S2", "S2", S1S2) + _ = x.F1 //@def("F1", S1F1),hover("F1", "F1", S1F1) + _ = x.F2 //@def("F2", S2F2),hover("F2", "F2", S2F2) + _ = x.S2.F1 //@def("F1", S2F1),hover("F1", "F1", S2F1) +} + +-- b/c.go -- +package b + +var _ = S1{ //@def("S1", S1),hover("S1", "S1", S1) + F1: 99, //@def("F1", S1F1),hover("F1", "F1", S1F1) +} + +-- @AHi -- +```go +func (a.A) Hi() +``` + +[`(a.A).Hi` on pkg.go.dev](https://pkg.go.dev/mod.com/a#A.Hi) +-- @F -- +```go +field F int +``` + +@loc(F, "F") + + +[`(b.Embed).F` on pkg.go.dev](https://pkg.go.dev/mod.com/b#Embed.F) +-- @HGoodbye -- +```go +func (a.H) Goodbye() +``` + +@loc(HGoodbye, "Goodbye") + + +[`(a.H).Goodbye` on pkg.go.dev](https://pkg.go.dev/mod.com/a#H.Goodbye) +-- @IB -- +```go +func (a.I) B() +``` + +@loc(IB, "B") + + +[`(a.I).B` on pkg.go.dev](https://pkg.go.dev/mod.com/a#I.B) +-- @JHello -- +```go +func (a.J) Hello() +``` + +@loc(JHello, "Hello") + + +[`(a.J).Hello` on pkg.go.dev](https://pkg.go.dev/mod.com/a#J.Hello) +-- @M -- +```go +func (embed) M() +``` + +[`(b.Embed).M` on pkg.go.dev](https://pkg.go.dev/mod.com/b#Embed.M) +-- @RField2 -- +```go +field Field2 int +``` + +@loc(RField2, "Field2") + + +[`(a.R).Field2` on pkg.go.dev](https://pkg.go.dev/mod.com/a#R.Field2) +-- @RHey -- +```go +func (r a.R) Hey() +``` + +[`(a.R).Hey` on pkg.go.dev](https://pkg.go.dev/mod.com/a#R.Hey) +-- @S1 -- +```go +type S1 struct { + F1 int //@loc(S1F1, "F1") + S2 //@loc(S1S2, "S2"),def("S2", S2),hover("S2", "S2", S2) + a.A //@def("A", AString),hover("A", "A", aA) + aAlias //@def("a", aAlias),hover("a", "aAlias", aAlias) +} +``` + +```go +// Embedded fields: +F2 int // through S2 +``` + +[`b.S1` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S1) +-- @S1F1 -- +```go +field F1 int +``` + +@loc(S1F1, "F1") + + +[`(b.S1).F1` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S1.F1) +-- @S1S2 -- +```go +field S2 S2 +``` + +@loc(S1S2, "S2"),def("S2", S2),hover("S2", "S2", S2) + + +[`(b.S1).S2` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S1.S2) +-- @S2 -- +```go +type S2 struct { // size=32 (0x20) + F1 string //@loc(S2F1, "F1") + F2 int //@loc(S2F2, "F2") + *a.A //@def("A", AString),def("a",AImport) +} +``` + +```go +func (a.A) Hi() +``` + +[`b.S2` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S2) +-- @S2F1 -- +```go +field F1 string +``` + +@loc(S2F1, "F1") + + +[`(b.S2).F1` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S2.F1) +-- @S2F2 -- +```go +field F2 int +``` + +@loc(S2F2, "F2") + + +[`(b.S2).F2` on pkg.go.dev](https://pkg.go.dev/mod.com/b#S2.F2) +-- @SField -- +```go +field Field int +``` + +@loc(SField, "Field") + + +[`(a.S).Field` on pkg.go.dev](https://pkg.go.dev/mod.com/a#S.Field) +-- @aA -- +```go +type A string // size=16 (0x10) +``` + +@loc(AString, "A") + + +```go +func (a.A) Hi() +``` + +[`a.A` on pkg.go.dev](https://pkg.go.dev/mod.com/a#A) +-- @aAlias -- +```go +type aAlias = a.A // size=16 (0x10) +``` + +@loc(aAlias, "aAlias") + + +```go +func (a.A) Hi() +``` diff --git a/contribs/gnopls/internal/test/marker/testdata/definition/import.txt b/contribs/gnopls/internal/test/marker/testdata/definition/import.txt new file mode 100644 index 00000000000..2ae95a8c29b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/definition/import.txt @@ -0,0 +1,53 @@ +This test checks definition and hover over imports. + +-- go.mod -- +module mod.com + +go 1.18 +-- foo/foo.go -- +package foo + +type Foo struct{} + +// DoFoo does foo. +func DoFoo() {} //@loc(DoFoo, "DoFoo") +-- bar/bar.go -- +package bar + +import ( + myFoo "mod.com/foo" //@loc(myFoo, "myFoo") +) + +var _ *myFoo.Foo //@def("myFoo", myFoo),hover("myFoo", "myFoo", myFoo) +-- bar/dotimport.go -- +package bar + +import . "mod.com/foo" + +func _() { + // variable of type foo.Foo + var _ Foo //@hover("_", "_", FooVar) + + DoFoo() //@hover("DoFoo", "DoFoo", DoFoo) +} +-- @DoFoo -- +```go +func DoFoo() +``` + +DoFoo does foo. + + +[`foo.DoFoo` on pkg.go.dev](https://pkg.go.dev/mod.com/foo#DoFoo) +-- @FooVar -- +```go +var _ Foo +``` + +variable of type foo.Foo +-- @myFoo -- +```go +package myFoo ("mod.com/foo") +``` + +[`myFoo` on pkg.go.dev](https://pkg.go.dev/mod.com/foo) diff --git a/contribs/gnopls/internal/test/marker/testdata/definition/misc.txt b/contribs/gnopls/internal/test/marker/testdata/definition/misc.txt new file mode 100644 index 00000000000..7c6bc27f316 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/definition/misc.txt @@ -0,0 +1,241 @@ +This test exercises miscellaneous definition and hover requests. + +Its size expectations assume a 64-bit machine. + +-- go.mod -- +module mod.com + +go 1.16 + +-- flags -- +-skip_goarch=386,arm + +-- a.go -- +package a //@loc(aPackage, re"package (a)"),hover(aPackage, aPackage, aPackage) + +var ( + // x is a variable. + x string //@loc(x, "x"),hover(x, x, hoverx) +) + +// Constant block. When I hover on h, I should see this comment. +const ( + // When I hover on g, I should see this comment. + g = 1 //@hover("g", "g", hoverg) + + h = 2 //@hover("h", "h", hoverh) +) + +// z is a variable too. +var z string //@loc(z, "z"),hover(z, z, hoverz) + +func AStuff() { //@loc(AStuff, "AStuff") + x := 5 + Random2(x) //@def("dom2", Random2) + Random() //@def("()", Random) +} + +type H interface { //@loc(H, "H") + Goodbye() +} + +type I interface { //@loc(I, "I") + B() + J +} + +type J interface { //@loc(J, "J") + Hello() +} + +func _() { + // 1st type declaration block + type ( + a struct { //@hover("a", "a", hoverDeclBlocka) + x string + } + ) + + // 2nd type declaration block + type ( + // b has a comment + b struct{} //@hover("b", "b", hoverDeclBlockb) + ) + + // 3rd type declaration block + type ( + // c is a struct + c struct { //@hover("c", "c", hoverDeclBlockc) + f string + } + + d string //@hover("d", "d", hoverDeclBlockd) + ) + + type ( + e struct { //@hover("e", "e", hoverDeclBlocke) + f float64 + } // e has a comment + ) +} + +var ( + hh H //@hover("H", "H", hoverH) + ii I //@hover("I", "I", hoverI) + jj J //@hover("J", "J", hoverJ) +) +-- a_test.go -- +package a + +import ( + "testing" +) + +func TestA(t *testing.T) { //@hover("TestA", "TestA", hoverTestA) +} +-- random.go -- +package a + +func Random() int { //@loc(Random, "Random") + y := 6 + 7 + return y +} + +func Random2(y int) int { //@loc(Random2, "Random2"),loc(RandomParamY, "y") + return y //@def("y", RandomParamY),hover("y", "y", hovery) +} + +type Pos struct { + x, y int //@loc(PosX, "x"),loc(PosY, "y") +} + +// Typ has a comment. Its fields do not. +type Typ struct{ field string } //@loc(TypField, "field") + +func _() { + x := &Typ{} + _ = x.field //@def("field", TypField),hover("field", "field", hoverfield) +} + +func (p *Pos) Sum() int { //@loc(PosSum, "Sum") + return p.x + p.y //@hover("x", "x", hoverpx) +} + +func _() { + var p Pos + _ = p.Sum() //@def("()", PosSum),hover("()", `Sum`, hoverSum) +} +-- @aPackage -- +-- @hoverDeclBlocka -- +```go +type a struct { // size=16 (0x10) + x string +} +``` + +1st type declaration block +-- @hoverDeclBlockb -- +```go +type b struct{} // size=0 +``` + +b has a comment +-- @hoverDeclBlockc -- +```go +type c struct { // size=16 (0x10) + f string +} +``` + +c is a struct +-- @hoverDeclBlockd -- +```go +type d string // size=16 (0x10) +``` + +3rd type declaration block +-- @hoverDeclBlocke -- +```go +type e struct { // size=8 + f float64 +} +``` + +e has a comment +-- @hoverH -- +```go +type H interface { + Goodbye() +} +``` + +[`a.H` on pkg.go.dev](https://pkg.go.dev/mod.com#H) +-- @hoverI -- +```go +type I interface { + B() + J +} +``` + +```go +func (J) Hello() +``` + +[`a.I` on pkg.go.dev](https://pkg.go.dev/mod.com#I) +-- @hoverJ -- +```go +type J interface { + Hello() +} +``` + +[`a.J` on pkg.go.dev](https://pkg.go.dev/mod.com#J) +-- @hoverSum -- +```go +func (p *Pos) Sum() int +``` + +[`(a.Pos).Sum` on pkg.go.dev](https://pkg.go.dev/mod.com#Pos.Sum) +-- @hoverTestA -- +```go +func TestA(t *testing.T) +``` +-- @hoverfield -- +```go +field field string +``` +-- @hoverg -- +```go +const g untyped int = 1 +``` + +When I hover on g, I should see this comment. +-- @hoverh -- +```go +const h untyped int = 2 +``` + +Constant block. When I hover on h, I should see this comment. +-- @hoverpx -- +```go +field x int +``` + +@loc(PosX, "x"),loc(PosY, "y") +-- @hoverx -- +```go +var x string +``` + +x is a variable. +-- @hovery -- +```go +var y int +``` +-- @hoverz -- +```go +var z string +``` + +z is a variable too. diff --git a/contribs/gnopls/internal/test/marker/testdata/definition/standalone.txt b/contribs/gnopls/internal/test/marker/testdata/definition/standalone.txt new file mode 100644 index 00000000000..2612f43d833 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/definition/standalone.txt @@ -0,0 +1,45 @@ +This test checks the behavior of standalone packages, in particular documenting +our failure to support test files as standalone packages (golang/go#64233). + +-- flags -- +-min_go=go1.20 + +-- go.mod -- +module golang.org/lsptests/a + +go 1.20 + +-- a.go -- +package a + +func F() {} //@loc(F, "F") + +-- standalone.go -- +//go:build ignore +package main + +import "golang.org/lsptests/a" + +func main() { + a.F() //@def("F", F) +} + +-- standalone_test.go -- +//go:build ignore +package main //@diag("main", re"No packages found") + +import "golang.org/lsptests/a" + +func main() { + a.F() //@hovererr("F", "no package") +} + +-- standalone_x_test.go -- +//go:build ignore +package main_test //@diag("main", re"No packages found") + +import "golang.org/lsptests/a" + +func main() { + a.F() //@hovererr("F", "no package") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/addgowork.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/addgowork.txt new file mode 100644 index 00000000000..5fbd890e65f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/addgowork.txt @@ -0,0 +1,51 @@ +This test demonstrates diagnostics for adding a go.work file. + +Quick-fixes change files on disk, so are tested by integration tests. + +TODO(rfindley): improve the "cannot find package" import errors. + +-- skip -- +These diagnostics are no longer produced, because in golang/go#57979 +(zero-config gopls) we made gopls function independent of a go.work file. +Preserving this test as we may want to re-enable the code actions go manage +a go.work file. + +Note that in go.dev/issue/60584#issuecomment-1622238115, this test was flaky. +However, critical error logic has since been rewritten. + +-- a/go.mod -- +module mod.com/a + +go 1.18 + +-- a/main.go -- +package main //@diag("main", re"add a go.work file") + +import "mod.com/a/lib" //@diag("\"mod.com", re"cannot find package") + +func main() { + _ = lib.C +} + +-- a/lib/lib.go -- +package lib //@diag("lib", re"add a go.work file") + +const C = "b" +-- b/go.mod -- +module mod.com/b + +go 1.18 + +-- b/main.go -- +package main //@diag("main", re"add a go.work file") + +import "mod.com/b/lib" //@diag("\"mod.com", re"cannot find package") + +func main() { + _ = lib.C +} + +-- b/lib/lib.go -- +package lib //@diag("lib", re"add a go.work file") + +const C = "b" diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/analyzers.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/analyzers.txt new file mode 100644 index 00000000000..34488bec417 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/analyzers.txt @@ -0,0 +1,82 @@ +Test of warning diagnostics from various analyzers: +copylocks, printf, slog, tests, timeformat, nilness, and cgocall. + +-- go.mod -- +module example.com +go 1.12 + +-- flags -- +-min_go=go1.21 +-cgo + +-- bad_test.go -- +package analyzer + +import ( + "fmt" + "sync" + "testing" + "time" +) + +// copylocks +func _() { + var x sync.Mutex + _ = x //@diag("x", re"assignment copies lock value to _: sync.Mutex") +} + +// printf +func _() { + printfWrapper("%s") //@diag(re`printfWrapper\(.*?\)`, re"example.com.printfWrapper format %s reads arg #1, but call has 0 args") +} + +func printfWrapper(format string, args ...interface{}) { + fmt.Printf(format, args...) +} + +// tests +func Testbad(t *testing.T) { //@diag("Testbad", re"Testbad has malformed name: first letter after 'Test' must not be lowercase") +} + +// timeformat +func _() { + now := time.Now() + fmt.Println(now.Format("2006-02-01")) //@diag("2006-02-01", re"2006-02-01 should be 2006-01-02") +} + +// nilness +func _(ptr *int) { + if ptr == nil { + _ = *ptr //@diag("*ptr", re"nil dereference in load") + } +} + +// unusedwrite +func _(s struct{x int}) { + s.x = 1 //@diag("x", re"unused write to field x") +} + +-- cgocall.go -- +package analyzer + +import "unsafe" + +// void f(void *ptr) {} +import "C" + +// cgocall +func _(c chan bool) { + C.f(unsafe.Pointer(&c)) //@ diag("unsafe", re"passing Go type with embedded pointer to C") +} + +-- bad_test_go121.go -- +//go:build go1.21 + +package analyzer + +import "log/slog" + +// slog +func _() { + slog.Info("msg", 1) //@diag("1", re`slog.Info arg "1" should be a string or a slog.Attr`) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/excludedfile.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/excludedfile.txt new file mode 100644 index 00000000000..ae3045b338d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/excludedfile.txt @@ -0,0 +1,36 @@ +This test demonstrates diagnostics for various forms of file exclusion. + +Note: this test used to also check the errors when a file was excluded due to +an inactive module, or mismatching GOOS/GOARCH, comment, but with zero-config +gopls (golang/go#57979) and improved build tag support (golang/go#29202), we no +longer get these errors. + +-- go.work -- +go 1.21 + +use ( + ./a +) +-- a/go.mod -- +module mod.com/a + +go 1.18 + +-- a/a.go -- +package a + +-- a/a_plan9.go -- +package a // Not excluded, due to improved build tag support. + +-- a/a_ignored.go -- +//go:build skip +package a //@diag(re"package (a)", re"excluded due to its build tags") + +-- b/go.mod -- +module mod.com/b + +go 1.18 + +-- b/b.go -- +package b // Not excluded, due to zero-config gopls. + diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/generated.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/generated.txt new file mode 100644 index 00000000000..ea5886dae03 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/generated.txt @@ -0,0 +1,21 @@ +Test of "undeclared" diagnostic in generated code. + +-- go.mod -- +module example.com +go 1.12 + +-- generated.go -- +package generated + +// Code generated by generator.go. DO NOT EDIT. + +func _() { + var y int //@diag("y", re"declared (and|but) not used") +} + +-- generator.go -- +package generated + +func _() { + var x int //@diag("x", re"declared (and|but) not used") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/initcycle.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/initcycle.txt new file mode 100644 index 00000000000..f306bccf52c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/initcycle.txt @@ -0,0 +1,17 @@ +This test verifies that gopls spreads initialization cycle errors across +multiple declarations. + +We set -ignore_extra_diags due to golang/go#65877: gopls produces redundant +diagnostics for initialization cycles. + +-- flags -- +-ignore_extra_diags + +-- p.go -- +package p + +var X = Y //@diag("X", re"initialization cycle") + +var Y = Z //@diag("Y", re"initialization cycle") + +var Z = X //@diag("Z", re"initialization cycle") diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue56943.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue56943.txt new file mode 100644 index 00000000000..cd3ad6e9c63 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue56943.txt @@ -0,0 +1,22 @@ +This test verifies that we produce diagnostics related to mismatching +unexported interface methods in non-workspace packages. + +Previously, we would fail to produce a diagnostic because we trimmed the AST. +See golang/go#56943. +-- main.go -- +package main + +import ( + "go/ast" + "go/token" +) + +func main() { + var a int //@diag(re"(a) int", re"declared.*not used") + var _ ast.Expr = node{} //@diag("node{}", re"missing.*exprNode") +} + +type node struct{} + +func (node) Pos() token.Pos { return 0 } +func (node) End() token.Pos { return 0 } diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue59005.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue59005.txt new file mode 100644 index 00000000000..cc1be7e7666 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue59005.txt @@ -0,0 +1,20 @@ +This test verifies that we don't drop type checking errors on the floor when we +fail to compute positions for their related errors. + +-- go.mod -- +module play.ground + +-- p.go -- +package p + +import ( + . "play.ground/foo" +) + +const C = 1 //@diag("C", re"C already declared through dot-import") +var _ = C + +-- foo/foo.go -- +package foo + +const C = 2 diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue60544.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue60544.txt new file mode 100644 index 00000000000..6b8d6ce0ad2 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue60544.txt @@ -0,0 +1,9 @@ +This test exercises a crash due to treatment of "comparable" in methodset +calculation (golang/go#60544). + +-- main.go -- +package main + +type X struct{} + +func (X) test(x comparable) {} //@diag("comparable", re"outside a type constraint") diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue60605.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue60605.txt new file mode 100644 index 00000000000..a2fb57ba0b1 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue60605.txt @@ -0,0 +1,15 @@ +This test verifies that we can export constants with unknown kind. +Previously, the exporter would panic while attempting to convert such constants +to their target type (float64, in this case). + +-- flags -- +-min_go=go1.20 + +-- go.mod -- +module mod.txt/p + +go 1.20 +-- p.go -- +package p + +const EPSILON float64 = 1e- //@diag(re"1e-()", re"exponent has no digits") diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue64547.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue64547.txt new file mode 100644 index 00000000000..3f3e13bdf67 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue64547.txt @@ -0,0 +1,14 @@ +This test checks the fix for golang/go#64547: the lostcancel analyzer reports +diagnostics that overflow the file. + +-- p.go -- +package p + +import "context" + +func _() { + _, cancel := context.WithCancel(context.Background()) //@diag("_, cancel", re"not used on all paths") + if false { + cancel() + } +} //@diag("}", re"may be reached without using the cancel") diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue67360.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue67360.txt new file mode 100644 index 00000000000..229c99b6890 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/issue67360.txt @@ -0,0 +1,16 @@ +Regression test for #67360. + +This file causes go list to report a "use of internal package +cmd/internal/browser" error. (It is important that this be a real +internal std package.) The line directive caused the position of the +error to lack a column. A bug in the error parser filled in 0, not 1, +for the missing information, and this is an invalid value in the +1-based UTF-8 domain, leading to a panic. + +-- flags -- +-min_go=go1.21 + +-- foo.go -- +//line foo.go:1 +package main //@ diag(re"package", re"internal package.*not allowed") +import _ "cmd/internal/browser" //@ diag(re`"`, re"could not import") diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/osarch_suffix.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/osarch_suffix.txt new file mode 100644 index 00000000000..95336085b2f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/osarch_suffix.txt @@ -0,0 +1,46 @@ +This test verifies that we add an [os,arch] suffix to each diagnostic +that doesn't appear in the default build (=runtime.{GOOS,GOARCH}). + +See golang/go#65496. + +The two p/*.go files below are written to trigger the same diagnostic +(range, message, source, etc) but varying only by URI. + +In the q test, a single location in the common code q.go has two +diagnostics, one of which is tagged. + +This test would fail on openbsd/mips64 because it will be +the same as the default build, so we skip that platform. + +-- flags -- +-skip_goos=openbsd + +-- go.mod -- +module example.com + +-- p/p.go -- +package p + +var _ fmt.Stringer //@diag("fmt", re"unde.*: fmt$") + +-- p/p_openbsd_mips64.go -- +package p + +var _ fmt.Stringer //@diag("fmt", re"unde.*: fmt \\[openbsd,mips64\\]") + +-- q/q_default.go -- +//+build !openbsd && !mips64 + +package q + +func f(int) int + +-- q/q_openbsd_mips64.go -- +package q + +func f(string) int + +-- q/q.go -- +package q + +var _ = f() //@ diag(")", re`.*want \(string\) \[openbsd,mips64\]`), diag(")", re`.*want \(int\)$`) diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/parseerr.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/parseerr.txt new file mode 100644 index 00000000000..d0df08d8b25 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/parseerr.txt @@ -0,0 +1,27 @@ + +This test exercises diagnostics produced for syntax errors. + +Because parser error recovery can be quite lossy, diagnostics +for type errors are suppressed in files with syntax errors; +see issue #59888. But diagnostics are reported for type errors +in well-formed files of the same package. + +-- go.mod -- +module example.com +go 1.12 + +-- bad.go -- +package p + +func f() { + append("") // no diagnostic for type error in file containing syntax error +} + +func .() {} //@diag(re"func ().", re"expected 'IDENT', found '.'") + +-- good.go -- +package p + +func g() { + append("") //@diag(re`""`, re"a slice") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/range-over-func-67237.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/range-over-func-67237.txt new file mode 100644 index 00000000000..e2aa14221e3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/range-over-func-67237.txt @@ -0,0 +1,82 @@ + +This test verifies that SSA-based analyzers don't run on packages that +use range-over-func. This is an emergency fix of #67237 (for buildssa) +until we land https://go.dev/cl/555075. + +Similarly, it is an emergency fix of dominikh/go-tools#1494 (for +buildir) until that package is similarly fixed for go1.23. + +Explanation: +- Package p depends on q and r, and analyzers buildssa and buildir + depend on norangeoverfunc. +- Analysis pass norangeoverfunc@q fails, thus norangeoverfunc@p is not + executed; but norangeoverfunc@r is ok +- nilness requires buildssa, which is not facty, so it can run on p and r. +- SA4010 (CheckIneffectiveAppend) requires buildir, which is facty, + so SA4010 can run only on r. + +We don't import any std packages because even "fmt" depends on +range-over-func now (which means that in practice, everything does). + +-- flags -- +-min_go=go1.23 + +-- settings.json -- +{ + "staticcheck": true, + "analyses": {"SA4010": true} +} + +-- go.mod -- +module example.com + +go 1.23 + +-- p/p.go -- +package p // a dependency uses range-over-func, so nilness runs but SA4010 cannot (buildir is facty) + +import ( + _ "example.com/q" + _ "example.com/r" +) + +func f(ptr *int) { + if ptr == nil { + println(*ptr) //@diag(re"[*]ptr", re"nil dereference in load") + } + + var s []int + s = append(s, 1) // no SA4010 finding +} + +-- q/q.go -- +package q // uses range-over-func, so no diagnostics from SA4010 + +type iterSeq[T any] func(yield func(T) bool) + +func f(seq iterSeq[int]) { + for x := range seq { + println(x) + } + + var s []int + s = append(s, 1) // no SA4010 finding +} + +func _(ptr *int) { + if ptr == nil { + println(*ptr) //@diag(re"[*]ptr", re"nil dereference in load") + } +} + +-- r/r.go -- +package r // does not use range-over-func, so nilness and SA4010 report diagnosticcs + +func f(ptr *int) { + if ptr == nil { + println(*ptr) //@diag(re"[*]ptr", re"nil dereference in load") + } + + var s []int + s = append(s, 1) //@ diag(re`s`, re`s is never used`), diag(re`append`, re`append is never used`) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/rundespiteerrors.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/rundespiteerrors.txt new file mode 100644 index 00000000000..b14f4dfabd0 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/rundespiteerrors.txt @@ -0,0 +1,21 @@ +This test verifies that analyzers without RunDespiteErrors are not +executed on a package containing type errors (see issue #54762). + +-- go.mod -- +module example.com +go 1.12 + +-- a.go -- +package a + +func _() { + // A type error. + _ = 1 + "" //@diag(`1 + ""`, re"mismatched types|cannot convert") + + // A violation of an analyzer for which RunDespiteErrors=false: + // no (simplifyrange, warning) diagnostic is produced; the diag + // comment is merely illustrative. + for _ = range "" { //diag("for _", "simplify range expression", ) + + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/stdversion.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/stdversion.txt new file mode 100644 index 00000000000..652ddd6b56a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/stdversion.txt @@ -0,0 +1,89 @@ +Test of "too new" diagnostics from the stdversion analyzer. + +This test references go1.21 symbols from std, but the analyzer itself +depends on the go1.22 behavior of versions.FileVersion. + +See also go/analysis/passes/stdversion/testdata/test.txtar, +which runs the same test in the analysistest framework. + +-- flags -- +-min_go=go1.22 + +-- go.mod -- +module example.com + +go 1.21 + +-- a/a.go -- +package a + +import "go/types" + +func _() { + // old package-level type + var _ types.Info // ok: defined by go1.0 + + // new field of older type + _ = new(types.Info).FileVersions //@diag("FileVersions", re`types.FileVersions requires go1.22 or later \(module is go1.21\)`) + + // new method of older type + _ = new(types.Info).PkgNameOf //@diag("PkgNameOf", re`types.PkgNameOf requires go1.22 or later \(module is go1.21\)`) + + // new package-level type + var a types.Alias //@diag("Alias", re`types.Alias requires go1.22 or later \(module is go1.21\)`) + + // new method of new type + a.Underlying() // no diagnostic +} + +-- sub/go.mod -- +module example.com/sub + +go 1.21 + +-- sub/sub.go -- +package sub + +import "go/types" + +func _() { + // old package-level type + var _ types.Info // ok: defined by go1.0 + + // new field of older type + _ = new(types.Info).FileVersions //@diag("FileVersions", re`types.FileVersions requires go1.22 or later \(module is go1.21\)`) + + // new method of older type + _ = new(types.Info).PkgNameOf //@diag("PkgNameOf", re`types.PkgNameOf requires go1.22 or later \(module is go1.21\)`) + + // new package-level type + var a types.Alias //@diag("Alias", re`types.Alias requires go1.22 or later \(module is go1.21\)`) + + // new method of new type + a.Underlying() // no diagnostic +} + +-- sub/tagged.go -- +//go:build go1.22 + +package sub + +import "go/types" + +func _() { + // old package-level type + var _ types.Info + + // new field of older type + _ = new(types.Info).FileVersions + + // new method of older type + _ = new(types.Info).PkgNameOf + + // new package-level type + var a types.Alias + + // new method of new type + a.Underlying() +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/strangefiles.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/strangefiles.txt new file mode 100644 index 00000000000..a77aef01c5a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/strangefiles.txt @@ -0,0 +1,22 @@ +This test checks diagnostics on files that are strange for one reason or +another. + +Note(rfindley): ported from the old marker tests. I'm not sure why these were +written originally. + +-ignore_extra_diags is required because the marker framework fails for +noparse.go, and we therefore can't match the EOF error. + +-- flags -- +-ignore_extra_diags +-errors_ok + +-- go.mod -- +module golang.org/lsptests + +go 1.18 +-- %percent/perc%ent.go -- +package percent //@diag("percent", re"No packages") + +-- noparse/noparse.go -- + diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/typeerr.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/typeerr.txt new file mode 100644 index 00000000000..686b05c371e --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/typeerr.txt @@ -0,0 +1,28 @@ + +This test exercises diagnostics produced for type errors +in the absence of syntax errors. + +The type error was chosen to exercise the 'nonewvars' type-error analyzer. +(The 'undeclaredname' analyzer depends on the text of the go/types +"undeclared name" error, which changed in go1.20.) + +The append() type error was also carefully chosen to have text and +position that are invariant across all versions of Go run by the builders. + +-- go.mod -- +module example.com +go 1.12 + +-- typeerr.go -- +package a + +func f(x int) { + append("") //@diag(re`""`, re"a slice") + + x := 123 //@diag(re"x := 123", re"no new variables"), suggestedfix(re"():", re"no new variables", fix) +} + +-- @fix/typeerr.go -- +@@ -6 +6 @@ +- x := 123 //@diag(re"x := 123", re"no new variables"), suggestedfix(re"():", re"no new variables", fix) ++ x = 123 //@diag(re"x := 123", re"no new variables"), suggestedfix(re"():", re"no new variables", fix) diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/useinternal.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/useinternal.txt new file mode 100644 index 00000000000..86010dc29c8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/useinternal.txt @@ -0,0 +1,24 @@ +This test checks a diagnostic for invalid use of internal packages. + +This list error changed in Go 1.21. + +See TestValidImportCheck_GoPackagesDriver for a test that no diagnostic +is produced when using a GOPACKAGESDRIVER (such as for Bazel). + +-- flags -- +-min_go=go1.21 + +-- go.mod -- +module bad.test + +go 1.18 + +-- assign/internal/secret/secret.go -- +package secret + +func Hello() {} + +-- bad/bad.go -- +package bad + +import _ "bad.test/assign/internal/secret" //@diag("\"bad.test/assign/internal/secret\"", re"could not import bad.test/assign/internal/secret \\(invalid use of internal package \"bad.test/assign/internal/secret\"\\)"),diag("_", re"use of internal package bad.test/assign/internal/secret not allowed") diff --git a/contribs/gnopls/internal/test/marker/testdata/diagnostics/usemodule.txt b/contribs/gnopls/internal/test/marker/testdata/diagnostics/usemodule.txt new file mode 100644 index 00000000000..699a4166692 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/diagnostics/usemodule.txt @@ -0,0 +1,52 @@ +This test demonstrates diagnostics for a module that is missing from the +go.work file. + +Quick-fixes change files on disk, so are tested by integration tests. + +-- skip -- +Temporary skip due to golang/go#57979, with zero-config gopls, these modules +are no longer orphaned. + +-- go.work -- +go 1.21 + +use ( + ./a +) + +-- a/go.mod -- +module mod.com/a + +go 1.18 + +-- a/main.go -- +package main + +import "mod.com/a/lib" + +func main() { + _ = lib.C +} + +-- a/lib/lib.go -- +package lib + +const C = "b" +-- b/go.mod -- +module mod.com/b + +go 1.18 + +-- b/main.go -- +package main //@diag("main", re"add this module to your go.work") + +import "mod.com/b/lib" //@diag("\"mod.com", re"not included in a workspace module") + +func main() { + _ = lib.C +} + +-- b/lib/lib.go -- +package lib //@diag("lib", re"add this module to your go.work") + +const C = "b" diff --git a/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue59318.txt b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue59318.txt new file mode 100644 index 00000000000..8a738718940 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue59318.txt @@ -0,0 +1,20 @@ +Previously, this test verifies that we can load multiple orphaned files as +command-line-arguments packages. In the distant past, we would load only one +because go/packages returns at most one command-line-arguments package per +query. + +With zero-config gopls, these packages are successfully loaded as ad-hoc +packages. + +-- a/main.go -- +package main + +func main() { + var a int //@diag(re"var (a)", re"not used") +} +-- b/main.go -- +package main + +func main() { + var b int //@diag(re"var (b)", re"not used") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue59944.txt b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue59944.txt new file mode 100644 index 00000000000..9e39d8f5fe9 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue59944.txt @@ -0,0 +1,33 @@ +This test verifies that gopls does not panic when encountering the go/types +bug described in golang/go#59944: the Bindingf function is not included in +the methodset of its receiver type. + +Adapted from the code in question from the issue. + +-- flags -- +-cgo + +-- go.mod -- +module example.com + +go 1.12 + +-- cgo.go -- +package x + +import "fmt" + +/* +struct layout { + int field; +}; +*/ +import "C" + +type Layout = C.struct_layout + +// Bindingf is a printf wrapper. This was necessary to trigger the panic in +// objectpath while encoding facts. +func (l *Layout) Bindingf(format string, args ...interface{}) { + fmt.Printf(format, args...) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue61543.txt b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue61543.txt new file mode 100644 index 00000000000..bc0f2e6de4b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue61543.txt @@ -0,0 +1,13 @@ +This test verifies that we fail loudly if a module name contains +command-line-arguments. + +-- flags -- +-errors_ok + +-- go.mod -- +module command-line-arguments //@diag("module", re`command-line-arguments.*disallowed`) + +go 1.12 + +-- x/x.go -- +package x //@diag("x", re`command-line-arguments.*disallowed`) diff --git a/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66109.txt b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66109.txt new file mode 100644 index 00000000000..c73390066ae --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66109.txt @@ -0,0 +1,25 @@ +This test exercises the crash in golang/go#66109: a dangling reference due to +test variants of a command-line-arguments package. + +-- flags -- +-min_go=go1.22 + +-- go.mod -- +module example.com/tools + +go 1.22 + +-- tools_test.go -- +//go:build tools + +package tools //@diag("tools", re"No packages found") + +import ( + _ "example.com/tools/tool" +) + +-- tool/tool.go -- +package main + +func main() { +} diff --git a/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66250.txt b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66250.txt new file mode 100644 index 00000000000..748d19de6d4 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66250.txt @@ -0,0 +1,17 @@ +This bug checks the fix for golang/go#66250. Package references should not +crash when one package file lacks a package name. + +TODO(rfindley): the -ignore_extra_diags flag is only necessary because of +problems matching diagnostics in the broken file, likely due to poor parser +recovery. + +-- flags -- +-ignore_extra_diags + +-- a.go -- +package x //@refs("x", "x") + +-- b.go -- + +func _() { +} diff --git a/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66876.txt b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66876.txt new file mode 100644 index 00000000000..d6edcb57a18 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/fixedbugs/issue66876.txt @@ -0,0 +1,27 @@ +This test checks that gopls successfully suppresses loopclosure diagnostics +when the go.mod go version is set to a 1.22 toolchain version (1.22.x). + +In golang/go#66876, gopls failed to handle this correctly. + +-- flags -- +-min_go=go1.22 + +-- go.mod -- +module example.com/loopclosure + +go 1.22.0 + +-- p.go -- +package main + +var x int //@loc(x, "x") + +func main() { + // Verify that type checking actually succeeded by jumping to + // an arbitrary definition. + _ = x //@def("x", x) + + for i := range 10 { + go func() { println(i) }() + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/foldingrange/a.txt b/contribs/gnopls/internal/test/marker/testdata/foldingrange/a.txt new file mode 100644 index 00000000000..6210fc25251 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/foldingrange/a.txt @@ -0,0 +1,154 @@ +This test checks basic behavior of textDocument/foldingRange. + +-- a.go -- +package folding //@foldingrange(raw) + +import ( + "fmt" + _ "log" +) + +import _ "os" + +// bar is a function. +// With a multiline doc comment. +func bar() string { + /* This is a single line comment */ + switch { + case true: + if true { + fmt.Println("true") + } else { + fmt.Println("false") + } + case false: + fmt.Println("false") + default: + fmt.Println("default") + } + /* This is a multiline + block + comment */ + + /* This is a multiline + block + comment */ + // Followed by another comment. + _ = []int{ + 1, + 2, + 3, + } + _ = [2]string{"d", + "e", + } + _ = map[string]int{ + "a": 1, + "b": 2, + "c": 3, + } + type T struct { + f string + g int + h string + } + _ = T{ + f: "j", + g: 4, + h: "i", + } + x, y := make(chan bool), make(chan bool) + select { + case val := <-x: + if val { + fmt.Println("true from x") + } else { + fmt.Println("false from x") + } + case <-y: + fmt.Println("y") + default: + fmt.Println("default") + } + // This is a multiline comment + // that is not a doc comment. + return ` +this string +is not indented` +} +-- @raw -- +package folding //@foldingrange(raw) + +import (<0 kind="imports"> + "fmt" + _ "log" +</0>) + +import _ "os" + +// bar is a function.<1 kind="comment"> +// With a multiline doc comment.</1> +func bar(<2 kind=""></2>) string {<3 kind=""> + /* This is a single line comment */ + switch {<4 kind=""> + case true:<5 kind=""> + if true {<6 kind=""> + fmt.Println(<7 kind="">"true"</7>) + </6>} else {<8 kind=""> + fmt.Println(<9 kind="">"false"</9>) + </8>}</5> + case false:<10 kind=""> + fmt.Println(<11 kind="">"false"</11>)</10> + default:<12 kind=""> + fmt.Println(<13 kind="">"default"</13>)</12> + </4>} + /* This is a multiline<14 kind="comment"> + block + comment */</14> + + /* This is a multiline<15 kind="comment"> + block + comment */ + // Followed by another comment.</15> + _ = []int{<16 kind=""> + 1, + 2, + 3, + </16>} + _ = [2]string{<17 kind="">"d", + "e", + </17>} + _ = map[string]int{<18 kind=""> + "a": 1, + "b": 2, + "c": 3, + </18>} + type T struct {<19 kind=""> + f string + g int + h string + </19>} + _ = T{<20 kind=""> + f: "j", + g: 4, + h: "i", + </20>} + x, y := make(<21 kind="">chan bool</21>), make(<22 kind="">chan bool</22>) + select {<23 kind=""> + case val := <-x:<24 kind=""> + if val {<25 kind=""> + fmt.Println(<26 kind="">"true from x"</26>) + </25>} else {<27 kind=""> + fmt.Println(<28 kind="">"false from x"</28>) + </27>}</24> + case <-y:<29 kind=""> + fmt.Println(<30 kind="">"y"</30>)</29> + default:<31 kind=""> + fmt.Println(<32 kind="">"default"</32>)</31> + </23>} + // This is a multiline comment<33 kind="comment"> + // that is not a doc comment.</33> + return <34 kind="">` +this string +is not indented`</34> +</3>} diff --git a/contribs/gnopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt b/contribs/gnopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt new file mode 100644 index 00000000000..0c532e760f1 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/foldingrange/a_lineonly.txt @@ -0,0 +1,163 @@ +This test checks basic behavior of the textDocument/foldingRange, when the +editor only supports line folding. + +-- capabilities.json -- +{ + "textDocument": { + "foldingRange": { + "lineFoldingOnly": true + } + } +} +-- a.go -- +package folding //@foldingrange(raw) + +import ( + "fmt" + _ "log" +) + +import _ "os" + +// bar is a function. +// With a multiline doc comment. +func bar() string { + /* This is a single line comment */ + switch { + case true: + if true { + fmt.Println("true") + } else { + fmt.Println("false") + } + case false: + fmt.Println("false") + default: + fmt.Println("default") + } + /* This is a multiline + block + comment */ + + /* This is a multiline + block + comment */ + // Followed by another comment. + _ = []int{ + 1, + 2, + 3, + } + _ = [2]string{"d", + "e", + } + _ = map[string]int{ + "a": 1, + "b": 2, + "c": 3, + } + type T struct { + f string + g int + h string + } + _ = T{ + f: "j", + g: 4, + h: "i", + } + x, y := make(chan bool), make(chan bool) + select { + case val := <-x: + if val { + fmt.Println("true from x") + } else { + fmt.Println("false from x") + } + case <-y: + fmt.Println("y") + default: + fmt.Println("default") + } + // This is a multiline comment + // that is not a doc comment. + return ` +this string +is not indented` +} +-- @raw -- +package folding //@foldingrange(raw) + +import (<0 kind="imports"> + "fmt" + _ "log"</0> +) + +import _ "os" + +// bar is a function.<1 kind="comment"> +// With a multiline doc comment.</1> +func bar() string {<2 kind=""> + /* This is a single line comment */ + switch {<3 kind=""> + case true:<4 kind=""> + if true {<5 kind=""> + fmt.Println("true")</5> + } else {<6 kind=""> + fmt.Println("false")</6> + }</4> + case false:<7 kind=""> + fmt.Println("false")</7> + default:<8 kind=""> + fmt.Println("default")</3></8> + } + /* This is a multiline<9 kind="comment"> + block + comment */</9> + + /* This is a multiline<10 kind="comment"> + block + comment */ + // Followed by another comment.</10> + _ = []int{<11 kind=""> + 1, + 2, + 3</11>, + } + _ = [2]string{"d", + "e", + } + _ = map[string]int{<12 kind=""> + "a": 1, + "b": 2, + "c": 3</12>, + } + type T struct {<13 kind=""> + f string + g int + h string</13> + } + _ = T{<14 kind=""> + f: "j", + g: 4, + h: "i"</14>, + } + x, y := make(chan bool), make(chan bool) + select {<15 kind=""> + case val := <-x:<16 kind=""> + if val {<17 kind=""> + fmt.Println("true from x")</17> + } else {<18 kind=""> + fmt.Println("false from x")</18> + }</16> + case <-y:<19 kind=""> + fmt.Println("y")</19> + default:<20 kind=""> + fmt.Println("default")</15></20> + } + // This is a multiline comment<21 kind="comment"> + // that is not a doc comment.</21> + return <22 kind="">` +this string +is not indented`</2></22> +} diff --git a/contribs/gnopls/internal/test/marker/testdata/foldingrange/bad.txt b/contribs/gnopls/internal/test/marker/testdata/foldingrange/bad.txt new file mode 100644 index 00000000000..f9f14a4fa7d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/foldingrange/bad.txt @@ -0,0 +1,41 @@ +This test verifies behavior of textDocument/foldingRange in the presence of +unformatted syntax. + +-- a.go -- +package folding //@foldingrange(raw) + +import ( "fmt" + _ "log" +) + +import ( + _ "os" ) + +// badBar is a function. +func badBar() string { x := true + if x { + // This is the only foldable thing in this file when lineFoldingOnly + fmt.Println("true") + } else { + fmt.Println("false") } + return "" +} +-- @raw -- +package folding //@foldingrange(raw) + +import (<0 kind="imports"> "fmt" + _ "log" +</0>) + +import (<1 kind="imports"> + _ "os" </1>) + +// badBar is a function. +func badBar(<2 kind=""></2>) string {<3 kind=""> x := true + if x {<4 kind=""> + // This is the only foldable thing in this file when lineFoldingOnly + fmt.Println(<5 kind="">"true"</5>) + </4>} else {<6 kind=""> + fmt.Println(<7 kind="">"false"</7>) </6>} + return "" +</3>} diff --git a/contribs/gnopls/internal/test/marker/testdata/format/format.txt b/contribs/gnopls/internal/test/marker/testdata/format/format.txt new file mode 100644 index 00000000000..a8d3543ffea --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/format/format.txt @@ -0,0 +1,80 @@ +This test checks basic behavior of textDocument/formatting requests. + +-- go.mod -- +module mod.com + +go 1.18 +-- good.go -- +package format //@format(good) + +import ( + "log" +) + +func goodbye() { + log.Printf("byeeeee") +} + +-- @good -- +package format //@format(good) + +import ( + "log" +) + +func goodbye() { + log.Printf("byeeeee") +} +-- bad.go -- +package format //@format(bad) + +import ( + "runtime" + "fmt" + "log" +) + +func hello() { + + + + + var x int //@diag("x", re"declared (and|but) not used") +} + +func hi() { + runtime.NumCPU() + fmt.Printf("") + + log.Printf("") +} +-- @bad -- +package format //@format(bad) + +import ( + "fmt" + "log" + "runtime" +) + +func hello() { + + var x int //@diag("x", re"declared (and|but) not used") +} + +func hi() { + runtime.NumCPU() + fmt.Printf("") + + log.Printf("") +} +-- newline.go -- +package format //@format(newline) +func _() {} +-- @newline -- +package format //@format(newline) +func _() {} +-- oneline.go -- +package format //@format(oneline) +-- @oneline -- +package format //@format(oneline) diff --git a/contribs/gnopls/internal/test/marker/testdata/format/issue59554.txt b/contribs/gnopls/internal/test/marker/testdata/format/issue59554.txt new file mode 100644 index 00000000000..816c9d1e06f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/format/issue59554.txt @@ -0,0 +1,34 @@ +Test case for golang/go#59554: data corruption on formatting due to line +directives. + +Note that gofumpt is needed for this test case, as it reformats var decls into +short var decls. + +Note that gofumpt requires Go 1.20. + +-- flags -- +-min_go=go1.20 + +-- settings.json -- +{ + "formatting.gofumpt": true +} + +-- main.go -- +package main //@format(main) + +func Match(data []byte) int { +//line :1 + var idx = ^uint(0) + _ = idx + return -1 +} +-- @main -- +package main //@format(main) + +func Match(data []byte) int { +//line :1 + idx := ^uint(0) + _ = idx + return -1 +} diff --git a/contribs/gnopls/internal/test/marker/testdata/format/noparse.txt b/contribs/gnopls/internal/test/marker/testdata/format/noparse.txt new file mode 100644 index 00000000000..afc96cc1ef3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/format/noparse.txt @@ -0,0 +1,27 @@ +This test checks that formatting does not run on code that has parse errors. + +-- parse.go -- +package noparse_format //@format(parse) + +func _() { +f() //@diag("f", re"(undefined|undeclared name): f") +} +-- @parse -- +package noparse_format //@format(parse) + +func _() { + f() //@diag("f", re"(undefined|undeclared name): f") +} +-- noparse.go -- +package noparse_format //@format(noparse) + +// The nonewvars expectation asserts that the go/analysis framework ran. + +func what() { + var hi func() + if { hi() //@diag(re"(){", re".*missing.*") + } + hi := nil +} +-- @noparse -- +7:5: missing condition in if statement diff --git a/contribs/gnopls/internal/test/marker/testdata/highlight/controlflow.txt b/contribs/gnopls/internal/test/marker/testdata/highlight/controlflow.txt new file mode 100644 index 00000000000..c09f748a553 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/highlight/controlflow.txt @@ -0,0 +1,74 @@ +This test verifies document highlighting for control flow. + +-- go.mod -- +module mod.com + +go 1.18 + +-- p.go -- +package p + +-- issue60589.go -- +package p + +// This test verifies that control flow highlighting correctly +// accounts for multi-name result parameters. +// In golang/go#60589, it did not. + +func _() (foo int, bar, baz string) { //@ hiloc(func, "func", text), hiloc(foo, "foo", text), hiloc(fooint, "foo int", text), hiloc(int, "int", text), hiloc(bar, "bar", text), hiloc(beforebaz, " baz", text), hiloc(baz, "baz", text), hiloc(barbazstring, "bar, baz string", text), hiloc(beforestring, re`() string`, text), hiloc(string, "string", text) + return 0, "1", "2" //@ hiloc(return, `return 0, "1", "2"`, text), hiloc(l0, "0", text), hiloc(l1, `"1"`, text), hiloc(l2, `"2"`, text) +} + +// Assertions, expressed here to avoid clutter above. +// Note that when the cursor is over the field type, there is some +// (likely harmless) redundancy. + +//@ highlight(func, func, return) +//@ highlight(foo, foo, l0) +//@ highlight(int, fooint, int, l0) +//@ highlight(bar, bar, l1) +//@ highlight(beforebaz) +//@ highlight(baz, baz, l2) +//@ highlight(beforestring, baz, l2) +//@ highlight(string, barbazstring, string, l1, l2) +//@ highlight(l0, foo, l0) +//@ highlight(l1, bar, l1) +//@ highlight(l2, baz, l2) + +// Check that duplicate result names do not cause +// inaccurate highlighting. + +func _() (x, x int32) { //@ loc(locx1, re`\((x)`), loc(locx2, re`(x) int`), hiloc(x1, re`\((x)`, text), hiloc(x2, re`(x) int`, text), diag(locx1, re"redeclared"), diag(locx2, re"redeclared") + return 1, 2 //@ hiloc(one, "1", text), hiloc(two, "2", text) +} + +//@ highlight(one, one, x1) +//@ highlight(two, two, x2) +//@ highlight(x1, x1, one) +//@ highlight(x2, x2, two) + +-- issue65516.go -- +package p + +// This test checks that gopls doesn't crash while highlighting +// functions with no body (golang/go#65516). + +func Foo() (int, string) //@hiloc(noBodyInt, "int", text), hiloc(noBodyFunc, "func", text) +//@highlight(noBodyInt, noBodyInt), highlight(noBodyFunc, noBodyFunc) + +-- issue65952.go -- +package p + +// This test checks that gopls doesn't crash while highlighting +// return values in functions with no results. + +func _() { + return 0 //@hiloc(ret1, "0", text), diag("0", re"too many return") + //@highlight(ret1, ret1) +} + +func _() () { + // TODO(golang/go#65966): fix the triplicate diagnostics here. + return 0 //@hiloc(ret2, "0", text), diag("0", re"too many return"), diag("0", re"too many return"), diag("0", re"too many return") + //@highlight(ret2, ret2) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/highlight/highlight.txt b/contribs/gnopls/internal/test/marker/testdata/highlight/highlight.txt new file mode 100644 index 00000000000..68d13d1ee64 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/highlight/highlight.txt @@ -0,0 +1,158 @@ +This test checks basic functionality of the textDocument/highlight request. + +-- highlights.go -- +package highlights + +import ( + "fmt" //@hiloc(fmtImp, "\"fmt\"", text),highlightall(fmtImp, fmt1, fmt2, fmt3, fmt4) + h2 "net/http" //@hiloc(hImp, "h2", text),highlightall(hImp, hUse) + "sort" +) + +type F struct{ bar int } //@hiloc(barDeclaration, "bar", text),highlightall(barDeclaration, bar1, bar2, bar3) + +func _() F { + return F{ + bar: 123, //@hiloc(bar1, "bar", write) + } +} + +var foo = F{bar: 52} //@hiloc(fooDeclaration, "foo", write),hiloc(bar2, "bar", write),highlightall(fooDeclaration, fooUse) + +func Print() { //@hiloc(printFunc, "Print", text),highlightall(printFunc, printTest) + _ = h2.Client{} //@hiloc(hUse, "h2", text) + + fmt.Println(foo) //@hiloc(fooUse, "foo", read),hiloc(fmt1, "fmt", text) + fmt.Print("yo") //@hiloc(printSep, "Print", text),highlightall(printSep, print1, print2),hiloc(fmt2, "fmt", text) +} + +func (x *F) Inc() { //@hiloc(xRightDecl, "x", text),hiloc(xLeftDecl, " *", text),highlightall(xRightDecl, xUse),highlight(xLeftDecl, xRightDecl, xUse) + x.bar++ //@hiloc(xUse, "x", read),hiloc(bar3, "bar", write) +} + +func testFunctions() { + fmt.Print("main start") //@hiloc(print1, "Print", text),hiloc(fmt3, "fmt", text) + fmt.Print("ok") //@hiloc(print2, "Print", text),hiloc(fmt4, "fmt", text) + Print() //@hiloc(printTest, "Print", text) +} + +// DocumentHighlight is undefined, so its uses below are type errors. +// Nevertheless, document highlighting should still work. +//@diag(locdoc1, re"undefined|undeclared"), diag(locdoc2, re"undefined|undeclared"), diag(locdoc3, re"undefined|undeclared") + +func toProtocolHighlight(rngs []int) []DocumentHighlight { //@loc(locdoc1, "DocumentHighlight"), hiloc(doc1, "DocumentHighlight", text),hiloc(docRet1, "[]DocumentHighlight", text),highlight(doc1, docRet1, doc1, doc2, doc3, result) + result := make([]DocumentHighlight, 0, len(rngs)) //@loc(locdoc2, "DocumentHighlight"), hiloc(doc2, "DocumentHighlight", text),highlight(doc2, doc1, doc2, doc3) + for _, rng := range rngs { + result = append(result, DocumentHighlight{ //@loc(locdoc3, "DocumentHighlight"), hiloc(doc3, "DocumentHighlight", text),highlight(doc3, doc1, doc2, doc3) + Range: rng, + }) + } + return result //@hiloc(result, "result", text) +} + +func testForLoops() { + for i := 0; i < 10; i++ { //@hiloc(forDecl1, "for", text),highlightall(forDecl1, brk1, cont1) + if i > 8 { + break //@hiloc(brk1, "break", text) + } + if i < 2 { + for j := 1; j < 10; j++ { //@hiloc(forDecl2, "for", text),highlightall(forDecl2, cont2) + if j < 3 { + for k := 1; k < 10; k++ { //@hiloc(forDecl3, "for", text),highlightall(forDecl3, cont3) + if k < 3 { + continue //@hiloc(cont3, "continue", text) + } + } + continue //@hiloc(cont2, "continue", text) + } + } + continue //@hiloc(cont1, "continue", text) + } + } + + arr := []int{} + for i := range arr { //@hiloc(forDecl4, "for", text),highlightall(forDecl4, brk4, cont4) + if i > 8 { + break //@hiloc(brk4, "break", text) + } + if i < 4 { + continue //@hiloc(cont4, "continue", text) + } + } + +Outer: + for i := 0; i < 10; i++ { //@hiloc(forDecl5, "for", text),highlightall(forDecl5, brk5, brk6, brk8) + break //@hiloc(brk5, "break", text) + for { //@hiloc(forDecl6, "for", text),highlightall(forDecl6, cont5), diag("for", re"unreachable") + if i == 1 { + break Outer //@hiloc(brk6, "break Outer", text) + } + switch i { //@hiloc(switch1, "switch", text),highlightall(switch1, brk7) + case 5: + break //@hiloc(brk7, "break", text) + case 6: + continue //@hiloc(cont5, "continue", text) + case 7: + break Outer //@hiloc(brk8, "break Outer", text) + } + } + } +} + +func testSwitch() { + var i, j int + +L1: + for { //@hiloc(forDecl7, "for", text),highlightall(forDecl7, brk10, cont6) + L2: + switch i { //@hiloc(switch2, "switch", text),highlightall(switch2, brk11, brk12, brk13) + case 1: + switch j { //@hiloc(switch3, "switch", text),highlightall(switch3, brk9) + case 1: + break //@hiloc(brk9, "break", text) + case 2: + break L1 //@hiloc(brk10, "break L1", text) + case 3: + break L2 //@hiloc(brk11, "break L2", text) + default: + continue //@hiloc(cont6, "continue", text) + } + case 2: + break //@hiloc(brk12, "break", text) + default: + break L2 //@hiloc(brk13, "break L2", text) + } + } +} + +func testReturn() bool { //@hiloc(func1, "func", text),hiloc(bool1, "bool", text),highlight(func1, func1, fullRet11, fullRet12),highlight(bool1, bool1, false1, bool2, true1) + if 1 < 2 { + return false //@hiloc(ret11, "return", text),hiloc(fullRet11, "return false", text),hiloc(false1, "false", text),highlight(ret11, func1, fullRet11, fullRet12) + } + candidates := []int{} + sort.SliceStable(candidates, func(i, j int) bool { //@hiloc(func2, "func", text),hiloc(bool2, "bool", text),highlight(func2, func2, fullRet2) + return candidates[i] > candidates[j] //@hiloc(ret2, "return", text),hiloc(fullRet2, "return candidates[i] > candidates[j]", text),highlight(ret2, func2, fullRet2) + }) + return true //@hiloc(ret12, "return", text),hiloc(fullRet12, "return true", text),hiloc(true1, "true", text),highlight(ret12, func1, fullRet11, fullRet12) +} + +func testReturnFields() float64 { //@hiloc(retVal1, "float64", text),highlight(retVal1, retVal1, retVal11, retVal21) + if 1 < 2 { + return 20.1 //@hiloc(retVal11, "20.1", text),highlight(retVal11, retVal1, retVal11, retVal21) + } + z := 4.3 //@hiloc(zDecl, "z", write) + return z //@hiloc(retVal21, "z", text),highlight(retVal21, retVal1, retVal11, zDecl, retVal21) +} + +func testReturnMultipleFields() (float32, string) { //@hiloc(retVal31, "float32", text),hiloc(retVal32, "string", text),highlight(retVal31, retVal31, retVal41, retVal51),highlight(retVal32, retVal32, retVal42, retVal52) + y := "im a var" //@hiloc(yDecl, "y", write), + if 1 < 2 { + return 20.1, y //@hiloc(retVal41, "20.1", text),hiloc(retVal42, "y", text),highlight(retVal41, retVal31, retVal41, retVal51),highlight(retVal42, retVal32, yDecl, retVal42, retVal52) + } + return 4.9, "test" //@hiloc(retVal51, "4.9", text),hiloc(retVal52, "\"test\"", text),highlight(retVal51, retVal31, retVal41, retVal51),highlight(retVal52, retVal32, retVal42, retVal52) +} + +func testReturnFunc() int32 { //@hiloc(retCall, "int32", text) + mulch := 1 //@hiloc(mulchDec, "mulch", write),highlight(mulchDec, mulchDec, mulchRet) + return int32(mulch) //@hiloc(mulchRet, "mulch", read),hiloc(retFunc, "int32", text),hiloc(retTotal, "int32(mulch)", text),highlight(mulchRet, mulchDec, mulchRet),highlight(retFunc, retCall, retFunc, retTotal) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/highlight/highlight_kind.txt b/contribs/gnopls/internal/test/marker/testdata/highlight/highlight_kind.txt new file mode 100644 index 00000000000..bd059f77450 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/highlight/highlight_kind.txt @@ -0,0 +1,88 @@ +This test checks textDocument/highlight with highlight kinds. +For example, a use of a variable is reported as a "read", +and an assignment to a variable is reported as a "write". +(Note that the details don't align exactly with the Go +type-checker notions of values versus addressable variables). + + +-- highlight_kind.go -- +package a + +type Nest struct { + nest *Nest //@hiloc(fNest, "nest", text) +} +type MyMap map[string]string + +type NestMap map[Nest]Nest + +func highlightTest() { + const constIdent = 1 //@hiloc(constIdent, "constIdent", write) + //@highlightall(constIdent) + var varNoInit int //@hiloc(varNoInit, "varNoInit", write) + (varNoInit) = 1 //@hiloc(varNoInitAssign, "varNoInit", write) + _ = varNoInit //@hiloc(varNoInitRead, "varNoInit", read) + //@highlightall(varNoInit, varNoInitAssign, varNoInitRead) + + str, num := "hello", 2 //@hiloc(str, "str", write), hiloc(num, "num", write) + _, _ = str, num //@hiloc(strRead, "str", read), hiloc(numRead, "num", read) + //@highlightall(str, strRead, strMapKey, strMapVal, strMyMapKey, strMyMapVal, strMyMapSliceKey, strMyMapSliceVal, strMyMapPtrSliceKey, strMyMapPtrSliceVal) + //@highlightall(num, numRead, numAddr, numIncr, numMul) + nest := &Nest{nest: nil} //@hiloc(nest, "nest", write),hiloc(fNestComp, re`(nest):`, write) + nest.nest = &Nest{} //@hiloc(nestSelX, "nest", read), hiloc(fNestSel, re`(nest) =`, write) + *nest.nest = Nest{} //@hiloc(nestSelXStar, "nest", read), hiloc(fNestSelStar, re`(nest) =`, write) + //@highlightall(nest, nestSelX, nestSelXStar, nestMapVal) + //@highlightall(fNest, fNestComp, fNestSel, fNestSelStar, fNestSliceComp, fNestPtrSliceComp, fNestMapKey) + + pInt := &num //@hiloc(pInt, "pInt", write),hiloc(numAddr, "num", read) + // StarExpr is reported as "write" in GoLand and Rust Analyzer + *pInt = 3 //@hiloc(pIntStar, "pInt", write) + var ppInt **int = &pInt //@hiloc(ppInt, "ppInt", write),hiloc(pIntAddr, re`&(pInt)`, read) + **ppInt = 4 //@hiloc(ppIntStar, "ppInt", write) + *(*ppInt) = 4 //@hiloc(ppIntParen, "ppInt", write) + //@highlightall(pInt, pIntStar, pIntAddr) + //@highlightall(ppInt, ppIntStar, ppIntParen) + + num++ //@hiloc(numIncr, "num", write) + num *= 1 //@hiloc(numMul, "num", write) + + var ch chan int = make(chan int, 10) //@hiloc(ch, "ch", write) + ch <- 3 //@hiloc(chSend, "ch", write) + <-ch //@hiloc(chRecv, "ch", read) + //@highlightall(ch, chSend, chRecv) + + var nums []int = []int{1, 2} //@hiloc(nums, "nums", write) + // IndexExpr is reported as "read" in GoLand, Rust Analyzer and Java JDT + nums[0] = 1 //@hiloc(numsIndex, "nums", read) + //@highlightall(nums, numsIndex) + + mapLiteral := map[string]string{ //@hiloc(mapLiteral, "mapLiteral", write) + str: str, //@hiloc(strMapKey, "str", read),hiloc(strMapVal, re`(str),`, read) + } + for key, value := range mapLiteral { //@hiloc(mapKey, "key", write), hiloc(mapVal, "value", write), hiloc(mapLiteralRange, "mapLiteral", read) + _, _ = key, value //@hiloc(mapKeyRead, "key", read), hiloc(mapValRead, "value", read) + } + //@highlightall(mapLiteral, mapLiteralRange) + //@highlightall(mapKey, mapKeyRead) + //@highlightall(mapVal, mapValRead) + + nestSlice := []Nest{ + {nest: nil}, //@hiloc(fNestSliceComp, "nest", write) + } + nestPtrSlice := []*Nest{ + {nest: nil}, //@hiloc(fNestPtrSliceComp, "nest", write) + } + myMap := MyMap{ + str: str, //@hiloc(strMyMapKey, "str", read),hiloc(strMyMapVal, re`(str),`, read) + } + myMapSlice := []MyMap{ + {str: str}, //@hiloc(strMyMapSliceKey, "str", read),hiloc(strMyMapSliceVal, re`: (str)`, read) + } + myMapPtrSlice := []*MyMap{ + {str: str}, //@hiloc(strMyMapPtrSliceKey, "str", read),hiloc(strMyMapPtrSliceVal, re`: (str)`, read) + } + nestMap := NestMap{ + Nest{nest: nil}: *nest, //@hiloc(fNestMapKey, "nest", write), hiloc(nestMapVal, re`(nest),`, read) + } + + _, _, _, _, _, _ = myMap, nestSlice, nestPtrSlice, myMapSlice, myMapPtrSlice, nestMap +} diff --git a/contribs/gnopls/internal/test/marker/testdata/highlight/issue60435.txt b/contribs/gnopls/internal/test/marker/testdata/highlight/issue60435.txt new file mode 100644 index 00000000000..0eef08029ee --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/highlight/issue60435.txt @@ -0,0 +1,15 @@ +This is a regression test for issue 60435: +Highlighting "net/http" shouldn't have any effect +on an import path that contains it as a substring, +such as httptest. + +-- highlights.go -- +package highlights + +import ( + "net/http" //@hiloc(httpImp, `"net/http"`, text) + "net/http/httptest" //@hiloc(httptestImp, `"net/http/httptest"`, text) +) + +var _ = httptest.NewRequest +var _ = http.NewRequest //@hiloc(here, "http", text), highlight(here, here, httpImp) diff --git a/contribs/gnopls/internal/test/marker/testdata/highlight/issue68918.txt b/contribs/gnopls/internal/test/marker/testdata/highlight/issue68918.txt new file mode 100644 index 00000000000..ff2afc18f07 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/highlight/issue68918.txt @@ -0,0 +1,9 @@ +Regression test for https://github.com/golang/go/issues/68918: +crash due to missing type information in CompositeLit. + +-- a.go -- +package a + +var _ = T{{ x }} //@hiloc(x, "x", text), diag("T", re"undefined"), diag("{ ", re"missing type") + +//@highlight(x, x) diff --git a/contribs/gnopls/internal/test/marker/testdata/highlight/switchbreak.txt b/contribs/gnopls/internal/test/marker/testdata/highlight/switchbreak.txt new file mode 100644 index 00000000000..3893b4c502d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/highlight/switchbreak.txt @@ -0,0 +1,21 @@ +This is a regression test for issue 65752: a break in a switch should +highlight the switch, not the enclosing loop. + +-- a.go -- +package a + +func _(x any) { + for { + // type switch + switch x.(type) { //@hiloc(tswitch, "switch", text) + default: + break //@hiloc(tbreak, "break", text),highlight(tbreak, tswitch, tbreak) + } + + // value switch + switch { //@hiloc(vswitch, "switch", text) + default: + break //@hiloc(vbreak, "break", text), highlight(vbreak, vswitch, vbreak) + } + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/basiclit.txt b/contribs/gnopls/internal/test/marker/testdata/hover/basiclit.txt new file mode 100644 index 00000000000..804277f6e0c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/basiclit.txt @@ -0,0 +1,87 @@ +This test checks gopls behavior when hovering over basic literals. + +Skipped on ppc64 as there appears to be a bug on aix-ppc64: golang/go#67526. + +-- flags -- +-skip_goarch=ppc64 + +-- basiclit.go -- +package basiclit + +func _() { + _ = 'a' //@hover("'a'", "'a'", latinA) + _ = 0x61 //@hover("0x61", "0x61", latinAHex) + + _ = '\u2211' //@hover("'\\u2211'", "'\\u2211'", summation) + _ = 0x2211 //@hover("0x2211", "0x2211", summationHex) + _ = "foo \u2211 bar" //@hover("\\u2211", "\\u2211", summation) + + _ = '\a' //@hover("'\\a'", "'\\a'", control) + _ = "foo \a bar" //@hover("\\a", "\\a", control) + + _ = '\U0001F30A' //@hover("'\\U0001F30A'", "'\\U0001F30A'", waterWave) + _ = 0x0001F30A //@hover("0x0001F30A", "0x0001F30A", waterWaveHex) + _ = 0X0001F30A //@hover("0X0001F30A", "0X0001F30A", waterWaveHex) + _ = "foo \U0001F30A bar" //@hover("\\U0001F30A", "\\U0001F30A", waterWave) + + _ = '\x7E' //@hover("'\\x7E'", "'\\x7E'", tilde) + _ = "foo \x7E bar" //@hover("\\x7E", "\\x7E", tilde) + _ = "foo \a bar" //@hover("\\a", "\\a", control) + + _ = '\173' //@hover("'\\173'", "'\\173'", leftCurly) + _ = "foo \173 bar" //@hover("\\173","\\173", leftCurly) + _ = "foo \173 bar \u2211 baz" //@hover("\\173","\\173", leftCurly) + _ = "foo \173 bar \u2211 baz" //@hover("\\u2211","\\u2211", summation) + _ = "foo\173bar\u2211baz" //@hover("\\173","\\173", leftCurly) + _ = "foo\173bar\u2211baz" //@hover("\\u2211","\\u2211", summation) + + // search for runes in string only if there is an escaped sequence + _ = "hello" //@hover(`"hello"`, _, _) + + // incorrect escaped rune sequences + _ = '\0' //@hover("'\\0'", _, _),diag(re`\\0()'`, re"illegal character") + _ = '\u22111' //@hover("'\\u22111'", _, _) + _ = '\U00110000' //@hover("'\\U00110000'", _, _) + _ = '\u12e45'//@hover("'\\u12e45'", _, _) + _ = '\xa' //@hover("'\\xa'", _, _) + _ = 'aa' //@hover("'aa'", _, _) + + // other basic lits + _ = 1 //@hover("1", _, _) + _ = 1.2 //@hover("1.2", _, _) + _ = 1.2i //@hover("1.2i", _, _) + _ = 0123 //@hover("0123", _, _) + _ = 0b1001 //@hover("0b", "0b1001", binaryNumber) + _ = 0B1001 //@hover("0B", "0B1001", binaryNumber) + _ = 0o77 //@hover("0o", "0o77", octalNumber) + _ = 0O77 //@hover("0O", "0O77", octalNumber) + _ = 0x1234567890 //@hover("0x1234567890", "0x1234567890", hexNumber) + _ = 0X1234567890 //@hover("0X1234567890", "0X1234567890", hexNumber) + _ = 0x1000000000000000000 //@hover("0x1", "0x1000000000000000000", bigHex) +) +-- @bigHex -- +4722366482869645213696 +-- @binaryNumber -- +9 +-- @control -- +U+0007, control +-- @hexNumber -- +78187493520 +-- @latinA -- +'a', U+0061, LATIN SMALL LETTER A +-- @latinAHex -- +97, 'a', U+0061, LATIN SMALL LETTER A +-- @leftCurly -- +'{', U+007B, LEFT CURLY BRACKET +-- @octalNumber -- +63 +-- @summation -- +'∑', U+2211, N-ARY SUMMATION +-- @summationHex -- +8721, '∑', U+2211, N-ARY SUMMATION +-- @tilde -- +'~', U+007E, TILDE +-- @waterWave -- +'🌊', U+1F30A, WATER WAVE +-- @waterWaveHex -- +127754, '🌊', U+1F30A, WATER WAVE diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/comment.txt b/contribs/gnopls/internal/test/marker/testdata/hover/comment.txt new file mode 100644 index 00000000000..479aff6473f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/comment.txt @@ -0,0 +1,82 @@ +This test checks hovering over doc links in comments. + +Requires go1.20+ for the unsafe package test. + +-- flags -- +-min_go=go1.20 + +-- go.mod -- +module mod.com + +go 1.20 + +-- a.go -- +package p + +import ( + "unsafe" + + "mod.com/util" //@hover(`"mod.com/util"`, `"mod.com/util"`, strconv) +) + +// [NumberBase] is the base to use for number parsing. //@hover("NumberBase", "NumberBase", NumberBase) +const NumberBase = 10 + +// [Conv] converts s to an int. //@hover("Conv", "Conv", Conv) +func Conv(s string) int { + // [util.ParseInt] parses s and returns the integer corresponding to it. //@hover("util", "util", util),hover("ParseInt", "ParseInt", strconvParseInt) + // [NumberBase] is the base to use for number parsing. + i, _ := util.ParseInt(s, NumberBase, 64) + return int(i) +} + +// unsafeConv converts s to a byte slice using [unsafe.Pointer]. hover("Pointer", "Pointer", unsafePointer) +func unsafeConv(s string) []byte { + p := unsafe.StringData(s) + b := unsafe.Slice(p, len(s)) + return b +} + +-- util/conv.go -- +// Package util provides utility functions. +package util + +import "strconv" + +// ParseInt interprets a string s in the given base (0, 2 to 36) and +// bit size (0 to 64) and returns the corresponding value i. +func ParseInt(s string, base int, bitSize int) (int64, error) { + return strconv.ParseInt(s, base, bitSize) +} + +-- @Conv -- +```go +func Conv(s string) int +``` + +\[Conv] converts s to an int. //@hover("Conv", "Conv", Conv) + + +[`p.Conv` on pkg.go.dev](https://pkg.go.dev/mod.com#Conv) +-- @NumberBase -- +```go +const NumberBase untyped int = 10 +``` + +\[NumberBase] is the base to use for number parsing. //@hover("NumberBase", "NumberBase", NumberBase) + + +[`p.NumberBase` on pkg.go.dev](https://pkg.go.dev/mod.com#NumberBase) +-- @strconv -- +Package util provides utility functions. +-- @strconvParseInt -- +```go +func ParseInt(s string, base int, bitSize int) (int64, error) +``` + +ParseInt interprets a string s in the given base (0, 2 to 36) and bit size (0 to 64) and returns the corresponding value i. + + +[`util.ParseInt` on pkg.go.dev](https://pkg.go.dev/mod.com/util#ParseInt) +-- @util -- +Package util provides utility functions. diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/const.txt b/contribs/gnopls/internal/test/marker/testdata/hover/const.txt new file mode 100644 index 00000000000..179ff155357 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/const.txt @@ -0,0 +1,154 @@ +This test checks hovering over constants. + +-- go.mod -- +module mod.com + +go 1.17 + +-- c.go -- +package c + +import ( + "math" + "time" +) + +const X = 0 //@hover("X", "X", bX) + +// dur is a constant of type time.Duration. +const dur = 15*time.Minute + 10*time.Second + 350*time.Millisecond //@hover("dur", "dur", dur) + +// MaxFloat32 is used in another package. +const MaxFloat32 = 0x1p127 * (1 + (1 - 0x1p-23)) + +// Numbers. +func _() { + const hex, bin = 0xe34e, 0b1001001 + + const ( + // no inline comment + decimal = 153 + + numberWithUnderscore int64 = 10_000_000_000 + octal = 0o777 + expr = 2 << (0b111&0b101 - 2) + boolean = (55 - 3) == (26 * 2) + ) + + _ = decimal //@hover("decimal", "decimal", decimalConst) + _ = hex //@hover("hex", "hex", hexConst) + _ = bin //@hover("bin", "bin", binConst) + _ = numberWithUnderscore //@hover("numberWithUnderscore", "numberWithUnderscore", numberWithUnderscoreConst) + _ = octal //@hover("octal", "octal", octalConst) + _ = expr //@hover("expr", "expr", exprConst) + _ = boolean //@hover("boolean", "boolean", boolConst) + + const ln10 = 2.30258509299404568401799145468436420760110148862877297603332790 + + _ = ln10 //@hover("ln10", "ln10", ln10Const) +} + +// Iota. +func _() { + const ( + a = 1 << iota + b + ) + + _ = a //@hover("a", "a", aIota) + _ = b //@hover("b", "b", bIota) +} + +// Strings. +func _() { + const ( + str = "hello" + " " + "world" + longStr = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eget ipsum non nunc +molestie mattis id quis augue. Mauris dictum tincidunt ipsum, in auctor arcu congue eu. +Morbi hendrerit fringilla libero commodo varius. Vestibulum in enim rutrum, rutrum tellus +aliquet, luctus enim. Nunc sem ex, consectetur id porta nec, placerat vel urna.` + ) + + _ = str //@hover("str", "str", strConst) + _ = longStr //@hover("longStr", "longStr", longStrConst) +} + +// Constants from other packages. +func _() { + _ = math.Log2E //@hover("Log2E", "Log2E", log2eConst) +} + +-- @bX -- +```go +const X untyped int = 0 +``` + +@hover("X", "X", bX) + + +[`c.X` on pkg.go.dev](https://pkg.go.dev/mod.com#X) +-- @dur -- +```go +const dur time.Duration = 15*time.Minute + 10*time.Second + 350*time.Millisecond // 15m10.35s +``` + +dur is a constant of type time.Duration. +-- @decimalConst -- +```go +const decimal untyped int = 153 +``` + +no inline comment +-- @hexConst -- +```go +const hex untyped int = 0xe34e // 58190 +``` +-- @binConst -- +```go +const bin untyped int = 0b1001001 // 73 +``` +-- @numberWithUnderscoreConst -- +```go +const numberWithUnderscore int64 = 10_000_000_000 // 10000000000 +``` +-- @octalConst -- +```go +const octal untyped int = 0o777 // 511 +``` +-- @exprConst -- +```go +const expr untyped int = 2 << (0b111&0b101 - 2) // 16 +``` +-- @boolConst -- +```go +const boolean untyped bool = (55 - 3) == (26 * 2) // true +``` +-- @ln10Const -- +```go +const ln10 untyped float = 2.30258509299404568401799145468436420760110148862877297603332790 // 2.30259 +``` +-- @aIota -- +```go +const a untyped int = 1 << iota // 1 +``` +-- @bIota -- +```go +const b untyped int = 2 +``` +-- @strConst -- +```go +const str untyped string = "hello world" +``` +-- @longStrConst -- +```go +const longStr untyped string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur e... +``` +-- @log2eConst -- +```go +const math.Log2E untyped float = 1 / Ln2 // 1.4427 +``` + +Mathematical constants. + + +[`math.Log2E` on pkg.go.dev](https://pkg.go.dev/math#Log2E) diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/embed.txt b/contribs/gnopls/internal/test/marker/testdata/hover/embed.txt new file mode 100644 index 00000000000..1dc3fcbfa12 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/embed.txt @@ -0,0 +1,57 @@ +This test checks that hover reports accessible embedded fields +(after the doc comment and before the accessible methods). + +-- go.mod -- +module example.com + +go 1.18 + +-- q/q.go -- +package q + +type Q struct { + One int + two string + q2[chan int] +} + +type q2[T any] struct { + Three *T + four string +} + +-- p.go -- +package p + +import "example.com/q" + +// doc +type P struct { + q.Q +} + +func (P) m() {} + +var p P //@hover("P", "P", P) + +-- @P -- +```go +type P struct { + q.Q +} +``` + +doc + + +```go +// Embedded fields: +One int // through Q +Three *chan int // through Q.q2 +``` + +```go +func (P) m() +``` + +[`p.P` on pkg.go.dev](https://pkg.go.dev/example.com#P) diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/generics.txt b/contribs/gnopls/internal/test/marker/testdata/hover/generics.txt new file mode 100644 index 00000000000..50ce49bb33f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/generics.txt @@ -0,0 +1,117 @@ +This file contains tests for hovering over generic Go code. + +Requires go1.20+ for the new go/doc/comment package, and a change in Go 1.20 +that affected the formatting of constraint interfaces. + +Its size expectations assume a 64-bit machine. + +-- flags -- +-min_go=go1.20 +-skip_goarch=386,arm + +-- go.mod -- +// A go.mod is require for correct pkgsite links. +// TODO(rfindley): don't link to ad-hoc or command-line-arguments packages! +module mod.com + +go 1.18 + +-- issue68213.go -- +package generics + +// Hovering over an interface with empty type set must not panic. +type empty interface { //@hover("empty", "empty", empty) + int + string +} + +-- @empty -- +```go +type empty interface { // size=16 (0x10) + int + string +} +``` + +Hovering over an interface with empty type set must not panic. +-- generics.go -- +package generics + +type value[T any] struct { //@hover("lue", "value", value),hover("T", "T", valueT) + val T //@hover("T", "T", valuevalT) + Q int64 //@hover("Q", "Q", valueQ) +} + +type Value[T any] struct { //@hover("T", "T", ValueT) + val T //@hover("T", "T", ValuevalT) + Q int64 //@hover("Q", "Q", ValueQ) +} + +func F[P interface{ ~int | string }]() { //@hover("P", "P", Ptparam) + var _ P //@hover("P","P",Pvar) +} + +-- @value -- +```go +type value[T any] struct { + val T //@hover("T", "T", valuevalT) + Q int64 //@hover("Q", "Q", valueQ) +} +``` +-- @valueT -- +```go +type parameter T any +``` +-- @valuevalT -- +```go +type parameter T any +``` +-- @valueQ -- +```go +field Q int64 // size=8 +``` + +@hover("Q", "Q", valueQ) +-- @ValueT -- +```go +type parameter T any +``` +-- @ValuevalT -- +```go +type parameter T any +``` +-- @ValueQ -- +```go +field Q int64 // size=8 +``` + +@hover("Q", "Q", ValueQ) + + +[`(generics.Value).Q` on pkg.go.dev](https://pkg.go.dev/mod.com#Value.Q) +-- @Ptparam -- +```go +type parameter P interface{~int | string} +``` +-- @Pvar -- +```go +type parameter P interface{~int | string} +``` +-- inferred.go -- +package generics + +func app[S interface{ ~[]E }, E interface{}](s S, e E) S { + return append(s, e) +} + +func _() { + _ = app[[]int] //@hover("app", "app", appint) + _ = app[[]int, int] //@hover("app", "app", appint) + _ = app[[]int]([]int{}, 0) //@hover("app", "app", appint), diag("[[]int]", re"unnecessary") + _ = app([]int{}, 0) //@hover("app", "app", appint) +} + +-- @appint -- +```go +func app(s []int, e int) []int // func[S interface{~[]E}, E interface{}](s S, e E) S +``` diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/godef.txt b/contribs/gnopls/internal/test/marker/testdata/hover/godef.txt new file mode 100644 index 00000000000..9b2e7ec2ce3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/godef.txt @@ -0,0 +1,426 @@ +This test was ported from 'godef' in the old marker tests. +It tests various hover and definition requests. + +Requires go1.19+ for the new go/doc/comment package. + +TODO(adonovan): figure out why this test also fails +without -min_go=go1.20. Or just wait... + +-- flags -- +-min_go=go1.19 + +-- flags -- +-min_go=go1.20 + +-- go.mod -- +module godef.test + +go 1.18 + +-- a/a_x_test.go -- +package a_test + +import ( + "testing" +) + +func TestA2(t *testing.T) { //@hover("TestA2", "TestA2", TestA2) + Nonexistant() //@diag("Nonexistant", re"(undeclared name|undefined): Nonexistant") +} + +-- @TestA2 -- +```go +func TestA2(t *testing.T) +``` +-- @ember -- +```go +field Member string +``` + +@loc(Member, "Member") + + +[`(a.Thing).Member` on pkg.go.dev](https://pkg.go.dev/godef.test/a#Thing.Member) +-- a/d.go -- +package a //@hover("a", _, a) + +import "fmt" + +type Thing struct { //@loc(Thing, "Thing") + Member string //@loc(Member, "Member") +} + +var Other Thing //@loc(Other, "Other") + +func Things(val []string) []Thing { //@loc(Things, "Things") + return nil +} + +func (t Thing) Method(i int) string { //@loc(Method, "Method") + return t.Member +} + +func (t Thing) Method3() { +} + +func (t *Thing) Method2(i int, j int) (error, string) { + return nil, t.Member +} + +func (t *Thing) private() { +} + +func useThings() { + t := Thing{ //@hover("ing", "Thing", ing) + Member: "string", //@hover("ember", "Member", ember), def("ember", Member) + } + fmt.Print(t.Member) //@hover("ember", "Member", ember), def("ember", Member) + fmt.Print(Other) //@hover("ther", "Other", ther), def("ther", Other) + Things(nil) //@hover("ings", "Things", ings), def("ings", Things) + t.Method(0) //@hover("eth", "Method", eth), def("eth", Method) +} + +type NextThing struct { //@loc(NextThing, "NextThing") + Thing + Value int +} + +func (n NextThing) another() string { + return n.Member +} + +// Shadows Thing.Method3 +func (n *NextThing) Method3() int { + return n.Value +} + +var nextThing NextThing //@hover("NextThing", "NextThing", NextThing), def("NextThing", NextThing) + +-- @ings -- +```go +func Things(val []string) []Thing +``` + +[`a.Things` on pkg.go.dev](https://pkg.go.dev/godef.test/a#Things) +-- @ther -- +```go +var Other Thing +``` + +@loc(Other, "Other") + + +[`a.Other` on pkg.go.dev](https://pkg.go.dev/godef.test/a#Other) +-- @a -- +-- @ing -- +```go +type Thing struct { + Member string //@loc(Member, "Member") +} +``` + +```go +func (t Thing) Method(i int) string +func (t *Thing) Method2(i int, j int) (error, string) +func (t Thing) Method3() +func (t *Thing) private() +``` + +[`a.Thing` on pkg.go.dev](https://pkg.go.dev/godef.test/a#Thing) +-- @NextThing -- +```go +type NextThing struct { + Thing + Value int +} +``` + +```go +// Embedded fields: +Member string // through Thing +``` + +```go +func (t Thing) Method(i int) string +func (t *Thing) Method2(i int, j int) (error, string) +func (n *NextThing) Method3() int +func (n NextThing) another() string +func (t *Thing) private() +``` + +[`a.NextThing` on pkg.go.dev](https://pkg.go.dev/godef.test/a#NextThing) +-- @eth -- +```go +func (t Thing) Method(i int) string +``` + +[`(a.Thing).Method` on pkg.go.dev](https://pkg.go.dev/godef.test/a#Thing.Method) +-- a/f.go -- +// Package a is a package for testing go to definition. +package a + +import "fmt" + +func TypeStuff() { + var x string + + switch y := interface{}(x).(type) { //@loc(y, "y"), hover("y", "y", y) , def("y", y) + case int: //@loc(intY, "int") + fmt.Printf("%v", y) //@hover("y", "y", inty), def("y", y) + case string: //@loc(stringY, "string") + fmt.Printf("%v", y) //@hover("y", "y", stringy), def("y", y) + } + +} +-- @inty -- +```go +var y int +``` +-- @stringy -- +```go +var y string +``` +-- @y -- +```go +var y interface{} +``` +-- a/h.go -- +package a + +func _() { + type s struct { + nested struct { + // nested number + number int64 //@loc(nestedNumber, "number") + } + nested2 []struct { + // nested string + str string //@loc(nestedString, "str") + } + x struct { + x struct { + x struct { + x struct { + x struct { + // nested map + m map[string]float64 //@loc(nestedMap, "m") + } + } + } + } + } + } + + var t s + _ = t.nested.number //@hover("number", "number", nestedNumber), def("number", nestedNumber) + _ = t.nested2[0].str //@hover("str", "str", nestedString), def("str", nestedString) + _ = t.x.x.x.x.x.m //@hover("m", "m", nestedMap), def("m", nestedMap) +} + +func _() { + var s struct { + // a field + a int //@loc(structA, "a") + // b nested struct + b struct { //@loc(structB, "b") + // c field of nested struct + c int //@loc(structC, "c") + } + } + _ = s.a //@def("a", structA) + _ = s.b //@def("b", structB) + _ = s.b.c //@def("c", structC) + + var arr []struct { + // d field + d int //@loc(arrD, "d") + // e nested struct + e struct { //@loc(arrE, "e") + // f field of nested struct + f int //@loc(arrF, "f") + } + } + _ = arr[0].d //@def("d", arrD) + _ = arr[0].e //@def("e", arrE) + _ = arr[0].e.f //@def("f", arrF) + + var complex []struct { + c <-chan map[string][]struct { + // h field + h int //@loc(complexH, "h") + // i nested struct + i struct { //@loc(complexI, "i") + // j field of nested struct + j int //@loc(complexJ, "j") + } + } + } + _ = (<-complex[0].c)["0"][0].h //@def("h", complexH) + _ = (<-complex[0].c)["0"][0].i //@def("i", complexI) + _ = (<-complex[0].c)["0"][0].i.j //@def("j", complexJ) + + var mapWithStructKey map[struct { //@diag("struct", re"invalid map key") + // X key field + x []string //@loc(mapStructKeyX, "x") + }]int + for k := range mapWithStructKey { + _ = k.x //@def("x", mapStructKeyX) + } + + var mapWithStructKeyAndValue map[struct { + // Y key field + y string //@loc(mapStructKeyY, "y") + }]struct { + // X value field + x string //@loc(mapStructValueX, "x") + } + for k, v := range mapWithStructKeyAndValue { + // TODO: we don't show docs for y field because both map key and value + // are structs. And in this case, we parse only map value + _ = k.y //@hover("y", "y", hoverStructKeyY), def("y", mapStructKeyY) + _ = v.x //@hover("x", "x", hoverStructKeyX), def("x", mapStructValueX) + } + + var i []map[string]interface { + // open method comment + open() error //@loc(openMethod, "open") + } + i[0]["1"].open() //@hover("pen","open", openMethod), def("open", openMethod) +} + +func _() { + test := struct { + // test description + desc string //@loc(testDescription, "desc") + }{} + _ = test.desc //@def("desc", testDescription) + + for _, tt := range []struct { + // test input + in map[string][]struct { //@loc(testInput, "in") + // test key + key string //@loc(testInputKey, "key") + // test value + value interface{} //@loc(testInputValue, "value") + } + result struct { + v <-chan struct { + // expected test value + value int //@loc(testResultValue, "value") + } + } + }{} { + _ = tt.in //@def("in", testInput) + _ = tt.in["0"][0].key //@def("key", testInputKey) + _ = tt.in["0"][0].value //@def("value", testInputValue) + + _ = (<-tt.result.v).value //@def("value", testResultValue) + } +} + +func _() { + getPoints := func() []struct { + // X coord + x int //@loc(returnX, "x") + // Y coord + y int //@loc(returnY, "y") + } { + return nil + } + + r := getPoints() + _ = r[0].x //@def("x", returnX) + _ = r[0].y //@def("y", returnY) +} +-- @hoverStructKeyX -- +```go +field x string +``` + +X value field +-- @hoverStructKeyY -- +```go +field y string +``` + +Y key field +-- @nestedNumber -- +```go +field number int64 +``` + +nested number +-- @nestedString -- +```go +field str string +``` + +nested string +-- @openMethod -- +```go +func (interface) open() error +``` + +open method comment +-- @nestedMap -- +```go +field m map[string]float64 +``` + +nested map +-- b/e.go -- +package b + +import ( + "fmt" + + "godef.test/a" +) + +func useThings() { + t := a.Thing{} //@loc(bStructType, "ing") + fmt.Print(t.Member) //@loc(bMember, "ember") + fmt.Print(a.Other) //@loc(bVar, "ther") + a.Things(nil) //@loc(bFunc, "ings") +} + +/*@ +def(bStructType, Thing) +def(bMember, Member) +def(bVar, Other) +def(bFunc, Things) +*/ + +func _() { + var x interface{} + switch x := x.(type) { //@hover("x", "x", xInterface) + case string: //@loc(eString, "string") + fmt.Println(x) //@hover("x", "x", xString) + case int: //@loc(eInt, "int") + fmt.Println(x) //@hover("x", "x", xInt) + } +} +-- @xInt -- +```go +var x int +``` +-- @xInterface -- +```go +var x interface{} +``` +-- @xString -- +```go +var x string +``` +-- broken/unclosedIf.go -- +package broken + +import "fmt" + +func unclosedIf() { + if false { + var myUnclosedIf string //@loc(myUnclosedIf, "myUnclosedIf") + fmt.Printf("s = %v\n", myUnclosedIf) //@def("my", myUnclosedIf) +} + +func _() {} //@diag("_", re"expected") diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/goprivate.txt b/contribs/gnopls/internal/test/marker/testdata/hover/goprivate.txt new file mode 100644 index 00000000000..202b4a11314 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/goprivate.txt @@ -0,0 +1,28 @@ +This test checks that links in hover obey GOPRIVATE. + +-- env -- +GOPRIVATE=mod.com +-- go.mod -- +module mod.com +-- p.go -- +package p + +// T should not be linked, as it is private. +type T struct{} //@hover("T", "T", T) +-- lib/lib.go -- +package lib + +// GOPRIVATE should also match nested packages. +type L struct{} //@hover("L", "L", L) +-- @L -- +```go +type L struct{} // size=0 +``` + +GOPRIVATE should also match nested packages. +-- @T -- +```go +type T struct{} // size=0 +``` + +T should not be linked, as it is private. diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/hover.txt b/contribs/gnopls/internal/test/marker/testdata/hover/hover.txt new file mode 100644 index 00000000000..35a07fc9522 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/hover.txt @@ -0,0 +1,33 @@ +This test demonstrates some features of the new marker test runner. + +-- a.go -- +// package comment +package aa //@hover("aa", "aa", aa) + +const abc = 0x2a //@hover("b", "abc", abc),hover(" =", "abc", abc) +-- typeswitch.go -- +package aa + +func _() { + var y interface{} + switch x := y.(type) { //@hover("x", "x", x) + case int: + println(x) //@hover("x", "x", xint),hover(")", "x", xint) + } +} +-- @abc -- +```go +const abc untyped int = 0x2a // 42 +``` + +@hover("b", "abc", abc),hover(" =", "abc", abc) +-- @x -- +```go +var x interface{} +``` +-- @xint -- +```go +var x int +``` +-- @aa -- +package comment diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/issues.txt b/contribs/gnopls/internal/test/marker/testdata/hover/issues.txt new file mode 100644 index 00000000000..6212964dff2 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/issues.txt @@ -0,0 +1,22 @@ +This test verifies fixes for various issues reported for hover. + +-- go.mod -- +module golang.org/lsptests + +-- issue64239/p.go -- +package issue64239 + +// golang/go#64239: hover fails for objects in the unsafe package. + +import "unsafe" + +var _ = unsafe.Sizeof(struct{}{}) //@hover("Sizeof", "Sizeof", "`Sizeof` on pkg.go.dev") + +-- issue64237/p.go -- +package issue64237 + +// golang/go#64237: hover panics for broken imports. + +import "golang.org/lsptests/nonexistant" //@diag("\"golang", re"could not import") + +var _ = nonexistant.Value //@hovererr("nonexistant", "no package data") diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/linkable.txt b/contribs/gnopls/internal/test/marker/testdata/hover/linkable.txt new file mode 100644 index 00000000000..fefedbceab6 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/linkable.txt @@ -0,0 +1,134 @@ +This test checks that we correctly determine pkgsite links for various +identifiers. + +We should only produce links that work, meaning the object is reachable via the +package's public API. + +-- go.mod -- +module mod.com + +go 1.18 +-- p.go -- +package p + +type E struct { + Embed int64 +} + +// T is in the package scope, and so should be linkable. +type T struct{ //@hover("T", "T", T) + // Only exported fields should be linkable + + f int64 //@hover("f", "f", f) + F int64 //@hover("F", "F", F) + + E + + // TODO(rfindley): is the link here correct? It ignores N. + N struct { + // Nested fields should also be linkable. + Nested int64 //@hover("Nested", "Nested", Nested) + } +} +// M is an exported method, and so should be linkable. +func (T) M() {} + +// m is not exported, and so should not be linkable. +func (T) m() {} + +func _() { + var t T + + // Embedded fields should be linkable. + _ = t.Embed //@hover("Embed", "Embed", Embed) + + // Local variables should not be linkable, even if they are capitalized. + var X int64 //@hover("X", "X", X) + _ = X + + // Local types should not be linkable, even if they are capitalized. + type Local struct { //@hover("Local", "Local", Local) + E + } + + // But the embedded field should still be linkable. + var l Local + _ = l.Embed //@hover("Embed", "Embed", Embed) +} +-- @Embed -- +```go +field Embed int64 +``` + +[`(p.E).Embed` on pkg.go.dev](https://pkg.go.dev/mod.com#E.Embed) +-- @F -- +```go +field F int64 // size=8, offset=8 +``` + +@hover("F", "F", F) + + +[`(p.T).F` on pkg.go.dev](https://pkg.go.dev/mod.com#T.F) +-- @Local -- +```go +type Local struct { // size=8 + E +} +``` + +Local types should not be linkable, even if they are capitalized. + + +```go +// Embedded fields: +Embed int64 // through E +``` +-- @Nested -- +```go +field Nested int64 // size=8, offset=0 +``` + +Nested fields should also be linkable. +-- @T -- +```go +type T struct { // size=32 (0x20) + f int64 //@hover("f", "f", f) + F int64 //@hover("F", "F", F) + + E + + // TODO(rfindley): is the link here correct? It ignores N. + N struct { + // Nested fields should also be linkable. + Nested int64 //@hover("Nested", "Nested", Nested) + } +} +``` + +T is in the package scope, and so should be linkable. + + +```go +// Embedded fields: +Embed int64 // through E +``` + +```go +func (T) M() +func (T) m() +``` + +[`p.T` on pkg.go.dev](https://pkg.go.dev/mod.com#T) +-- @X -- +```go +var X int64 +``` + +Local variables should not be linkable, even if they are capitalized. +-- @f -- +```go +field f int64 // size=8, offset=0 +``` + +@hover("f", "f", f) diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/linkable_generics.txt b/contribs/gnopls/internal/test/marker/testdata/hover/linkable_generics.txt new file mode 100644 index 00000000000..0b7ade7965e --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/linkable_generics.txt @@ -0,0 +1,148 @@ +This file contains tests for documentation links to generic code in hover. + +-- go.mod -- +module mod.com + +go 1.19 + +-- a.go -- +package a + +import "mod.com/generic" + +func _() { + // Hovering over instantiated object should produce accurate type + // information, but link to the generic declarations. + + var x generic.GT[int] //@hover("GT", "GT", xGT) + _ = x.F //@hover("x", "x", x),hover("F", "F", xF) + + f := generic.GF[int] //@hover("GF", "GF", fGF) + _ = f //@hover("f", "f", f) +} + +-- generic/generic.go -- +package generic + +// Hovering over type parameters should link to documentation. +// +// TODO(rfindley): should it? We should probably link to the type. +type GT[P any] struct{ //@hover("GT", "GT", GT),hover("P", "P", GTP) + F P //@hover("F", "F", F),hover("P", "P", FP) +} + +func (GT[P]) M(p P) { //@hover("GT", "GT", GTrecv),hover("M","M", M),hover(re"p (P)", re"p (P)", pP) +} + +func GF[P any] (p P) { //@hover("GF", "GF", GF) +} + +-- @F -- +```go +field F P +``` + +@hover("F", "F", F),hover("P", "P", FP) + + +[`(generic.GT).F` on pkg.go.dev](https://pkg.go.dev/mod.com/generic#GT.F) +-- @FP -- +```go +type parameter P any +``` +-- @GF -- +```go +func GF[P any](p P) +``` + +[`generic.GF` on pkg.go.dev](https://pkg.go.dev/mod.com/generic#GF) +-- @GT -- +```go +type GT[P any] struct { + F P //@hover("F", "F", F),hover("P", "P", FP) +} +``` + +Hovering over type parameters should link to documentation. + +TODO(rfindley): should it? We should probably link to the type. + + +```go +func (GT[P]) M(p P) +``` + +[`generic.GT` on pkg.go.dev](https://pkg.go.dev/mod.com/generic#GT) +-- @GTP -- +```go +type parameter P any +``` +-- @GTrecv -- +```go +type GT[P any] struct { + F P //@hover("F", "F", F),hover("P", "P", FP) +} +``` + +Hovering over type parameters should link to documentation. + +TODO(rfindley): should it? We should probably link to the type. + + +```go +func (GT[P]) M(p P) +``` + +[`generic.GT` on pkg.go.dev](https://pkg.go.dev/mod.com/generic#GT) +-- @M -- +```go +func (GT[P]) M(p P) +``` + +[`(generic.GT).M` on pkg.go.dev](https://pkg.go.dev/mod.com/generic#GT.M) +-- @f -- +```go +var f func(p int) +``` +-- @fGF -- +```go +func generic.GF(p int) // func[P any](p P) +``` + +[`generic.GF` on pkg.go.dev](https://pkg.go.dev/mod.com/generic#GF) +-- @pP -- +```go +type parameter P any +``` +-- @x -- +```go +var x generic.GT[int] +``` + +@hover("GT", "GT", xGT) +-- @xF -- +```go +field F int +``` + +@hover("F", "F", F),hover("P", "P", FP) + + +[`(generic.GT).F` on pkg.go.dev](https://pkg.go.dev/mod.com/generic#GT.F) +-- @xGT -- +```go +type GT[P any] struct { + F P //@hover("F", "F", F),hover("P", "P", FP) +} +``` + +Hovering over type parameters should link to documentation. + +TODO(rfindley): should it? We should probably link to the type. + + +```go +func (generic.GT[P]) M(p P) +``` + +[`generic.GT` on pkg.go.dev](https://pkg.go.dev/mod.com/generic#GT) diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/linkname.txt b/contribs/gnopls/internal/test/marker/testdata/hover/linkname.txt new file mode 100644 index 00000000000..8bb2eeb33cd --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/linkname.txt @@ -0,0 +1,30 @@ +This test check hover on the 2nd argument in go:linkname directives. + +-- go.mod -- +module mod.com + +-- upper/upper.go -- +package upper + +import ( + _ "unsafe" + _ "mod.com/lower" +) + +//go:linkname foo mod.com/lower.bar //@hover("mod.com/lower.bar", "mod.com/lower.bar", bar) +func foo() string + +-- lower/lower.go -- +package lower + +// bar does foo. +func bar() string { + return "foo by bar" +} + +-- @bar -- +```go +func bar() string +``` + +bar does foo. diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/methods.txt b/contribs/gnopls/internal/test/marker/testdata/hover/methods.txt new file mode 100644 index 00000000000..8af22494f75 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/methods.txt @@ -0,0 +1,71 @@ +This test checks the formatting of the list of accessible methods. + +Observe that: +- interface methods that appear in the syntax are not repeated + in the method set of the type; +- promoted methods of structs are shown; +- receiver variables are correctly named; +- receiver variables have a pointer type if appropriate; +- only accessible methods are shown. + +-- go.mod -- +module example.com + +-- lib/lib.go -- +package lib + +type I interface { + A() + b() + J +} + +type J interface { C() } + +type S struct { I } +func (s S) A() {} +func (s S) b() {} +func (s *S) PA() {} +func (s *S) pb() {} + +-- a/a.go -- +package a + +import "example.com/lib" + +var _ lib.I //@hover("I", "I", I) +var _ lib.J //@hover("J", "J", J) +var _ lib.S //@hover("S", "S", S) + +-- @I -- +```go +type I interface { + A() + b() + J +} +``` + +```go +func (lib.J) C() +``` + +[`lib.I` on pkg.go.dev](https://pkg.go.dev/example.com/lib#I) +-- @J -- +```go +type J interface{ C() } +``` + +[`lib.J` on pkg.go.dev](https://pkg.go.dev/example.com/lib#J) +-- @S -- +```go +type S struct{ I } +``` + +```go +func (s lib.S) A() +func (lib.J) C() +func (s *lib.S) PA() +``` + +[`lib.S` on pkg.go.dev](https://pkg.go.dev/example.com/lib#S) diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/sizeoffset.txt b/contribs/gnopls/internal/test/marker/testdata/hover/sizeoffset.txt new file mode 100644 index 00000000000..62f3b76dd60 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/sizeoffset.txt @@ -0,0 +1,117 @@ +This test checks that hover reports the sizes of vars/types, +and the offsets of struct fields. + +Notes: +- this only works on the declaring identifier, not on refs. +- the size of a type is undefined if it depends on type parameters. +- the offset of a field is undefined if it or any preceding field + has undefined size/alignment. +- the test's size expectations assumes a 64-bit machine. +- requires go1.22 because size information was inaccurate before. + +-- flags -- +-skip_goarch=386,arm +-min_go=go1.22 + +-- go.mod -- +module example.com + +go 1.18 +-- a.go -- +package a + +type T struct { //@ hover("T", "T", T) + a int //@ hover("a", "a", a) + U U //@ hover("U", "U", U) + y, z int //@ hover("y", "y", y), hover("z", "z", z) +} + +type U struct { + slice []string +} + +type G[T any] struct { + p T //@ hover("p", "p", p) + q int //@ hover("q", "q", q) +} + +var _ struct { + Gint G[int] //@ hover("Gint", "Gint", Gint) + Gstring G[string] //@ hover("Gstring", "Gstring", Gstring) +} + +type wasteful struct { //@ hover("wasteful", "wasteful", wasteful) + a bool + b [2]string + c bool +} + +-- @T -- +```go +type T struct { // size=48 (0x30) + a int //@ hover("a", "a", a) + U U //@ hover("U", "U", U) + y, z int //@ hover("y", "y", y), hover("z", "z", z) +} +``` + +[`a.T` on pkg.go.dev](https://pkg.go.dev/example.com#T) +-- @wasteful -- +```go +type wasteful struct { // size=48 (0x30) (29% wasted) + a bool + b [2]string + c bool +} +``` +-- @a -- +```go +field a int // size=8, offset=0 +``` + +@ hover("a", "a", a) +-- @U -- +```go +field U U // size=24 (0x18), offset=8 +``` + +@ hover("U", "U", U) + + +[`(a.T).U` on pkg.go.dev](https://pkg.go.dev/example.com#T.U) +-- @y -- +```go +field y int // size=8, offset=32 (0x20) +``` + +@ hover("y", "y", y), hover("z", "z", z) +-- @z -- +```go +field z int // size=8, offset=40 (0x28) +``` + +@ hover("y", "y", y), hover("z", "z", z) +-- @p -- +```go +field p T +``` + +@ hover("p", "p", p) +-- @q -- +```go +field q int // size=8 +``` + +@ hover("q", "q", q) +-- @Gint -- +```go +field Gint G[int] // size=16 (0x10), offset=0 +``` + +@ hover("Gint", "Gint", Gint) +-- @Gstring -- +```go +field Gstring G[string] // size=24 (0x18), offset=16 (0x10) +``` + +@ hover("Gstring", "Gstring", Gstring) diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/std.txt b/contribs/gnopls/internal/test/marker/testdata/hover/std.txt new file mode 100644 index 00000000000..c0db135f6b1 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/std.txt @@ -0,0 +1,83 @@ +This test checks hover results for built-in or standard library symbols. + +It uses synopsis documentation as full documentation for some of these +built-ins varies across Go versions, where as it just so happens that the +synopsis does not. + +In the future we may need to limit this test to the latest Go version to avoid +documentation churn. + +-- settings.json -- +{ + "hoverKind": "SynopsisDocumentation" +} + +-- go.mod -- +module mod.com + +go 1.18 + +-- std.go -- +package std + +import ( + "fmt" + "go/types" + "sync" +) + +func _() { + var err error //@loc(err, "err") + fmt.Printf("%v", err) //@def("err", err) + + var _ string //@hover("string", "string", hoverstring) + _ = make([]int, 0) //@hover("make", "make", hovermake) + + var mu sync.Mutex + mu.Lock() //@hover("Lock", "Lock", hoverLock) + + var typ *types.Named //@hover("types", "types", hoverTypes) + typ.Obj().Name() //@hover("Name", "Name", hoverName) +} +-- @hoverLock -- +```go +func (m *sync.Mutex) Lock() +``` + +Lock locks m. + + +[`(sync.Mutex).Lock` on pkg.go.dev](https://pkg.go.dev/sync#Mutex.Lock) +-- @hoverName -- +```go +func (obj *types.object) Name() string +``` + +Name returns the object's (package-local, unqualified) name. + + +[`(types.TypeName).Name` on pkg.go.dev](https://pkg.go.dev/go/types#TypeName.Name) +-- @hoverTypes -- +```go +package types ("go/types") +``` + +[`types` on pkg.go.dev](https://pkg.go.dev/go/types) +-- @hovermake -- +```go +func make(t Type, size ...int) Type +``` + +The make built-in function allocates and initializes an object of type slice, map, or chan (only). + + +[`make` on pkg.go.dev](https://pkg.go.dev/builtin#make) +-- @hoverstring -- +```go +type string string +``` + +string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. + + +[`string` on pkg.go.dev](https://pkg.go.dev/builtin#string) diff --git a/contribs/gnopls/internal/test/marker/testdata/hover/structfield.txt b/contribs/gnopls/internal/test/marker/testdata/hover/structfield.txt new file mode 100644 index 00000000000..82115f7908d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/hover/structfield.txt @@ -0,0 +1,29 @@ +This test checks that the complete struct field is +shown on hover (including struct tags and comments). + +-- go.mod -- +module example.com + +-- lib/lib.go -- +package lib + +type Something struct { + // Field with a tag + Field int `json:"field"` +} + +func DoSomething() Something { + var s Something + s.Field = 42 //@hover("i", "Field", field) + return s +} + +-- @field -- +```go +field Field int `json:"field"` +``` + +Field with a tag + + +[`(lib.Something).Field` on pkg.go.dev](https://pkg.go.dev/example.com/lib#Something.Field) diff --git a/contribs/gnopls/internal/test/marker/testdata/implementation/basic.txt b/contribs/gnopls/internal/test/marker/testdata/implementation/basic.txt new file mode 100644 index 00000000000..3f63a5d00c1 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/implementation/basic.txt @@ -0,0 +1,73 @@ +Basic test of implementation query. + +-- go.mod -- +module example.com +go 1.12 + +-- implementation/implementation.go -- +package implementation + +import "example.com/other" + +type ImpP struct{} //@loc(ImpP, "ImpP"),implementation("ImpP", Laugher, OtherLaugher) + +func (*ImpP) Laugh() { //@loc(LaughP, "Laugh"),implementation("Laugh", Laugh, OtherLaugh) +} + +type ImpS struct{} //@loc(ImpS, "ImpS"),implementation("ImpS", Laugher, OtherLaugher) + +func (ImpS) Laugh() { //@loc(LaughS, "Laugh"),implementation("Laugh", Laugh, OtherLaugh) +} + +type Laugher interface { //@loc(Laugher, "Laugher"),implementation("Laugher", ImpP, OtherImpP, ImpS, OtherImpS, embedsImpP) + Laugh() //@loc(Laugh, "Laugh"),implementation("Laugh", LaughP, OtherLaughP, LaughS, OtherLaughS) +} + +type Foo struct { //@implementation("Foo", Joker) + other.Foo +} + +type Joker interface { //@loc(Joker, "Joker") + Joke() //@loc(Joke, "Joke"),implementation("Joke", ImpJoker) +} + +type cryer int //@implementation("cryer", Cryer) + +func (cryer) Cry(other.CryType) {} //@loc(CryImpl, "Cry"),implementation("Cry", Cry) + +type Empty interface{} //@implementation("Empty") + +var _ interface{ Joke() } //@implementation("Joke", ImpJoker) + +type embedsImpP struct { //@loc(embedsImpP, "embedsImpP") + ImpP //@implementation("ImpP", Laugher, OtherLaugher) +} + +-- other/other.go -- +package other + +type ImpP struct{} //@loc(OtherImpP, "ImpP") + +func (*ImpP) Laugh() { //@loc(OtherLaughP, "Laugh") +} + +type ImpS struct{} //@loc(OtherImpS, "ImpS") + +func (ImpS) Laugh() { //@loc(OtherLaughS, "Laugh") +} + +type ImpI interface { //@loc(OtherLaugher, "ImpI") + Laugh() //@loc(OtherLaugh, "Laugh") +} + +type Foo struct { //@implementation("Foo", Joker) +} + +func (Foo) Joke() { //@loc(ImpJoker, "Joke"),implementation("Joke", Joke) +} + +type CryType int + +type Cryer interface { //@loc(Cryer, "Cryer") + Cry(CryType) //@loc(Cry, "Cry"),implementation("Cry", CryImpl) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/implementation/generics.txt b/contribs/gnopls/internal/test/marker/testdata/implementation/generics.txt new file mode 100644 index 00000000000..4a6c31b22f8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/implementation/generics.txt @@ -0,0 +1,31 @@ +Test of 'implementation' query on generic types. + +-- go.mod -- +module example.com +go 1.18 + +-- implementation/implementation.go -- +package implementation + +type GenIface[T any] interface { //@loc(GenIface, "GenIface"),implementation("GenIface", GC) + F(int, string, T) //@loc(GenIfaceF, "F"),implementation("F", GCF) +} + +type GenConc[U any] int //@loc(GenConc, "GenConc"),implementation("GenConc", GI) + +func (GenConc[V]) F(int, string, V) {} //@loc(GenConcF, "F"),implementation("F", GIF) + +type GenConcString struct{ GenConc[string] } //@loc(GenConcString, "GenConcString"),implementation(GenConcString, GIString) + +-- other/other.go -- +package other + +type GI[T any] interface { //@loc(GI, "GI"),implementation("GI", GenConc) + F(int, string, T) //@loc(GIF, "F"),implementation("F", GenConcF) +} + +type GIString GI[string] //@loc(GIString, "GIString"),implementation("GIString", GenConcString) + +type GC[U any] int //@loc(GC, "GC"),implementation("GC", GenIface) + +func (GC[V]) F(int, string, V) {} //@loc(GCF, "F"),implementation("F", GenIfaceF) diff --git a/contribs/gnopls/internal/test/marker/testdata/implementation/issue43655.txt b/contribs/gnopls/internal/test/marker/testdata/implementation/issue43655.txt new file mode 100644 index 00000000000..a7f1d57f80d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/implementation/issue43655.txt @@ -0,0 +1,22 @@ +This test verifies that we fine implementations of the built-in error interface. + +-- go.mod -- +module example.com +go 1.12 + +-- p.go -- +package p + +type errA struct{ error } //@loc(errA, "errA") + +type errB struct{} //@loc(errB, "errB") +func (errB) Error() string{ return "" } //@loc(errBError, "Error") + +type notAnError struct{} +func (notAnError) Error() int { return 0 } + +func _() { + var _ error //@implementation("error", errA, errB) + var a errA + _ = a.Error //@implementation("Error", errBError) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/implementation/issue67041.txt b/contribs/gnopls/internal/test/marker/testdata/implementation/issue67041.txt new file mode 100644 index 00000000000..3b058534cd3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/implementation/issue67041.txt @@ -0,0 +1,37 @@ +This test verifies that implementations uses the correct object when querying +local implementations . As described in golang/go#67041), a bug led to it +comparing types from different realms. + +-- go.mod -- +module example.com + +go 1.18 + +-- a/a.go -- +package a + +type A struct{} + +type Aer interface { //@loc(Aer, "Aer") + GetA() A +} + +type X struct{} //@loc(X, "X") + +func (X) GetA() A + +-- a/a_test.go -- +package a + +// Verify that we also find implementations in a test variant. +type Y struct{} //@loc(Y, "Y") + +func (Y) GetA() A +-- b/b.go -- +package b + +import "example.com/a" + +var _ a.X //@implementation("X", Aer) + +var _ a.Aer //@implementation("Aer", X, Y) diff --git a/contribs/gnopls/internal/test/marker/testdata/inlayhints/inlayhints.txt b/contribs/gnopls/internal/test/marker/testdata/inlayhints/inlayhints.txt new file mode 100644 index 00000000000..e690df72c1c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/inlayhints/inlayhints.txt @@ -0,0 +1,405 @@ + +-- flags -- +-ignore_extra_diags + +-- settings.json -- +{ + "hints": { + "assignVariableTypes": true, + "compositeLiteralFields": true, + "compositeLiteralTypes": true, + "constantValues": true, + "functionTypeParameters": true, + "parameterNames": true, + "rangeVariabletypes": true + } +} + +-- composite_literals.go -- +package inlayHint //@inlayhints(complit) + +import "fmt" + +func fieldNames() { + for _, c := range []struct { + in, want string + }{ + struct{ in, want string }{"Hello, world", "dlrow ,olleH"}, + {"Hello, 世界", "界世 ,olleH"}, + {"", ""}, + } { + fmt.Println(c.in == c.want) + } +} + +func fieldNamesPointers() { + for _, c := range []*struct { + in, want string + }{ + &struct{ in, want string }{"Hello, world", "dlrow ,olleH"}, + {"Hello, 世界", "界世 ,olleH"}, + {"", ""}, + } { + fmt.Println(c.in == c.want) + } +} + +-- @complit -- +package inlayHint //@inlayhints(complit) + +import "fmt" + +func fieldNames() { + for _, c := range []struct { + in, want string + }{ + struct{ in, want string }{<in: >"Hello, world", <want: >"dlrow ,olleH"}, + <struct{in string; want string}>{<in: >"Hello, 世界", <want: >"界世 ,olleH"}, + <struct{in string; want string}>{<in: >"", <want: >""}, + } { + fmt.Println(<a...: >c.in == c.want) + } +} + +func fieldNamesPointers() { + for _, c := range []*struct { + in, want string + }{ + &struct{ in, want string }{<in: >"Hello, world", <want: >"dlrow ,olleH"}, + <&struct{in string; want string}>{<in: >"Hello, 世界", <want: >"界世 ,olleH"}, + <&struct{in string; want string}>{<in: >"", <want: >""}, + } { + fmt.Println(<a...: >c.in == c.want) + } +} + +-- constant_values.go -- +package inlayHint //@inlayhints(values) + +const True = true + +type Kind int + +const ( + KindNone Kind = iota + KindPrint + KindPrintf + KindErrorf +) + +const ( + u = iota * 4 + v float64 = iota * 42 + w = iota * 42 +) + +const ( + a, b = 1, 2 + c, d + e, f = 5 * 5, "hello" + "world" + g, h + i, j = true, f +) + +// No hint +const ( + Int = 3 + Float = 3.14 + Bool = true + Rune = '3' + Complex = 2.7i + String = "Hello, world!" +) + +var ( + varInt = 3 + varFloat = 3.14 + varBool = true + varRune = '3' + '4' + varComplex = 2.7i + varString = "Hello, world!" +) + +-- @values -- +package inlayHint //@inlayhints(values) + +const True = true + +type Kind int + +const ( + KindNone Kind = iota< = 0> + KindPrint< = 1> + KindPrintf< = 2> + KindErrorf< = 3> +) + +const ( + u = iota * 4< = 0> + v float64 = iota * 42< = 42> + w = iota * 42< = 84> +) + +const ( + a, b = 1, 2 + c, d< = 1, 2> + e, f = 5 * 5, "hello" + "world"< = 25, "helloworld"> + g, h< = 25, "helloworld"> + i, j = true, f< = true, "helloworld"> +) + +// No hint +const ( + Int = 3 + Float = 3.14 + Bool = true + Rune = '3' + Complex = 2.7i + String = "Hello, world!" +) + +var ( + varInt = 3 + varFloat = 3.14 + varBool = true + varRune = '3' + '4' + varComplex = 2.7i + varString = "Hello, world!" +) + +-- parameter_names.go -- +package inlayHint //@inlayhints(parameters) + +import "fmt" + +func hello(name string) string { + return "Hello " + name +} + +func helloWorld() string { + return hello("World") +} + +type foo struct{} + +func (*foo) bar(baz string, qux int) int { + if baz != "" { + return qux + 1 + } + return qux +} + +func kase(foo int, bar bool, baz ...string) { + fmt.Println(foo, bar, baz) +} + +func kipp(foo string, bar, baz string) { + fmt.Println(foo, bar, baz) +} + +func plex(foo, bar string, baz string) { + fmt.Println(foo, bar, baz) +} + +func tars(foo string, bar, baz string) { + fmt.Println(foo, bar, baz) +} + +func foobar() { + var x foo + x.bar("", 1) + kase(0, true, "c", "d", "e") + kipp("a", "b", "c") + plex("a", "b", "c") + tars("a", "b", "c") + foo, bar, baz := "a", "b", "c" + kipp(foo, bar, baz) + plex("a", bar, baz) + tars(foo+foo, (bar), "c") + +} + +-- @parameters -- +package inlayHint //@inlayhints(parameters) + +import "fmt" + +func hello(name string) string { + return "Hello " + name +} + +func helloWorld() string { + return hello(<name: >"World") +} + +type foo struct{} + +func (*foo) bar(baz string, qux int) int { + if baz != "" { + return qux + 1 + } + return qux +} + +func kase(foo int, bar bool, baz ...string) { + fmt.Println(<a...: >foo, bar, baz) +} + +func kipp(foo string, bar, baz string) { + fmt.Println(<a...: >foo, bar, baz) +} + +func plex(foo, bar string, baz string) { + fmt.Println(<a...: >foo, bar, baz) +} + +func tars(foo string, bar, baz string) { + fmt.Println(<a...: >foo, bar, baz) +} + +func foobar() { + var x foo + x.bar(<baz: >"", <qux: >1) + kase(<foo: >0, <bar: >true, <baz...: >"c", "d", "e") + kipp(<foo: >"a", <bar: >"b", <baz: >"c") + plex(<foo: >"a", <bar: >"b", <baz: >"c") + tars(<foo: >"a", <bar: >"b", <baz: >"c") + foo< string>, bar< string>, baz< string> := "a", "b", "c" + kipp(foo, bar, baz) + plex(<foo: >"a", bar, baz) + tars(<foo: >foo+foo, <bar: >(bar), <baz: >"c") + +} + +-- type_params.go -- +package inlayHint //@inlayhints(typeparams) + +func main() { + ints := map[string]int64{ + "first": 34, + "second": 12, + } + + floats := map[string]float64{ + "first": 35.98, + "second": 26.99, + } + + SumIntsOrFloats[string, int64](ints) + SumIntsOrFloats[string, float64](floats) + + SumIntsOrFloats(ints) + SumIntsOrFloats(floats) + + SumNumbers(ints) + SumNumbers(floats) +} + +type Number interface { + int64 | float64 +} + +func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V { + var s V + for _, v := range m { + s += v + } + return s +} + +func SumNumbers[K comparable, V Number](m map[K]V) V { + var s V + for _, v := range m { + s += v + } + return s +} + +-- @typeparams -- +package inlayHint //@inlayhints(typeparams) + +func main() { + ints< map[string]int64> := map[string]int64{ + "first": 34, + "second": 12, + } + + floats< map[string]float64> := map[string]float64{ + "first": 35.98, + "second": 26.99, + } + + SumIntsOrFloats[string, int64](<m: >ints) + SumIntsOrFloats[string, float64](<m: >floats) + + SumIntsOrFloats<[string, int64]>(<m: >ints) + SumIntsOrFloats<[string, float64]>(<m: >floats) + + SumNumbers<[string, int64]>(<m: >ints) + SumNumbers<[string, float64]>(<m: >floats) +} + +type Number interface { + int64 | float64 +} + +func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V { + var s V + for _, v := range m { + s += v + } + return s +} + +func SumNumbers[K comparable, V Number](m map[K]V) V { + var s V + for _, v := range m { + s += v + } + return s +} + +-- variable_types.go -- +package inlayHint //@inlayhints(vartypes) + +func assignTypes() { + i, j := 0, len([]string{})-1 + println(i, j) +} + +func rangeTypes() { + for k, v := range []string{} { + println(k, v) + } +} + +func funcLitType() { + myFunc := func(a string) string { return "" } +} + +func compositeLitType() { + foo := map[string]interface{}{"": ""} +} + +-- @vartypes -- +package inlayHint //@inlayhints(vartypes) + +func assignTypes() { + i< int>, j< int> := 0, len([]string{})-1 + println(i, j) +} + +func rangeTypes() { + for k, v := range []string{} { + println(k, v) + } +} + +func funcLitType() { + myFunc< func(a string) string> := func(a string) string { return "" } +} + +func compositeLitType() { + foo< map[string]interface{}> := map[string]interface{}{"": ""} +} + diff --git a/contribs/gnopls/internal/test/marker/testdata/inlayhints/issue67142.txt b/contribs/gnopls/internal/test/marker/testdata/inlayhints/issue67142.txt new file mode 100644 index 00000000000..18e98e81acb --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/inlayhints/issue67142.txt @@ -0,0 +1,36 @@ +Regression test for golang/go#67142. + +-- flags -- +-ignore_extra_diags +-min_go=go1.21 + +-- settings.json -- +{ + "hints": { + "assignVariableTypes": true, + "compositeLiteralFields": true, + "compositeLiteralTypes": true, + "constantValues": true, + "functionTypeParameters": true, + "parameterNames": true, + "rangeVariabletypes": true + } +} + +-- go.mod -- +module w + +go 1.21.9 + +-- p.go -- +//@inlayhints(out) +package p + +var _ = rand.Float64() + +-- @out -- +//@inlayhints(out) +package p + +var _ = rand.Float64() + diff --git a/contribs/gnopls/internal/test/marker/testdata/links/links.txt b/contribs/gnopls/internal/test/marker/testdata/links/links.txt new file mode 100644 index 00000000000..19ebcb4cb1a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/links/links.txt @@ -0,0 +1,47 @@ +This test verifies behavior of textDocument/documentLink. + +-- go.mod -- +module golang.org/lsptests + +go 1.18 +-- foo/foo.go -- +package foo + +type StructFoo struct {} + +-- links/links.go -- +package links //@documentlink(links) + +import ( + "fmt" + + "golang.org/lsptests/foo" + + _ "database/sql" +) + +var ( + _ fmt.Formatter + _ foo.StructFoo + _ errors.Formatter //@diag("errors", re"(undeclared|undefined)") +) + +// Foo function +func Foo() string { + /*https://example.com/comment */ + + url := "https://example.com/string_literal" + return url + + // TODO(golang/go#1234): Link the relevant issue. + // TODO(microsoft/vscode-go#12): Another issue. +} + +-- @links -- +links/links.go:4:3-6 https://pkg.go.dev/fmt +links/links.go:6:3-26 https://pkg.go.dev/golang.org/lsptests/foo +links/links.go:8:5-17 https://pkg.go.dev/database/sql +links/links.go:21:10-44 https://example.com/string_literal +links/links.go:19:4-31 https://example.com/comment +links/links.go:24:10-24 https://github.com/golang/go/issues/1234 +links/links.go:25:10-32 https://github.com/microsoft/vscode-go/issues/12 diff --git a/contribs/gnopls/internal/test/marker/testdata/modfile/godebug.txt b/contribs/gnopls/internal/test/marker/testdata/modfile/godebug.txt new file mode 100644 index 00000000000..dbee5faae01 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/modfile/godebug.txt @@ -0,0 +1,43 @@ +This test basic gopls functionality in a workspace with a godebug +directive in its modfile. + +-- flags -- +-min_go=go1.23 + +-- go.mod -- +module example.com/m + +go 1.23 + +godebug ( + gotypesalias=0 +) +godebug gotypesalias=1 + +-- a/a.go -- +package a + +import "example.com/m/b" + +const A = b.B //@def("B", B) + +-- b/b.go -- +package b + +const B = 42 //@loc(B, "B") + +-- format/go.mod -- +module example.com/m/format //@format(formatted) + +godebug ( +gotypesalias=0 +) +godebug gotypesalias=1 +-- @formatted -- +module example.com/m/format //@format(formatted) + +godebug ( + gotypesalias=0 +) + +godebug gotypesalias=1 diff --git a/contribs/gnopls/internal/test/marker/testdata/modfile/godebug_bad.txt b/contribs/gnopls/internal/test/marker/testdata/modfile/godebug_bad.txt new file mode 100644 index 00000000000..1d06c7cf73c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/modfile/godebug_bad.txt @@ -0,0 +1,17 @@ +This test checks that we surface the error for unexpected godebug values. + +TODO(golang/go#67623): the diagnostic should be on the bad godebug value. + +-- flags -- +-min_go=go1.23 +-errors_ok + +-- go.mod -- +module example.com/m //@diag("module", re`unknown godebug "gotypealias"`) + +go 1.23 + +godebug ( + gotypealias=0 // misspelled +) +godebug gotypesalias=1 diff --git a/contribs/gnopls/internal/test/marker/testdata/references/crosspackage.txt b/contribs/gnopls/internal/test/marker/testdata/references/crosspackage.txt new file mode 100644 index 00000000000..bac330b9369 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/crosspackage.txt @@ -0,0 +1,37 @@ +Test of basic cross-package references. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type X struct { + Y int //@loc(typeXY, "Y") +} + +-- b/b.go -- +package b + +import "example.com/a" + +func GetXes() []a.X { + return []a.X{ + { + Y: 1, //@loc(GetXesY, "Y"), refs("Y", typeXY, GetXesY, anotherXY) + }, + } +} + +-- c/c.go -- +package c + +import "example.com/b" + +func _() { + xes := b.GetXes() + for _, x := range xes { //@loc(defX, "x") + _ = x.Y //@loc(useX, "x"), loc(anotherXY, "Y"), refs("Y", typeXY, anotherXY, GetXesY), refs(".", defX, useX), refs("x", defX, useX) + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/references/imports.txt b/contribs/gnopls/internal/test/marker/testdata/references/imports.txt new file mode 100644 index 00000000000..ae9b207fa1d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/imports.txt @@ -0,0 +1,17 @@ +Test of references to local package names (imports). + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +import "os" //@loc(osDef, `"os"`), refs("os", osDef, osUse) + +import fmt2 "fmt" //@loc(fmt2Def, `fmt2`), refs("fmt2", fmt2Def, fmt2Use) + +func _() { + os.Getwd() //@loc(osUse, "os") + fmt2.Println() //@loc(fmt2Use, "fmt2") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/references/interfaces.txt b/contribs/gnopls/internal/test/marker/testdata/references/interfaces.txt new file mode 100644 index 00000000000..c25cf4fee3b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/interfaces.txt @@ -0,0 +1,42 @@ +Test of references applied to concrete and interface types that are +related by assignability. The result includes references to both. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type first interface { + common() //@loc(firCommon, "common"), refs("common", firCommon, xCommon, zCommon) + firstMethod() //@loc(firMethod, "firstMethod"), refs("firstMethod", firMethod, xfMethod, zfMethod) +} + +type second interface { + common() //@loc(secCommon, "common"), refs("common", secCommon, yCommon, zCommon) + secondMethod() //@loc(secMethod, "secondMethod"), refs("secondMethod", secMethod, ysMethod, zsMethod) +} + +type s struct {} + +func (*s) common() {} //@loc(sCommon, "common"), refs("common", sCommon, xCommon, yCommon, zCommon) + +func (*s) firstMethod() {} //@loc(sfMethod, "firstMethod"), refs("firstMethod", sfMethod, xfMethod, zfMethod) + +func (*s) secondMethod() {} //@loc(ssMethod, "secondMethod"), refs("secondMethod", ssMethod, ysMethod, zsMethod) + +func main() { + var x first = &s{} + var y second = &s{} + + x.common() //@loc(xCommon, "common"), refs("common", firCommon, xCommon, zCommon) + x.firstMethod() //@loc(xfMethod, "firstMethod"), refs("firstMethod", firMethod, xfMethod, zfMethod) + y.common() //@loc(yCommon, "common"), refs("common", secCommon, yCommon, zCommon) + y.secondMethod() //@loc(ysMethod, "secondMethod"), refs("secondMethod", secMethod, ysMethod, zsMethod) + + var z *s = &s{} + z.firstMethod() //@loc(zfMethod, "firstMethod"), refs("firstMethod", sfMethod, xfMethod, zfMethod) + z.secondMethod() //@loc(zsMethod, "secondMethod"), refs("secondMethod", ssMethod, ysMethod, zsMethod) + z.common() //@loc(zCommon, "common"), refs("common", sCommon, xCommon, yCommon, zCommon) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/references/intrapackage.txt b/contribs/gnopls/internal/test/marker/testdata/references/intrapackage.txt new file mode 100644 index 00000000000..ea95468c85c --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/intrapackage.txt @@ -0,0 +1,36 @@ +Basic test of references within a single package. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type i int //@loc(decli, "i"), refs("i", decli, argi, returni, embeddedi) + +func _(_ i) []bool { //@loc(argi, "i") + return nil +} + +func _(_ []byte) i { //@loc(returni, "i") + return 0 +} + +var q string //@loc(declq, "q"), refs("q", declq, assignq, bobq) + +var Q string //@loc(declQ, "Q"), refs("Q", declQ) + +func _() { + q = "hello" //@loc(assignq, "q") + bob := func(_ string) {} + bob(q) //@loc(bobq, "q") +} + +type e struct { + i //@loc(embeddedi, "i"), refs("i", embeddedi, embeddediref) +} + +func _() { + _ = e{}.i //@loc(embeddediref, "i") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/references/issue58506.txt b/contribs/gnopls/internal/test/marker/testdata/references/issue58506.txt new file mode 100644 index 00000000000..6285ad425a8 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/issue58506.txt @@ -0,0 +1,56 @@ +Regression test for 'references' bug golang/go#58506. + +The 'references' query below, applied to method A.F, implicitly uses +the 'implementation' operation. The correct response includes two +references to B.F, one from package b and one from package d. +However, the incremental 'implementation' algorithm had a bug that +cause it to fail to report the reference from package b. + +The reason was that the incremental implementation uses different +algorithms for the local and global cases (with disjoint results), and +that when it discovered that type A satisfies interface B and thus +that B.F must be included among the global search targets, the +implementation forgot to also search package b for local references +to B.F. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type A int + +func (A) F() {} //@loc(refa, "F"), refs("F", refa, refb, refd) + +-- b/b.go -- +package b + +import ( + "example.com/a" + "example.com/c" +) + +type B interface{ F() } + +var _ B = a.A(0) +var _ B = c.C(0) + +var _ = B.F //@loc(refb, "F") + +-- c/c.go -- +package c + +type C int + +// Even though C.F is "rename coupled" to A.F by B.F, +// it should not be among the results. +func (C) F() {} + +-- d/d.go -- +package d + +import "example.com/b" + +var _ interface{} = b.B.F //@loc(refd, "F") diff --git a/contribs/gnopls/internal/test/marker/testdata/references/issue59851.txt b/contribs/gnopls/internal/test/marker/testdata/references/issue59851.txt new file mode 100644 index 00000000000..86a6359a000 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/issue59851.txt @@ -0,0 +1,29 @@ +Regression test for 'references' bug golang/go#59851. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type Iface interface { + Method() +} + +type implOne struct{} + +func (implOne) Method() {} //@loc(def1, "Method"), refs(def1, def1, ref1, iref, ireftest) + +var _ = implOne.Method //@loc(ref1, "Method") +var _ = Iface(nil).Method //@loc(iref, "Method") + +-- a/a_test.go -- +package a + +type implTwo struct{} + +func (implTwo) Method() {} //@loc(def2, "Method"), refs(def2, def2, iref, ref2, ireftest) + +var _ = implTwo.Method //@loc(ref2, "Method") +var _ = Iface(nil).Method //@loc(ireftest, "Method") diff --git a/contribs/gnopls/internal/test/marker/testdata/references/issue60369.txt b/contribs/gnopls/internal/test/marker/testdata/references/issue60369.txt new file mode 100644 index 00000000000..0d868de8a15 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/issue60369.txt @@ -0,0 +1,29 @@ +Regression test for 'references' bug golang/go#60369: a references +query on the embedded type name T in struct{p.T} instead reports all +references to the package name p. + +The bug was fixed in release go1.21 of go/types. + +-- flags -- +-min_go=go1.21 + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type A struct{} +const C = 0 + +-- b/b.go -- +package b + +import a "example.com/a" //@loc(adef, "a") +type s struct { + a.A //@loc(Aref1, "A"), loc(aref1, "a"), refs(Aref1, Aref1, Aref3), refs(aref1, adef, aref1, aref2, aref3) +} +var _ a.A //@loc(aref2, re" (a)"), loc(Aref2, "A") +var _ = s{}.A //@loc(Aref3, "A") +const c = a.C //@loc(aref3, "a") diff --git a/contribs/gnopls/internal/test/marker/testdata/references/issue60622.txt b/contribs/gnopls/internal/test/marker/testdata/references/issue60622.txt new file mode 100644 index 00000000000..45d7ec58023 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/issue60622.txt @@ -0,0 +1,22 @@ +Regression test for 'references' bug golang/go#60622: +references to methods of generics were missing. + +-- go.mod -- +module example.com +go 1.18 + +-- a/a.go -- +package a + +type G[T any] struct{} + +func (G[T]) M() {} //@loc(Mdef, "M"), refs(Mdef, Mdef, Mref) + +-- b/b.go -- +package b + +import "example.com/a" + +func _() { + new(a.G[int]).M() //@loc(Mref, "M") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/references/issue60676.txt b/contribs/gnopls/internal/test/marker/testdata/references/issue60676.txt new file mode 100644 index 00000000000..5cef978927f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/issue60676.txt @@ -0,0 +1,68 @@ +This test verifies that even after importing from export data, the references +algorithm is able to find all references to struct fields or methods that are +shared by types from multiple packages. See golang/go#60676. + +Note that the marker test runner awaits the initial workspace load, so export +data should be populated at the time references are requested. + +-- go.mod -- +module mod.test + +go 1.18 + +-- a/a.go -- +package a + +type A struct { + F int //@loc(FDef, "F") + E //@loc(EDef, "E") +} + +type E struct { + G string //@loc(GDef, "G") +} + +type AI interface { + M() //@loc(MDef, "M") + EI + error +} + +type EI interface { + N() //@loc(NDef, "N") +} + +type T[P any] struct{ f P } + +type Error error + + +-- b/b.go -- +package b + +import "mod.test/a" + +type B a.A + +type BI a.AI + +type T a.T[int] // must not panic + +-- c/c.go -- +package c + +import "mod.test/b" + +func _() { + x := b.B{ + F: 42, //@refs("F", FDef, "F", Fuse) + } + x.G = "hi" //@refs("G", GDef, "G") + _ = x.E //@refs("E", EDef, "E") + _ = x.F //@loc(Fuse, "F") +} + +func _(y b.BI) { + _ = y.M //@refs("M", MDef, "M") + _ = y.N //@refs("N", NDef, "N") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/references/issue61618.txt b/contribs/gnopls/internal/test/marker/testdata/references/issue61618.txt new file mode 100644 index 00000000000..47dc02ef793 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/issue61618.txt @@ -0,0 +1,36 @@ +Regression test for 'references' bug golang/go#61618: +references to instantiated fields were missing. + +-- go.mod -- +module example.com +go 1.18 + +-- a.go -- +package a + +// This file is adapted from the example in the issue. + +type builder[S ~[]F, F ~string] struct { + name string + elements S //@loc(def, "elements"), refs(def, def, assign, use) + elemData map[F][]ElemData[F] +} + +type ElemData[F ~string] struct { + Name F +} + +type BuilderImpl[S ~[]F, F ~string] struct{ builder[S, F] } + +func NewBuilderImpl[S ~[]F, F ~string](name string) *BuilderImpl[S, F] { + impl := &BuilderImpl[S,F]{ + builder[S, F]{ + name: name, + elements: S{}, //@loc(assign, "elements"), refs(assign, def, assign, use) + elemData: map[F][]ElemData[F]{}, + }, + } + + _ = impl.elements //@loc(use, "elements"), refs(use, def, assign, use) + return impl +} diff --git a/contribs/gnopls/internal/test/marker/testdata/references/issue67978.txt b/contribs/gnopls/internal/test/marker/testdata/references/issue67978.txt new file mode 100644 index 00000000000..c214116e74d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/issue67978.txt @@ -0,0 +1,18 @@ + +This test exercises a references query on an exported method that +conflicts with a field name. This ill-typed input violates the +assumption that if type T has a method, then the method set of T is +nonempty, which led to a crash. + +See https://github.com/golang/go/issues/67978. + +-- a.go -- +package p + +type E struct { X int } //@ diag(re"()X", re"field.*same name") + +func (E) X() {} //@ loc(a, "X"), refs("X", a, b), diag(re"()X", re"method.*same name") + +var _ = new(E).X //@ loc(b, "X") + + diff --git a/contribs/gnopls/internal/test/marker/testdata/references/shadow.txt b/contribs/gnopls/internal/test/marker/testdata/references/shadow.txt new file mode 100644 index 00000000000..66819355431 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/shadow.txt @@ -0,0 +1,17 @@ +Test of references in the presence of shadowing. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +func _() { + x := 123 //@loc(x1, "x"), refs("x", x1, x1ref) + _ = x //@loc(x1ref, "x") + { + x := "hi" //@loc(x2, "x"), refs("x", x2, x2ref) + _ = x //@loc(x2ref, "x") + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/references/test.txt b/contribs/gnopls/internal/test/marker/testdata/references/test.txt new file mode 100644 index 00000000000..ec7f189a962 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/test.txt @@ -0,0 +1,29 @@ +Test of references between the extra files of a test variant +and the regular package. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +func fn() {} //@loc(def, "fn"), refs("fn", def, use) + +type t struct { g int } //@loc(gdef, "g") +type u struct { t } + +var _ = new(u).g //@loc(gref, "g"), refs("g", gdef, gref) +// TODO(adonovan): fix: gref2 and gdef2 are missing. + +-- a/a_test.go -- +package a + +func _() { + fn() //@loc(use, "fn") + + _ = new(u).g //@loc(gref2, "g"), refs("g", gdef2, gref, gref2) +} + +// This declaration changes the meaning of u.t in the test. +func (u) g() {} //@loc(gdef2, "g") diff --git a/contribs/gnopls/internal/test/marker/testdata/references/typeswitch.txt b/contribs/gnopls/internal/test/marker/testdata/references/typeswitch.txt new file mode 100644 index 00000000000..63a3f13825a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/references/typeswitch.txt @@ -0,0 +1,18 @@ +Tests of reference to implicit type switch vars, which are +a special case in go/types.Info{Def,Use,Implicits}. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +func _(x interface{}) { + switch y := x.(type) { //@loc(yDecl, "y"), refs("y", yDecl, yInt, yDefault) + case int: + println(y) //@loc(yInt, "y"), refs("y", yDecl, yInt, yDefault) + default: + println(y) //@loc(yDefault, "y") + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/bad.txt b/contribs/gnopls/internal/test/marker/testdata/rename/bad.txt new file mode 100644 index 00000000000..c596ad13c92 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/bad.txt @@ -0,0 +1,19 @@ +This test checks that rename fails in the presence of errors. + +-- go.mod -- +module golang.org/lsptests/bad + +go 1.18 + +-- bad.go -- +package bad + +type myStruct struct { +} + +func (s *myStruct) sFunc() bool { //@renameerr("sFunc", "rFunc", re"not possible") + return s.Bad //@diag("Bad", re"no field or method") +} + +-- bad_test.go -- +package bad diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/basic.txt b/contribs/gnopls/internal/test/marker/testdata/rename/basic.txt new file mode 100644 index 00000000000..618f9593668 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/basic.txt @@ -0,0 +1,35 @@ +This test performs basic coverage of 'rename' within a single package. + +-- basic.go -- +package p + +func f(x int) { println(x) } //@rename("x", "y", xToy) + +-- @xToy/basic.go -- +@@ -3 +3 @@ +-func f(x int) { println(x) } //@rename("x", "y", xToy) ++func f(y int) { println(y) } //@rename("x", "y", xToy) +-- alias.go -- +package p + +// from golang/go#61625 +type LongNameHere struct{} +type A = LongNameHere //@rename("A", "B", AToB) +func Foo() A + +-- errors.go -- +package p + +func _(x []int) { //@renameerr("_", "blank", `can't rename "_"`) + x = append(x, 1) //@renameerr("append", "blank", "built in and cannot be renamed") + x = nil //@renameerr("nil", "blank", "built in and cannot be renamed") + x = nil //@renameerr("x", "x", "old and new names are the same: x") + _ = 1 //@renameerr("1", "x", "no identifier found") +} + +-- @AToB/alias.go -- +@@ -5,2 +5,2 @@ +-type A = LongNameHere //@rename("A", "B", AToB) +-func Foo() A ++type B = LongNameHere //@rename("A", "B", AToB) ++func Foo() B diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/conflict.txt b/contribs/gnopls/internal/test/marker/testdata/rename/conflict.txt new file mode 100644 index 00000000000..3d7d21cb3e4 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/conflict.txt @@ -0,0 +1,59 @@ +This test exercises some renaming conflict scenarios +and ensures that the errors are informative. + +-- go.mod -- +module example.com +go 1.12 + +-- super/p.go -- +package super + +var x int + +func f(y int) { + println(x) + println(y) //@renameerr("y", "x", errSuperBlockConflict) +} + +-- @errSuperBlockConflict -- +super/p.go:5:8: renaming this var "y" to "x" +super/p.go:6:10: would shadow this reference +super/p.go:3:5: to the var declared here +-- sub/p.go -- +package sub + +var a int + +func f2(b int) { + println(a) //@renameerr("a", "b", errSubBlockConflict) + println(b) +} + +-- @errSubBlockConflict -- +sub/p.go:3:5: renaming this var "a" to "b" +sub/p.go:6:10: would cause this reference to become shadowed +sub/p.go:5:9: by this intervening var definition +-- pkgname/p.go -- +package pkgname + +import e1 "errors" //@renameerr("e1", "errors", errImportConflict) +import "errors" + +var _ = errors.New +var _ = e1.New + +-- @errImportConflict -- +pkgname/p.go:3:8: renaming this imported package name "e1" to "errors" +pkgname/p.go:4:8: conflicts with imported package name in same block +-- pkgname2/p1.go -- +package pkgname2 +var x int + +-- pkgname2/p2.go -- +package pkgname2 +import "errors" //@renameerr("errors", "x", errImportConflict2) +var _ = errors.New + +-- @errImportConflict2 -- +pkgname2/p2.go:2:8: renaming this imported package name "errors" to "x" would conflict +pkgname2/p1.go:2:5: with this package member var diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/crosspkg.txt b/contribs/gnopls/internal/test/marker/testdata/rename/crosspkg.txt new file mode 100644 index 00000000000..c60930b0114 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/crosspkg.txt @@ -0,0 +1,72 @@ +This test checks cross-package renaming. + +-- go.mod -- +module golang.org/lsptests/rename + +go 1.18 + +-- crosspkg/crosspkg.go -- +package crosspkg + +func Foo() { //@rename("Foo", "Dolphin", FooToDolphin) + +} + +var Bar int //@rename("Bar", "Tomato", BarToTomato) + +-- crosspkg/another/another.go -- +package another + +type ( + I interface{ F() } + C struct{ I } +) + +func (C) g() + +func _() { + var x I = C{} + x.F() //@rename("F", "G", FToG) +} + +-- crosspkg/other/other.go -- +package other + +import "golang.org/lsptests/rename/crosspkg" + +func Other() { + crosspkg.Bar //@diag("crosspkg", re"not used") + crosspkg.Foo() //@rename("Foo", "Flamingo", FooToFlamingo) +} + +-- @BarToTomato/crosspkg/crosspkg.go -- +@@ -7 +7 @@ +-var Bar int //@rename("Bar", "Tomato", BarToTomato) ++var Tomato int //@rename("Bar", "Tomato", BarToTomato) +-- @BarToTomato/crosspkg/other/other.go -- +@@ -6 +6 @@ +- crosspkg.Bar //@diag("crosspkg", re"not used") ++ crosspkg.Tomato //@diag("crosspkg", re"not used") +-- @FToG/crosspkg/another/another.go -- +@@ -4 +4 @@ +- I interface{ F() } ++ I interface{ G() } +@@ -12 +12 @@ +- x.F() //@rename("F", "G", FToG) ++ x.G() //@rename("F", "G", FToG) +-- @FooToDolphin/crosspkg/crosspkg.go -- +@@ -3 +3 @@ +-func Foo() { //@rename("Foo", "Dolphin", FooToDolphin) ++func Dolphin() { //@rename("Foo", "Dolphin", FooToDolphin) +-- @FooToDolphin/crosspkg/other/other.go -- +@@ -7 +7 @@ +- crosspkg.Foo() //@rename("Foo", "Flamingo", FooToFlamingo) ++ crosspkg.Dolphin() //@rename("Foo", "Flamingo", FooToFlamingo) +-- @FooToFlamingo/crosspkg/crosspkg.go -- +@@ -3 +3 @@ +-func Foo() { //@rename("Foo", "Dolphin", FooToDolphin) ++func Flamingo() { //@rename("Foo", "Dolphin", FooToDolphin) +-- @FooToFlamingo/crosspkg/other/other.go -- +@@ -7 +7 @@ +- crosspkg.Foo() //@rename("Foo", "Flamingo", FooToFlamingo) ++ crosspkg.Flamingo() //@rename("Foo", "Flamingo", FooToFlamingo) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/doclink.txt b/contribs/gnopls/internal/test/marker/testdata/rename/doclink.txt new file mode 100644 index 00000000000..bbd9bf1287a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/doclink.txt @@ -0,0 +1,183 @@ +This test checks that doc links are also handled correctly (golang/go#64495). + +-- flags -- +-min_go=go1.21 + +-- go.mod -- +module example.com + +go 1.21 + +-- a/a.go -- +package a + +// Foo just for test [Foo] +// reference others objects [A] [B] [C] [C.F] [C.PF] +func Foo() {} //@rename("Foo", "Bar", FooToBar) + +const A = 1 //@rename("A", "AA", AToAA) + +var B = 1 //@rename("B", "BB", BToBB) + +type C int //@rename("C", "CC", CToCC) + +func (C) F() {} //@rename("F", "FF", FToFF) + +func (*C) PF() {} //@rename("PF", "PFF", PFToPFF) + +// D just for test [*D] +type D int //@rename("D", "DD", DToDD) + +// E test generic type doc link [E] [E.Foo] +type E[T any] struct { //@rename("E", "EE", EToEE) + Field T +} + +func (E[T]) Foo() {} //@rename("Foo", "Bar", EFooToEBar) + +-- b/b.go -- +package b + +import aa "example.com/a" //@rename("aa", "a", pkgRename) + +// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] +// reference pointer type [*aa.D] +// reference generic type links [aa.E] [aa.E.Foo] +func FooBar() { + aa.Foo() + var e aa.E[int] + e.Foo() +} + + +-- @FooToBar/a/a.go -- +@@ -3 +3 @@ +-// Foo just for test [Foo] ++// Bar just for test [Bar] +@@ -5 +5 @@ +-func Foo() {} //@rename("Foo", "Bar", FooToBar) ++func Bar() {} //@rename("Foo", "Bar", FooToBar) +-- @FooToBar/b/b.go -- +@@ -5 +5 @@ +-// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] ++// FooBar just for test [aa.Bar] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] +@@ -9 +9 @@ +- aa.Foo() ++ aa.Bar() +-- @AToAA/a/a.go -- +@@ -4 +4 @@ +-// reference others objects [A] [B] [C] [C.F] [C.PF] ++// reference others objects [AA] [B] [C] [C.F] [C.PF] +@@ -7 +7 @@ +-const A = 1 //@rename("A", "AA", AToAA) ++const AA = 1 //@rename("A", "AA", AToAA) +-- @AToAA/b/b.go -- +@@ -5 +5 @@ +-// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] ++// FooBar just for test [aa.Foo] [aa.AA] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] +-- @BToBB/a/a.go -- +@@ -4 +4 @@ +-// reference others objects [A] [B] [C] [C.F] [C.PF] ++// reference others objects [A] [BB] [C] [C.F] [C.PF] +@@ -9 +9 @@ +-var B = 1 //@rename("B", "BB", BToBB) ++var BB = 1 //@rename("B", "BB", BToBB) +-- @BToBB/b/b.go -- +@@ -5 +5 @@ +-// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] ++// FooBar just for test [aa.Foo] [aa.A] [aa.BB] [aa.C] [aa.C.F] [aa.C.PF] +-- @CToCC/a/a.go -- +@@ -4 +4 @@ +-// reference others objects [A] [B] [C] [C.F] [C.PF] ++// reference others objects [A] [B] [CC] [CC.F] [CC.PF] +@@ -11 +11 @@ +-type C int //@rename("C", "CC", CToCC) ++type CC int //@rename("C", "CC", CToCC) +@@ -13 +13 @@ +-func (C) F() {} //@rename("F", "FF", FToFF) ++func (CC) F() {} //@rename("F", "FF", FToFF) +@@ -15 +15 @@ +-func (*C) PF() {} //@rename("PF", "PFF", PFToPFF) ++func (*CC) PF() {} //@rename("PF", "PFF", PFToPFF) +-- @CToCC/b/b.go -- +@@ -5 +5 @@ +-// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] ++// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.CC] [aa.CC.F] [aa.CC.PF] +-- @FToFF/a/a.go -- +@@ -4 +4 @@ +-// reference others objects [A] [B] [C] [C.F] [C.PF] ++// reference others objects [A] [B] [C] [C.FF] [C.PF] +@@ -13 +13 @@ +-func (C) F() {} //@rename("F", "FF", FToFF) ++func (C) FF() {} //@rename("F", "FF", FToFF) +-- @FToFF/b/b.go -- +@@ -5 +5 @@ +-// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] ++// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.FF] [aa.C.PF] +-- @PFToPFF/a/a.go -- +@@ -4 +4 @@ +-// reference others objects [A] [B] [C] [C.F] [C.PF] ++// reference others objects [A] [B] [C] [C.F] [C.PFF] +@@ -15 +15 @@ +-func (*C) PF() {} //@rename("PF", "PFF", PFToPFF) ++func (*C) PFF() {} //@rename("PF", "PFF", PFToPFF) +-- @PFToPFF/b/b.go -- +@@ -5 +5 @@ +-// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] ++// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PFF] +-- @pkgRename/b/b.go -- +@@ -3 +3 @@ +-import aa "example.com/a" //@rename("aa", "a", pkgRename) ++import "example.com/a" //@rename("aa", "a", pkgRename) +@@ -5,3 +5,3 @@ +-// FooBar just for test [aa.Foo] [aa.A] [aa.B] [aa.C] [aa.C.F] [aa.C.PF] +-// reference pointer type [*aa.D] +-// reference generic type links [aa.E] [aa.E.Foo] ++// FooBar just for test [a.Foo] [a.A] [a.B] [a.C] [a.C.F] [a.C.PF] ++// reference pointer type [*a.D] ++// reference generic type links [a.E] [a.E.Foo] +@@ -9,2 +9,2 @@ +- aa.Foo() +- var e aa.E[int] ++ a.Foo() ++ var e a.E[int] +-- @DToDD/a/a.go -- +@@ -17,2 +17,2 @@ +-// D just for test [*D] +-type D int //@rename("D", "DD", DToDD) ++// DD just for test [*DD] ++type DD int //@rename("D", "DD", DToDD) +-- @DToDD/b/b.go -- +@@ -6 +6 @@ +-// reference pointer type [*aa.D] ++// reference pointer type [*aa.DD] +-- @EToEE/a/a.go -- +@@ -20,2 +20,2 @@ +-// E test generic type doc link [E] [E.Foo] +-type E[T any] struct { //@rename("E", "EE", EToEE) ++// EE test generic type doc link [EE] [EE.Foo] ++type EE[T any] struct { //@rename("E", "EE", EToEE) +@@ -25 +25 @@ +-func (E[T]) Foo() {} //@rename("Foo", "Bar", EFooToEBar) ++func (EE[T]) Foo() {} //@rename("Foo", "Bar", EFooToEBar) +-- @EToEE/b/b.go -- +@@ -7 +7 @@ +-// reference generic type links [aa.E] [aa.E.Foo] ++// reference generic type links [aa.EE] [aa.EE.Foo] +@@ -10 +10 @@ +- var e aa.E[int] ++ var e aa.EE[int] +-- @EFooToEBar/a/a.go -- +@@ -20 +20 @@ +-// E test generic type doc link [E] [E.Foo] ++// E test generic type doc link [E] [E.Bar] +@@ -25 +25 @@ +-func (E[T]) Foo() {} //@rename("Foo", "Bar", EFooToEBar) ++func (E[T]) Bar() {} //@rename("Foo", "Bar", EFooToEBar) +-- @EFooToEBar/b/b.go -- +@@ -7 +7 @@ +-// reference generic type links [aa.E] [aa.E.Foo] ++// reference generic type links [aa.E] [aa.E.Bar] +@@ -11 +11 @@ +- e.Foo() ++ e.Bar() diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/embed.txt b/contribs/gnopls/internal/test/marker/testdata/rename/embed.txt new file mode 100644 index 00000000000..8e6009e42ca --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/embed.txt @@ -0,0 +1,33 @@ +This test exercises renaming of types used as embedded fields. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type A int //@rename("A", "A2", type) + +-- b/b.go -- +package b + +import "example.com/a" + +type B struct { a.A } //@renameerr("A", "A3", errAnonField) + +var _ = new(B).A //@renameerr("A", "A4", errAnonField) + +-- @errAnonField -- +can't rename embedded fields: rename the type directly or name the field +-- @type/a/a.go -- +@@ -3 +3 @@ +-type A int //@rename("A", "A2", type) ++type A2 int //@rename("A", "A2", type) +-- @type/b/b.go -- +@@ -5 +5 @@ +-type B struct { a.A } //@renameerr("A", "A3", errAnonField) ++type B struct { a.A2 } //@renameerr("A", "A3", errAnonField) +@@ -7 +7 @@ +-var _ = new(B).A //@renameerr("A", "A4", errAnonField) ++var _ = new(B).A2 //@renameerr("A", "A4", errAnonField) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/generics.txt b/contribs/gnopls/internal/test/marker/testdata/rename/generics.txt new file mode 100644 index 00000000000..71e56dd9bc4 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/generics.txt @@ -0,0 +1,188 @@ +This test exercises various renaming features on generic code. + +Fixed bugs: + +- golang/go#61614: renaming a method of a type in a package that uses type + parameter composite lits used to panic, because previous iterations of the + satisfy analysis did not account for this language feature. + +- golang/go#61635: renaming type parameters did not work when they were + capitalized and the package was imported by another package. + +-- flags -- +-min_go=go1.20 + +-- go.mod -- +module example.com +go 1.20 + +-- a.go -- +package a + +type I int + +func (I) m() {} //@rename("m", "M", mToM) + +func _[P ~[]int]() { + _ = P{} +} + +-- @mToM/a.go -- +@@ -5 +5 @@ +-func (I) m() {} //@rename("m", "M", mToM) ++func (I) M() {} //@rename("m", "M", mToM) +-- g.go -- +package a + +type S[P any] struct { //@rename("P", "Q", PToQ) + P P + F func(P) P +} + +func F[R any](r R) { + var _ R //@rename("R", "S", RToS) +} + +-- @PToQ/g.go -- +@@ -3,3 +3,3 @@ +-type S[P any] struct { //@rename("P", "Q", PToQ) +- P P +- F func(P) P ++type S[Q any] struct { //@rename("P", "Q", PToQ) ++ P Q ++ F func(Q) Q +-- @RToS/g.go -- +@@ -8,2 +8,2 @@ +-func F[R any](r R) { +- var _ R //@rename("R", "S", RToS) ++func F[S any](r S) { ++ var _ S //@rename("R", "S", RToS) +-- issue61635/p.go -- +package issue61635 + +type builder[S ~[]F, F ~string] struct { //@rename("S", "T", SToT) + name string + elements S + elemData map[F][]ElemData[F] + // other fields... +} + +type ElemData[F ~string] struct { + Name F + // other fields... +} + +type BuilderImpl[S ~[]F, F ~string] struct{ builder[S, F] } + +-- importer/i.go -- +package importer + +import "example.com/issue61635" // importing is necessary to repro golang/go#61635 + +var _ issue61635.ElemData[string] + +-- @SToT/issue61635/p.go -- +@@ -3 +3 @@ +-type builder[S ~[]F, F ~string] struct { //@rename("S", "T", SToT) ++type builder[T ~[]F, F ~string] struct { //@rename("S", "T", SToT) +@@ -5 +5 @@ +- elements S ++ elements T +-- instances/type.go -- +package instances + +type R[P any] struct { //@rename("R", "u", Rtou) + Next *R[P] //@rename("R", "s", RTos) +} + +func (rv R[P]) Do(R[P]) R[P] { //@rename("Do", "Do1", DoToDo1) + var x R[P] + return rv.Do(x) //@rename("Do", "Do2", DoToDo2) +} + +func _() { + var x R[int] //@rename("R", "r", RTor) + x = x.Do(x) +} + +-- @RTos/instances/type.go -- +@@ -3,2 +3,2 @@ +-type R[P any] struct { //@rename("R", "u", Rtou) +- Next *R[P] //@rename("R", "s", RTos) ++type s[P any] struct { //@rename("R", "u", Rtou) ++ Next *s[P] //@rename("R", "s", RTos) +@@ -7,2 +7,2 @@ +-func (rv R[P]) Do(R[P]) R[P] { //@rename("Do", "Do1", DoToDo1) +- var x R[P] ++func (rv s[P]) Do(s[P]) s[P] { //@rename("Do", "Do1", DoToDo1) ++ var x s[P] +@@ -13 +13 @@ +- var x R[int] //@rename("R", "r", RTor) ++ var x s[int] //@rename("R", "r", RTor) +-- @Rtou/instances/type.go -- +@@ -3,2 +3,2 @@ +-type R[P any] struct { //@rename("R", "u", Rtou) +- Next *R[P] //@rename("R", "s", RTos) ++type u[P any] struct { //@rename("R", "u", Rtou) ++ Next *u[P] //@rename("R", "s", RTos) +@@ -7,2 +7,2 @@ +-func (rv R[P]) Do(R[P]) R[P] { //@rename("Do", "Do1", DoToDo1) +- var x R[P] ++func (rv u[P]) Do(u[P]) u[P] { //@rename("Do", "Do1", DoToDo1) ++ var x u[P] +@@ -13 +13 @@ +- var x R[int] //@rename("R", "r", RTor) ++ var x u[int] //@rename("R", "r", RTor) +-- @DoToDo1/instances/type.go -- +@@ -7 +7 @@ +-func (rv R[P]) Do(R[P]) R[P] { //@rename("Do", "Do1", DoToDo1) ++func (rv R[P]) Do1(R[P]) R[P] { //@rename("Do", "Do1", DoToDo1) +@@ -9 +9 @@ +- return rv.Do(x) //@rename("Do", "Do2", DoToDo2) ++ return rv.Do1(x) //@rename("Do", "Do2", DoToDo2) +@@ -14 +14 @@ +- x = x.Do(x) ++ x = x.Do1(x) +-- @DoToDo2/instances/type.go -- +@@ -7 +7 @@ +-func (rv R[P]) Do(R[P]) R[P] { //@rename("Do", "Do1", DoToDo1) ++func (rv R[P]) Do2(R[P]) R[P] { //@rename("Do", "Do1", DoToDo1) +@@ -9 +9 @@ +- return rv.Do(x) //@rename("Do", "Do2", DoToDo2) ++ return rv.Do2(x) //@rename("Do", "Do2", DoToDo2) +@@ -14 +14 @@ +- x = x.Do(x) ++ x = x.Do2(x) +-- instances/func.go -- +package instances + +func Foo[P any](p P) { //@rename("Foo", "Bar", FooToBar) + Foo(p) //@rename("Foo", "Baz", FooToBaz) +} + +-- @FooToBar/instances/func.go -- +@@ -3,2 +3,2 @@ +-func Foo[P any](p P) { //@rename("Foo", "Bar", FooToBar) +- Foo(p) //@rename("Foo", "Baz", FooToBaz) ++func Bar[P any](p P) { //@rename("Foo", "Bar", FooToBar) ++ Bar(p) //@rename("Foo", "Baz", FooToBaz) +-- @FooToBaz/instances/func.go -- +@@ -3,2 +3,2 @@ +-func Foo[P any](p P) { //@rename("Foo", "Bar", FooToBar) +- Foo(p) //@rename("Foo", "Baz", FooToBaz) ++func Baz[P any](p P) { //@rename("Foo", "Bar", FooToBar) ++ Baz(p) //@rename("Foo", "Baz", FooToBaz) +-- @RTor/instances/type.go -- +@@ -3,2 +3,2 @@ +-type R[P any] struct { //@rename("R", "u", Rtou) +- Next *R[P] //@rename("R", "s", RTos) ++type r[P any] struct { //@rename("R", "u", Rtou) ++ Next *r[P] //@rename("R", "s", RTos) +@@ -7,2 +7,2 @@ +-func (rv R[P]) Do(R[P]) R[P] { //@rename("Do", "Do1", DoToDo1) +- var x R[P] ++func (rv r[P]) Do(r[P]) r[P] { //@rename("Do", "Do1", DoToDo1) ++ var x r[P] +@@ -13 +13 @@ +- var x R[int] //@rename("R", "r", RTor) ++ var x r[int] //@rename("R", "r", RTor) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/generics_basic.txt b/contribs/gnopls/internal/test/marker/testdata/rename/generics_basic.txt new file mode 100644 index 00000000000..16b0a00c87b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/generics_basic.txt @@ -0,0 +1,107 @@ +This test exercise basic renaming of generic code. + +-- embedded.go -- +package a + +type foo[P any] int //@rename("foo", "bar", fooTobar) + +var x struct{ foo[int] } + +var _ = x.foo + +-- @fooTobar/embedded.go -- +@@ -3 +3 @@ +-type foo[P any] int //@rename("foo", "bar", fooTobar) ++type bar[P any] int //@rename("foo", "bar", fooTobar) +@@ -5 +5 @@ +-var x struct{ foo[int] } ++var x struct{ bar[int] } +@@ -7 +7 @@ +-var _ = x.foo ++var _ = x.bar +-- generics.go -- +package a + +type G[P any] struct { + F int +} + +func (G[_]) M() {} + +func F[P any](P) { + var p P //@rename("P", "Q", PToQ) + _ = p +} + +func _() { + var x G[int] //@rename("G", "H", GToH) + _ = x.F //@rename("F", "K", FToK) + x.M() //@rename("M", "N", MToN) + + var y G[string] + _ = y.F + y.M() +} + +-- @FToK/generics.go -- +@@ -4 +4 @@ +- F int ++ K int +@@ -16 +16 @@ +- _ = x.F //@rename("F", "K", FToK) ++ _ = x.K //@rename("F", "K", FToK) +@@ -20 +20 @@ +- _ = y.F ++ _ = y.K +-- @GToH/generics.go -- +@@ -3 +3 @@ +-type G[P any] struct { ++type H[P any] struct { +@@ -7 +7 @@ +-func (G[_]) M() {} ++func (H[_]) M() {} +@@ -15 +15 @@ +- var x G[int] //@rename("G", "H", GToH) ++ var x H[int] //@rename("G", "H", GToH) +@@ -19 +19 @@ +- var y G[string] ++ var y H[string] +-- @MToN/generics.go -- +@@ -7 +7 @@ +-func (G[_]) M() {} ++func (G[_]) N() {} +@@ -17 +17 @@ +- x.M() //@rename("M", "N", MToN) ++ x.N() //@rename("M", "N", MToN) +@@ -21 +21 @@ +- y.M() ++ y.N() +-- @PToQ/generics.go -- +@@ -9,2 +9,2 @@ +-func F[P any](P) { +- var p P //@rename("P", "Q", PToQ) ++func F[Q any](Q) { ++ var p Q //@rename("P", "Q", PToQ) +-- unions.go -- +package a + +type T string //@rename("T", "R", TToR) + +type C interface { + T | ~int //@rename("T", "S", TToS) +} + +-- @TToR/unions.go -- +@@ -3 +3 @@ +-type T string //@rename("T", "R", TToR) ++type R string //@rename("T", "R", TToR) +@@ -6 +6 @@ +- T | ~int //@rename("T", "S", TToS) ++ R | ~int //@rename("T", "S", TToS) +-- @TToS/unions.go -- +@@ -3 +3 @@ +-type T string //@rename("T", "R", TToR) ++type S string //@rename("T", "R", TToR) +@@ -6 +6 @@ +- T | ~int //@rename("T", "S", TToS) ++ S | ~int //@rename("T", "S", TToS) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue39614.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue39614.txt new file mode 100644 index 00000000000..d6d9c241ba7 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue39614.txt @@ -0,0 +1,18 @@ + +-- flags -- +-ignore_extra_diags + +-- p.go -- +package issue39614 + +func fn() { + var foo bool //@rename("foo", "bar", fooTobar) + make(map[string]bool + if true { + } +} + +-- @fooTobar/p.go -- +@@ -4 +4 @@ +- var foo bool //@rename("foo", "bar", fooTobar) ++ var bar bool //@rename("foo", "bar", fooTobar) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue42134.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue42134.txt new file mode 100644 index 00000000000..05fee50bed9 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue42134.txt @@ -0,0 +1,80 @@ +Regression test for #42134, +"rename fails to update doc comment for local variable of function type" + +-- 1.go -- +package issue42134 + +func _() { + // foo computes things. + foo := func() {} + + foo() //@rename("foo", "bar", fooTobar) +} +-- @fooTobar/1.go -- +@@ -4,2 +4,2 @@ +- // foo computes things. +- foo := func() {} ++ // bar computes things. ++ bar := func() {} +@@ -7 +7 @@ +- foo() //@rename("foo", "bar", fooTobar) ++ bar() //@rename("foo", "bar", fooTobar) +-- 2.go -- +package issue42134 + +import "fmt" + +func _() { + // minNumber is a min number. + // Second line. + minNumber := min(1, 2) + fmt.Println(minNumber) //@rename("minNumber", "res", minNumberTores) +} + +func min(a, b int) int { return a + b } +-- @minNumberTores/2.go -- +@@ -6 +6 @@ +- // minNumber is a min number. ++ // res is a min number. +@@ -8,2 +8,2 @@ +- minNumber := min(1, 2) +- fmt.Println(minNumber) //@rename("minNumber", "res", minNumberTores) ++ res := min(1, 2) ++ fmt.Println(res) //@rename("minNumber", "res", minNumberTores) +-- 3.go -- +package issue42134 + +func _() { + /* + tests contains test cases + */ + tests := []struct { //@rename("tests", "testCases", testsTotestCases) + in, out string + }{} + _ = tests +} +-- @testsTotestCases/3.go -- +@@ -5 +5 @@ +- tests contains test cases ++ testCases contains test cases +@@ -7 +7 @@ +- tests := []struct { //@rename("tests", "testCases", testsTotestCases) ++ testCases := []struct { //@rename("tests", "testCases", testsTotestCases) +@@ -10 +10 @@ +- _ = tests ++ _ = testCases +-- 4.go -- +package issue42134 + +func _() { + // a is equal to 5. Comment must stay the same + + a := 5 + _ = a //@rename("a", "b", aTob) +} +-- @aTob/4.go -- +@@ -6,2 +6,2 @@ +- a := 5 +- _ = a //@rename("a", "b", aTob) ++ b := 5 ++ _ = b //@rename("a", "b", aTob) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue43616.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue43616.txt new file mode 100644 index 00000000000..19cfac4a435 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue43616.txt @@ -0,0 +1,21 @@ +This test verifies the fix for golang/go#43616: renaming mishandles embedded +fields. + +-- p.go -- +package issue43616 + +type foo int //@rename("foo", "bar", fooToBar),preparerename("oo","foo","foo") + +var x struct{ foo } //@renameerr("foo", "baz", "rename the type directly") + +var _ = x.foo //@renameerr("foo", "quux", "rename the type directly") +-- @fooToBar/p.go -- +@@ -3 +3 @@ +-type foo int //@rename("foo", "bar", fooToBar),preparerename("oo","foo","foo") ++type bar int //@rename("foo", "bar", fooToBar),preparerename("oo","foo","foo") +@@ -5 +5 @@ +-var x struct{ foo } //@renameerr("foo", "baz", "rename the type directly") ++var x struct{ bar } //@renameerr("foo", "baz", "rename the type directly") +@@ -7 +7 @@ +-var _ = x.foo //@renameerr("foo", "quux", "rename the type directly") ++var _ = x.bar //@renameerr("foo", "quux", "rename the type directly") diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue57479.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue57479.txt new file mode 100644 index 00000000000..78004591398 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue57479.txt @@ -0,0 +1,37 @@ +Test renaming a parameter to the name of an imported package +referenced by one of the function parameters. + +See golang/go#57479 + +-- flags -- +-min_go=go1.22 + +-- go.mod -- +module golang.org/lsptests/rename + +go 1.18 +-- a/a.go -- +package a + +import ( + "fmt" + "math" +) + +func _(x fmt.Stringer) {} //@rename("x", "fmt", xToFmt) + +func _(x int, y fmt.Stringer) {} //@rename("x", "fmt", xyToFmt) + +func _(x [math.MaxInt]bool) {} //@rename("x", "math", xToMath) +-- @xToFmt/a/a.go -- +@@ -8 +8 @@ +-func _(x fmt.Stringer) {} //@rename("x", "fmt", xToFmt) ++func _(fmt fmt.Stringer) {} //@rename("x", "fmt", xToFmt) +-- @xToMath/a/a.go -- +@@ -12 +12 @@ +-func _(x [math.MaxInt]bool) {} //@rename("x", "math", xToMath) ++func _(math [math.MaxInt]bool) {} //@rename("x", "math", xToMath) +-- @xyToFmt/a/a.go -- +@@ -10 +10 @@ +-func _(x int, y fmt.Stringer) {} //@rename("x", "fmt", xyToFmt) ++func _(fmt int, y fmt.Stringer) {} //@rename("x", "fmt", xyToFmt) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue60752.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue60752.txt new file mode 100644 index 00000000000..eec24b8e9de --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue60752.txt @@ -0,0 +1,57 @@ + +This test renames a receiver, type parameter, parameter or result var +whose name matches a package-level decl. Prior to go1.22, this used to +cause a spurious shadowing error because of an edge case in the +behavior of types.Scope for function parameters and results. + +This is a regression test for issue #60752, a bug in the type checker. + +-- flags -- +-min_go=go1.22 + +-- go.mod -- +module example.com +go 1.18 + +-- a/type.go -- +package a + +type t int + +-- a/recv.go -- +package a + +func (v t) _() {} //@ rename("v", "t", recv) + +-- a/param.go -- +package a + +func _(v t) {} //@ rename("v", "t", param) + +-- a/result.go -- +package a + +func _() (v t) { return } //@ rename("v", "t", result) + +-- a/typeparam.go -- +package a + +func _[v t]() {} //@ renameerr("v", "t", re"would shadow (.|\n)*type.go:3:6") + +-- b/b.go -- +package b + +import _ "example.com/a" + +-- @param/a/param.go -- +@@ -3 +3 @@ +-func _(v t) {} //@ rename("v", "t", param) ++func _(t t) {} //@ rename("v", "t", param) +-- @recv/a/recv.go -- +@@ -3 +3 @@ +-func (v t) _() {} //@ rename("v", "t", recv) ++func (t t) _() {} //@ rename("v", "t", recv) +-- @result/a/result.go -- +@@ -3 +3 @@ +-func _() (v t) { return } //@ rename("v", "t", result) ++func _() (t t) { return } //@ rename("v", "t", result) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue60789.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue60789.txt new file mode 100644 index 00000000000..d5a0b9bb5ae --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue60789.txt @@ -0,0 +1,35 @@ + +This test renames an exported method of an unexported type, +which is an edge case for objectpath, since it computes a path +from a syntax package that is no good when applied to an +export data package. + +See issue #60789. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type unexported int +func (unexported) F() {} //@rename("F", "G", fToG) + +var _ = unexported(0).F + +-- b/b.go -- +package b + +// The existence of this package is sufficient to exercise +// the bug even though it cannot reference a.unexported. + +import _ "example.com/a" + +-- @fToG/a/a.go -- +@@ -4 +4 @@ +-func (unexported) F() {} //@rename("F", "G", fToG) ++func (unexported) G() {} //@rename("F", "G", fToG) +@@ -6 +6 @@ +-var _ = unexported(0).F ++var _ = unexported(0).G diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue61294.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue61294.txt new file mode 100644 index 00000000000..f376cf1d29a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue61294.txt @@ -0,0 +1,26 @@ + +This test renames a parameter var whose name is the same as a +package-level var, which revealed a bug in isLocal. + +This is a regression test for issue #61294. + +-- go.mod -- +module example.com +go 1.18 + +-- a/a.go -- +package a + +func One() + +func Two(One int) //@rename("One", "Three", OneToThree) + +-- b/b.go -- +package b + +import _ "example.com/a" + +-- @OneToThree/a/a.go -- +@@ -5 +5 @@ +-func Two(One int) //@rename("One", "Three", OneToThree) ++func Two(Three int) //@rename("One", "Three", OneToThree) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue61640.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue61640.txt new file mode 100644 index 00000000000..d195399bee4 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue61640.txt @@ -0,0 +1,33 @@ +This test verifies that gopls can rename instantiated fields. + +-- a.go -- +package a + +// This file is adapted from the example in the issue. + +type builder[S ~[]int] struct { + elements S //@rename("elements", "elements2", OneToTwo) +} + +type BuilderImpl[S ~[]int] struct{ builder[S] } + +func NewBuilderImpl[S ~[]int](name string) *BuilderImpl[S] { + impl := &BuilderImpl[S]{ + builder[S]{ + elements: S{}, + }, + } + + _ = impl.elements + return impl +} +-- @OneToTwo/a.go -- +@@ -6 +6 @@ +- elements S //@rename("elements", "elements2", OneToTwo) ++ elements2 S //@rename("elements", "elements2", OneToTwo) +@@ -14 +14 @@ +- elements: S{}, ++ elements2: S{}, +@@ -18 +18 @@ +- _ = impl.elements ++ _ = impl.elements2 diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue61813.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue61813.txt new file mode 100644 index 00000000000..9d3779bb427 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue61813.txt @@ -0,0 +1,14 @@ +This test exercises the panic reported in golang/go#61813. + +-- p.go -- +package p + +type P struct{} + +func (P) M() {} //@rename("M", "N", MToN) + +var x = []*P{{}} +-- @MToN/p.go -- +@@ -5 +5 @@ +-func (P) M() {} //@rename("M", "N", MToN) ++func (P) N() {} //@rename("M", "N", MToN) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/issue67069.txt b/contribs/gnopls/internal/test/marker/testdata/rename/issue67069.txt new file mode 100644 index 00000000000..2656de16970 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/issue67069.txt @@ -0,0 +1,54 @@ +This test verifies spurious pkgname conflicts. +Issue golang/go#67069. + +-- go.mod -- +module example +go 1.19 + +-- aa/a.go -- +package aa + +var cc int //@rename("cc", "aa", CToA) +const C = 0 +const D = 0 + +-- aa/a_test.go -- +package aa_test + +import "example/aa" + +var _ = aa.C //@rename("aa", "bb", AToB) +-- @CToA/aa/a.go -- +@@ -3 +3 @@ +-var cc int //@rename("cc", "aa", CToA) ++var aa int //@rename("cc", "aa", CToA) +-- @AToB/aa/a_test.go -- +@@ -3 +3 @@ +-import "example/aa" ++import bb "example/aa" +@@ -5 +5 @@ +-var _ = aa.C //@rename("aa", "bb", AToB) ++var _ = bb.C //@rename("aa", "bb", AToB) +-- bb/b.go -- +package bb + +import "example/aa" + +var _ = aa.C +var bb int //@renameerr("bb", "aa", errImportConflict) + +-- @errImportConflict -- +bb/b.go:6:5: renaming this var "bb" to "aa" would conflict +bb/b.go:3:8: with this imported package name +-- aa/a_internal_test.go -- +package aa + +var _ = D //@rename("D", "aa", DToA) +-- @DToA/aa/a_internal_test.go -- +@@ -3 +3 @@ +-var _ = D //@rename("D", "aa", DToA) ++var _ = aa //@rename("D", "aa", DToA) +-- @DToA/aa/a.go -- +@@ -5 +5 @@ +-const D = 0 ++const aa = 0 diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/methods.txt b/contribs/gnopls/internal/test/marker/testdata/rename/methods.txt new file mode 100644 index 00000000000..5f5c5688479 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/methods.txt @@ -0,0 +1,57 @@ +This test exercises renaming of interface methods. + +The golden is currently wrong due to https://github.com/golang/go/issues/58506: +the reference to B.F in package b should be renamed too. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type A int + +func (A) F() {} //@renameerr("F", "G", errAfToG) + +-- b/b.go -- +package b + +import "example.com/a" +import "example.com/c" + +type B interface { F() } //@rename("F", "G", BfToG) + +var _ B = a.A(0) +var _ B = c.C(0) + +-- c/c.go -- +package c + +type C int + +func (C) F() {} //@renameerr("F", "G", errCfToG) + +-- d/d.go -- +package d + +import "example.com/b" + +var _ = b.B.F + +-- @errAfToG -- +a/a.go:5:10: renaming this method "F" to "G" +b/b.go:6:6: would make example.com/a.A no longer assignable to interface B +b/b.go:6:20: (rename example.com/b.B.F if you intend to change both types) +-- @BfToG/b/b.go -- +@@ -6 +6 @@ +-type B interface { F() } //@rename("F", "G", BfToG) ++type B interface { G() } //@rename("F", "G", BfToG) +-- @BfToG/d/d.go -- +@@ -5 +5 @@ +-var _ = b.B.F ++var _ = b.B.G +-- @errCfToG -- +c/c.go:5:10: renaming this method "F" to "G" +b/b.go:6:6: would make example.com/c.C no longer assignable to interface B +b/b.go:6:20: (rename example.com/b.B.F if you intend to change both types) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/prepare.txt b/contribs/gnopls/internal/test/marker/testdata/rename/prepare.txt new file mode 100644 index 00000000000..cd8439e41b3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/prepare.txt @@ -0,0 +1,62 @@ +This test verifies the behavior of textDocument/prepareRename. + +-- settings.json -- +{ + "deepCompletion": false +} + +-- go.mod -- +module golang.org/lsptests + +go 1.18 +-- types/types.go -- +package types + +type CoolAlias = int //@item(CoolAlias, "CoolAlias", "int", "type") + +type X struct { //@item(X_struct, "X", "struct{...}", "struct") + x int +} + +type Y struct { //@item(Y_struct, "Y", "struct{...}", "struct") + y int +} + + +type Bob interface { //@item(Bob_interface, "Bob", "interface{...}", "interface") + Bobby() +} + +func (*X) Bobby() {} +func (*Y) Bobby() {} + +-- good/good0.go -- +package good + +func stuff() { //@item(good_stuff, "stuff", "func()", "func"),preparerename("stu", "stuff", "stuff") + x := 5 + random2(x) //@preparerename("dom", "random2", "random2") +} + +-- good/good1.go -- +package good + +import ( + "golang.org/lsptests/types" //@item(types_import, "types", "\"golang.org/lsptests/types\"", "package") +) + +func random() int { //@item(good_random, "random", "func() int", "func") + _ = "random() int" //@preparerename("random", "", "") + y := 6 + 7 //@preparerename("7", "", "") + return y //@preparerename("return", "","") +} + +func random2(y int) int { //@item(good_random2, "random2", "func(y int) int", "func"),item(good_y_param, "y", "int", "var") + //@complete("", good_y_param, types_import, good_random, good_random2, good_stuff) + var b types.Bob = &types.X{} //@preparerename("ypes","types", "types") + if _, ok := b.(*types.X); ok { //@complete("X", X_struct, Y_struct, Bob_interface, CoolAlias) + _ = 0 // suppress "empty branch" diagnostic + } + + return y +} diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/random.txt b/contribs/gnopls/internal/test/marker/testdata/rename/random.txt new file mode 100644 index 00000000000..5c58b3db626 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/random.txt @@ -0,0 +1,238 @@ +This test ports some "random" rename tests from the old marker tests. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests/rename + +go 1.18 +-- a/a.go -- +package a + +import ( + lg "log" + "fmt" //@rename("fmt", "fmty", fmtTofmty) + f2 "fmt" //@rename("f2", "f2name", f2Tof2name),rename("fmt", "f2y", fmtTof2y) +) + +func Random() int { + y := 6 + 7 + return y +} + +func Random2(y int) int { //@rename("y", "z", yToz) + return y +} + +type Pos struct { + x, y int +} + +func (p *Pos) Sum() int { + return p.x + p.y //@rename("x", "myX", xTomyX) +} + +func _() { + var p Pos //@rename("p", "pos", pTopos) + _ = p.Sum() //@rename("Sum", "GetSum", SumToGetSum) +} + +func sw() { + var x interface{} + + switch y := x.(type) { //@rename("y", "y0", yToy0) + case int: + fmt.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) + case string: + lg.Printf("%s", y) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) + default: + f2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) + } +} +-- @SumToGetSum/a/a.go -- +@@ -22 +22 @@ +-func (p *Pos) Sum() int { ++func (p *Pos) GetSum() int { +@@ -28 +28 @@ +- _ = p.Sum() //@rename("Sum", "GetSum", SumToGetSum) ++ _ = p.GetSum() //@rename("Sum", "GetSum", SumToGetSum) +-- @f2Tof2name/a/a.go -- +@@ -6 +6 @@ +- f2 "fmt" //@rename("f2", "f2name", f2Tof2name),rename("fmt", "f2y", fmtTof2y) ++ f2name "fmt" //@rename("f2", "f2name", f2Tof2name),rename("fmt", "f2y", fmtTof2y) +@@ -40 +40 @@ +- f2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) ++ f2name.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) +-- @f2Tofmt2/a/a.go -- +@@ -6 +6 @@ +- f2 "fmt" //@rename("f2", "f2name", f2Tof2name),rename("fmt", "f2y", fmtTof2y) ++ fmt2 "fmt" //@rename("f2", "f2name", f2Tof2name),rename("fmt", "f2y", fmtTof2y) +@@ -40 +40 @@ +- f2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) ++ fmt2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) +-- @fmtTof2y/a/a.go -- +@@ -6 +6 @@ +- f2 "fmt" //@rename("f2", "f2name", f2Tof2name),rename("fmt", "f2y", fmtTof2y) ++ f2y "fmt" //@rename("f2", "f2name", f2Tof2name),rename("fmt", "f2y", fmtTof2y) +@@ -40 +40 @@ +- f2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) ++ f2y.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) +-- @fmtTofmty/a/a.go -- +@@ -5 +5 @@ +- "fmt" //@rename("fmt", "fmty", fmtTofmty) ++ fmty "fmt" //@rename("fmt", "fmty", fmtTofmty) +@@ -36 +36 @@ +- fmt.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) ++ fmty.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) +-- @fmtToformat/a/a.go -- +@@ -5 +5 @@ +- "fmt" //@rename("fmt", "fmty", fmtTofmty) ++ format "fmt" //@rename("fmt", "fmty", fmtTofmty) +@@ -36 +36 @@ +- fmt.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) ++ format.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) +-- @lgTolog/a/a.go -- +@@ -4 +4 @@ +- lg "log" ++ "log" +@@ -38 +38 @@ +- lg.Printf("%s", y) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) ++ log.Printf("%s", y) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) +-- @pTopos/a/a.go -- +@@ -27,2 +27,2 @@ +- var p Pos //@rename("p", "pos", pTopos) +- _ = p.Sum() //@rename("Sum", "GetSum", SumToGetSum) ++ var pos Pos //@rename("p", "pos", pTopos) ++ _ = pos.Sum() //@rename("Sum", "GetSum", SumToGetSum) +-- @xTomyX/a/a.go -- +@@ -19 +19 @@ +- x, y int ++ myX, y int +@@ -23 +23 @@ +- return p.x + p.y //@rename("x", "myX", xTomyX) ++ return p.myX + p.y //@rename("x", "myX", xTomyX) +-- @yToy0/a/a.go -- +@@ -34 +34 @@ +- switch y := x.(type) { //@rename("y", "y0", yToy0) ++ switch y0 := x.(type) { //@rename("y", "y0", yToy0) +@@ -36 +36 @@ +- fmt.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) ++ fmt.Printf("%d", y0) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) +@@ -38 +38 @@ +- lg.Printf("%s", y) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) ++ lg.Printf("%s", y0) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) +@@ -40 +40 @@ +- f2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) ++ f2.Printf("%v", y0) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) +-- @yToy1/a/a.go -- +@@ -34 +34 @@ +- switch y := x.(type) { //@rename("y", "y0", yToy0) ++ switch y1 := x.(type) { //@rename("y", "y0", yToy0) +@@ -36 +36 @@ +- fmt.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) ++ fmt.Printf("%d", y1) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) +@@ -38 +38 @@ +- lg.Printf("%s", y) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) ++ lg.Printf("%s", y1) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) +@@ -40 +40 @@ +- f2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) ++ f2.Printf("%v", y1) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) +-- @yToy2/a/a.go -- +@@ -34 +34 @@ +- switch y := x.(type) { //@rename("y", "y0", yToy0) ++ switch y2 := x.(type) { //@rename("y", "y0", yToy0) +@@ -36 +36 @@ +- fmt.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) ++ fmt.Printf("%d", y2) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) +@@ -38 +38 @@ +- lg.Printf("%s", y) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) ++ lg.Printf("%s", y2) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) +@@ -40 +40 @@ +- f2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) ++ f2.Printf("%v", y2) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) +-- @yToy3/a/a.go -- +@@ -34 +34 @@ +- switch y := x.(type) { //@rename("y", "y0", yToy0) ++ switch y3 := x.(type) { //@rename("y", "y0", yToy0) +@@ -36 +36 @@ +- fmt.Printf("%d", y) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) ++ fmt.Printf("%d", y3) //@rename("y", "y1", yToy1),rename("fmt", "format", fmtToformat) +@@ -38 +38 @@ +- lg.Printf("%s", y) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) ++ lg.Printf("%s", y3) //@rename("y", "y2", yToy2),rename("lg", "log", lgTolog) +@@ -40 +40 @@ +- f2.Printf("%v", y) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) ++ f2.Printf("%v", y3) //@rename("y", "y3", yToy3),rename("f2", "fmt2", f2Tofmt2) +-- @yToz/a/a.go -- +@@ -14,2 +14,2 @@ +-func Random2(y int) int { //@rename("y", "z", yToz) +- return y ++func Random2(z int) int { //@rename("y", "z", yToz) ++ return z +-- b/b.go -- +package b + +var c int //@renameerr("int", "uint", re"cannot be renamed") + +func _() { + a := 1 //@rename("a", "error", aToerror) + a = 2 + _ = a +} + +var ( + // Hello there. + // Foo does the thing. + Foo int //@rename("Foo", "Bob", FooToBob) +) + +/* +Hello description +*/ +func Hello() {} //@rename("Hello", "Goodbye", HelloToGoodbye) + +-- c/c.go -- +package c + +import "golang.org/lsptests/rename/b" + +func _() { + b.Hello() //@rename("Hello", "Goodbye", HelloToGoodbye) +} + +-- c/c2.go -- +package c + +//go:embed Static/* +var Static embed.FS //@rename("Static", "static", StaticTostatic) + +-- @FooToBob/b/b.go -- +@@ -13,2 +13,2 @@ +- // Foo does the thing. +- Foo int //@rename("Foo", "Bob", FooToBob) ++ // Bob does the thing. ++ Bob int //@rename("Foo", "Bob", FooToBob) +-- @HelloToGoodbye/b/b.go -- +@@ -18 +18 @@ +-Hello description ++Goodbye description +@@ -20 +20 @@ +-func Hello() {} //@rename("Hello", "Goodbye", HelloToGoodbye) ++func Goodbye() {} //@rename("Hello", "Goodbye", HelloToGoodbye) +-- @aToerror/b/b.go -- +@@ -6,3 +6,3 @@ +- a := 1 //@rename("a", "error", aToerror) +- a = 2 +- _ = a ++ error := 1 //@rename("a", "error", aToerror) ++ error = 2 ++ _ = error +-- @HelloToGoodbye/c/c.go -- +@@ -6 +6 @@ +- b.Hello() //@rename("Hello", "Goodbye", HelloToGoodbye) ++ b.Goodbye() //@rename("Hello", "Goodbye", HelloToGoodbye) +-- @StaticTostatic/c/c2.go -- +@@ -4 +4 @@ +-var Static embed.FS //@rename("Static", "static", StaticTostatic) ++var static embed.FS //@rename("Static", "static", StaticTostatic) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/shadow.txt b/contribs/gnopls/internal/test/marker/testdata/rename/shadow.txt new file mode 100644 index 00000000000..8f6239e7dbb --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/shadow.txt @@ -0,0 +1,36 @@ + +-- shadow.go -- +package shadow + +func _() { + a := true + b, c, _ := A(), B(), D() //@renameerr("A", "a", re"shadowed"),rename("B", "b", BTob),renameerr("b", "c", re"conflict"),rename("D", "d", DTod) + d := false + _, _, _, _ = a, b, c, d +} + +func A() int { + return 0 +} + +func B() int { + return 0 +} + +func D() int { + return 0 +} +-- @BTob/shadow.go -- +@@ -5 +5 @@ +- b, c, _ := A(), B(), D() //@renameerr("A", "a", re"shadowed"),rename("B", "b", BTob),renameerr("b", "c", re"conflict"),rename("D", "d", DTod) ++ b, c, _ := A(), b(), D() //@renameerr("A", "a", re"shadowed"),rename("B", "b", BTob),renameerr("b", "c", re"conflict"),rename("D", "d", DTod) +@@ -14 +14 @@ +-func B() int { ++func b() int { +-- @DTod/shadow.go -- +@@ -5 +5 @@ +- b, c, _ := A(), B(), D() //@renameerr("A", "a", re"shadowed"),rename("B", "b", BTob),renameerr("b", "c", re"conflict"),rename("D", "d", DTod) ++ b, c, _ := A(), B(), d() //@renameerr("A", "a", re"shadowed"),rename("B", "b", BTob),renameerr("b", "c", re"conflict"),rename("D", "d", DTod) +@@ -18 +18 @@ +-func D() int { ++func d() int { diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/testy.txt b/contribs/gnopls/internal/test/marker/testdata/rename/testy.txt new file mode 100644 index 00000000000..e7f75038a06 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/testy.txt @@ -0,0 +1,41 @@ + +-- flags -- +-ignore_extra_diags + +-- testy.go -- +package testy + +type tt int //@rename("tt", "testyType", ttTotestyType) + +func a() { + foo := 42 //@rename("foo", "bar", fooTobar) +} +-- testy_test.go -- +package testy + +import "testing" + +func TestSomething(t *testing.T) { + var x int //@rename("x", "testyX", xTotestyX) + a() //@rename("a", "b", aTob) +} +-- @aTob/testy.go -- +@@ -5 +5 @@ +-func a() { ++func b() { +-- @aTob/testy_test.go -- +@@ -7 +7 @@ +- a() //@rename("a", "b", aTob) ++ b() //@rename("a", "b", aTob) +-- @fooTobar/testy.go -- +@@ -6 +6 @@ +- foo := 42 //@rename("foo", "bar", fooTobar) ++ bar := 42 //@rename("foo", "bar", fooTobar) +-- @ttTotestyType/testy.go -- +@@ -3 +3 @@ +-type tt int //@rename("tt", "testyType", ttTotestyType) ++type testyType int //@rename("tt", "testyType", ttTotestyType) +-- @xTotestyX/testy_test.go -- +@@ -6 +6 @@ +- var x int //@rename("x", "testyX", xTotestyX) ++ var testyX int //@rename("x", "testyX", xTotestyX) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/typeswitch.txt b/contribs/gnopls/internal/test/marker/testdata/rename/typeswitch.txt new file mode 100644 index 00000000000..ec550021745 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/typeswitch.txt @@ -0,0 +1,24 @@ +This test covers the special case of renaming a type switch var. + +-- p.go -- +package p + +func _(x interface{}) { + switch y := x.(type) { //@rename("y", "z", yToZ) + case string: + print(y) //@rename("y", "z", yToZ) + default: + print(y) //@rename("y", "z", yToZ) + } +} + +-- @yToZ/p.go -- +@@ -4 +4 @@ +- switch y := x.(type) { //@rename("y", "z", yToZ) ++ switch z := x.(type) { //@rename("y", "z", yToZ) +@@ -6 +6 @@ +- print(y) //@rename("y", "z", yToZ) ++ print(z) //@rename("y", "z", yToZ) +@@ -8 +8 @@ +- print(y) //@rename("y", "z", yToZ) ++ print(z) //@rename("y", "z", yToZ) diff --git a/contribs/gnopls/internal/test/marker/testdata/rename/unexported.txt b/contribs/gnopls/internal/test/marker/testdata/rename/unexported.txt new file mode 100644 index 00000000000..ed60f666d4b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/rename/unexported.txt @@ -0,0 +1,25 @@ + +This test attempts to rename a.S.X to x, which would make it +inaccessible from its external test package. The rename tool +should report an error rather than wrecking the program. +See issue #59403. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +var S struct{ X int } //@renameerr("X", "x", oops) + +-- a/a_test.go -- +package a_test + +import "example.com/a" + +var Y = a.S.X + +-- @oops -- +a/a.go:3:15: renaming "X" to "x" would make it unexported +a/a_test.go:5:13: breaking references from packages such as "example.com/a_test" diff --git a/contribs/gnopls/internal/test/marker/testdata/selectionrange/selectionrange.txt b/contribs/gnopls/internal/test/marker/testdata/selectionrange/selectionrange.txt new file mode 100644 index 00000000000..d186ae2da52 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/selectionrange/selectionrange.txt @@ -0,0 +1,42 @@ +This test checks selection range functionality. + +-- foo.go -- +package foo + +import "time" + +func Bar(x, y int, t time.Time) int { + zs := []int{1, 2, 3} //@selectionrange("1", a) + + for _, z := range zs { + x = x + z + y + zs[1] //@selectionrange("1", b) + } + + return x + y //@selectionrange("+", c) +} +-- @a -- +Ranges 0: + 5:13-5:14 "1" + 5:7-5:21 "[]int{1, 2, 3}" + 5:1-5:21 "zs := []int{1, 2, 3}" + 4:36-12:1 "{\\n\tzs := []int{...range(\"+\", c)\\n}" + 4:0-12:1 "func Bar(x, y i...range(\"+\", c)\\n}" + 0:0-12:1 "package foo\\n\\nim...range(\"+\", c)\\n}" +-- @b -- +Ranges 0: + 8:21-8:22 "1" + 8:18-8:23 "zs[1]" + 8:6-8:23 "x + z + y + zs[1]" + 8:2-8:23 "x = x + z + y + zs[1]" + 7:22-9:2 "{\\n\t\tx = x + z +...ange(\"1\", b)\\n\t}" + 7:1-9:2 "for _, z := ran...ange(\"1\", b)\\n\t}" + 4:36-12:1 "{\\n\tzs := []int{...range(\"+\", c)\\n}" + 4:0-12:1 "func Bar(x, y i...range(\"+\", c)\\n}" + 0:0-12:1 "package foo\\n\\nim...range(\"+\", c)\\n}" +-- @c -- +Ranges 0: + 11:8-11:13 "x + y" + 11:1-11:13 "return x + y" + 4:36-12:1 "{\\n\tzs := []int{...range(\"+\", c)\\n}" + 4:0-12:1 "func Bar(x, y i...range(\"+\", c)\\n}" + 0:0-12:1 "package foo\\n\\nim...range(\"+\", c)\\n}" diff --git a/contribs/gnopls/internal/test/marker/testdata/signature/generic.txt b/contribs/gnopls/internal/test/marker/testdata/signature/generic.txt new file mode 100644 index 00000000000..e99abbf1dad --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/signature/generic.txt @@ -0,0 +1,21 @@ +This test checks signature help on generic signatures. + +-- g.go -- +package g + +type M[K comparable, V any] map[K]V + +// golang/go#61189: signatureHelp must handle pointer receivers. +func (m *M[K, V]) Get(k K) V { + return (*m)[k] +} + +func Get[K comparable, V any](m M[K, V], k K) V { + return m[k] +} + +func _() { + var m M[int, string] + _ = m.Get(0) //@signature("(", "Get(k int) string", 0) + _ = Get(m, 0) //@signature("0", "Get(m M[int, string], k int) string", 1) +} diff --git a/contribs/gnopls/internal/test/marker/testdata/signature/issue63804.txt b/contribs/gnopls/internal/test/marker/testdata/signature/issue63804.txt new file mode 100644 index 00000000000..b65183391ef --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/signature/issue63804.txt @@ -0,0 +1,13 @@ +Regresson test for #63804: conversion to built-in type caused panic. + +the server's Signature method never returns an actual error, +so the best we can assert is that there is no result. + +-- go.mod -- +module example.com +go 1.18 + +-- a/a.go -- +package a + +var _ = int(123) //@signature("123", "", 0) diff --git a/contribs/gnopls/internal/test/marker/testdata/signature/signature.txt b/contribs/gnopls/internal/test/marker/testdata/signature/signature.txt new file mode 100644 index 00000000000..1da4eb5843e --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/signature/signature.txt @@ -0,0 +1,236 @@ +This test exercises basic tests for signature help. + +-- flags -- +-ignore_extra_diags + +-- go.mod -- +module golang.org/lsptests + +go 1.18 + +-- signature/signature.go -- +// Package signature has tests for signature help. +package signature + +import ( + "bytes" + "encoding/json" + "math/big" + "fmt" +) + +func Foo(a string, b int) (c bool) { + return +} + +func Bar(float64, ...byte) { +} + +type myStruct struct{} + +func (*myStruct) foo(e *json.Decoder) (*big.Int, error) { + return nil, nil +} + +type MyType struct{} + +type MyFunc func(foo int) string + +type Alias = int +type OtherAlias = int +type StringAlias = string + +func AliasSlice(a []*Alias) (b Alias) { return 0 } +func AliasMap(a map[*Alias]StringAlias) (b, c map[*Alias]StringAlias) { return nil, nil } +func OtherAliasMap(a, b map[Alias]OtherAlias) map[Alias]OtherAlias { return nil } + +func Qux() { + Foo("foo", 123) //@signature("(", "Foo(a string, b int) (c bool)", 0) + Foo("foo", 123) //@signature("123", "Foo(a string, b int) (c bool)", 1) + Foo("foo", 123) //@signature(",", "Foo(a string, b int) (c bool)", 0) + Foo("foo", 123) //@signature(" 1", "Foo(a string, b int) (c bool)", 1) + Foo("foo", 123) //@signature(")", "Foo(a string, b int) (c bool)", 1) + Foo("foo", 123) //@signature("o", "Foo(a string, b int) (c bool)", 0) + _ = Foo //@signature("o", "Foo(a string, b int) (c bool)", 0) + Foo //@signature("o", "Foo(a string, b int) (c bool)", 0) + + Bar(13.37, 0x13) //@signature("13.37", "Bar(float64, ...byte)", 0) + Bar(13.37, 0x37) //@signature("0x37", "Bar(float64, ...byte)", 1) + Bar(13.37, 1, 2, 3, 4) //@signature("4", "Bar(float64, ...byte)", 1) + + fn := func(hi, there string) func(i int) rune { + return func(int) rune { return 0 } + } + + fn("hi", "there") //@signature("hi", "", 0) + fn("hi", "there") //@signature(",", "fn(hi string, there string) func(i int) rune", 0) + fn("hi", "there")(1) //@signature("1", "func(i int) rune", 0) + + fnPtr := &fn + (*fnPtr)("hi", "there") //@signature(",", "func(hi string, there string) func(i int) rune", 0) + + var fnIntf interface{} = Foo + fnIntf.(func(string, int) bool)("hi", 123) //@signature("123", "func(string, int) bool", 1) + + (&bytes.Buffer{}).Next(2) //@signature("2", "Next(n int) []byte", 0) + + myFunc := MyFunc(func(n int) string { return "" }) + myFunc(123) //@signature("123", "myFunc(foo int) string", 0) + + var ms myStruct + ms.foo(nil) //@signature("nil", "foo(e *json.Decoder) (*big.Int, error)", 0) + + _ = make([]int, 1, 2) //@signature("2", "make(t Type, size ...int) Type", 1) + + Foo(myFunc(123), 456) //@signature("o(", "Foo(a string, b int) (c bool)", 0) + Foo(myFunc(123), 456) //@signature("(m", "Foo(a string, b int) (c bool)", 0) + Foo( myFunc(123), 456) //@signature(" m", "Foo(a string, b int) (c bool)", 0) + Foo(myFunc(123), 456) //@signature(", ", "Foo(a string, b int) (c bool)", 0) + Foo(myFunc(123), 456) //@signature("456", "Foo(a string, b int) (c bool)", 1) + Foo(myFunc) //@signature(")", "Foo(a string, b int) (c bool)", 0) + Foo(myFunc(123), 456) //@signature("(1", "myFunc(foo int) string", 0) + Foo(myFunc(123), 456) //@signature("123", "myFunc(foo int) string", 0) + + fmt.Println //@signature("ln", "Println(a ...any) (n int, err error)", 0) + fmt.Println(myFunc) //@signature("ln", "Println(a ...any) (n int, err error)", 0) + fmt.Println(myFunc) //@signature("Func", "myFunc(foo int) string", 0) + + var hi string = "hello" + var wl string = " world: %s" + fmt.Println(fmt.Sprintf(wl, myFunc)) //@signature("Func", "myFunc(foo int) string", 0) + fmt.Println(fmt.Sprintf(wl, myFunc)) //@signature("wl", "Sprintf(format string, a ...any) string", 0) + fmt.Println(fmt.Sprintf(wl, myFunc)) //@signature(" m", "Sprintf(format string, a ...any) string", 1) + fmt.Println(hi, fmt.Sprintf(wl, myFunc)) //@signature("Sprint", "Sprintf(format string, a ...any) string", 0) + fmt.Println(hi, fmt.Sprintf(wl, myFunc)) //@signature(" fmt", "Println(a ...any) (n int, err error)", 0) + fmt.Println(hi, fmt.Sprintf(wl, myFunc)) //@signature("hi", "Println(a ...any) (n int, err error)", 0) + + panic("oops!") //@signature(")", "panic(v any)", 0) + println("hello", "world") //@signature(",", "println(args ...Type)", 0) + + Hello(func() { + //@signature("//", "", 0) + }) + + AliasSlice() //@signature(")", "AliasSlice(a []*Alias) (b Alias)", 0) + AliasMap() //@signature(")", "AliasMap(a map[*Alias]StringAlias) (b map[*Alias]StringAlias, c map[*Alias]StringAlias)", 0) + OtherAliasMap() //@signature(")", "OtherAliasMap(a map[Alias]OtherAlias, b map[Alias]OtherAlias) map[Alias]OtherAlias", 0) +} + +func Hello(func()) {} + +-- signature/signature2.go -- +package signature + +func _() { + Foo(//@signature("//", "Foo(a string, b int) (c bool)", 0) + Foo.//@signature("//", "Foo(a string, b int) (c bool)", 0) + Foo.//@signature("oo", "Foo(a string, b int) (c bool)", 0) +} + +-- signature/signature3.go -- +package signature + +func _() { + Foo("hello",//@signature("//", "Foo(a string, b int) (c bool)", 1) +} + +-- signature/nonsignature.go -- +package signature + +var x = (1) //@signature("1)", "", 0) + +-- signature/signature_test.go -- +package signature_test + +import ( + "testing" + + sig "golang.org/lsptests/signature" +) + +func TestSignature(t *testing.T) { + sig.AliasSlice() //@signature(")", "AliasSlice(a []*sig.Alias) (b sig.Alias)", 0) + sig.AliasMap() //@signature(")", "AliasMap(a map[*sig.Alias]sig.StringAlias) (b map[*sig.Alias]sig.StringAlias, c map[*sig.Alias]sig.StringAlias)", 0) + sig.OtherAliasMap() //@signature(")", "OtherAliasMap(a map[sig.Alias]sig.OtherAlias, b map[sig.Alias]sig.OtherAlias) map[sig.Alias]sig.OtherAlias", 0) +} + +-- snippets/snippets.go -- +package snippets + +import ( + "golang.org/lsptests/signature" +) + +type CoolAlias = int //@item(CoolAlias, "CoolAlias", "int", "type") + +type structy struct { + x signature.MyType +} + +func X(_ map[signature.Alias]CoolAlias) (map[signature.Alias]CoolAlias) { + return nil +} + +func _() { + X() //@signature(")", "X(_ map[signature.Alias]CoolAlias) map[signature.Alias]CoolAlias", 0) + _ = signature.MyType{} //@item(literalMyType, "signature.MyType{}", "", "var") + s := structy{ + x: //@snippet(" //", literalMyType, "signature.MyType{\\}") + } +} + +-- importedcomplit/importedcomplit.go -- +package importedcomplit + +import ( + // TODO(rfindley): re-enable after moving to new framework + // "golang.org/lsptests/foo" + + // import completions (separate blocks to avoid comment alignment) + "crypto/elli" //@complete("\" //", cryptoImport) + + "fm" //@complete("\" //", fmtImport) + + "go/pars" //@complete("\" //", parserImport) + + namedParser "go/pars" //@complete("\" //", parserImport) + + "golang.org/lspte" //@complete("\" //", lsptestsImport) + + "golang.org/lsptests/sign" //@complete("\" //", signatureImport) + + "golang.org/lsptests/sign" //@complete("ests", lsptestsImport) + + "golang.org/lsptests/signa" //@complete("na\" //", signatureImport) +) + +func _() { + var V int //@item(icVVar, "V", "int", "var") + + // TODO(rfindley): re-enable after moving to new framework + // _ = foo.StructFoo{V} // complete("}", Value, icVVar) +} + +func _() { + var ( + aa string //@item(icAAVar, "aa", "string", "var") + ab int //@item(icABVar, "ab", "int", "var") + ) + + // TODO(rfindley): re-enable after moving to new framework + // _ = foo.StructFoo{a} // complete("}", abVar, aaVar) + + var s struct { + AA string //@item(icFieldAA, "AA", "string", "field") + AB int //@item(icFieldAB, "AB", "int", "field") + } + + // TODO(rfindley): re-enable after moving to new framework + //_ = foo.StructFoo{s.} // complete("}", icFieldAB, icFieldAA) +} + +/* "fmt" */ //@item(fmtImport, "fmt", "\"fmt\"", "package") +/* "go/parser" */ //@item(parserImport, "parser", "\"go/parser\"", "package") +/* "golang.org/lsptests/signature" */ //@item(signatureImport, "signature", "\"golang.org/lsptests/signature\"", "package") +/* "golang.org/lsptests/" */ //@item(lsptestsImport, "lsptests/", "\"golang.org/lsptests/\"", "package") +/* "crypto/elliptic" */ //@item(cryptoImport, "elliptic", "\"crypto/elliptic\"", "package") diff --git a/contribs/gnopls/internal/test/marker/testdata/stubmethods/basic.txt b/contribs/gnopls/internal/test/marker/testdata/stubmethods/basic.txt new file mode 100644 index 00000000000..e4cfb6d05a0 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/stubmethods/basic.txt @@ -0,0 +1,20 @@ +This test exercises basic 'stub methods' functionality. +See basic_resolve.txt for the same test with resolve support. + +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type C int + +var _ error = C(0) //@suggestedfix(re"C.0.", re"missing method Error", stub) +-- @stub/a/a.go -- +@@ -5 +5,5 @@ ++// Error implements error. ++func (c C) Error() string { ++ panic("unimplemented") ++} ++ diff --git a/contribs/gnopls/internal/test/marker/testdata/stubmethods/basic_resolve.txt b/contribs/gnopls/internal/test/marker/testdata/stubmethods/basic_resolve.txt new file mode 100644 index 00000000000..183b7d526eb --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/stubmethods/basic_resolve.txt @@ -0,0 +1,31 @@ +This test exercises basic 'stub methods' functionality, with resolve support. +See basic.txt for the same test without resolve support. + +-- capabilities.json -- +{ + "textDocument": { + "codeAction": { + "dataSupport": true, + "resolveSupport": { + "properties": ["edit"] + } + } + } +} +-- go.mod -- +module example.com +go 1.12 + +-- a/a.go -- +package a + +type C int + +var _ error = C(0) //@suggestedfix(re"C.0.", re"missing method Error", stub) +-- @stub/a/a.go -- +@@ -5 +5,5 @@ ++// Error implements error. ++func (c C) Error() string { ++ panic("unimplemented") ++} ++ diff --git a/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue61693.txt b/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue61693.txt new file mode 100644 index 00000000000..387b494bc72 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue61693.txt @@ -0,0 +1,26 @@ +This test exercises stub methods functionality with variadic parameters. + +In golang/go#61693 stubmethods was panicking in this case. + +-- go.mod -- +module mod.com + +go 1.18 +-- main.go -- +package main + +type C int + +func F(err ...error) {} + +func _() { + var x error + F(x, C(0)) //@suggestedfix(re"C.0.", re"missing method Error", stub) +} +-- @stub/main.go -- +@@ -5 +5,5 @@ ++// Error implements error. ++func (c C) Error() string { ++ panic("unimplemented") ++} ++ diff --git a/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue61830.txt b/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue61830.txt new file mode 100644 index 00000000000..bf5bcc5ca2e --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue61830.txt @@ -0,0 +1,24 @@ +This test verifies that method stubbing qualifies types relative to the current +package. + +-- p.go -- +package p + +import "io" + +type B struct{} + +type I interface { + M(io.Reader, B) +} + +type A struct{} + +var _ I = &A{} //@suggestedfix(re"&A..", re"missing method M", stub) +-- @stub/p.go -- +@@ -13 +13,5 @@ ++// M implements I. ++func (a *A) M(io.Reader, B) { ++ panic("unimplemented") ++} ++ diff --git a/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue64078.txt b/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue64078.txt new file mode 100644 index 00000000000..50db6f27cfd --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue64078.txt @@ -0,0 +1,36 @@ +This test verifies that the named receiver is generated. + +-- p.go -- +package p + +type A struct{} + +func (aa *A) M1() { + panic("unimplemented") +} + +type I interface { + M1() + M2(aa string) + M3(bb string) + M4() (aa string) +} + +var _ I = &A{} //@suggestedfix(re"&A..", re"missing method M", stub) +-- @stub/p.go -- +@@ -5 +5,15 @@ ++// M2 implements I. ++func (*A) M2(aa string) { ++ panic("unimplemented") ++} ++ ++// M3 implements I. ++func (aa *A) M3(bb string) { ++ panic("unimplemented") ++} ++ ++// M4 implements I. ++func (*A) M4() (aa string) { ++ panic("unimplemented") ++} ++ diff --git a/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue64114.txt b/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue64114.txt new file mode 100644 index 00000000000..35f6db728bb --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/stubmethods/issue64114.txt @@ -0,0 +1,37 @@ +This test verifies that the embedded field has a method with the same name. + +-- issue64114.go -- +package stub + +// Regression test for issue #64114: code action "implement" is not listed. + +var _ WriteTest = (*WriteStruct)(nil) //@suggestedfix("(", re"does not implement", issue64114) + +type WriterTwoStruct struct{} + +// Write implements io.ReadWriter. +func (t *WriterTwoStruct) RRRR(str string) error { + panic("unimplemented") +} + +type WriteTest interface { + RRRR() + WWWW() +} + +type WriteStruct struct { + WriterTwoStruct +} +-- @issue64114/issue64114.go -- +@@ -22 +22,11 @@ ++ ++// RRRR implements WriteTest. ++// Subtle: this method shadows the method (WriterTwoStruct).RRRR of WriteStruct.WriterTwoStruct. ++func (w *WriteStruct) RRRR() { ++ panic("unimplemented") ++} ++ ++// WWWW implements WriteTest. ++func (w *WriteStruct) WWWW() { ++ panic("unimplemented") ++} diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/embeddirective.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/embeddirective.txt new file mode 100644 index 00000000000..821eb10ef20 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/embeddirective.txt @@ -0,0 +1,22 @@ +This test checks the quick fix to add a missing "embed" import. + +-- embed.txt -- +text +-- fix_import.go -- +package embeddirective + +import ( + "io" + "os" +) + +//go:embed embed.txt //@suggestedfix("//go:embed", re`must import "embed"`, fix_import) +var t string + +func unused() { + _ = os.Stdin + _ = io.EOF +} +-- @fix_import/fix_import.go -- +@@ -4 +4 @@ ++ _ "embed" diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/issue65024.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/issue65024.txt new file mode 100644 index 00000000000..afdfce9f1cc --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/issue65024.txt @@ -0,0 +1,78 @@ +Regression example.com for #65024, "incorrect package qualification when +stubbing method in v2 module". + +The second test (a-a) ensures that we don't use path-based heuristics +to guess the PkgName of an import. + +-- a/v2/go.mod -- +module example.com/a/v2 +go 1.18 + +-- a/v2/a.go -- +package a + +type I interface { F() T } + +type T struct {} + +-- a/v2/b/b.go -- +package b + +import "example.com/a/v2" + +type B struct{} + +var _ a.I = &B{} //@ suggestedfix("&B{}", re"does not implement", out) + +// This line makes the diff tidier. + +-- @out/a/v2/b/b.go -- +@@ -7 +7,5 @@ ++// F implements a.I. ++func (b *B) F() a.T { ++ panic("unimplemented") ++} ++ +@@ -10 +15 @@ +- +-- a-a/v2/go.mod -- +// This module has a hyphenated name--how posh. +// It won't do to use it as an identifier. +// The correct name is the one in the package decl, +// which in this case is not what the path heuristic would guess. +module example.com/a-a/v2 +go 1.18 + +-- a-a/v2/a.go -- +package a +type I interface { F() T } +type T struct {} + +-- a-a/v2/b/b.go -- +package b + +// Note: no existing import of a. + +type B struct{} + +var _ I = &B{} //@ suggestedfix("&B{}", re"does not implement", out2) + +// This line makes the diff tidier. + +-- a-a/v2/b/import-a-I.go -- +package b +import "example.com/a-a/v2" +type I = a.I + +-- @out2/a-a/v2/b/b.go -- +@@ -3 +3,2 @@ ++import a "example.com/a-a/v2" ++ +@@ -7 +9,5 @@ ++// F implements a.I. ++func (b *B) F() a.T { ++ panic("unimplemented") ++} ++ +@@ -10 +17 @@ +- diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/missingfunction.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/missingfunction.txt new file mode 100644 index 00000000000..b19095a06f3 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/missingfunction.txt @@ -0,0 +1,127 @@ +This test checks the quick fix for undefined functions. + +-- channels.go -- +package missingfunction + +func channels(s string) { + undefinedChannels(c()) //@suggestedfix("undefinedChannels", re"(undeclared|undefined)", channels) +} + +func c() (<-chan string, chan string) { + return make(<-chan string), make(chan string) +} +-- @channels/channels.go -- +@@ -7 +7,4 @@ ++func undefinedChannels(ch1 <-chan string, ch2 chan string) { ++ panic("unimplemented") ++} ++ +-- consecutive.go -- +package missingfunction + +func consecutiveParams() { + var s string + undefinedConsecutiveParams(s, s) //@suggestedfix("undefinedConsecutiveParams", re"(undeclared|undefined)", consecutive) +} +-- @consecutive/consecutive.go -- +@@ -7 +7,4 @@ ++ ++func undefinedConsecutiveParams(s1, s2 string) { ++ panic("unimplemented") ++} +-- error.go -- +package missingfunction + +func errorParam() { + var err error + undefinedErrorParam(err) //@suggestedfix("undefinedErrorParam", re"(undeclared|undefined)", error) +} +-- @error/error.go -- +@@ -7 +7,4 @@ ++ ++func undefinedErrorParam(err error) { ++ panic("unimplemented") ++} +-- literals.go -- +package missingfunction + +type T struct{} + +func literals() { + undefinedLiterals("hey compiler", T{}, &T{}) //@suggestedfix("undefinedLiterals", re"(undeclared|undefined)", literals) +} +-- @literals/literals.go -- +@@ -8 +8,4 @@ ++ ++func undefinedLiterals(s string, t1 T, t2 *T) { ++ panic("unimplemented") ++} +-- operation.go -- +package missingfunction + +import "time" + +func operation() { + undefinedOperation(10 * time.Second) //@suggestedfix("undefinedOperation", re"(undeclared|undefined)", operation) +} +-- @operation/operation.go -- +@@ -8 +8,4 @@ ++ ++func undefinedOperation(duration time.Duration) { ++ panic("unimplemented") ++} +-- selector.go -- +package missingfunction + +func selector() { + m := map[int]bool{} + undefinedSelector(m[1]) //@suggestedfix("undefinedSelector", re"(undeclared|undefined)", selector) +} +-- @selector/selector.go -- +@@ -7 +7,4 @@ ++ ++func undefinedSelector(b bool) { ++ panic("unimplemented") ++} +-- slice.go -- +package missingfunction + +func slice() { + undefinedSlice([]int{1, 2}) //@suggestedfix("undefinedSlice", re"(undeclared|undefined)", slice) +} +-- @slice/slice.go -- +@@ -6 +6,4 @@ ++ ++func undefinedSlice(i []int) { ++ panic("unimplemented") ++} +-- tuple.go -- +package missingfunction + +func tuple() { + undefinedTuple(b()) //@suggestedfix("undefinedTuple", re"(undeclared|undefined)", tuple) +} + +func b() (string, error) { + return "", nil +} +-- @tuple/tuple.go -- +@@ -7 +7,4 @@ ++func undefinedTuple(s string, err error) { ++ panic("unimplemented") ++} ++ +-- unique_params.go -- +package missingfunction + +func uniqueArguments() { + var s string + var i int + undefinedUniqueArguments(s, i, s) //@suggestedfix("undefinedUniqueArguments", re"(undeclared|undefined)", unique) +} +-- @unique/unique_params.go -- +@@ -8 +8,4 @@ ++ ++func undefinedUniqueArguments(s1 string, i int, s2 string) { ++ panic("unimplemented") ++} diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/noresultvalues.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/noresultvalues.txt new file mode 100644 index 00000000000..5847cea15b7 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/noresultvalues.txt @@ -0,0 +1,18 @@ +This test checks the quick fix for removing extra return values. + +Note: gopls should really discard unnecessary return statements. + +-- noresultvalues.go -- +package typeerrors + +func x() { return nil } //@suggestedfix("nil", re"too many return", x) + +func y() { return nil, "hello" } //@suggestedfix("nil", re"too many return", y) +-- @x/noresultvalues.go -- +@@ -3 +3 @@ +-func x() { return nil } //@suggestedfix("nil", re"too many return", x) ++func x() { return } //@suggestedfix("nil", re"too many return", x) +-- @y/noresultvalues.go -- +@@ -5 +5 @@ +-func y() { return nil, "hello" } //@suggestedfix("nil", re"too many return", y) ++func y() { return } //@suggestedfix("nil", re"too many return", y) diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/self_assignment.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/self_assignment.txt new file mode 100644 index 00000000000..9f3c7ca5618 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/self_assignment.txt @@ -0,0 +1,19 @@ +Test of the suggested fix to remove unnecessary assignments. + +-- a.go -- +package suggestedfix + +import ( + "log" +) + +func goodbye() { + s := "hiiiiiii" + s = s //@suggestedfix("s = s", re"self-assignment", fix) + log.Print(s) +} + +-- @fix/a.go -- +@@ -9 +9 @@ +- s = s //@suggestedfix("s = s", re"self-assignment", fix) ++ //@suggestedfix("s = s", re"self-assignment", fix) diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/stub.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/stub.txt new file mode 100644 index 00000000000..e31494ae461 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/stub.txt @@ -0,0 +1,365 @@ +This test checks the 'implement interface' quick fix. + +-- go.mod -- +module golang.org/lsptests/stub + +go 1.18 + +-- other/other.go -- +package other + +import ( + "bytes" + renamed_context "context" +) + +type Interface interface { + Get(renamed_context.Context) *bytes.Buffer +} + +-- add_selector.go -- +package stub + +import "io" + +// This file tests that if an interface +// method references a type from its own package +// then our implementation must add the import/package selector +// in the concrete method if the concrete type is outside of the interface +// package +var _ io.ReaderFrom = &readerFrom{} //@suggestedfix("&readerFrom", re"cannot use", readerFrom) + +type readerFrom struct{} +-- @readerFrom/add_selector.go -- +@@ -13 +13,5 @@ ++ ++// ReadFrom implements io.ReaderFrom. ++func (*readerFrom) ReadFrom(r io.Reader) (n int64, err error) { ++ panic("unimplemented") ++} +-- assign.go -- +package stub + +import "io" + +func _() { + var br io.ByteWriter + br = &byteWriter{} //@suggestedfix("&", re"does not implement", assign) + _ = br +} + +type byteWriter struct{} +-- @assign/assign.go -- +@@ -12 +12,5 @@ ++ ++// WriteByte implements io.ByteWriter. ++func (b *byteWriter) WriteByte(c byte) error { ++ panic("unimplemented") ++} +-- assign_multivars.go -- +package stub + +import "io" + +func _() { + var br io.ByteWriter + var i int + i, br = 1, &multiByteWriter{} //@suggestedfix("&", re"does not implement", assign_multivars) + _, _ = i, br +} + +type multiByteWriter struct{} +-- @assign_multivars/assign_multivars.go -- +@@ -13 +13,5 @@ ++ ++// WriteByte implements io.ByteWriter. ++func (m *multiByteWriter) WriteByte(c byte) error { ++ panic("unimplemented") ++} +-- call_expr.go -- +package stub + +func main() { + check(&callExpr{}) //@suggestedfix("&", re"does not implement", call_expr) +} + +func check(err error) { + if err != nil { + panic(err) + } +} + +type callExpr struct{} +-- @call_expr/call_expr.go -- +@@ -14 +14,5 @@ ++ ++// Error implements error. ++func (c *callExpr) Error() string { ++ panic("unimplemented") ++} +-- embedded.go -- +package stub + +import ( + "io" + "sort" +) + +var _ embeddedInterface = (*embeddedConcrete)(nil) //@suggestedfix("(", re"does not implement", embedded) + +type embeddedConcrete struct{} + +type embeddedInterface interface { + sort.Interface + io.Reader +} +-- @embedded/embedded.go -- +@@ -12 +12,20 @@ ++// Len implements embeddedInterface. ++func (e *embeddedConcrete) Len() int { ++ panic("unimplemented") ++} ++ ++// Less implements embeddedInterface. ++func (e *embeddedConcrete) Less(i int, j int) bool { ++ panic("unimplemented") ++} ++ ++// Read implements embeddedInterface. ++func (e *embeddedConcrete) Read(p []byte) (n int, err error) { ++ panic("unimplemented") ++} ++ ++// Swap implements embeddedInterface. ++func (e *embeddedConcrete) Swap(i int, j int) { ++ panic("unimplemented") ++} ++ +-- err.go -- +package stub + +func _() { + var br error = &customErr{} //@suggestedfix("&", re"does not implement", err) + _ = br +} + +type customErr struct{} +-- @err/err.go -- +@@ -9 +9,5 @@ ++ ++// Error implements error. ++func (c *customErr) Error() string { ++ panic("unimplemented") ++} +-- function_return.go -- +package stub + +import ( + "io" +) + +func newCloser() io.Closer { + return closer{} //@suggestedfix("c", re"does not implement", function_return) +} + +type closer struct{} +-- @function_return/function_return.go -- +@@ -12 +12,5 @@ ++ ++// Close implements io.Closer. ++func (c closer) Close() error { ++ panic("unimplemented") ++} +-- generic_receiver.go -- +package stub + +import "io" + +// This file tests that that the stub method generator accounts for concrete +// types that have type parameters defined. +var _ io.ReaderFrom = &genReader[string, int]{} //@suggestedfix("&genReader", re"does not implement", generic_receiver) + +type genReader[T, Y any] struct { + T T + Y Y +} +-- @generic_receiver/generic_receiver.go -- +@@ -13 +13,5 @@ ++ ++// ReadFrom implements io.ReaderFrom. ++func (g *genReader[T, Y]) ReadFrom(r io.Reader) (n int64, err error) { ++ panic("unimplemented") ++} +-- ignored_imports.go -- +package stub + +import ( + "compress/zlib" + . "io" + _ "io" +) + +// This file tests that dot-imports and underscore imports +// are properly ignored and that a new import is added to +// reference method types + +var ( + _ Reader + _ zlib.Resetter = (*ignoredResetter)(nil) //@suggestedfix("(", re"does not implement", ignored_imports) +) + +type ignoredResetter struct{} +-- @ignored_imports/ignored_imports.go -- +@@ -19 +19,5 @@ ++ ++// Reset implements zlib.Resetter. ++func (i *ignoredResetter) Reset(r Reader, dict []byte) error { ++ panic("unimplemented") ++} +-- issue2606.go -- +package stub + +type I interface{ error } + +type C int + +var _ I = C(0) //@suggestedfix("C", re"does not implement", issue2606) +-- @issue2606/issue2606.go -- +@@ -7 +7,5 @@ ++// Error implements I. ++func (c C) Error() string { ++ panic("unimplemented") ++} ++ +-- multi_var.go -- +package stub + +import "io" + +// This test ensures that a variable declaration that +// has multiple values on the same line can still be +// analyzed correctly to target the interface implementation +// diagnostic. +var one, two, three io.Reader = nil, &multiVar{}, nil //@suggestedfix("&", re"does not implement", multi_var) + +type multiVar struct{} +-- @multi_var/multi_var.go -- +@@ -12 +12,5 @@ ++ ++// Read implements io.Reader. ++func (m *multiVar) Read(p []byte) (n int, err error) { ++ panic("unimplemented") ++} +-- pointer.go -- +package stub + +import "io" + +func getReaderFrom() io.ReaderFrom { + return &pointerImpl{} //@suggestedfix("&", re"does not implement", pointer) +} + +type pointerImpl struct{} +-- @pointer/pointer.go -- +@@ -10 +10,5 @@ ++ ++// ReadFrom implements io.ReaderFrom. ++func (p *pointerImpl) ReadFrom(r io.Reader) (n int64, err error) { ++ panic("unimplemented") ++} +-- renamed_import.go -- +package stub + +import ( + "compress/zlib" + myio "io" +) + +var _ zlib.Resetter = &myIO{} //@suggestedfix("&", re"does not implement", renamed_import) +var _ myio.Reader + +type myIO struct{} +-- @renamed_import/renamed_import.go -- +@@ -12 +12,5 @@ ++ ++// Reset implements zlib.Resetter. ++func (m *myIO) Reset(r myio.Reader, dict []byte) error { ++ panic("unimplemented") ++} +-- renamed_import_iface.go -- +package stub + +import ( + "golang.org/lsptests/stub/other" +) + +// This file tests that if an interface +// method references an import from its own package +// that the concrete type does not yet import, and that import happens +// to be renamed, then we prefer the renaming of the interface. +var _ other.Interface = &otherInterfaceImpl{} //@suggestedfix("&otherInterfaceImpl", re"does not implement", renamed_import_iface) + +type otherInterfaceImpl struct{} +-- @renamed_import_iface/renamed_import_iface.go -- +@@ -4 +4,2 @@ ++ "bytes" ++ "context" +@@ -14 +16,5 @@ ++ ++// Get implements other.Interface. ++func (o *otherInterfaceImpl) Get(context.Context) *bytes.Buffer { ++ panic("unimplemented") ++} +-- stdlib.go -- +package stub + +import ( + "io" +) + +var _ io.Writer = writer{} //@suggestedfix("w", re"does not implement", stdlib) + +type writer struct{} +-- @stdlib/stdlib.go -- +@@ -10 +10,5 @@ ++ ++// Write implements io.Writer. ++func (w writer) Write(p []byte) (n int, err error) { ++ panic("unimplemented") ++} +-- typedecl_group.go -- +package stub + +// Regression test for Issue #56825: file corrupted by insertion of +// methods after TypeSpec in a parenthesized TypeDecl. + +import "io" + +func newReadCloser() io.ReadCloser { + return rdcloser{} //@suggestedfix("rd", re"does not implement", typedecl_group) +} + +type ( + A int + rdcloser struct{} + B int +) + +func _() { + // Local types can't be stubbed as there's nowhere to put the methods. + // The suggestedfix assertion can't express this yet. TODO(adonovan): support it. + type local struct{} + var _ io.ReadCloser = local{} //@diag("local", re"does not implement") +} +-- @typedecl_group/typedecl_group.go -- +@@ -18 +18,10 @@ ++// Close implements io.ReadCloser. ++func (r rdcloser) Close() error { ++ panic("unimplemented") ++} ++ ++// Read implements io.ReadCloser. ++func (r rdcloser) Read(p []byte) (n int, err error) { ++ panic("unimplemented") ++} ++ diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/undeclared.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/undeclared.txt new file mode 100644 index 00000000000..897e9b14952 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/undeclared.txt @@ -0,0 +1,42 @@ +Tests of suggested fixes for "undeclared name" diagnostics, +which are of ("compiler", "error") type. + +-- go.mod -- +module example.com +go 1.12 + +-- a.go -- +package p + +func a() { + z, _ := 1+y, 11 //@suggestedfix("y", re"(undeclared name|undefined): y", a) + _ = z +} + +-- @a/a.go -- +@@ -4 +4 @@ ++ y := +-- b.go -- +package p + +func b() { + if 100 < 90 { + } else if 100 > n+2 { //@suggestedfix("n", re"(undeclared name|undefined): n", b) + } +} + +-- @b/b.go -- +@@ -4 +4 @@ ++ n := +-- c.go -- +package p + +func c() { + for i < 200 { //@suggestedfix("i", re"(undeclared name|undefined): i", c) + } + r() //@diag("r", re"(undeclared name|undefined): r") +} + +-- @c/c.go -- +@@ -4 +4 @@ ++ i := diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/undeclaredfunc.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/undeclaredfunc.txt new file mode 100644 index 00000000000..d54dcae073f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/undeclaredfunc.txt @@ -0,0 +1,19 @@ +This test checks the quick fix for "undeclared: f" that declares the +missing function. See #47558. + +TODO(adonovan): infer the result variables from the context (int, in this case). + +-- a.go -- +package a + +func _() int { return f(1, "") } //@suggestedfix(re"f.1", re"unde(fined|clared name): f", x) + +-- @x/a.go -- +@@ -3 +3 @@ +-func _() int { return f(1, "") } //@suggestedfix(re"f.1", re"unde(fined|clared name): f", x) ++func _() int { return f(1, "") } +@@ -5 +5,4 @@ ++func f(i int, s string) { ++ panic("unimplemented") ++} //@suggestedfix(re"f.1", re"unde(fined|clared name): f", x) ++ diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/unusedrequire.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/unusedrequire.txt new file mode 100644 index 00000000000..8ec46e9ea68 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/unusedrequire.txt @@ -0,0 +1,25 @@ +This test checks the suggested fix to remove unused require statements from +go.mod files. + +-- flags -- +-write_sumfile=a + +-- proxy/example.com@v1.0.0/x.go -- +package pkg +const X = 1 + +-- a/go.mod -- +module mod.com + +go 1.14 + +require example.com v1.0.0 //@suggestedfix("require", re"not used", a) + +-- @a/a/go.mod -- +@@ -4,3 +4 @@ +- +-require example.com v1.0.0 //@suggestedfix("require", re"not used", a) +- +-- a/main.go -- +package main +func main() {} diff --git a/contribs/gnopls/internal/test/marker/testdata/suggestedfix/unusedrequire_gowork.txt b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/unusedrequire_gowork.txt new file mode 100644 index 00000000000..73b0eb9607f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/suggestedfix/unusedrequire_gowork.txt @@ -0,0 +1,48 @@ +This test checks the suggested fix to remove unused require statements from +go.mod files, when a go.work file is used. + +Note that unlike unusedrequire.txt, we need not write go.sum files when +a go.work file is used. + +-- proxy/example.com@v1.0.0/x.go -- +package pkg +const X = 1 + +-- go.work -- +go 1.21 + +use ( + ./a + ./b +) +-- a/go.mod -- +module mod.com/a + +go 1.14 + +require example.com v1.0.0 //@suggestedfix("require", re"not used", a) + +-- @a/a/go.mod -- +@@ -4,3 +4 @@ +- +-require example.com v1.0.0 //@suggestedfix("require", re"not used", a) +- +-- a/main.go -- +package main +func main() {} + +-- b/go.mod -- +module mod.com/b + +go 1.14 + +require example.com v1.0.0 //@suggestedfix("require", re"not used", b) + +-- @b/b/go.mod -- +@@ -4,3 +4 @@ +- +-require example.com v1.0.0 //@suggestedfix("require", re"not used", b) +- +-- b/main.go -- +package main +func main() {} diff --git a/contribs/gnopls/internal/test/marker/testdata/symbol/basic.txt b/contribs/gnopls/internal/test/marker/testdata/symbol/basic.txt new file mode 100644 index 00000000000..49c54b0fdb0 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/symbol/basic.txt @@ -0,0 +1,114 @@ +Basic tests of textDocument/documentSymbols. + +-- symbol.go -- +package main + +//@symbol(want) + +import "io" + +var _ = 1 + +var x = 42 + +var nested struct { + nestedField struct { + f int + } +} + +const y = 43 + +type Number int + +type Alias = string + +type NumberAlias = Number + +type ( + Boolean bool + BoolAlias = bool +) + +type Foo struct { + Quux + W io.Writer + Bar int + baz string + funcField func(int) int +} + +type Quux struct { + X, Y float64 +} + +type EmptyStruct struct{} + +func (f Foo) Baz() string { + return f.baz +} + +func _() {} + +func (q *Quux) Do() {} + +func main() { +} + +type Stringer interface { + String() string +} + +type ABer interface { + B() + A() string +} + +type WithEmbeddeds interface { + Do() + ABer + io.Writer +} + +type EmptyInterface interface{} + +func Dunk() int { return 0 } + +func dunk() {} + +-- @want -- +(*Quux).Do "func()" +(Foo).Baz "func() string" +2 lines +ABer "interface{...}" +3 lines +ABer.A "func() string" +ABer.B "func()" +Alias "string" +BoolAlias "bool" +Boolean "bool" +Dunk "func() int" +EmptyInterface "interface{}" +EmptyStruct "struct{}" +Foo "struct{...}" +6 lines +Foo.Bar "int" +Foo.Quux "Quux" +Foo.W "io.Writer" +Foo.baz "string" +Foo.funcField "func(int) int" +Number "int" +NumberAlias "Number" +Quux "struct{...}" +2 lines +Quux.X "float64" +Quux.Y "float64" +Stringer "interface{...}" +2 lines +Stringer.String "func() string" +WithEmbeddeds "interface{...}" +4 lines +WithEmbeddeds.ABer "ABer" +WithEmbeddeds.Do "func()" +WithEmbeddeds.Writer "io.Writer" +dunk "func()" +main "func()" +1 lines +nested "struct{...}" +4 lines +nested.nestedField "struct{...}" +2 lines +nested.nestedField.f "int" +x "" +y "" diff --git a/contribs/gnopls/internal/test/marker/testdata/symbol/generic.txt b/contribs/gnopls/internal/test/marker/testdata/symbol/generic.txt new file mode 100644 index 00000000000..1254851ad14 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/symbol/generic.txt @@ -0,0 +1,23 @@ +Basic tests of textDocument/documentSymbols with generics. + +-- symbol.go -- +//@symbol(want) + +package main + +type T[P any] struct { + F P +} + +type Constraint interface { + ~int | struct{ int } + interface{ M() } +} + +-- @want -- +Constraint "interface{...}" +3 lines +Constraint.interface{...} "" +Constraint.interface{...}.M "func()" +Constraint.~int | struct{int} "" +T "struct{...}" +2 lines +T.F "P" diff --git a/contribs/gnopls/internal/test/marker/testdata/token/comment.txt b/contribs/gnopls/internal/test/marker/testdata/token/comment.txt new file mode 100644 index 00000000000..a5ce9139c4e --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/token/comment.txt @@ -0,0 +1,55 @@ +This test checks the semantic tokens in comments (golang/go#64648). + +There will be doc links in the comments to reference other objects. Parse these +links and output tokens according to the referenced object types, so that the +editor can highlight them. This will help in checking the doc link errors and +reading comments in the code. + +-- settings.json -- +{ + "semanticTokens": true +} + +-- a.go -- +package p + +import "strconv" + +const A = 1 +var B = 2 + +type Foo int + + +// [F] accept a [Foo], and print it. //@token("F", "function", ""),token("Foo", "type", "defaultLibrary number") +func F(v Foo) { + println(v) + +} + +/* + [F1] print [A] and [B] //@token("F1", "function", ""),token("A", "variable", ""),token("B", "variable", "") +*/ +func F1() { + // print [A] and [B]. //@token("A", "variable", ""),token("B", "variable", "") + println(A, B) +} + +// [F2] use [strconv.Atoi] convert s, then print it //@token("F2", "function", ""),token("strconv", "namespace", ""),token("Atoi", "function", "") +func F2(s string) { + a, _ := strconv.Atoi("42") + b, _ := strconv.Atoi("42") + println(a, b) // this is a tail comment in F2 //hover(F2, "F2", F2) +} +-- b.go -- +package p + +// [F3] accept [*Foo] //@token("F3", "function", ""),token("Foo", "type", "defaultLibrary number") +func F3(v *Foo) { + println(*v) +} + +// [F4] equal [strconv.Atoi] //@token("F4", "function", ""),token("strconv", "namespace", ""),token("Atoi", "function", "") +func F4(s string) (int, error) { + return 0, nil +} diff --git a/contribs/gnopls/internal/test/marker/testdata/token/illformed.txt b/contribs/gnopls/internal/test/marker/testdata/token/illformed.txt new file mode 100644 index 00000000000..2a3b81e46a5 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/token/illformed.txt @@ -0,0 +1,15 @@ +This test checks semanticTokens on ill-formed code. +(Regression test for #68205.) + +-- settings.json -- +{ + "semanticTokens": true +} + +-- flags -- +-ignore_extra_diags + +-- a.go -- +package p + +type _ <-<-chan int //@ token("<-", "operator", ""), token("chan", "keyword", "") diff --git a/contribs/gnopls/internal/test/marker/testdata/token/range.txt b/contribs/gnopls/internal/test/marker/testdata/token/range.txt new file mode 100644 index 00000000000..2f98c043d8e --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/token/range.txt @@ -0,0 +1,29 @@ +This test checks the output of textDocument/semanticTokens/range. + +TODO: add more assertions. + +-- settings.json -- +{ + "semanticTokens": true +} + +-- a.go -- +package p //@token("package", "keyword", "") + +const C = 42 //@token("C", "variable", "definition readonly") + +func F() { //@token("F", "function", "definition") + x := 2 + 3//@token("x", "variable", "definition"),token("2", "number", ""),token("+", "operator", "") + _ = x //@token("x", "variable", "") + _ = F //@token("F", "function", "") +} + +func _() { + // A goto's label cannot be found by ascending the syntax tree. + goto loop //@ token("goto", "keyword", ""), token("loop", "label", "") + +loop: //@token("loop", "label", "definition") + for { + continue loop //@ token("continue", "keyword", ""), token("loop", "label", "") + } +} diff --git a/contribs/gnopls/internal/test/marker/testdata/typedef/typedef.txt b/contribs/gnopls/internal/test/marker/testdata/typedef/typedef.txt new file mode 100644 index 00000000000..3bc9dabdb8b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/typedef/typedef.txt @@ -0,0 +1,68 @@ +This test exercises the textDocument/typeDefinition action. + +-- typedef.go -- +package typedef + +type Struct struct { //@loc(Struct, "Struct"), + Field string +} + +type Int int //@loc(Int, "Int") + +func _() { + var ( + value Struct + point *Struct + ) + _ = value //@typedef("value", Struct) + _ = point //@typedef("point", Struct) + + var ( + array [3]Struct + slice []Struct + ch chan Struct + complex [3]chan *[5][]Int + ) + _ = array //@typedef("array", Struct) + _ = slice //@typedef("slice", Struct) + _ = ch //@typedef("ch", Struct) + _ = complex //@typedef("complex", Int) + + var s struct { + x struct { + xx struct { + field1 []Struct + field2 []Int + } + } + } + _ = s.x.xx.field1 //@typedef("field1", Struct) + _ = s.x.xx.field2 //@typedef("field2", Int) +} + +func F1() Int { return 0 } +func F2() (Int, float64) { return 0, 0 } +func F3() (Struct, int, bool, error) { return Struct{}, 0, false, nil } +func F4() (**int, Int, bool, *error) { return nil, 0, false, nil } +func F5() (int, float64, error, Struct) { return 0, 0, nil, Struct{} } +func F6() (int, float64, ***Struct, error) { return 0, 0, nil, nil } + +func _() { + F1() //@typedef("F1", Int) + F2() //@typedef("F2", Int) + F3() //@typedef("F3", Struct) + F4() //@typedef("F4", Int) + F5() //@typedef("F5", Struct) + F6() //@typedef("F6", Struct) + + f := func() Int { return 0 } + f() //@typedef("f", Int) +} + +// https://github.com/golang/go/issues/38589#issuecomment-620350922 +func _() { + type myFunc func(int) Int //@loc(myFunc, "myFunc") + + var foo myFunc + _ = foo() //@typedef("foo", myFunc), diag(")", re"not enough arguments") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/workfile/godebug.txt b/contribs/gnopls/internal/test/marker/testdata/workfile/godebug.txt new file mode 100644 index 00000000000..fb7d7d5df2d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/workfile/godebug.txt @@ -0,0 +1,60 @@ +This test basic gopls functionality in a workspace with a godebug +directive in its modfile. + +-- flags -- +-min_go=go1.23 + +-- a/go.work -- +go 1.23 + +use . + +godebug ( + gotypesalias=0 +) +godebug gotypesalias=1 + +-- a/go.mod -- +module example.com/a + +go 1.23 + +-- a/a.go -- +package a + +import "example.com/a/b" + +const A = b.B //@def("B", B) + +-- a/b/b.go -- +package b + +const B = 42 //@loc(B, "B") + +-- format/go.work -- +go 1.23 //@format(formatted) + +use . + +godebug ( +gotypesalias=0 +) +godebug gotypesalias=1 + +-- @formatted -- +go 1.23 //@format(formatted) + +use . + +godebug ( + gotypesalias=0 +) + +godebug gotypesalias=1 +-- format/go.mod -- +module example.com/format + +go 1.23 + +-- format/p.go -- +package format diff --git a/contribs/gnopls/internal/test/marker/testdata/workfile/godebug_bad.txt b/contribs/gnopls/internal/test/marker/testdata/workfile/godebug_bad.txt new file mode 100644 index 00000000000..52ad7c07d57 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/workfile/godebug_bad.txt @@ -0,0 +1,22 @@ +This test checks that we surface the error for unexpected godebug values. + +TODO(golang/go#67623): the diagnostic should be on the bad godebug value. + +-- flags -- +-min_go=go1.23 +-errors_ok + +-- go.work -- +go 1.23 + +use . + +godebug ( + gotypealias=0 // misspelled +) +godebug gotypesalias=1 + +-- go.mod -- +module example.com/m //@diag("module", re`unknown godebug "gotypealias"`) + +go 1.23 diff --git a/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/allscope.txt b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/allscope.txt new file mode 100644 index 00000000000..18fe4e5446f --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/allscope.txt @@ -0,0 +1,30 @@ +This test verifies behavior when "symbolScope" is set to "all". + +-- settings.json -- +{ + "symbolStyle": "full", + "symbolMatcher": "casesensitive", + "symbolScope": "all" +} + +-- go.mod -- +module mod.test/symbols + +go 1.18 + +-- query.go -- +package symbols + +//@workspacesymbol("fmt.Println", println) + +-- fmt/fmt.go -- +package fmt + +import "fmt" + +func Println(s string) { + fmt.Println(s) +} +-- @println -- +fmt/fmt.go:5:6-13 mod.test/symbols/fmt.Println Function +<unknown> fmt.Println Function diff --git a/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/caseinsensitive.txt b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/caseinsensitive.txt new file mode 100644 index 00000000000..f853e8da81b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/caseinsensitive.txt @@ -0,0 +1,26 @@ +This file contains test for symbol matches using the caseinsensitive matcher. + +-- settings.json -- +{ + "symbolMatcher": "caseinsensitive" +} + +-- go.mod -- +module mod.test/caseinsensitive + +go 1.18 + +-- p.go -- +package caseinsensitive + +//@workspacesymbol("", blank) +//@workspacesymbol("randomgophervar", randomgophervar) + +var RandomGopherVariableA int +var randomgopherVariableB int +var RandomGopherOtherVariable int + +-- @blank -- +-- @randomgophervar -- +p.go:6:5-26 RandomGopherVariableA Variable +p.go:7:5-26 randomgopherVariableB Variable diff --git a/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/casesensitive.txt b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/casesensitive.txt new file mode 100644 index 00000000000..725e9dbb52d --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/casesensitive.txt @@ -0,0 +1,116 @@ +This file contains tests for symbol matches using the casesensitive matcher. + +For historical reasons, it also verifies general behavior of the symbol search. + +-- settings.json -- +{ + "symbolMatcher": "casesensitive" +} + +-- go.mod -- +module mod.test/casesensitive + +go 1.18 + +-- main.go -- +package main + +//@workspacesymbol("main.main", main) +//@workspacesymbol("p.Message", Message) +//@workspacesymbol("main.myvar", myvar) +//@workspacesymbol("main.myType", myType) +//@workspacesymbol("main.myType.Blahblah", blahblah) +//@workspacesymbol("main.myStruct", myStruct) +//@workspacesymbol("main.myStruct.myStructField", myStructField) +//@workspacesymbol("main.myInterface", myInterface) +//@workspacesymbol("main.myInterface.DoSomeCoolStuff", DoSomeCoolStuff) +//@workspacesymbol("main.embed.myStruct", embeddedStruct) +//@workspacesymbol("main.embed.nestedStruct.nestedStruct2.int", int) +//@workspacesymbol("main.embed.nestedInterface.myInterface", nestedInterface) +//@workspacesymbol("main.embed.nestedInterface.nestedMethod", nestedMethod) +//@workspacesymbol("dunk", dunk) +//@workspacesymbol("Dunk", Dunk) + +import ( + "encoding/json" + "fmt" +) + +func main() { // function + fmt.Println("Hello") +} + +var myvar int // variable + +type myType string // basic type + +type myDecoder json.Decoder // to use the encoding/json import + +func (m *myType) Blahblah() {} // method + +type myStruct struct { // struct type + myStructField int // struct field +} + +type myInterface interface { // interface + DoSomeCoolStuff() string // interface method +} + +type embed struct { + myStruct + + nestedStruct struct { + nestedField int + + nestedStruct2 struct { + int + } + } + + nestedInterface interface { + myInterface + nestedMethod() + } +} + +func Dunk() int { return 0 } + +func dunk() {} + +-- p/p.go -- +package p + +const Message = "Hello World." // constant +-- @DoSomeCoolStuff -- +main.go:41:2-17 main.myInterface.DoSomeCoolStuff Method +-- @Dunk -- +main.go:61:6-10 Dunk Function +-- @Message -- +p/p.go:3:7-14 p.Message Constant +-- @blahblah -- +main.go:34:18-26 main.myType.Blahblah Method +-- @dunk -- +main.go:63:6-10 dunk Function +-- @int -- +main.go:51:4-7 main.embed.nestedStruct.nestedStruct2.int Field +-- @main -- +main.go:24:6-10 main.main Function +-- @myInterface -- +main.go:40:6-17 main.myInterface Interface +main.go:41:2-17 main.myInterface.DoSomeCoolStuff Method +-- @myStruct -- +main.go:36:6-14 main.myStruct Struct +main.go:37:2-15 main.myStruct.myStructField Field +-- @myStructField -- +main.go:37:2-15 main.myStruct.myStructField Field +-- @myType -- +main.go:30:6-12 main.myType Class +main.go:34:18-26 main.myType.Blahblah Method +-- @myvar -- +main.go:28:5-10 main.myvar Variable +-- @nestedInterface -- +main.go:56:3-14 main.embed.nestedInterface.myInterface Interface +-- @nestedMethod -- +main.go:57:3-15 main.embed.nestedInterface.nestedMethod Method +-- @embeddedStruct -- +main.go:45:2-10 main.embed.myStruct Field diff --git a/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/issue44806.txt b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/issue44806.txt new file mode 100644 index 00000000000..b2cd0b5c5a2 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/issue44806.txt @@ -0,0 +1,27 @@ +This test verifies the fix for the crash encountered in golang/go#44806. + +-- go.mod -- +module mod.test/symbol + +go 1.18 +-- symbol.go -- +package symbol + +//@workspacesymbol("m", m) + +type T struct{} + +// We should accept all valid receiver syntax when scanning symbols. +func (*(T)) m1() {} +func (*T) m2() {} +func (T) m3() {} +func ((T)) m4() {} +func ((*T)) m5() {} + +-- @m -- +symbol.go:8:13-15 T.m1 Method +symbol.go:9:11-13 T.m2 Method +symbol.go:10:10-12 T.m3 Method +symbol.go:11:12-14 T.m4 Method +symbol.go:12:13-15 T.m5 Method +symbol.go:5:6-7 symbol.T Struct diff --git a/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/workspacesymbol.txt b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/workspacesymbol.txt new file mode 100644 index 00000000000..cdf9e26b4b2 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/workspacesymbol.txt @@ -0,0 +1,72 @@ +This test contains tests for basic functionality of the workspace/symbol +request. + +TODO(rfindley): add a test for the legacy 'fuzzy' symbol matcher using setting ("symbolMatcher": "fuzzy"). This test uses the default matcher ("fastFuzzy"). + +-- go.mod -- +module mod.test/symbols + +go 1.18 + +-- query.go -- +package symbols + +//@workspacesymbol("rgop", rgop) +//@workspacesymbol("randoma", randoma) +//@workspacesymbol("randomb", randomb) + +-- a/a.go -- +package a + +var RandomGopherVariableA = "a" + +const RandomGopherConstantA = "a" + +const ( + randomgopherinvariable = iota +) + +-- a/a_test.go -- +package a + +var RandomGopherTestVariableA = "a" + +-- a/a_x_test.go -- +package a_test + +var RandomGopherXTestVariableA = "a" + +-- b/b.go -- +package b + +var RandomGopherVariableB = "b" + +type RandomGopherStructB struct { + Bar int +} + +-- @rgop -- +b/b.go:5:6-25 RandomGopherStructB Struct +a/a.go:5:7-28 RandomGopherConstantA Constant +a/a.go:3:5-26 RandomGopherVariableA Variable +b/b.go:3:5-26 RandomGopherVariableB Variable +a/a_test.go:3:5-30 RandomGopherTestVariableA Variable +a/a_x_test.go:3:5-31 RandomGopherXTestVariableA Variable +a/a.go:8:2-24 randomgopherinvariable Constant +b/b.go:6:2-5 RandomGopherStructB.Bar Field +-- @randoma -- +a/a.go:5:7-28 RandomGopherConstantA Constant +a/a.go:3:5-26 RandomGopherVariableA Variable +b/b.go:3:5-26 RandomGopherVariableB Variable +a/a.go:8:2-24 randomgopherinvariable Constant +a/a_test.go:3:5-30 RandomGopherTestVariableA Variable +a/a_x_test.go:3:5-31 RandomGopherXTestVariableA Variable +b/b.go:6:2-5 RandomGopherStructB.Bar Field +-- @randomb -- +b/b.go:5:6-25 RandomGopherStructB Struct +a/a.go:3:5-26 RandomGopherVariableA Variable +b/b.go:3:5-26 RandomGopherVariableB Variable +a/a.go:8:2-24 randomgopherinvariable Constant +a/a_test.go:3:5-30 RandomGopherTestVariableA Variable +a/a_x_test.go:3:5-31 RandomGopherXTestVariableA Variable +b/b.go:6:2-5 RandomGopherStructB.Bar Field diff --git a/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/wsscope.txt b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/wsscope.txt new file mode 100644 index 00000000000..e49483ad450 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/workspacesymbol/wsscope.txt @@ -0,0 +1,29 @@ +This test verifies behavior when "symbolScope" is set to "workspace". + +-- settings.json -- +{ + "symbolStyle": "full", + "symbolMatcher": "casesensitive", + "symbolScope": "workspace" +} + +-- go.mod -- +module mod.test/symbols + +go 1.18 + +-- query.go -- +package symbols + +//@workspacesymbol("fmt.Println", println) + +-- fmt/fmt.go -- +package fmt + +import "fmt" + +func Println(s string) { + fmt.Println(s) +} +-- @println -- +fmt/fmt.go:5:6-13 mod.test/symbols/fmt.Println Function diff --git a/contribs/gnopls/internal/test/marker/testdata/zeroconfig/adhoc.txt b/contribs/gnopls/internal/test/marker/testdata/zeroconfig/adhoc.txt new file mode 100644 index 00000000000..ccef3b6fe6b --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/zeroconfig/adhoc.txt @@ -0,0 +1,49 @@ +This test checks that gopls works with multiple ad-hoc packages, which lack +a go.mod file. + +We should be able to import standard library packages, get diagnostics, and +reference symbols defined in the same directory. + +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println(mainMsg) //@def("mainMsg", mainMsg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} +-- main2.go -- +package main + +const mainMsg = "main" //@loc(mainMsg, "mainMsg") + +-- a/a.go -- +package a + +import "fmt" + +func _() { + fmt.Println(aMsg) //@def("aMsg", aMsg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} + +-- a/a2.go -- +package a + +const aMsg = "a" //@loc(aMsg, "aMsg") + +-- b/b.go -- +package b + +import "fmt" + +func _() { + fmt.Println(bMsg) //@def("bMsg", bMsg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} + +-- b/b2.go -- +package b + +const bMsg = "b" //@loc(bMsg, "bMsg") diff --git a/contribs/gnopls/internal/test/marker/testdata/zeroconfig/dynamicports.txt b/contribs/gnopls/internal/test/marker/testdata/zeroconfig/dynamicports.txt new file mode 100644 index 00000000000..6dcdfe4cd7a --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/zeroconfig/dynamicports.txt @@ -0,0 +1,118 @@ +This test checks that the zero-config algorithm selects Views to cover first +class ports. + +In this test, package a imports b, and b imports c. Package a contains files +constrained by go:build directives, package b contains files constrained by the +GOOS matching their file name, and package c is unconstrained. Various +assertions check that diagnostics and navigation work as expected. + +-- go.mod -- +module golang.org/lsptests + +-- a/a.go -- +package a + +import "golang.org/lsptests/b" + +var _ = b.F //@loc(F, "F") + +-- a/linux64.go -- +//go:build (linux && amd64) + +package a + +import "golang.org/lsptests/b" + +var _ int = 1<<32 -1 // OK on 64 bit platforms. Compare linux32.go below. + +var ( + _ = b.LinuxOnly //@def("LinuxOnly", LinuxOnly) + _ = b.DarwinOnly //@diag("DarwinOnly", re"(undefined|declared)") + _ = b.WindowsOnly //@diag("WindowsOnly", re"(undefined|declared)") +) + +-- a/linux32.go -- +//go:build (linux && 386) + +package a + +import "golang.org/lsptests/b" + +var _ int = 1<<32 -1 //@diag("1<<32", re"overflows") + +var ( + _ = b.LinuxOnly //@def("LinuxOnly", LinuxOnly) + _ = b.DarwinOnly //@diag("DarwinOnly", re"(undefined|declared)") + _ = b.WindowsOnly //@diag("WindowsOnly", re"(undefined|declared)") +) + +-- a/darwin64.go -- +//go:build (darwin && amd64) + +package a + +import "golang.org/lsptests/b" + +var ( + _ = b.LinuxOnly //@diag("LinuxOnly", re"(undefined|declared)") + _ = b.DarwinOnly //@def("DarwinOnly", DarwinOnly) + _ = b.WindowsOnly //@diag("WindowsOnly", re"(undefined|declared)") +) + +-- a/windows64.go -- +//go:build (windows && amd64) + +package a + +import "golang.org/lsptests/b" + +var ( + _ = b.LinuxOnly //@diag("LinuxOnly", re"(undefined|declared)") + _ = b.DarwinOnly //@diag("DarwinOnly", re"(undefined|declared)") + _ = b.WindowsOnly //@def("WindowsOnly", WindowsOnly) +) + +-- b/b_other.go -- +//go:build !linux && !darwin && !windows +package b + +func F() {} + +-- b/b_linux.go -- +package b + +import "golang.org/lsptests/c" + +func F() { //@refs("F", "F", F) + x := c.Common //@diag("x", re"not used"),def("Common", Common) +} + +const LinuxOnly = "darwin" //@loc(LinuxOnly, "LinuxOnly") + +-- b/b_darwin.go -- +package b + +import "golang.org/lsptests/c" + +func F() { //@refs("F", "F", F) + x := c.Common //@diag("x", re"not used"),def("Common", Common) +} + +const DarwinOnly = "darwin" //@loc(DarwinOnly, "DarwinOnly") + +-- b/b_windows.go -- +package b + +import "golang.org/lsptests/c" + +func F() { //@refs("F", "F", F) + x := c.Common //@diag("x", re"not used"),def("Common", Common) +} + +const WindowsOnly = "windows" //@loc(WindowsOnly, "WindowsOnly") + +-- c/c.go -- +package c + +const Common = 0 //@loc(Common, "Common") + diff --git a/contribs/gnopls/internal/test/marker/testdata/zeroconfig/nested.txt b/contribs/gnopls/internal/test/marker/testdata/zeroconfig/nested.txt new file mode 100644 index 00000000000..e76bb0c6ec0 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/zeroconfig/nested.txt @@ -0,0 +1,72 @@ +This test checks that gopls works with nested modules, including multiple +nested modules. + +-- flags -- +-min_go=go1.20 + +-- main.go -- +package main + +import "fmt" + +func main() { + fmt.Println(mainMsg) //@def("mainMsg", mainMsg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} +-- main2.go -- +package main + +const mainMsg = "main" //@loc(mainMsg, "mainMsg") + +-- mod1/go.mod -- +module golang.org/lsptests/mod1 + +go 1.20 + +-- mod1/a/a.go -- +package a + +import ( + "fmt" + "golang.org/lsptests/mod1/b" +) + +func _() { + fmt.Println(b.Msg) //@def("Msg", Msg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} + +-- mod1/a/tagged.go -- +//go:build tag1 + +// golang/go#60776: verify that we get an accurate error about build tags +// here, rather than an inaccurate error suggesting to add a go.work +// file (which won't help). +package a //@diag(re`package (a)`, re`excluded due to its build tags`) + +-- mod1/b/b.go -- +package b + +const Msg = "1" //@loc(Msg, "Msg") + +-- mod2/go.mod -- +module golang.org/lsptests/mod2 + +require golang.org/lsptests/mod1 v0.0.1 + +replace golang.org/lsptests/mod1 => ../mod1 + +go 1.20 + +-- mod2/c/c.go -- +package c + +import ( + "fmt" + "golang.org/lsptests/mod1/b" +) + +func _() { + fmt.Println(b.Msg) //@def("Msg", Msg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} diff --git a/contribs/gnopls/internal/test/marker/testdata/zeroconfig/nonworkspacemodule.txt b/contribs/gnopls/internal/test/marker/testdata/zeroconfig/nonworkspacemodule.txt new file mode 100644 index 00000000000..747635e6bb1 --- /dev/null +++ b/contribs/gnopls/internal/test/marker/testdata/zeroconfig/nonworkspacemodule.txt @@ -0,0 +1,79 @@ +This test checks that gopls works with modules that aren't included in the +workspace file. + +-- go.work -- +go 1.20 + +use ( + ./a + ./b +) + +-- a/go.mod -- +module golang.org/lsptests/a + +go 1.18 + +-- a/a.go -- +package a + +import ( + "fmt" + "golang.org/lsptests/a/lib" +) + +func _() { + fmt.Println(lib.Msg) //@def("Msg", aMsg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} + +-- a/lib/lib.go -- +package lib + +const Msg = "hi" //@loc(aMsg, "Msg") + +-- b/go.mod -- +module golang.org/lsptests/b + +go 1.18 + +-- b/b.go -- +package b + +import ( + "fmt" + "golang.org/lsptests/b/lib" +) + +func main() { + fmt.Println(lib.Msg) //@def("Msg", bMsg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} + +-- b/lib/lib.go -- +package lib + +const Msg = "hi" //@loc(bMsg, "Msg") + +-- c/go.mod -- +module golang.org/lsptests/c + +go 1.18 + +-- c/c.go -- +package c + +import ( + "fmt" + "golang.org/lsptests/c/lib" +) + +func main() { + fmt.Println(lib.Msg) //@def("Msg", cMsg) + fmt.Println(undef) //@diag("undef", re"undefined|undeclared") +} + +-- c/lib/lib.go -- +package lib + +const Msg = "hi" //@loc(cMsg, "Msg") diff --git a/contribs/gnopls/internal/util/README.md b/contribs/gnopls/internal/util/README.md new file mode 100644 index 00000000000..6be2ad51efa --- /dev/null +++ b/contribs/gnopls/internal/util/README.md @@ -0,0 +1,7 @@ +# util + +This directory is not a Go package. + +Its subdirectories are for utility packages, defined as implementation +helpers (not core machinery) that are used in different ways across +the gopls codebase. \ No newline at end of file diff --git a/contribs/gnopls/internal/util/astutil/purge.go b/contribs/gnopls/internal/util/astutil/purge.go new file mode 100644 index 00000000000..95117c568ba --- /dev/null +++ b/contribs/gnopls/internal/util/astutil/purge.go @@ -0,0 +1,74 @@ +// 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 astutil provides various AST utility functions for gopls. +package astutil + +import ( + "bytes" + "go/scanner" + "go/token" + + "golang.org/x/tools/gopls/internal/util/safetoken" +) + +// PurgeFuncBodies returns a copy of src in which the contents of each +// outermost {...} region except struct and interface types have been +// deleted. This reduces the amount of work required to parse the +// top-level declarations. +// +// PurgeFuncBodies does not preserve newlines or position information. +// Also, if the input is invalid, parsing the output of +// PurgeFuncBodies may result in a different tree due to its effects +// on parser error recovery. +func PurgeFuncBodies(src []byte) []byte { + // Destroy the content of any {...}-bracketed regions that are + // not immediately preceded by a "struct" or "interface" + // token. That includes function bodies, composite literals, + // switch/select bodies, and all blocks of statements. + // This will lead to non-void functions that don't have return + // statements, which of course is a type error, but that's ok. + + var out bytes.Buffer + file := token.NewFileSet().AddFile("", -1, len(src)) + var sc scanner.Scanner + sc.Init(file, src, nil, 0) + var prev token.Token + var cursor int // last consumed src offset + var braces []token.Pos // stack of unclosed braces or -1 for struct/interface type + for { + pos, tok, _ := sc.Scan() + if tok == token.EOF { + break + } + switch tok { + case token.COMMENT: + // TODO(adonovan): opt: skip, to save an estimated 20% of time. + + case token.LBRACE: + if prev == token.STRUCT || prev == token.INTERFACE { + pos = -1 + } + braces = append(braces, pos) + + case token.RBRACE: + if last := len(braces) - 1; last >= 0 { + top := braces[last] + braces = braces[:last] + if top < 0 { + // struct/interface type: leave alone + } else if len(braces) == 0 { // toplevel only + // Delete {...} body. + start, _ := safetoken.Offset(file, top) + end, _ := safetoken.Offset(file, pos) + out.Write(src[cursor : start+len("{")]) + cursor = end + } + } + } + prev = tok + } + out.Write(src[cursor:]) + return out.Bytes() +} diff --git a/contribs/gnopls/internal/util/astutil/purge_test.go b/contribs/gnopls/internal/util/astutil/purge_test.go new file mode 100644 index 00000000000..c67f9039adc --- /dev/null +++ b/contribs/gnopls/internal/util/astutil/purge_test.go @@ -0,0 +1,89 @@ +// 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 astutil_test + +import ( + "go/ast" + "go/parser" + "go/token" + "os" + "reflect" + "testing" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/util/astutil" + "golang.org/x/tools/internal/testenv" +) + +// TestPurgeFuncBodies tests PurgeFuncBodies by comparing it against a +// (less efficient) reference implementation that purges after parsing. +func TestPurgeFuncBodies(t *testing.T) { + testenv.NeedsGoBuild(t) // we need the source code for std + + // Load a few standard packages. + config := packages.Config{Mode: packages.NeedCompiledGoFiles} + pkgs, err := packages.Load(&config, "encoding/...") + if err != nil { + t.Fatal(err) + } + + // preorder returns the nodes of tree f in preorder. + preorder := func(f *ast.File) (nodes []ast.Node) { + ast.Inspect(f, func(n ast.Node) bool { + if n != nil { + nodes = append(nodes, n) + } + return true + }) + return nodes + } + + packages.Visit(pkgs, nil, func(p *packages.Package) { + for _, filename := range p.CompiledGoFiles { + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + + fset := token.NewFileSet() + + // Parse then purge (reference implementation). + f1, _ := parser.ParseFile(fset, filename, content, 0) + ast.Inspect(f1, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.FuncDecl: + if n.Body != nil { + n.Body.List = nil + } + case *ast.FuncLit: + n.Body.List = nil + case *ast.CompositeLit: + n.Elts = nil + } + return true + }) + + // Purge before parse (logic under test). + f2, _ := parser.ParseFile(fset, filename, astutil.PurgeFuncBodies(content), 0) + + // Compare sequence of node types. + nodes1 := preorder(f1) + nodes2 := preorder(f2) + if len(nodes2) < len(nodes1) { + t.Errorf("purged file has fewer nodes: %d vs %d", + len(nodes2), len(nodes1)) + nodes1 = nodes1[:len(nodes2)] // truncate + } + for i := range nodes1 { + x, y := nodes1[i], nodes2[i] + if reflect.TypeOf(x) != reflect.TypeOf(y) { + t.Errorf("%s: got %T, want %T", + fset.Position(x.Pos()), y, x) + break + } + } + } + }) +} diff --git a/contribs/gnopls/internal/util/astutil/util.go b/contribs/gnopls/internal/util/astutil/util.go new file mode 100644 index 00000000000..ac7515d1daf --- /dev/null +++ b/contribs/gnopls/internal/util/astutil/util.go @@ -0,0 +1,71 @@ +// 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 astutil + +import ( + "go/ast" + "go/token" + + "golang.org/x/tools/internal/typeparams" +) + +// UnpackRecv unpacks a receiver type expression, reporting whether it is a +// pointer recever, along with the type name identifier and any receiver type +// parameter identifiers. +// +// Copied (with modifications) from go/types. +func UnpackRecv(rtyp ast.Expr) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) { +L: // unpack receiver type + // This accepts invalid receivers such as ***T and does not + // work for other invalid receivers, but we don't care. The + // validity of receiver expressions is checked elsewhere. + for { + switch t := rtyp.(type) { + case *ast.ParenExpr: + rtyp = t.X + case *ast.StarExpr: + ptr = true + rtyp = t.X + default: + break L + } + } + + // unpack type parameters, if any + switch rtyp.(type) { + case *ast.IndexExpr, *ast.IndexListExpr: + var indices []ast.Expr + rtyp, _, indices, _ = typeparams.UnpackIndexExpr(rtyp) + for _, arg := range indices { + var par *ast.Ident + switch arg := arg.(type) { + case *ast.Ident: + par = arg + default: + // ignore errors + } + if par == nil { + par = &ast.Ident{NamePos: arg.Pos(), Name: "_"} + } + tparams = append(tparams, par) + } + } + + // unpack receiver name + if name, _ := rtyp.(*ast.Ident); name != nil { + rname = name + } + + return +} + +// NodeContains returns true if a node encloses a given position pos. +// The end point will also be inclusive, which will to allow hovering when the +// cursor is behind some nodes. +// +// Precondition: n must not be nil. +func NodeContains(n ast.Node, pos token.Pos) bool { + return n.Pos() <= pos && pos <= n.End() +} diff --git a/contribs/gnopls/internal/util/browser/README.md b/contribs/gnopls/internal/util/browser/README.md new file mode 100644 index 00000000000..e5f04df4d49 --- /dev/null +++ b/contribs/gnopls/internal/util/browser/README.md @@ -0,0 +1 @@ +This package is a copy of cmd/internal/browser from the go distribution \ No newline at end of file diff --git a/contribs/gnopls/internal/util/browser/browser.go b/contribs/gnopls/internal/util/browser/browser.go new file mode 100644 index 00000000000..6867c85d232 --- /dev/null +++ b/contribs/gnopls/internal/util/browser/browser.go @@ -0,0 +1,67 @@ +// Copyright 2016 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 browser provides utilities for interacting with users' browsers. +package browser + +import ( + "os" + "os/exec" + "runtime" + "time" +) + +// Commands returns a list of possible commands to use to open a url. +func Commands() [][]string { + var cmds [][]string + if exe := os.Getenv("BROWSER"); exe != "" { + cmds = append(cmds, []string{exe}) + } + switch runtime.GOOS { + case "darwin": + cmds = append(cmds, []string{"/usr/bin/open"}) + case "windows": + cmds = append(cmds, []string{"cmd", "/c", "start"}) + default: + if os.Getenv("DISPLAY") != "" { + // xdg-open is only for use in a desktop environment. + cmds = append(cmds, []string{"xdg-open"}) + } + } + cmds = append(cmds, + []string{"chrome"}, + []string{"google-chrome"}, + []string{"chromium"}, + []string{"firefox"}, + ) + return cmds +} + +// Open tries to open url in a browser and reports whether it succeeded. +func Open(url string) bool { + for _, args := range Commands() { + cmd := exec.Command(args[0], append(args[1:], url)...) + if cmd.Start() == nil && appearsSuccessful(cmd, 3*time.Second) { + return true + } + } + return false +} + +// appearsSuccessful reports whether the command appears to have run successfully. +// If the command runs longer than the timeout, it's deemed successful. +// If the command runs within the timeout, it's deemed successful if it exited cleanly. +func appearsSuccessful(cmd *exec.Cmd, timeout time.Duration) bool { + errc := make(chan error, 1) + go func() { + errc <- cmd.Wait() + }() + + select { + case <-time.After(timeout): + return true + case err := <-errc: + return err == nil + } +} diff --git a/contribs/gnopls/internal/util/bug/bug.go b/contribs/gnopls/internal/util/bug/bug.go new file mode 100644 index 00000000000..dcd242d4856 --- /dev/null +++ b/contribs/gnopls/internal/util/bug/bug.go @@ -0,0 +1,145 @@ +// 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 bug provides utilities for reporting internal bugs, and being +// notified when they occur. +// +// Philosophically, because gopls runs as a sidecar process that the user does +// not directly control, sometimes it keeps going on broken invariants rather +// than panicking. In those cases, bug reports provide a mechanism to alert +// developers and capture relevant metadata. +package bug + +import ( + "fmt" + "runtime" + "runtime/debug" + "sort" + "sync" + "time" + + "golang.org/x/telemetry/counter" +) + +// PanicOnBugs controls whether to panic when bugs are reported. +// +// It may be set to true during testing. +// +// TODO(adonovan): should we make the default true, and +// suppress it only in the product (gopls/main.go)? +var PanicOnBugs = false + +var ( + mu sync.Mutex + exemplars map[string]Bug + handlers []func(Bug) +) + +// A Bug represents an unexpected event or broken invariant. They are used for +// capturing metadata that helps us understand the event. +// +// Bugs are JSON-serializable. +type Bug struct { + File string // file containing the call to bug.Report + Line int // line containing the call to bug.Report + Description string // description of the bug + Key string // key identifying the bug (file:line if available) + Stack string // call stack + AtTime time.Time // time the bug was reported +} + +// Reportf reports a formatted bug message. +func Reportf(format string, args ...interface{}) { + report(fmt.Sprintf(format, args...)) +} + +// Errorf calls fmt.Errorf for the given arguments, and reports the resulting +// error message as a bug. +func Errorf(format string, args ...interface{}) error { + err := fmt.Errorf(format, args...) + report(err.Error()) + return err +} + +// Report records a new bug encountered on the server. +// It uses reflection to report the position of the immediate caller. +func Report(description string) { + report(description) +} + +// BugReportCount is a telemetry counter that tracks # of bug reports. +var BugReportCount = counter.NewStack("gopls/bug", 16) + +func report(description string) { + _, file, line, ok := runtime.Caller(2) // all exported reporting functions call report directly + + key := "<missing callsite>" + if ok { + key = fmt.Sprintf("%s:%d", file, line) + } + + if PanicOnBugs { + panic(fmt.Sprintf("%s: %s", key, description)) + } + + bug := Bug{ + File: file, + Line: line, + Description: description, + Key: key, + Stack: string(debug.Stack()), + AtTime: time.Now(), + } + + newBug := false + mu.Lock() + if _, ok := exemplars[key]; !ok { + if exemplars == nil { + exemplars = make(map[string]Bug) + } + exemplars[key] = bug // capture one exemplar per key + newBug = true + } + hh := handlers + handlers = nil + mu.Unlock() + + if newBug { + BugReportCount.Inc() + } + // Call the handlers outside the critical section since a + // handler may itself fail and call bug.Report. Since handlers + // are one-shot, the inner call should be trivial. + for _, handle := range hh { + handle(bug) + } +} + +// Handle adds a handler function that will be called with the next +// bug to occur on the server. The handler only ever receives one bug. +// It is called synchronously, and should return in a timely manner. +func Handle(h func(Bug)) { + mu.Lock() + defer mu.Unlock() + handlers = append(handlers, h) +} + +// List returns a slice of bug exemplars -- the first bugs to occur at each +// callsite. +func List() []Bug { + mu.Lock() + defer mu.Unlock() + + var bugs []Bug + + for _, bug := range exemplars { + bugs = append(bugs, bug) + } + + sort.Slice(bugs, func(i, j int) bool { + return bugs[i].Key < bugs[j].Key + }) + + return bugs +} diff --git a/contribs/gnopls/internal/util/bug/bug_test.go b/contribs/gnopls/internal/util/bug/bug_test.go new file mode 100644 index 00000000000..8ca2aa5fd64 --- /dev/null +++ b/contribs/gnopls/internal/util/bug/bug_test.go @@ -0,0 +1,91 @@ +// 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 bug + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" +) + +func resetForTesting() { + exemplars = nil + handlers = nil +} + +func TestListBugs(t *testing.T) { + defer resetForTesting() + + Report("bad") + + wantBugs(t, "bad") + + for i := 0; i < 3; i++ { + Report(fmt.Sprintf("index:%d", i)) + } + + wantBugs(t, "bad", "index:0") +} + +func wantBugs(t *testing.T, want ...string) { + t.Helper() + + bugs := List() + if got, want := len(bugs), len(want); got != want { + t.Errorf("List(): got %d bugs, want %d", got, want) + return + } + + for i, b := range bugs { + if got, want := b.Description, want[i]; got != want { + t.Errorf("bug.List()[%d] = %q, want %q", i, got, want) + } + } +} + +func TestBugHandler(t *testing.T) { + defer resetForTesting() + + Report("unseen") + + // Both handlers are called, in order of registration, only once. + var got string + Handle(func(b Bug) { got += "1:" + b.Description }) + Handle(func(b Bug) { got += "2:" + b.Description }) + + Report("seen") + + Report("again") + + if want := "1:seen2:seen"; got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestBugJSON(t *testing.T) { + b1 := Bug{ + File: "foo.go", + Line: 1, + Description: "a bug", + Key: "foo.go:1", + Stack: "<stack>", + AtTime: time.Now(), + } + + data, err := json.Marshal(b1) + if err != nil { + t.Fatal(err) + } + var b2 Bug + if err := json.Unmarshal(data, &b2); err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(b1, b2); diff != "" { + t.Errorf("bugs differ after JSON Marshal/Unmarshal (-b1 +b2):\n%s", diff) + } +} diff --git a/contribs/gnopls/internal/util/constraints/constraint.go b/contribs/gnopls/internal/util/constraints/constraint.go new file mode 100644 index 00000000000..4e6ab61ea34 --- /dev/null +++ b/contribs/gnopls/internal/util/constraints/constraint.go @@ -0,0 +1,52 @@ +// 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 constraints defines a set of useful constraints to be used +// with type parameters. +package constraints + +// Copied from x/exp/constraints. + +// Signed is a constraint that permits any signed integer type. +// If future releases of Go add new predeclared signed integer types, +// this constraint will be modified to include them. +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +// Unsigned is a constraint that permits any unsigned integer type. +// If future releases of Go add new predeclared unsigned integer types, +// this constraint will be modified to include them. +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +// Integer is a constraint that permits any integer type. +// If future releases of Go add new predeclared integer types, +// this constraint will be modified to include them. +type Integer interface { + Signed | Unsigned +} + +// Float is a constraint that permits any floating-point type. +// If future releases of Go add new predeclared floating-point types, +// this constraint will be modified to include them. +type Float interface { + ~float32 | ~float64 +} + +// Complex is a constraint that permits any complex numeric type. +// If future releases of Go add new predeclared complex numeric types, +// this constraint will be modified to include them. +type Complex interface { + ~complex64 | ~complex128 +} + +// Ordered is a constraint that permits any ordered type: any type +// that supports the operators < <= >= >. +// If future releases of Go add new ordered types, +// this constraint will be modified to include them. +type Ordered interface { + Integer | Float | ~string +} diff --git a/contribs/gnopls/internal/util/frob/frob.go b/contribs/gnopls/internal/util/frob/frob.go new file mode 100644 index 00000000000..a5fa584215f --- /dev/null +++ b/contribs/gnopls/internal/util/frob/frob.go @@ -0,0 +1,403 @@ +// 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 frob is a fast restricted object encoder/decoder in the +// spirit of encoding/gob. +// +// As with gob, types that recursively contain functions, channels, +// and unsafe.Pointers cannot be encoded, but frob has these +// additional restrictions: +// +// - Interface values are not supported; this avoids the need for +// the encoding to describe types. +// +// - Types that recursively contain private struct fields are not +// permitted. +// +// - The encoding is unspecified and subject to change, so the encoder +// and decoder must exactly agree on their implementation and on the +// definitions of the target types. +// +// - Lengths (of arrays, slices, and maps) are currently assumed to +// fit in 32 bits. +// +// - There is no error handling. All errors are reported by panicking. +// +// - Values are serialized as trees, not graphs, so shared subgraphs +// are encoded repeatedly. +// +// - No attempt is made to detect cyclic data structures. +package frob + +import ( + "encoding/binary" + "fmt" + "math" + "reflect" + "sync" +) + +// A Codec[T] is an immutable encoder and decoder for values of type T. +type Codec[T any] struct{ frob *frob } + +// CodecFor[T] returns a codec for values of type T. +// It panics if type T is unsuitable. +func CodecFor[T any]() Codec[T] { + frobsMu.Lock() + defer frobsMu.Unlock() + return Codec[T]{frobFor(reflect.TypeOf((*T)(nil)).Elem())} +} + +func (codec Codec[T]) Encode(v T) []byte { return codec.frob.Encode(v) } +func (codec Codec[T]) Decode(data []byte, ptr *T) { codec.frob.Decode(data, ptr) } + +var ( + frobsMu sync.Mutex + frobs = make(map[reflect.Type]*frob) +) + +// A frob is an encoder/decoder for a specific type. +type frob struct { + t reflect.Type + kind reflect.Kind + elems []*frob // elem (array/slice/ptr), key+value (map), fields (struct) +} + +// frobFor returns the frob for a particular type. +// Precondition: caller holds frobsMu. +func frobFor(t reflect.Type) *frob { + fr, ok := frobs[t] + if !ok { + fr = &frob{t: t, kind: t.Kind()} + frobs[t] = fr + + switch fr.kind { + case reflect.Bool, + reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64, + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Uintptr, + reflect.Float32, + reflect.Float64, + reflect.Complex64, + reflect.Complex128, + reflect.String: + + case reflect.Array, + reflect.Slice, + reflect.Pointer: + fr.addElem(fr.t.Elem()) + + case reflect.Map: + fr.addElem(fr.t.Key()) + fr.addElem(fr.t.Elem()) + + case reflect.Struct: + for i := 0; i < fr.t.NumField(); i++ { + field := fr.t.Field(i) + if field.PkgPath != "" { + panic(fmt.Sprintf("unexported field %v", field)) + } + fr.addElem(field.Type) + } + + default: + // chan, func, interface, unsafe.Pointer + panic(fmt.Sprintf("type %v is not supported by frob", fr.t)) + } + } + return fr +} + +func (fr *frob) addElem(t reflect.Type) { + fr.elems = append(fr.elems, frobFor(t)) +} + +const magic = "frob" + +func (fr *frob) Encode(v any) []byte { + rv := reflect.ValueOf(v) + if rv.Type() != fr.t { + panic(fmt.Sprintf("got %v, want %v", rv.Type(), fr.t)) + } + w := &writer{} + w.bytes([]byte(magic)) + fr.encode(w, rv) + if uint64(len(w.data))>>32 != 0 { + panic("too large") // includes all cases where len doesn't fit in 32 bits + } + return w.data +} + +// encode appends the encoding of value v, whose type must be fr.t. +func (fr *frob) encode(out *writer, v reflect.Value) { + switch fr.kind { + case reflect.Bool: + var b byte + if v.Bool() { + b = 1 + } + out.uint8(b) + case reflect.Int: + out.uint64(uint64(v.Int())) + case reflect.Int8: + out.uint8(uint8(v.Int())) + case reflect.Int16: + out.uint16(uint16(v.Int())) + case reflect.Int32: + out.uint32(uint32(v.Int())) + case reflect.Int64: + out.uint64(uint64(v.Int())) + case reflect.Uint: + out.uint64(v.Uint()) + case reflect.Uint8: + out.uint8(uint8(v.Uint())) + case reflect.Uint16: + out.uint16(uint16(v.Uint())) + case reflect.Uint32: + out.uint32(uint32(v.Uint())) + case reflect.Uint64: + out.uint64(v.Uint()) + case reflect.Uintptr: + out.uint64(v.Uint()) + case reflect.Float32: + out.uint32(math.Float32bits(float32(v.Float()))) + case reflect.Float64: + out.uint64(math.Float64bits(v.Float())) + case reflect.Complex64: + z := complex64(v.Complex()) + out.uint32(math.Float32bits(real(z))) + out.uint32(math.Float32bits(imag(z))) + case reflect.Complex128: + z := v.Complex() + out.uint64(math.Float64bits(real(z))) + out.uint64(math.Float64bits(imag(z))) + + case reflect.Array: + len := v.Type().Len() + elem := fr.elems[0] + for i := 0; i < len; i++ { + elem.encode(out, v.Index(i)) + } + + case reflect.Slice: + len := v.Len() + out.uint32(uint32(len)) + if len > 0 { + elem := fr.elems[0] + if elem.kind == reflect.Uint8 { + // []byte fast path + out.bytes(v.Bytes()) + } else { + for i := 0; i < len; i++ { + elem.encode(out, v.Index(i)) + } + } + } + + case reflect.Map: + len := v.Len() + out.uint32(uint32(len)) + if len > 0 { + kfrob, vfrob := fr.elems[0], fr.elems[1] + for iter := v.MapRange(); iter.Next(); { + kfrob.encode(out, iter.Key()) + vfrob.encode(out, iter.Value()) + } + } + + case reflect.Pointer: + if v.IsNil() { + out.uint8(0) + } else { + out.uint8(1) + fr.elems[0].encode(out, v.Elem()) + } + + case reflect.String: + len := v.Len() + out.uint32(uint32(len)) + if len > 0 { + out.data = append(out.data, v.String()...) + } + + case reflect.Struct: + for i, elem := range fr.elems { + elem.encode(out, v.Field(i)) + } + + default: + panic(fr.t) + } +} + +func (fr *frob) Decode(data []byte, ptr any) { + rv := reflect.ValueOf(ptr).Elem() + if rv.Type() != fr.t { + panic(fmt.Sprintf("got %v, want %v", rv.Type(), fr.t)) + } + rd := &reader{data} + if string(rd.bytes(4)) != magic { + panic("not a frob-encoded message") + } + fr.decode(rd, rv) + if len(rd.data) > 0 { + panic("surplus bytes") + } +} + +// decode reads from in, decodes a value, and sets addr to it. +// addr must be a zero-initialized addressable variable of type fr.t. +func (fr *frob) decode(in *reader, addr reflect.Value) { + switch fr.kind { + case reflect.Bool: + addr.SetBool(in.uint8() != 0) + case reflect.Int: + addr.SetInt(int64(in.uint64())) + case reflect.Int8: + addr.SetInt(int64(in.uint8())) + case reflect.Int16: + addr.SetInt(int64(in.uint16())) + case reflect.Int32: + addr.SetInt(int64(in.uint32())) + case reflect.Int64: + addr.SetInt(int64(in.uint64())) + case reflect.Uint: + addr.SetUint(in.uint64()) + case reflect.Uint8: + addr.SetUint(uint64(in.uint8())) + case reflect.Uint16: + addr.SetUint(uint64(in.uint16())) + case reflect.Uint32: + addr.SetUint(uint64(in.uint32())) + case reflect.Uint64: + addr.SetUint(in.uint64()) + case reflect.Uintptr: + addr.SetUint(in.uint64()) + case reflect.Float32: + addr.SetFloat(float64(math.Float32frombits(in.uint32()))) + case reflect.Float64: + addr.SetFloat(math.Float64frombits(in.uint64())) + case reflect.Complex64: + addr.SetComplex(complex128(complex( + math.Float32frombits(in.uint32()), + math.Float32frombits(in.uint32()), + ))) + case reflect.Complex128: + addr.SetComplex(complex( + math.Float64frombits(in.uint64()), + math.Float64frombits(in.uint64()), + )) + + case reflect.Array: + len := fr.t.Len() + for i := 0; i < len; i++ { + fr.elems[0].decode(in, addr.Index(i)) + } + + case reflect.Slice: + len := int(in.uint32()) + if len > 0 { + elem := fr.elems[0] + if elem.kind == reflect.Uint8 { + // []byte fast path + // (Not addr.SetBytes: we must make a copy.) + addr.Set(reflect.AppendSlice(addr, reflect.ValueOf(in.bytes(len)))) + } else { + addr.Set(reflect.MakeSlice(fr.t, len, len)) + for i := 0; i < len; i++ { + elem.decode(in, addr.Index(i)) + } + } + } + + case reflect.Map: + len := int(in.uint32()) + if len > 0 { + m := reflect.MakeMapWithSize(fr.t, len) + addr.Set(m) + kfrob, vfrob := fr.elems[0], fr.elems[1] + k := reflect.New(kfrob.t).Elem() + v := reflect.New(vfrob.t).Elem() + for i := 0; i < len; i++ { + k.SetZero() + v.SetZero() + kfrob.decode(in, k) + vfrob.decode(in, v) + m.SetMapIndex(k, v) + } + } + + case reflect.Pointer: + isNil := in.uint8() == 0 + if !isNil { + ptr := reflect.New(fr.elems[0].t) + addr.Set(ptr) + fr.elems[0].decode(in, ptr.Elem()) + } + + case reflect.String: + len := int(in.uint32()) + if len > 0 { + addr.SetString(string(in.bytes(len))) + } + + case reflect.Struct: + for i, elem := range fr.elems { + elem.decode(in, addr.Field(i)) + } + + default: + panic(fr.t) + } +} + +var le = binary.LittleEndian + +type reader struct{ data []byte } + +func (r *reader) uint8() uint8 { + v := r.data[0] + r.data = r.data[1:] + return v +} + +func (r *reader) uint16() uint16 { + v := le.Uint16(r.data) + r.data = r.data[2:] + return v +} + +func (r *reader) uint32() uint32 { + v := le.Uint32(r.data) + r.data = r.data[4:] + return v +} + +func (r *reader) uint64() uint64 { + v := le.Uint64(r.data) + r.data = r.data[8:] + return v +} + +func (r *reader) bytes(n int) []byte { + v := r.data[:n] + r.data = r.data[n:] + return v +} + +type writer struct{ data []byte } + +func (w *writer) uint8(v uint8) { w.data = append(w.data, v) } +func (w *writer) uint16(v uint16) { w.data = le.AppendUint16(w.data, v) } +func (w *writer) uint32(v uint32) { w.data = le.AppendUint32(w.data, v) } +func (w *writer) uint64(v uint64) { w.data = le.AppendUint64(w.data, v) } +func (w *writer) bytes(v []byte) { w.data = append(w.data, v...) } diff --git a/contribs/gnopls/internal/util/frob/frob_test.go b/contribs/gnopls/internal/util/frob/frob_test.go new file mode 100644 index 00000000000..5765c9642ef --- /dev/null +++ b/contribs/gnopls/internal/util/frob/frob_test.go @@ -0,0 +1,119 @@ +// 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 frob_test + +import ( + "math" + "reflect" + "testing" + + "golang.org/x/tools/gopls/internal/util/frob" +) + +func TestBasics(t *testing.T) { + type Basics struct { + A []*string + B [2]int + C *Basics + D map[string]int + E []byte + F []string + } + codec := frob.CodecFor[Basics]() + + s1, s2 := "hello", "world" + x := Basics{ + A: []*string{&s1, nil, &s2}, + B: [...]int{1, 2}, + C: &Basics{ + B: [...]int{3, 4}, + D: map[string]int{"one": 1}, + }, + E: []byte("hello"), + F: []string{s1, s2}, + } + var y Basics + codec.Decode(codec.Encode(x), &y) + if !reflect.DeepEqual(x, y) { + t.Fatalf("bad roundtrip: got %#v, want %#v", y, x) + } +} + +func TestInts(t *testing.T) { + type Ints struct { + U uint + U8 uint8 + U16 uint16 + U32 uint32 + U64 uint64 + UP uintptr + I int + I8 int8 + I16 int16 + I32 int32 + I64 int64 + F32 float32 + F64 float64 + C64 complex64 + C128 complex128 + } + codec := frob.CodecFor[Ints]() + + // maxima + max1 := Ints{ + U: math.MaxUint, + U8: math.MaxUint8, + U16: math.MaxUint16, + U32: math.MaxUint32, + U64: math.MaxUint64, + UP: math.MaxUint, + I: math.MaxInt, + I8: math.MaxInt8, + I16: math.MaxInt16, + I32: math.MaxInt32, + I64: math.MaxInt64, + F32: math.MaxFloat32, + F64: math.MaxFloat64, + C64: complex(math.MaxFloat32, math.MaxFloat32), + C128: complex(math.MaxFloat64, math.MaxFloat64), + } + var max2 Ints + codec.Decode(codec.Encode(max1), &max2) + if !reflect.DeepEqual(max1, max2) { + t.Fatalf("max: bad roundtrip: got %#v, want %#v", max2, max1) + } + + // minima + min1 := Ints{ + I: math.MinInt, + I8: math.MinInt8, + I16: math.MinInt16, + I32: math.MinInt32, + I64: math.MinInt64, + F32: -math.MaxFloat32, + F64: -math.MaxFloat32, + C64: complex(-math.MaxFloat32, -math.MaxFloat32), + C128: complex(-math.MaxFloat64, -math.MaxFloat64), + } + var min2 Ints + codec.Decode(codec.Encode(min1), &min2) + if !reflect.DeepEqual(min1, min2) { + t.Fatalf("min: bad roundtrip: got %#v, want %#v", min2, min1) + } + + // negatives (other than MinInt), to exercise conversions + neg1 := Ints{ + I: -1, + I8: -1, + I16: -1, + I32: -1, + I64: -1, + } + var neg2 Ints + codec.Decode(codec.Encode(neg1), &neg2) + if !reflect.DeepEqual(neg1, neg2) { + t.Fatalf("neg: bad roundtrip: got %#v, want %#v", neg2, neg1) + } +} diff --git a/contribs/gnopls/internal/util/goversion/goversion.go b/contribs/gnopls/internal/util/goversion/goversion.go new file mode 100644 index 00000000000..8353487ddce --- /dev/null +++ b/contribs/gnopls/internal/util/goversion/goversion.go @@ -0,0 +1,95 @@ +// 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 goversions defines gopls's policy for which versions of Go it supports. +package goversion + +import ( + "fmt" + "strings" +) + +// Support holds information about end-of-life Go version support. +// +// Exposed for testing. +type Support struct { + // GoVersion is the Go version to which these settings relate. + GoVersion int + + // DeprecatedVersion is the first version of gopls that no longer supports + // this Go version. + // + // If unset, the version is already deprecated. + DeprecatedVersion string + + // InstallGoplsVersion is the latest gopls version that supports this Go + // version without warnings. + InstallGoplsVersion string +} + +// Supported maps Go versions to the gopls version in which support will +// be deprecated, and the final gopls version supporting them without warnings. +// Keep this in sync with gopls/README.md. +// +// Must be sorted in ascending order of Go version. +// +// Exposed (and mutable) for testing. +var Supported = []Support{ + {12, "", "v0.7.5"}, + {15, "", "v0.9.5"}, + {16, "", "v0.11.0"}, + {17, "", "v0.11.0"}, + {18, "", "v0.14.2"}, + {19, "v0.17.0", "v0.15.3"}, + {20, "v0.17.0", "v0.15.3"}, +} + +// OldestSupported is the last X in Go 1.X that this version of gopls +// supports without warnings. +// +// Exported for testing. +func OldestSupported() int { + return Supported[len(Supported)-1].GoVersion + 1 +} + +// Message returns the message to display if the user has the given Go +// version, if any. The goVersion variable is the X in Go 1.X. If +// fromBuild is set, the Go version is the version used to build +// gopls. Otherwise, it is the go command version. +// +// The second component of the result indicates whether the message is +// an error, not a mere warning. +// +// If goVersion is invalid (< 0), it returns "", false. +func Message(goVersion int, fromBuild bool) (string, bool) { + if goVersion < 0 { + return "", false + } + + for _, v := range Supported { + if goVersion <= v.GoVersion { + var msgBuilder strings.Builder + + isError := true + if fromBuild { + fmt.Fprintf(&msgBuilder, "Gopls was built with Go version 1.%d", goVersion) + } else { + fmt.Fprintf(&msgBuilder, "Found Go version 1.%d", goVersion) + } + if v.DeprecatedVersion != "" { + // not deprecated yet, just a warning + fmt.Fprintf(&msgBuilder, ", which will be unsupported by gopls %s. ", v.DeprecatedVersion) + isError = false // warning + } else { + fmt.Fprint(&msgBuilder, ", which is not supported by this version of gopls. ") + } + fmt.Fprintf(&msgBuilder, "Please upgrade to Go 1.%d or later and reinstall gopls. ", OldestSupported()) + fmt.Fprintf(&msgBuilder, "If you can't upgrade and want this message to go away, please install gopls %s. ", v.InstallGoplsVersion) + fmt.Fprint(&msgBuilder, "See https://go.dev/s/gopls-support-policy for more details.") + + return msgBuilder.String(), isError + } + } + return "", false +} diff --git a/contribs/gnopls/internal/util/goversion/goversion_test.go b/contribs/gnopls/internal/util/goversion/goversion_test.go new file mode 100644 index 00000000000..e2df9f23118 --- /dev/null +++ b/contribs/gnopls/internal/util/goversion/goversion_test.go @@ -0,0 +1,74 @@ +// 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 goversion_test + +import ( + "fmt" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/util/goversion" +) + +func TestMessage(t *testing.T) { + // Note(rfindley): this test is a change detector, as it must be updated + // whenever we deprecate a version. + // + // However, I chose to leave it as is since it gives us confidence in error + // messages served for Go versions that we no longer support (and therefore + // no longer run in CI). + type test struct { + goVersion int + fromBuild bool + wantContains []string // string fragments that we expect to see + wantIsError bool // an error, not a mere warning + } + + deprecated := func(goVersion int, lastVersion string) test { + return test{ + goVersion: goVersion, + fromBuild: false, + wantContains: []string{ + fmt.Sprintf("Found Go version 1.%d", goVersion), + "not supported", + fmt.Sprintf("upgrade to Go 1.%d", goversion.OldestSupported()), + fmt.Sprintf("install gopls %s", lastVersion), + }, + wantIsError: true, + } + } + + tests := []test{ + {-1, false, nil, false}, + deprecated(12, "v0.7.5"), + deprecated(13, "v0.9.5"), + deprecated(15, "v0.9.5"), + deprecated(16, "v0.11.0"), + deprecated(17, "v0.11.0"), + deprecated(18, "v0.14.2"), + {19, false, []string{"Found Go version 1.19", "unsupported by gopls v0.17.0", "upgrade to Go 1.21", "install gopls v0.15.3"}, false}, + {19, true, []string{"Gopls was built with Go version 1.19", "unsupported by gopls v0.17.0", "upgrade to Go 1.21", "install gopls v0.15.3"}, false}, + {20, false, []string{"Found Go version 1.20", "unsupported by gopls v0.17.0", "upgrade to Go 1.21", "install gopls v0.15.3"}, false}, + {20, true, []string{"Gopls was built with Go version 1.20", "unsupported by gopls v0.17.0", "upgrade to Go 1.21", "install gopls v0.15.3"}, false}, + } + + for _, test := range tests { + gotMsg, gotIsError := goversion.Message(test.goVersion, test.fromBuild) + + if len(test.wantContains) == 0 && gotMsg != "" { + t.Errorf("versionMessage(%d) = %q, want \"\"", test.goVersion, gotMsg) + } + + for _, want := range test.wantContains { + if !strings.Contains(gotMsg, want) { + t.Errorf("versionMessage(%d) = %q, want containing %q", test.goVersion, gotMsg, want) + } + } + + if gotIsError != test.wantIsError { + t.Errorf("versionMessage(%d) isError = %v, want %v", test.goVersion, gotIsError, test.wantIsError) + } + } +} diff --git a/contribs/gnopls/internal/util/immutable/immutable.go b/contribs/gnopls/internal/util/immutable/immutable.go new file mode 100644 index 00000000000..a88133fe92f --- /dev/null +++ b/contribs/gnopls/internal/util/immutable/immutable.go @@ -0,0 +1,43 @@ +// 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. + +// The immutable package defines immutable wrappers around common data +// structures. These are used for additional type safety inside gopls. +// +// See the "persistent" package for copy-on-write data structures. +package immutable + +// Map is an immutable wrapper around an ordinary Go map. +type Map[K comparable, V any] struct { + m map[K]V +} + +// MapOf wraps the given Go map. +// +// The caller must not subsequently mutate the map. +func MapOf[K comparable, V any](m map[K]V) Map[K, V] { + return Map[K, V]{m} +} + +// Value returns the mapped value for k. +// It is equivalent to the commaok form of an ordinary go map, and returns +// (zero, false) if the key is not present. +func (m Map[K, V]) Value(k K) (V, bool) { + v, ok := m.m[k] + return v, ok +} + +// Len returns the number of entries in the Map. +func (m Map[K, V]) Len() int { + return len(m.m) +} + +// Range calls f for each mapped (key, value) pair. +// There is no way to break out of the loop. +// TODO: generalize when Go iterators (#61405) land. +func (m Map[K, V]) Range(f func(k K, v V)) { + for k, v := range m.m { + f(k, v) + } +} diff --git a/contribs/gnopls/internal/util/lru/lru.go b/contribs/gnopls/internal/util/lru/lru.go new file mode 100644 index 00000000000..4ed8eafad76 --- /dev/null +++ b/contribs/gnopls/internal/util/lru/lru.go @@ -0,0 +1,179 @@ +// 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. + +// The lru package implements a fixed-size in-memory LRU cache. +package lru + +import ( + "container/heap" + "fmt" + "sync" +) + +// A Cache is a fixed-size in-memory LRU cache, storing values of type V keyed +// by keys of type K. +type Cache[K comparable, V any] struct { + impl *cache +} + +// Get retrieves the value for the specified key. +// If the key is found, its access time is updated. +// +// The second result reports whether the key was found. +func (c *Cache[K, V]) Get(key K) (V, bool) { + v, ok := c.impl.get(key) + if !ok { + var zero V + return zero, false + } + // Handle untyped nil explicitly to avoid a panic in the type assertion + // below. + if v == nil { + var zero V + return zero, true + } + return v.(V), true +} + +// Set stores a value for the specified key, using its given size to update the +// current cache size, evicting old entries as necessary to fit in the cache +// capacity. +// +// Size must be a non-negative value. If size is larger than the cache +// capacity, the value is not stored and the cache is not modified. +func (c *Cache[K, V]) Set(key K, value V, size int) { + c.impl.set(key, value, size) +} + +// New creates a new Cache with the given capacity, which must be positive. +// +// The cache capacity uses arbitrary units, which are specified during the Set +// operation. +func New[K comparable, V any](capacity int) *Cache[K, V] { + if capacity == 0 { + panic("zero capacity") + } + + return &Cache[K, V]{&cache{ + capacity: capacity, + m: make(map[any]*entry), + }} +} + +// cache is the non-generic implementation of [Cache]. +// +// (Using a generic wrapper around a non-generic impl avoids unnecessary +// "stenciling" or code duplication.) +type cache struct { + capacity int + + mu sync.Mutex + used int // used capacity, in user-specified units + m map[any]*entry // k/v lookup + lru queue // min-atime priority queue of *entry + clock int64 // clock time, incremented whenever the cache is updated +} + +type entry struct { + key any + value any + size int // caller-specified size + atime int64 // last access / set time + index int // index of entry in the heap slice +} + +func (c *cache) get(key any) (any, bool) { + c.mu.Lock() + defer c.mu.Unlock() + + c.clock++ // every access updates the clock + + if e, ok := c.m[key]; ok { // cache hit + e.atime = c.clock + heap.Fix(&c.lru, e.index) + return e.value, true + } + + return nil, false +} + +func (c *cache) set(key, value any, size int) { + if size < 0 { + panic(fmt.Sprintf("size must be non-negative, got %d", size)) + } + if size > c.capacity { + return // uncacheable + } + + c.mu.Lock() + defer c.mu.Unlock() + + c.clock++ + + // Remove the existing cache entry for key, if it exists. + e, ok := c.m[key] + if ok { + c.used -= e.size + heap.Remove(&c.lru, e.index) + delete(c.m, key) + } + + // Evict entries until the new value will fit. + newUsed := c.used + size + if newUsed < 0 { + return // integer overflow; return silently + } + c.used = newUsed + for c.used > c.capacity { + // evict oldest entry + e = heap.Pop(&c.lru).(*entry) + c.used -= e.size + delete(c.m, e.key) + } + + // Store the new value. + // Opt: e is evicted, so it can be reused to reduce allocation. + if e == nil { + e = new(entry) + } + e.key = key + e.value = value + e.size = size + e.atime = c.clock + c.m[e.key] = e + heap.Push(&c.lru, e) + + if len(c.m) != len(c.lru) { + panic("map and LRU are inconsistent") + } +} + +// -- priority queue boilerplate -- + +// queue is a min-atime priority queue of cache entries. +type queue []*entry + +func (q queue) Len() int { return len(q) } + +func (q queue) Less(i, j int) bool { return q[i].atime < q[j].atime } + +func (q queue) Swap(i, j int) { + q[i], q[j] = q[j], q[i] + q[i].index = i + q[j].index = j +} + +func (q *queue) Push(x any) { + e := x.(*entry) + e.index = len(*q) + *q = append(*q, e) +} + +func (q *queue) Pop() any { + last := len(*q) - 1 + e := (*q)[last] + (*q)[last] = nil // aid GC + *q = (*q)[:last] + return e +} diff --git a/contribs/gnopls/internal/util/lru/lru_fuzz_test.go b/contribs/gnopls/internal/util/lru/lru_fuzz_test.go new file mode 100644 index 00000000000..2f5f43cb9f5 --- /dev/null +++ b/contribs/gnopls/internal/util/lru/lru_fuzz_test.go @@ -0,0 +1,38 @@ +// 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 lru_test + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/util/lru" +) + +// Simple fuzzing test for consistency. +func FuzzCache(f *testing.F) { + type op struct { + set bool + key, value byte + } + f.Fuzz(func(t *testing.T, data []byte) { + var ops []op + for len(data) >= 3 { + ops = append(ops, op{data[0]%2 == 0, data[1], data[2]}) + data = data[3:] + } + cache := lru.New[byte, byte](100) + var reference [256]byte + for _, op := range ops { + if op.set { + reference[op.key] = op.value + cache.Set(op.key, op.value, 1) + } else { + if v, ok := cache.Get(op.key); ok && v != reference[op.key] { + t.Fatalf("cache.Get(%d) = %d, want %d", op.key, v, reference[op.key]) + } + } + } + }) +} diff --git a/contribs/gnopls/internal/util/lru/lru_nil_test.go b/contribs/gnopls/internal/util/lru/lru_nil_test.go new file mode 100644 index 00000000000..443d2a67818 --- /dev/null +++ b/contribs/gnopls/internal/util/lru/lru_nil_test.go @@ -0,0 +1,19 @@ +// 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 lru_test + +import ( + "testing" + + "golang.org/x/tools/gopls/internal/util/lru" +) + +func TestSetUntypedNil(t *testing.T) { + cache := lru.New[any, any](100 * 1e6) + cache.Set(nil, nil, 1) + if got, ok := cache.Get(nil); !ok || got != nil { + t.Errorf("cache.Get(nil) = %v, %v, want nil, true", got, ok) + } +} diff --git a/contribs/gnopls/internal/util/lru/lru_test.go b/contribs/gnopls/internal/util/lru/lru_test.go new file mode 100644 index 00000000000..bf96e8d31b7 --- /dev/null +++ b/contribs/gnopls/internal/util/lru/lru_test.go @@ -0,0 +1,153 @@ +// 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 lru_test + +import ( + "bytes" + cryptorand "crypto/rand" + "fmt" + "log" + mathrand "math/rand" + "strings" + "testing" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/gopls/internal/util/lru" +) + +func TestCache(t *testing.T) { + type get struct { + key string + want string + } + type set struct { + key, value string + } + + tests := []struct { + label string + steps []any + }{ + {"empty cache", []any{ + get{"a", ""}, + get{"b", ""}, + }}, + {"zero-length string", []any{ + set{"a", ""}, + get{"a", ""}, + }}, + {"under capacity", []any{ + set{"a", "123"}, + set{"b", "456"}, + get{"a", "123"}, + get{"b", "456"}, + }}, + {"over capacity", []any{ + set{"a", "123"}, + set{"b", "456"}, + set{"c", "78901"}, + get{"a", ""}, + get{"b", "456"}, + get{"c", "78901"}, + }}, + {"access ordering", []any{ + set{"a", "123"}, + set{"b", "456"}, + get{"a", "123"}, + set{"c", "78901"}, + get{"a", "123"}, + get{"b", ""}, + get{"c", "78901"}, + }}, + } + + for _, test := range tests { + t.Run(test.label, func(t *testing.T) { + c := lru.New[string, string](10) + for i, step := range test.steps { + switch step := step.(type) { + case get: + if got, _ := c.Get(step.key); got != step.want { + t.Errorf("#%d: c.Get(%q) = %q, want %q", i, step.key, got, step.want) + } + case set: + c.Set(step.key, step.value, len(step.value)) + } + } + }) + } +} + +// TestConcurrency exercises concurrent access to the same entry. +// +// It is a copy of TestConcurrency from the filecache package. +func TestConcurrency(t *testing.T) { + key := uniqueKey() + const N = 100 // concurrency level + + // Construct N distinct values, each larger + // than a typical 4KB OS file buffer page. + var values [N][8192]byte + for i := range values { + if _, err := mathrand.Read(values[i][:]); err != nil { + t.Fatalf("rand: %v", err) + } + } + + cache := lru.New[[32]byte, []byte](100 * 1e6) // 100MB cache + + // get calls Get and verifies that the cache entry + // matches one of the values passed to Set. + get := func(mustBeFound bool) error { + got, ok := cache.Get(key) + if !ok { + if !mustBeFound { + return nil + } + return fmt.Errorf("Get did not return a value") + } + for _, want := range values { + if bytes.Equal(want[:], got) { + return nil // a match + } + } + return fmt.Errorf("Get returned a value that was never Set") + } + + // Perform N concurrent calls to Set and Get. + // All sets must succeed. + // All gets must return nothing, or one of the Set values; + // there is no third possibility. + var group errgroup.Group + for i := range values { + i := i + v := values[i][:] + group.Go(func() error { + cache.Set(key, v, len(v)) + return nil + }) + group.Go(func() error { return get(false) }) + } + if err := group.Wait(); err != nil { + if strings.Contains(err.Error(), "operation not supported") || + strings.Contains(err.Error(), "not implemented") { + t.Skipf("skipping: %v", err) + } + t.Fatal(err) + } + + // A final Get must report one of the values that was Set. + if err := get(true); err != nil { + t.Fatalf("final Get failed: %v", err) + } +} + +// uniqueKey returns a key that has never been used before. +func uniqueKey() (key [32]byte) { + if _, err := cryptorand.Read(key[:]); err != nil { + log.Fatalf("rand: %v", err) + } + return +} diff --git a/contribs/gnopls/internal/util/moremaps/maps.go b/contribs/gnopls/internal/util/moremaps/maps.go new file mode 100644 index 00000000000..c8484d9fecd --- /dev/null +++ b/contribs/gnopls/internal/util/moremaps/maps.go @@ -0,0 +1,66 @@ +// 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 moremaps + +import ( + "cmp" + "iter" + "maps" + "slices" +) + +// Group returns a new non-nil map containing the elements of s grouped by the +// keys returned from the key func. +func Group[K comparable, V any](s []V, key func(V) K) map[K][]V { + m := make(map[K][]V) + for _, v := range s { + k := key(v) + m[k] = append(m[k], v) + } + return m +} + +// Keys returns the keys of the map M, like slices.Collect(maps.Keys(m)). +func KeySlice[M ~map[K]V, K comparable, V any](m M) []K { + r := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) + } + return r +} + +// Values returns the values of the map M, like slices.Collect(maps.Values(m)). +func ValueSlice[M ~map[K]V, K comparable, V any](m M) []V { + r := make([]V, 0, len(m)) + for _, v := range m { + r = append(r, v) + } + return r +} + +// SameKeys reports whether x and y have equal sets of keys. +func SameKeys[K comparable, V1, V2 any](x map[K]V1, y map[K]V2) bool { + if len(x) != len(y) { + return false + } + for k := range x { + if _, ok := y[k]; !ok { + return false + } + } + return true +} + +// Sorted returns an iterator over the entries of m in key order. +func Sorted[M ~map[K]V, K cmp.Ordered, V any](m M) iter.Seq2[K, V] { + return func(yield func(K, V) bool) { + keys := slices.Sorted(maps.Keys(m)) + for _, k := range keys { + if !yield(k, m[k]) { + break + } + } + } +} diff --git a/contribs/gnopls/internal/util/moreslices/slices.go b/contribs/gnopls/internal/util/moreslices/slices.go new file mode 100644 index 00000000000..5905e360bfa --- /dev/null +++ b/contribs/gnopls/internal/util/moreslices/slices.go @@ -0,0 +1,20 @@ +// 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 moreslices + +// Remove removes all values equal to elem from slice. +// +// The closest equivalent in the standard slices package is: +// +// DeleteFunc(func(x T) bool { return x == elem }) +func Remove[T comparable](slice []T, elem T) []T { + out := slice[:0] + for _, v := range slice { + if v != elem { + out = append(out, v) + } + } + return out +} diff --git a/contribs/gnopls/internal/util/pathutil/util.go b/contribs/gnopls/internal/util/pathutil/util.go new file mode 100644 index 00000000000..e19863e202a --- /dev/null +++ b/contribs/gnopls/internal/util/pathutil/util.go @@ -0,0 +1,49 @@ +// 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 pathutil + +import ( + "path/filepath" + "strings" +) + +// InDir checks whether path is in the file tree rooted at dir. +// It checks only the lexical form of the file names. +// It does not consider symbolic links. +// +// Copied from go/src/cmd/go/internal/search/search.go. +func InDir(dir, path string) bool { + pv := strings.ToUpper(filepath.VolumeName(path)) + dv := strings.ToUpper(filepath.VolumeName(dir)) + path = path[len(pv):] + dir = dir[len(dv):] + switch { + default: + return false + case pv != dv: + return false + case len(path) == len(dir): + if path == dir { + return true + } + return false + case dir == "": + return path != "" + case len(path) > len(dir): + if dir[len(dir)-1] == filepath.Separator { + if path[:len(dir)] == dir { + return path[len(dir):] != "" + } + return false + } + if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir { + if len(path) == len(dir)+1 { + return true + } + return path[len(dir)+1:] != "" + } + return false + } +} diff --git a/contribs/gnopls/internal/util/persistent/map.go b/contribs/gnopls/internal/util/persistent/map.go new file mode 100644 index 00000000000..b0e49f27d42 --- /dev/null +++ b/contribs/gnopls/internal/util/persistent/map.go @@ -0,0 +1,322 @@ +// 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. + +// The persistent package defines various persistent data structures; +// that is, data structures that can be efficiently copied and modified +// in sublinear time. +package persistent + +import ( + "fmt" + "math/rand" + "strings" + "sync/atomic" + + "golang.org/x/tools/gopls/internal/util/constraints" +) + +// Implementation details: +// * Each value is reference counted by nodes which hold it. +// * Each node is reference counted by its parent nodes. +// * Each map is considered a top-level parent node from reference counting perspective. +// * Each change does always effectively produce a new top level node. +// +// Functions which operate directly with nodes do have a notation in form of +// `foo(arg1:+n1, arg2:+n2) (ret1:+n3)`. +// Each argument is followed by a delta change to its reference counter. +// In case if no change is expected, the delta will be `-0`. + +// Map is an associative mapping from keys to values. +// +// Maps can be Cloned in constant time. +// Get, Set, and Delete operations are done on average in logarithmic time. +// Maps can be merged (via SetAll) in O(m log(n/m)) time for maps of size n and m, where m < n. +// +// Values are reference counted, and a client-supplied release function +// is called when a value is no longer referenced by a map or any clone. +// +// Internally the implementation is based on a randomized persistent treap: +// https://en.wikipedia.org/wiki/Treap. +// +// The zero value is ready to use. +type Map[K constraints.Ordered, V any] struct { + // Map is a generic wrapper around a non-generic implementation to avoid a + // significant increase in the size of the executable. + root *mapNode +} + +func (*Map[K, V]) less(l, r any) bool { + return l.(K) < r.(K) +} + +func (m *Map[K, V]) String() string { + var buf strings.Builder + buf.WriteByte('{') + var sep string + m.Range(func(k K, v V) { + fmt.Fprintf(&buf, "%s%v: %v", sep, k, v) + sep = ", " + }) + buf.WriteByte('}') + return buf.String() +} + +type mapNode struct { + key any + value *refValue + weight uint64 + refCount int32 + left, right *mapNode +} + +type refValue struct { + refCount int32 + value any + release func(key, value any) +} + +func newNodeWithRef[K constraints.Ordered, V any](key K, value V, release func(key, value any)) *mapNode { + return &mapNode{ + key: key, + value: &refValue{ + value: value, + release: release, + refCount: 1, + }, + refCount: 1, + weight: rand.Uint64(), + } +} + +func (node *mapNode) shallowCloneWithRef() *mapNode { + atomic.AddInt32(&node.value.refCount, 1) + return &mapNode{ + key: node.key, + value: node.value, + weight: node.weight, + refCount: 1, + } +} + +func (node *mapNode) incref() *mapNode { + if node != nil { + atomic.AddInt32(&node.refCount, 1) + } + return node +} + +func (node *mapNode) decref() { + if node == nil { + return + } + if atomic.AddInt32(&node.refCount, -1) == 0 { + if atomic.AddInt32(&node.value.refCount, -1) == 0 { + if node.value.release != nil { + node.value.release(node.key, node.value.value) + } + node.value.value = nil + node.value.release = nil + } + node.left.decref() + node.right.decref() + } +} + +// Clone returns a copy of the given map. It is a responsibility of the caller +// to Destroy it at later time. +func (pm *Map[K, V]) Clone() *Map[K, V] { + return &Map[K, V]{ + root: pm.root.incref(), + } +} + +// Destroy destroys the map. +// +// After Destroy, the Map should not be used again. +func (pm *Map[K, V]) Destroy() { + // The implementation of these two functions is the same, + // but their intent is different. + pm.Clear() +} + +// Clear removes all entries from the map. +func (pm *Map[K, V]) Clear() { + pm.root.decref() + pm.root = nil +} + +// Keys returns all keys present in the map. +func (pm *Map[K, V]) Keys() []K { + var keys []K + pm.root.forEach(func(k, _ any) { + keys = append(keys, k.(K)) + }) + return keys +} + +// Range calls f sequentially in ascending key order for all entries in the map. +func (pm *Map[K, V]) Range(f func(key K, value V)) { + pm.root.forEach(func(k, v any) { + f(k.(K), v.(V)) + }) +} + +func (node *mapNode) forEach(f func(key, value any)) { + if node == nil { + return + } + node.left.forEach(f) + f(node.key, node.value.value) + node.right.forEach(f) +} + +// Get returns the map value associated with the specified key. +// The ok result indicates whether an entry was found in the map. +func (pm *Map[K, V]) Get(key K) (V, bool) { + node := pm.root + for node != nil { + if key < node.key.(K) { + node = node.left + } else if node.key.(K) < key { + node = node.right + } else { + return node.value.value.(V), true + } + } + var zero V + return zero, false +} + +// SetAll updates the map with key/value pairs from the other map, overwriting existing keys. +// It is equivalent to calling Set for each entry in the other map but is more efficient. +func (pm *Map[K, V]) SetAll(other *Map[K, V]) { + root := pm.root + pm.root = union(root, other.root, pm.less, true) + root.decref() +} + +// Set updates the value associated with the specified key. +// If release is non-nil, it will be called with entry's key and value once the +// key is no longer contained in the map or any clone. +func (pm *Map[K, V]) Set(key K, value V, release func(key, value any)) { + first := pm.root + second := newNodeWithRef(key, value, release) + pm.root = union(first, second, pm.less, true) + first.decref() + second.decref() +} + +// union returns a new tree which is a union of first and second one. +// If overwrite is set to true, second one would override a value for any duplicate keys. +// +// union(first:-0, second:-0) (result:+1) +// Union borrows both subtrees without affecting their refcount and returns a +// new reference that the caller is expected to call decref. +func union(first, second *mapNode, less func(any, any) bool, overwrite bool) *mapNode { + if first == nil { + return second.incref() + } + if second == nil { + return first.incref() + } + + if first.weight < second.weight { + second, first, overwrite = first, second, !overwrite + } + + left, mid, right := split(second, first.key, less, false) + var result *mapNode + if overwrite && mid != nil { + result = mid.shallowCloneWithRef() + } else { + result = first.shallowCloneWithRef() + } + result.weight = first.weight + result.left = union(first.left, left, less, overwrite) + result.right = union(first.right, right, less, overwrite) + left.decref() + mid.decref() + right.decref() + return result +} + +// split the tree midway by the key into three different ones. +// Return three new trees: left with all nodes with smaller than key, mid with +// the node matching the key, right with all nodes larger than key. +// If there are no nodes in one of trees, return nil instead of it. +// If requireMid is set (such as during deletion), then all return arguments +// are nil if mid is not found. +// +// split(n:-0) (left:+1, mid:+1, right:+1) +// Split borrows n without affecting its refcount, and returns three +// new references that the caller is expected to call decref. +func split(n *mapNode, key any, less func(any, any) bool, requireMid bool) (left, mid, right *mapNode) { + if n == nil { + return nil, nil, nil + } + + if less(n.key, key) { + left, mid, right := split(n.right, key, less, requireMid) + if requireMid && mid == nil { + return nil, nil, nil + } + newN := n.shallowCloneWithRef() + newN.left = n.left.incref() + newN.right = left + return newN, mid, right + } else if less(key, n.key) { + left, mid, right := split(n.left, key, less, requireMid) + if requireMid && mid == nil { + return nil, nil, nil + } + newN := n.shallowCloneWithRef() + newN.left = right + newN.right = n.right.incref() + return left, mid, newN + } + mid = n.shallowCloneWithRef() + return n.left.incref(), mid, n.right.incref() +} + +// Delete deletes the value for a key. +// +// The result reports whether the key was present in the map. +func (pm *Map[K, V]) Delete(key K) bool { + root := pm.root + left, mid, right := split(root, key, pm.less, true) + if mid == nil { + return false + } + pm.root = merge(left, right) + left.decref() + mid.decref() + right.decref() + root.decref() + return true +} + +// merge two trees while preserving the weight invariant. +// All nodes in left must have smaller keys than any node in right. +// +// merge(left:-0, right:-0) (result:+1) +// Merge borrows its arguments without affecting their refcount +// and returns a new reference that the caller is expected to call decref. +func merge(left, right *mapNode) *mapNode { + switch { + case left == nil: + return right.incref() + case right == nil: + return left.incref() + case left.weight > right.weight: + root := left.shallowCloneWithRef() + root.left = left.left.incref() + root.right = merge(left.right, right) + return root + default: + root := right.shallowCloneWithRef() + root.left = merge(left, right.left) + root.right = right.right.incref() + return root + } +} diff --git a/contribs/gnopls/internal/util/persistent/map_test.go b/contribs/gnopls/internal/util/persistent/map_test.go new file mode 100644 index 00000000000..effa1c1da85 --- /dev/null +++ b/contribs/gnopls/internal/util/persistent/map_test.go @@ -0,0 +1,352 @@ +// 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 persistent + +import ( + "fmt" + "math/rand" + "reflect" + "sync/atomic" + "testing" +) + +type mapEntry struct { + key int + value int +} + +type validatedMap struct { + impl *Map[int, int] + expected map[int]int // current key-value mapping. + deleted map[mapEntry]int // maps deleted entries to their clock time of last deletion + seen map[mapEntry]int // maps seen entries to their clock time of last insertion + clock int +} + +func TestSimpleMap(t *testing.T) { + deletedEntries := make(map[mapEntry]int) + seenEntries := make(map[mapEntry]int) + + m1 := &validatedMap{ + impl: new(Map[int, int]), + expected: make(map[int]int), + deleted: deletedEntries, + seen: seenEntries, + } + + m3 := m1.clone() + validateRef(t, m1, m3) + m3.set(t, 8, 8) + validateRef(t, m1, m3) + m3.destroy() + + assertSameMap(t, entrySet(deletedEntries), map[mapEntry]struct{}{ + {key: 8, value: 8}: {}, + }) + + validateRef(t, m1) + m1.set(t, 1, 1) + validateRef(t, m1) + m1.set(t, 2, 2) + validateRef(t, m1) + m1.set(t, 3, 3) + validateRef(t, m1) + m1.remove(t, 2) + validateRef(t, m1) + m1.set(t, 6, 6) + validateRef(t, m1) + + assertSameMap(t, entrySet(deletedEntries), map[mapEntry]struct{}{ + {key: 2, value: 2}: {}, + {key: 8, value: 8}: {}, + }) + + m2 := m1.clone() + validateRef(t, m1, m2) + m1.set(t, 6, 60) + validateRef(t, m1, m2) + m1.remove(t, 1) + validateRef(t, m1, m2) + + gotAllocs := int(testing.AllocsPerRun(10, func() { + m1.impl.Delete(100) + m1.impl.Delete(1) + })) + wantAllocs := 0 + if gotAllocs != wantAllocs { + t.Errorf("wanted %d allocs, got %d", wantAllocs, gotAllocs) + } + + for i := 10; i < 14; i++ { + m1.set(t, i, i) + validateRef(t, m1, m2) + } + + m1.set(t, 10, 100) + validateRef(t, m1, m2) + + m1.remove(t, 12) + validateRef(t, m1, m2) + + m2.set(t, 4, 4) + validateRef(t, m1, m2) + m2.set(t, 5, 5) + validateRef(t, m1, m2) + + m1.destroy() + + assertSameMap(t, entrySet(deletedEntries), map[mapEntry]struct{}{ + {key: 2, value: 2}: {}, + {key: 6, value: 60}: {}, + {key: 8, value: 8}: {}, + {key: 10, value: 10}: {}, + {key: 10, value: 100}: {}, + {key: 11, value: 11}: {}, + {key: 12, value: 12}: {}, + {key: 13, value: 13}: {}, + }) + + m2.set(t, 7, 7) + validateRef(t, m2) + + m2.destroy() + + assertSameMap(t, entrySet(seenEntries), entrySet(deletedEntries)) +} + +func TestRandomMap(t *testing.T) { + deletedEntries := make(map[mapEntry]int) + seenEntries := make(map[mapEntry]int) + + m := &validatedMap{ + impl: new(Map[int, int]), + expected: make(map[int]int), + deleted: deletedEntries, + seen: seenEntries, + } + + keys := make([]int, 0, 1000) + for i := 0; i < 1000; i++ { + key := rand.Intn(10000) + m.set(t, key, key) + keys = append(keys, key) + + if i%10 == 1 { + index := rand.Intn(len(keys)) + last := len(keys) - 1 + key = keys[index] + keys[index], keys[last] = keys[last], keys[index] + keys = keys[:last] + + m.remove(t, key) + } + } + + m.destroy() + assertSameMap(t, entrySet(seenEntries), entrySet(deletedEntries)) +} + +func entrySet(m map[mapEntry]int) map[mapEntry]struct{} { + set := make(map[mapEntry]struct{}) + for k := range m { + set[k] = struct{}{} + } + return set +} + +func TestUpdate(t *testing.T) { + deletedEntries := make(map[mapEntry]int) + seenEntries := make(map[mapEntry]int) + + m1 := &validatedMap{ + impl: new(Map[int, int]), + expected: make(map[int]int), + deleted: deletedEntries, + seen: seenEntries, + } + m2 := m1.clone() + + m1.set(t, 1, 1) + m1.set(t, 2, 2) + m2.set(t, 2, 20) + m2.set(t, 3, 3) + m1.setAll(t, m2) + + m1.destroy() + m2.destroy() + assertSameMap(t, entrySet(seenEntries), entrySet(deletedEntries)) +} + +func validateRef(t *testing.T, maps ...*validatedMap) { + t.Helper() + + actualCountByEntry := make(map[mapEntry]int32) + nodesByEntry := make(map[mapEntry]map[*mapNode]struct{}) + expectedCountByEntry := make(map[mapEntry]int32) + for i, m := range maps { + dfsRef(m.impl.root, actualCountByEntry, nodesByEntry) + dumpMap(t, fmt.Sprintf("%d:", i), m.impl.root) + } + for entry, nodes := range nodesByEntry { + expectedCountByEntry[entry] = int32(len(nodes)) + } + assertSameMap(t, expectedCountByEntry, actualCountByEntry) +} + +func dfsRef(node *mapNode, countByEntry map[mapEntry]int32, nodesByEntry map[mapEntry]map[*mapNode]struct{}) { + if node == nil { + return + } + + entry := mapEntry{key: node.key.(int), value: node.value.value.(int)} + countByEntry[entry] = atomic.LoadInt32(&node.value.refCount) + + nodes, ok := nodesByEntry[entry] + if !ok { + nodes = make(map[*mapNode]struct{}) + nodesByEntry[entry] = nodes + } + nodes[node] = struct{}{} + + dfsRef(node.left, countByEntry, nodesByEntry) + dfsRef(node.right, countByEntry, nodesByEntry) +} + +func dumpMap(t *testing.T, prefix string, n *mapNode) { + if n == nil { + t.Logf("%s nil", prefix) + return + } + t.Logf("%s {key: %v, value: %v (ref: %v), ref: %v, weight: %v}", prefix, n.key, n.value.value, n.value.refCount, n.refCount, n.weight) + dumpMap(t, prefix+"l", n.left) + dumpMap(t, prefix+"r", n.right) +} + +func (vm *validatedMap) validate(t *testing.T) { + t.Helper() + + validateNode(t, vm.impl.root) + + // Note: this validation may not make sense if maps were constructed using + // SetAll operations. If this proves to be problematic, remove the clock, + // deleted, and seen fields. + for key, value := range vm.expected { + entry := mapEntry{key: key, value: value} + if deleteAt := vm.deleted[entry]; deleteAt > vm.seen[entry] { + t.Fatalf("entry is deleted prematurely, key: %d, value: %d", key, value) + } + } + + actualMap := make(map[int]int, len(vm.expected)) + vm.impl.Range(func(key, value int) { + if other, ok := actualMap[key]; ok { + t.Fatalf("key is present twice, key: %d, first value: %d, second value: %d", key, value, other) + } + actualMap[key] = value + }) + + assertSameMap(t, actualMap, vm.expected) +} + +func validateNode(t *testing.T, node *mapNode) { + if node == nil { + return + } + + if node.left != nil { + if node.key.(int) < node.left.key.(int) { + t.Fatalf("left child has larger key: %v vs %v", node.left.key, node.key) + } + if node.left.weight > node.weight { + t.Fatalf("left child has larger weight: %v vs %v", node.left.weight, node.weight) + } + } + + if node.right != nil { + if node.right.key.(int) < node.key.(int) { + t.Fatalf("right child has smaller key: %v vs %v", node.right.key, node.key) + } + if node.right.weight > node.weight { + t.Fatalf("right child has larger weight: %v vs %v", node.right.weight, node.weight) + } + } + + validateNode(t, node.left) + validateNode(t, node.right) +} + +func (vm *validatedMap) setAll(t *testing.T, other *validatedMap) { + vm.impl.SetAll(other.impl) + + // Note: this is buggy because we are not updating vm.clock, vm.deleted, or + // vm.seen. + for key, value := range other.expected { + vm.expected[key] = value + } + vm.validate(t) +} + +func (vm *validatedMap) set(t *testing.T, key, value int) { + entry := mapEntry{key: key, value: value} + + vm.clock++ + vm.seen[entry] = vm.clock + + vm.impl.Set(key, value, func(deletedKey, deletedValue any) { + if deletedKey != key || deletedValue != value { + t.Fatalf("unexpected passed in deleted entry: %v/%v, expected: %v/%v", deletedKey, deletedValue, key, value) + } + // Not safe if closure shared between two validatedMaps. + vm.deleted[entry] = vm.clock + }) + vm.expected[key] = value + vm.validate(t) + + gotValue, ok := vm.impl.Get(key) + if !ok || gotValue != value { + t.Fatalf("unexpected get result after insertion, key: %v, expected: %v, got: %v (%v)", key, value, gotValue, ok) + } +} + +func (vm *validatedMap) remove(t *testing.T, key int) { + vm.clock++ + deleted := vm.impl.Delete(key) + if _, ok := vm.expected[key]; ok != deleted { + t.Fatalf("Delete(%d) = %t, want %t", key, deleted, ok) + } + delete(vm.expected, key) + vm.validate(t) + + gotValue, ok := vm.impl.Get(key) + if ok { + t.Fatalf("unexpected get result after removal, key: %v, got: %v", key, gotValue) + } +} + +func (vm *validatedMap) clone() *validatedMap { + expected := make(map[int]int, len(vm.expected)) + for key, value := range vm.expected { + expected[key] = value + } + + return &validatedMap{ + impl: vm.impl.Clone(), + expected: expected, + deleted: vm.deleted, + seen: vm.seen, + } +} + +func (vm *validatedMap) destroy() { + vm.impl.Destroy() +} + +func assertSameMap(t *testing.T, map1, map2 any) { + t.Helper() + + if !reflect.DeepEqual(map1, map2) { + t.Fatalf("different maps:\n%v\nvs\n%v", map1, map2) + } +} diff --git a/contribs/gnopls/internal/util/persistent/set.go b/contribs/gnopls/internal/util/persistent/set.go new file mode 100644 index 00000000000..2d5f4edac96 --- /dev/null +++ b/contribs/gnopls/internal/util/persistent/set.go @@ -0,0 +1,78 @@ +// 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 persistent + +import "golang.org/x/tools/gopls/internal/util/constraints" + +// Set is a collection of elements of type K. +// +// It uses immutable data structures internally, so that sets can be cloned in +// constant time. +// +// The zero value is a valid empty set. +type Set[K constraints.Ordered] struct { + impl *Map[K, struct{}] +} + +// Clone creates a copy of the receiver. +func (s *Set[K]) Clone() *Set[K] { + clone := new(Set[K]) + if s.impl != nil { + clone.impl = s.impl.Clone() + } + return clone +} + +// Destroy destroys the set. +// +// After Destroy, the Set should not be used again. +func (s *Set[K]) Destroy() { + if s.impl != nil { + s.impl.Destroy() + } +} + +// Contains reports whether s contains the given key. +func (s *Set[K]) Contains(key K) bool { + if s.impl == nil { + return false + } + _, ok := s.impl.Get(key) + return ok +} + +// Range calls f sequentially in ascending key order for all entries in the set. +func (s *Set[K]) Range(f func(key K)) { + if s.impl != nil { + s.impl.Range(func(key K, _ struct{}) { + f(key) + }) + } +} + +// AddAll adds all elements from other to the receiver set. +func (s *Set[K]) AddAll(other *Set[K]) { + if other.impl != nil { + if s.impl == nil { + s.impl = new(Map[K, struct{}]) + } + s.impl.SetAll(other.impl) + } +} + +// Add adds an element to the set. +func (s *Set[K]) Add(key K) { + if s.impl == nil { + s.impl = new(Map[K, struct{}]) + } + s.impl.Set(key, struct{}{}, nil) +} + +// Remove removes an element from the set. +func (s *Set[K]) Remove(key K) { + if s.impl != nil { + s.impl.Delete(key) + } +} diff --git a/contribs/gnopls/internal/util/persistent/set_test.go b/contribs/gnopls/internal/util/persistent/set_test.go new file mode 100644 index 00000000000..31911b451b3 --- /dev/null +++ b/contribs/gnopls/internal/util/persistent/set_test.go @@ -0,0 +1,132 @@ +// 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 persistent_test + +import ( + "fmt" + "strings" + "testing" + + "golang.org/x/tools/gopls/internal/util/constraints" + "golang.org/x/tools/gopls/internal/util/persistent" +) + +func TestSet(t *testing.T) { + const ( + add = iota + remove + ) + type op struct { + op int + v int + } + + tests := []struct { + label string + ops []op + want []int + }{ + {"empty", nil, nil}, + {"singleton", []op{{add, 1}}, []int{1}}, + {"add and remove", []op{ + {add, 1}, + {remove, 1}, + }, nil}, + {"interleaved and remove", []op{ + {add, 1}, + {add, 2}, + {remove, 1}, + {add, 3}, + }, []int{2, 3}}, + } + + for _, test := range tests { + t.Run(test.label, func(t *testing.T) { + var s persistent.Set[int] + for _, op := range test.ops { + switch op.op { + case add: + s.Add(op.v) + case remove: + s.Remove(op.v) + } + } + + if d := diff(&s, test.want); d != "" { + t.Errorf("unexpected diff:\n%s", d) + } + }) + } +} + +func TestSet_Clone(t *testing.T) { + s1 := new(persistent.Set[int]) + s1.Add(1) + s1.Add(2) + s2 := s1.Clone() + s1.Add(3) + s2.Add(4) + if d := diff(s1, []int{1, 2, 3}); d != "" { + t.Errorf("s1: unexpected diff:\n%s", d) + } + if d := diff(s2, []int{1, 2, 4}); d != "" { + t.Errorf("s2: unexpected diff:\n%s", d) + } +} + +func TestSet_AddAll(t *testing.T) { + s1 := new(persistent.Set[int]) + s1.Add(1) + s1.Add(2) + s2 := new(persistent.Set[int]) + s2.Add(2) + s2.Add(3) + s2.Add(4) + s3 := new(persistent.Set[int]) + + s := new(persistent.Set[int]) + s.AddAll(s1) + s.AddAll(s2) + s.AddAll(s3) + + if d := diff(s1, []int{1, 2}); d != "" { + t.Errorf("s1: unexpected diff:\n%s", d) + } + if d := diff(s2, []int{2, 3, 4}); d != "" { + t.Errorf("s2: unexpected diff:\n%s", d) + } + if d := diff(s3, nil); d != "" { + t.Errorf("s3: unexpected diff:\n%s", d) + } + if d := diff(s, []int{1, 2, 3, 4}); d != "" { + t.Errorf("s: unexpected diff:\n%s", d) + } +} + +func diff[K constraints.Ordered](got *persistent.Set[K], want []K) string { + wantSet := make(map[K]struct{}) + for _, w := range want { + wantSet[w] = struct{}{} + } + var diff []string + got.Range(func(key K) { + if _, ok := wantSet[key]; !ok { + diff = append(diff, fmt.Sprintf("+%v", key)) + } + }) + for key := range wantSet { + if !got.Contains(key) { + diff = append(diff, fmt.Sprintf("-%v", key)) + } + } + if len(diff) > 0 { + d := new(strings.Builder) + for _, l := range diff { + fmt.Fprintln(d, l) + } + return d.String() + } + return "" +} diff --git a/contribs/gnopls/internal/util/safetoken/safetoken.go b/contribs/gnopls/internal/util/safetoken/safetoken.go new file mode 100644 index 00000000000..bb5ee0d7bf0 --- /dev/null +++ b/contribs/gnopls/internal/util/safetoken/safetoken.go @@ -0,0 +1,127 @@ +// 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 safetoken provides wrappers around methods in go/token, +// that return errors rather than panicking. +// +// It also provides a central place for workarounds in the underlying +// packages. The use of this package's functions instead of methods of +// token.File (such as Offset, Position, and PositionFor) is mandatory +// throughout the gopls codebase and enforced by a static check. +package safetoken + +import ( + "fmt" + "go/token" +) + +// Offset returns f.Offset(pos), but first checks that the file +// contains the pos. +// +// The definition of "contains" here differs from that of token.File +// in order to work around a bug in the parser (issue #57490): during +// error recovery, the parser may create syntax nodes whose computed +// End position is 1 byte beyond EOF, which would cause +// token.File.Offset to panic. The workaround is that this function +// accepts a Pos that is exactly 1 byte beyond EOF and maps it to the +// EOF offset. +func Offset(f *token.File, pos token.Pos) (int, error) { + if !inRange(f, pos) { + // Accept a Pos that is 1 byte beyond EOF, + // and map it to the EOF offset. + // (Workaround for #57490.) + if int(pos) == f.Base()+f.Size()+1 { + return f.Size(), nil + } + + return -1, fmt.Errorf("pos %d is not in range [%d:%d] of file %s", + pos, f.Base(), f.Base()+f.Size(), f.Name()) + } + return int(pos) - f.Base(), nil +} + +// Offsets returns Offset(start) and Offset(end). +func Offsets(f *token.File, start, end token.Pos) (int, int, error) { + startOffset, err := Offset(f, start) + if err != nil { + return 0, 0, fmt.Errorf("start: %v", err) + } + endOffset, err := Offset(f, end) + if err != nil { + return 0, 0, fmt.Errorf("end: %v", err) + } + return startOffset, endOffset, nil +} + +// Pos returns f.Pos(offset), but first checks that the offset is +// non-negative and not larger than the size of the file. +func Pos(f *token.File, offset int) (token.Pos, error) { + if !(0 <= offset && offset <= f.Size()) { + return token.NoPos, fmt.Errorf("offset %d is not in range for file %s of size %d", offset, f.Name(), f.Size()) + } + return token.Pos(f.Base() + offset), nil +} + +// inRange reports whether file f contains position pos, +// according to the invariants of token.File. +// +// This function is not public because of the ambiguity it would +// create w.r.t. the definition of "contains". Use Offset instead. +func inRange(f *token.File, pos token.Pos) bool { + return token.Pos(f.Base()) <= pos && pos <= token.Pos(f.Base()+f.Size()) +} + +// Position returns the Position for the pos value in the given file. +// +// p must be NoPos, a valid Pos in the range of f, or exactly 1 byte +// beyond the end of f. (See [Offset] for explanation.) +// Any other value causes a panic. +// +// Line directives (//line comments) are ignored. +func Position(f *token.File, pos token.Pos) token.Position { + // Work around issue #57490. + if int(pos) == f.Base()+f.Size()+1 { + pos-- + } + + // TODO(adonovan): centralize the workaround for + // golang/go#41029 (newline at EOF) here too. + + return f.PositionFor(pos, false) +} + +// Line returns the line number for the given offset in the given file. +func Line(f *token.File, pos token.Pos) int { + return Position(f, pos).Line +} + +// StartPosition converts a start Pos in the FileSet into a Position. +// +// Call this function only if start represents the start of a token or +// parse tree, such as the result of Node.Pos(). If start is the end of +// an interval, such as Node.End(), call EndPosition instead, as it +// may need the correction described at [Position]. +func StartPosition(fset *token.FileSet, start token.Pos) (_ token.Position) { + if f := fset.File(start); f != nil { + return Position(f, start) + } + return +} + +// EndPosition converts an end Pos in the FileSet into a Position. +// +// Call this function only if pos represents the end of +// a non-empty interval, such as the result of Node.End(). +func EndPosition(fset *token.FileSet, end token.Pos) (_ token.Position) { + if f := fset.File(end); f != nil && int(end) > f.Base() { + return Position(f, end) + } + + // Work around issue #57490. + if f := fset.File(end - 1); f != nil { + return Position(f, end) + } + + return +} diff --git a/contribs/gnopls/internal/util/safetoken/safetoken_test.go b/contribs/gnopls/internal/util/safetoken/safetoken_test.go new file mode 100644 index 00000000000..4cdce7a97b9 --- /dev/null +++ b/contribs/gnopls/internal/util/safetoken/safetoken_test.go @@ -0,0 +1,131 @@ +// 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 safetoken_test + +import ( + "fmt" + "go/parser" + "go/token" + "go/types" + "os" + "testing" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/gopls/internal/util/safetoken" + "golang.org/x/tools/internal/testenv" +) + +func TestWorkaroundIssue57490(t *testing.T) { + // During error recovery the parser synthesizes various close + // tokens at EOF, causing the End position of incomplete + // syntax nodes, computed as Rbrace+len("}"), to be beyond EOF. + src := `package p; func f() { var x struct` + fset := token.NewFileSet() + file, _ := parser.ParseFile(fset, "a.go", src, 0) + tf := fset.File(file.Pos()) + + // Add another file to the FileSet. + file2, _ := parser.ParseFile(fset, "b.go", "package q", 0) + + // This is the ambiguity of #57490... + if file.End() != file2.Pos() { + t.Errorf("file.End() %d != %d file2.Pos()", file.End(), file2.Pos()) + } + // ...which causes these statements to panic. + if false { + tf.Offset(file.End()) // panic: invalid Pos value 36 (should be in [1, 35]) + tf.Position(file.End()) // panic: invalid Pos value 36 (should be in [1, 35]) + } + + // The offset of the EOF position is the file size. + offset, err := safetoken.Offset(tf, file.End()-1) + if err != nil || offset != tf.Size() { + t.Errorf("Offset(EOF) = (%d, %v), want token.File.Size %d", offset, err, tf.Size()) + } + + // The offset of the file.End() position, 1 byte beyond EOF, + // is also the size of the file. + offset, err = safetoken.Offset(tf, file.End()) + if err != nil || offset != tf.Size() { + t.Errorf("Offset(ast.File.End()) = (%d, %v), want token.File.Size %d", offset, err, tf.Size()) + } + + if got, want := safetoken.Position(tf, file.End()).String(), "a.go:1:35"; got != want { + t.Errorf("Position(ast.File.End()) = %s, want %s", got, want) + } + + if got, want := safetoken.EndPosition(fset, file.End()).String(), "a.go:1:35"; got != want { + t.Errorf("EndPosition(ast.File.End()) = %s, want %s", got, want) + } + + // Note that calling StartPosition on an end may yield the wrong file: + if got, want := safetoken.StartPosition(fset, file.End()).String(), "b.go:1:1"; got != want { + t.Errorf("StartPosition(ast.File.End()) = %s, want %s", got, want) + } +} + +// To reduce the risk of panic, or bugs for which this package +// provides a workaround, this test statically reports references to +// forbidden methods of token.File or FileSet throughout gopls and +// suggests alternatives. +func TestGoplsSourceDoesNotCallTokenFileMethods(t *testing.T) { + testenv.NeedsGoPackages(t) + testenv.NeedsLocalXTools(t) + + cfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedModule | packages.NeedCompiledGoFiles | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps, + } + cfg.Env = os.Environ() + cfg.Env = append(cfg.Env, + "GOPACKAGESDRIVER=off", + "GOWORK=off", // necessary for -mod=mod below + "GOFLAGS=-mod=mod", + ) + + pkgs, err := packages.Load(cfg, "go/token", "golang.org/x/tools/gopls/...") + if err != nil { + t.Fatal(err) + } + var tokenPkg *packages.Package + for _, pkg := range pkgs { + if pkg.PkgPath == "go/token" { + tokenPkg = pkg + break + } + } + if tokenPkg == nil { + t.Fatal("missing package go/token") + } + + File := tokenPkg.Types.Scope().Lookup("File") + FileSet := tokenPkg.Types.Scope().Lookup("FileSet") + + alternative := make(map[types.Object]string) + setAlternative := func(recv types.Object, old, new string) { + oldMethod, _, _ := types.LookupFieldOrMethod(recv.Type(), true, recv.Pkg(), old) + alternative[oldMethod] = new + } + setAlternative(File, "Line", "safetoken.Line") + setAlternative(File, "Offset", "safetoken.Offset") + setAlternative(File, "Position", "safetoken.Position") + setAlternative(File, "PositionFor", "safetoken.Position") + setAlternative(FileSet, "Position", "safetoken.StartPosition or EndPosition") + setAlternative(FileSet, "PositionFor", "safetoken.StartPosition or EndPosition") + + for _, pkg := range pkgs { + switch pkg.PkgPath { + case "go/token", "golang.org/x/tools/gopls/internal/util/safetoken": + continue // allow calls within these packages + } + + for ident, obj := range pkg.TypesInfo.Uses { + if alt, ok := alternative[obj]; ok { + posn := safetoken.StartPosition(pkg.Fset, ident.Pos()) + fmt.Fprintf(os.Stderr, "%s: forbidden use of %v; use %s instead.\n", posn, obj, alt) + t.Fail() + } + } + } +} diff --git a/contribs/gnopls/internal/util/typesutil/typesutil.go b/contribs/gnopls/internal/util/typesutil/typesutil.go new file mode 100644 index 00000000000..6e61c7ed874 --- /dev/null +++ b/contribs/gnopls/internal/util/typesutil/typesutil.go @@ -0,0 +1,36 @@ +// 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 typesutil + +import ( + "go/ast" + "go/types" +) + +// FileQualifier returns a [types.Qualifier] function that qualifies +// imported symbols appropriately based on the import environment of a +// given file. +func FileQualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier { + // Construct mapping of import paths to their defined or implicit names. + imports := make(map[*types.Package]string) + for _, imp := range f.Imports { + if pkgname := info.PkgNameOf(imp); pkgname != nil { + imports[pkgname.Imported()] = pkgname.Name() + } + } + // Define qualifier to replace full package paths with names of the imports. + return func(p *types.Package) string { + if p == pkg { + return "" + } + if name, ok := imports[p]; ok { + if name == "." { + return "" + } + return name + } + return p.Name() + } +} diff --git a/contribs/gnopls/internal/version/version.go b/contribs/gnopls/internal/version/version.go new file mode 100644 index 00000000000..96f18190aff --- /dev/null +++ b/contribs/gnopls/internal/version/version.go @@ -0,0 +1,29 @@ +// 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 version manages the gopls version. +// +// The VersionOverride variable may be used to set the gopls version at link +// time. +package version + +import "runtime/debug" + +var VersionOverride = "" + +// Version returns the gopls version. +// +// By default, this is read from runtime/debug.ReadBuildInfo, but may be +// overridden by the [VersionOverride] variable. +func Version() string { + if VersionOverride != "" { + return VersionOverride + } + if info, ok := debug.ReadBuildInfo(); ok { + if info.Main.Version != "" { + return info.Main.Version + } + } + return "(unknown)" +} diff --git a/contribs/gnopls/internal/vulncheck/copier.go b/contribs/gnopls/internal/vulncheck/copier.go new file mode 100644 index 00000000000..ade5a5f6be2 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/copier.go @@ -0,0 +1,142 @@ +// 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. + +//go:build ignore +// +build ignore + +//go:generate go run ./copier.go + +// Copier is a tool to automate copy of govulncheck's internal files. +// +// - copy golang.org/x/vuln/internal/osv/ to osv +// - copy golang.org/x/vuln/internal/govulncheck/ to govulncheck +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "go/parser" + "go/token" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "golang.org/x/tools/internal/edit" +) + +func main() { + log.SetPrefix("copier: ") + log.SetFlags(log.Lshortfile) + + srcMod := "golang.org/x/vuln" + srcModVers := "@latest" + srcDir, srcVer := downloadModule(srcMod + srcModVers) + + cfg := rewrite{ + banner: fmt.Sprintf("// Code generated by copying from %v@%v (go run copier.go); DO NOT EDIT.", srcMod, srcVer), + srcImportPath: "golang.org/x/vuln/internal", + dstImportPath: currentPackagePath(), + } + + copyFiles("osv", filepath.Join(srcDir, "internal", "osv"), cfg) + copyFiles("govulncheck", filepath.Join(srcDir, "internal", "govulncheck"), cfg) +} + +type rewrite struct { + // DO NOT EDIT marker to add at the beginning + banner string + // rewrite srcImportPath with dstImportPath + srcImportPath string + dstImportPath string +} + +func copyFiles(dst, src string, cfg rewrite) { + entries, err := os.ReadDir(src) + if err != nil { + log.Fatalf("failed to read dir: %v", err) + } + if err := os.MkdirAll(dst, 0777); err != nil { + log.Fatalf("failed to create dir: %v", err) + } + + for _, e := range entries { + fname := e.Name() + // we need only non-test go files. + if e.IsDir() || !strings.HasSuffix(fname, ".go") || strings.HasSuffix(fname, "_test.go") { + continue + } + data, err := os.ReadFile(filepath.Join(src, fname)) + if err != nil { + log.Fatal(err) + } + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, fname, data, parser.ParseComments|parser.ImportsOnly) + if err != nil { + log.Fatalf("parsing source module:\n%s", err) + } + + buf := edit.NewBuffer(data) + at := func(p token.Pos) int { + return fset.File(p).Offset(p) + } + + // Add banner right after the copyright statement (the first comment) + bannerInsert, banner := f.FileStart, cfg.banner + if len(f.Comments) > 0 && strings.HasPrefix(f.Comments[0].Text(), "Copyright ") { + bannerInsert = f.Comments[0].End() + banner = "\n\n" + banner + } + buf.Replace(at(bannerInsert), at(bannerInsert), banner) + + // Adjust imports + for _, spec := range f.Imports { + path, err := strconv.Unquote(spec.Path.Value) + if err != nil { + log.Fatal(err) + } + if strings.HasPrefix(path, cfg.srcImportPath) { + newPath := strings.Replace(path, cfg.srcImportPath, cfg.dstImportPath, 1) + buf.Replace(at(spec.Path.Pos()), at(spec.Path.End()), strconv.Quote(newPath)) + } + } + data = buf.Bytes() + + if err := os.WriteFile(filepath.Join(dst, fname), data, 0666); err != nil { + log.Fatal(err) + } + } +} + +func downloadModule(srcModVers string) (dir, ver string) { + var stdout, stderr bytes.Buffer + cmd := exec.Command("go", "mod", "download", "-json", srcModVers) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + log.Fatalf("go mod download -json %s: %v\n%s%s", srcModVers, err, stderr.Bytes(), stdout.Bytes()) + } + var info struct { + Dir string + Version string + } + if err := json.Unmarshal(stdout.Bytes(), &info); err != nil { + log.Fatalf("go mod download -json %s: invalid JSON output: %v\n%s%s", srcModVers, err, stderr.Bytes(), stdout.Bytes()) + } + return info.Dir, info.Version +} + +func currentPackagePath() string { + var stdout, stderr bytes.Buffer + cmd := exec.Command("go", "list", ".") + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + log.Fatalf("go list: %v\n%s%s", err, stderr.Bytes(), stdout.Bytes()) + } + return strings.TrimSpace(stdout.String()) +} diff --git a/contribs/gnopls/internal/vulncheck/govulncheck/govulncheck.go b/contribs/gnopls/internal/vulncheck/govulncheck/govulncheck.go new file mode 100644 index 00000000000..fd0390703ae --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/govulncheck/govulncheck.go @@ -0,0 +1,160 @@ +// 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. + +// Code generated by copying from golang.org/x/vuln@v1.0.1 (go run copier.go); DO NOT EDIT. + +// Package govulncheck contains the JSON output structs for govulncheck. +package govulncheck + +import ( + "time" + + "golang.org/x/tools/gopls/internal/vulncheck/osv" +) + +const ( + // ProtocolVersion is the current protocol version this file implements + ProtocolVersion = "v1.0.0" +) + +// Message is an entry in the output stream. It will always have exactly one +// field filled in. +type Message struct { + Config *Config `json:"config,omitempty"` + Progress *Progress `json:"progress,omitempty"` + OSV *osv.Entry `json:"osv,omitempty"` + Finding *Finding `json:"finding,omitempty"` +} + +// Config must occur as the first message of a stream and informs the client +// about the information used to generate the findings. +// The only required field is the protocol version. +type Config struct { + // ProtocolVersion specifies the version of the JSON protocol. + ProtocolVersion string `json:"protocol_version"` + + // ScannerName is the name of the tool, for example, govulncheck. + // + // We expect this JSON format to be used by other tools that wrap + // govulncheck, which will have a different name. + ScannerName string `json:"scanner_name,omitempty"` + + // ScannerVersion is the version of the tool. + ScannerVersion string `json:"scanner_version,omitempty"` + + // DB is the database used by the tool, for example, + // vuln.go.dev. + DB string `json:"db,omitempty"` + + // LastModified is the last modified time of the data source. + DBLastModified *time.Time `json:"db_last_modified,omitempty"` + + // GoVersion is the version of Go used for analyzing standard library + // vulnerabilities. + GoVersion string `json:"go_version,omitempty"` + + // ScanLevel instructs govulncheck to analyze at a specific level of detail. + // Valid values include module, package and symbol. + ScanLevel ScanLevel `json:"scan_level,omitempty"` +} + +// Progress messages are informational only, intended to allow users to monitor +// the progress of a long running scan. +// A stream must remain fully valid and able to be interpreted with all progress +// messages removed. +type Progress struct { + // A time stamp for the message. + Timestamp *time.Time `json:"time,omitempty"` + + // Message is the progress message. + Message string `json:"message,omitempty"` +} + +// Vuln represents a single OSV entry. +type Finding struct { + // OSV is the id of the detected vulnerability. + OSV string `json:"osv,omitempty"` + + // FixedVersion is the module version where the vulnerability was + // fixed. This is empty if a fix is not available. + // + // If there are multiple fixed versions in the OSV report, this will + // be the fixed version in the latest range event for the OSV report. + // + // For example, if the range events are + // {introduced: 0, fixed: 1.0.0} and {introduced: 1.1.0}, the fixed version + // will be empty. + // + // For the stdlib, we will show the fixed version closest to the + // Go version that is used. For example, if a fix is available in 1.17.5 and + // 1.18.5, and the GOVERSION is 1.17.3, 1.17.5 will be returned as the + // fixed version. + FixedVersion string `json:"fixed_version,omitempty"` + + // Trace contains an entry for each frame in the trace. + // + // Frames are sorted starting from the imported vulnerable symbol + // until the entry point. The first frame in Frames should match + // Symbol. + // + // In binary mode, trace will contain a single-frame with no position + // information. + // + // When a package is imported but no vulnerable symbol is called, the trace + // will contain a single-frame with no symbol or position information. + Trace []*Frame `json:"trace,omitempty"` +} + +// Frame represents an entry in a finding trace. +type Frame struct { + // Module is the module path of the module containing this symbol. + // + // Importable packages in the standard library will have the path "stdlib". + Module string `json:"module"` + + // Version is the module version from the build graph. + Version string `json:"version,omitempty"` + + // Package is the import path. + Package string `json:"package,omitempty"` + + // Function is the function name. + Function string `json:"function,omitempty"` + + // Receiver is the receiver type if the called symbol is a method. + // + // The client can create the final symbol name by + // prepending Receiver to FuncName. + Receiver string `json:"receiver,omitempty"` + + // Position describes an arbitrary source position + // including the file, line, and column location. + // A Position is valid if the line number is > 0. + Position *Position `json:"position,omitempty"` +} + +// Position represents arbitrary source position. +type Position struct { + Filename string `json:"filename,omitempty"` // filename, if any + Offset int `json:"offset"` // byte offset, starting at 0 + Line int `json:"line"` // line number, starting at 1 + Column int `json:"column"` // column number, starting at 1 (byte count) +} + +// ScanLevel represents the detail level at which a scan occurred. +// This can be necessary to correctly interpret the findings, for instance if +// a scan is at symbol level and a finding does not have a symbol it means the +// vulnerability was imported but not called. If the scan however was at +// "package" level, that determination cannot be made. +type ScanLevel string + +const ( + scanLevelModule = "module" + scanLevelPackage = "package" + scanLevelSymbol = "symbol" +) + +// WantSymbols can be used to check whether the scan level is one that is able +// to generate symbols called findings. +func (l ScanLevel) WantSymbols() bool { return l == scanLevelSymbol } diff --git a/contribs/gnopls/internal/vulncheck/govulncheck/handler.go b/contribs/gnopls/internal/vulncheck/govulncheck/handler.go new file mode 100644 index 00000000000..4100910a3c3 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/govulncheck/handler.go @@ -0,0 +1,61 @@ +// 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. + +// Code generated by copying from golang.org/x/vuln@v1.0.1 (go run copier.go); DO NOT EDIT. + +package govulncheck + +import ( + "encoding/json" + "io" + + "golang.org/x/tools/gopls/internal/vulncheck/osv" +) + +// Handler handles messages to be presented in a vulnerability scan output +// stream. +type Handler interface { + // Config communicates introductory message to the user. + Config(config *Config) error + + // Progress is called to display a progress message. + Progress(progress *Progress) error + + // OSV is invoked for each osv Entry in the stream. + OSV(entry *osv.Entry) error + + // Finding is called for each vulnerability finding in the stream. + Finding(finding *Finding) error +} + +// HandleJSON reads the json from the supplied stream and hands the decoded +// output to the handler. +func HandleJSON(from io.Reader, to Handler) error { + dec := json.NewDecoder(from) + for dec.More() { + msg := Message{} + // decode the next message in the stream + if err := dec.Decode(&msg); err != nil { + return err + } + // dispatch the message + var err error + if msg.Config != nil { + err = to.Config(msg.Config) + } + if msg.Progress != nil { + err = to.Progress(msg.Progress) + } + if msg.OSV != nil { + err = to.OSV(msg.OSV) + } + if msg.Finding != nil { + err = to.Finding(msg.Finding) + } + if err != nil { + return err + } + } + return nil +} diff --git a/contribs/gnopls/internal/vulncheck/govulncheck/jsonhandler.go b/contribs/gnopls/internal/vulncheck/govulncheck/jsonhandler.go new file mode 100644 index 00000000000..eb110a2aee9 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/govulncheck/jsonhandler.go @@ -0,0 +1,46 @@ +// 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. + +// Code generated by copying from golang.org/x/vuln@v1.0.1 (go run copier.go); DO NOT EDIT. + +package govulncheck + +import ( + "encoding/json" + + "io" + + "golang.org/x/tools/gopls/internal/vulncheck/osv" +) + +type jsonHandler struct { + enc *json.Encoder +} + +// NewJSONHandler returns a handler that writes govulncheck output as json. +func NewJSONHandler(w io.Writer) Handler { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return &jsonHandler{enc: enc} +} + +// Config writes config block in JSON to the underlying writer. +func (h *jsonHandler) Config(config *Config) error { + return h.enc.Encode(Message{Config: config}) +} + +// Progress writes a progress message in JSON to the underlying writer. +func (h *jsonHandler) Progress(progress *Progress) error { + return h.enc.Encode(Message{Progress: progress}) +} + +// OSV writes an osv entry in JSON to the underlying writer. +func (h *jsonHandler) OSV(entry *osv.Entry) error { + return h.enc.Encode(Message{OSV: entry}) +} + +// Finding writes a finding in JSON to the underlying writer. +func (h *jsonHandler) Finding(finding *Finding) error { + return h.enc.Encode(Message{Finding: finding}) +} diff --git a/contribs/gnopls/internal/vulncheck/osv/osv.go b/contribs/gnopls/internal/vulncheck/osv/osv.go new file mode 100644 index 00000000000..08e18abf87d --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/osv/osv.go @@ -0,0 +1,240 @@ +// 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. + +// Code generated by copying from golang.org/x/vuln@v1.0.1 (go run copier.go); DO NOT EDIT. + +// Package osv implements the Go OSV vulnerability format +// (https://go.dev/security/vuln/database#schema), which is a subset of +// the OSV shared vulnerability format +// (https://ossf.github.io/osv-schema), with database and +// ecosystem-specific meanings and fields. +// +// As this package is intended for use with the Go vulnerability +// database, only the subset of features which are used by that +// database are implemented (for instance, only the SEMVER affected +// range type is implemented). +package osv + +import "time" + +// RangeType specifies the type of version range being recorded and +// defines the interpretation of the RangeEvent object's Introduced +// and Fixed fields. +// +// In this implementation, only the "SEMVER" type is supported. +// +// See https://ossf.github.io/osv-schema/#affectedrangestype-field. +type RangeType string + +// RangeTypeSemver indicates a semantic version as defined by +// SemVer 2.0.0, with no leading "v" prefix. +const RangeTypeSemver RangeType = "SEMVER" + +// Ecosystem identifies the overall library ecosystem. +// In this implementation, only the "Go" ecosystem is supported. +type Ecosystem string + +// GoEcosystem indicates the Go ecosystem. +const GoEcosystem Ecosystem = "Go" + +// Pseudo-module paths used to describe vulnerabilities +// in the Go standard library and toolchain. +const ( + // GoStdModulePath is the pseudo-module path string used + // to describe vulnerabilities in the Go standard library. + GoStdModulePath = "stdlib" + // GoCmdModulePath is the pseudo-module path string used + // to describe vulnerabilities in the go command. + GoCmdModulePath = "toolchain" +) + +// Module identifies the Go module containing the vulnerability. +// Note that this field is called "package" in the OSV specification. +// +// See https://ossf.github.io/osv-schema/#affectedpackage-field. +type Module struct { + // The Go module path. Required. + // For the Go standard library, this is "stdlib". + // For the Go toolchain, this is "toolchain." + Path string `json:"name"` + // The ecosystem containing the module. Required. + // This should always be "Go". + Ecosystem Ecosystem `json:"ecosystem"` +} + +// RangeEvent describes a single module version that either +// introduces or fixes a vulnerability. +// +// Exactly one of Introduced and Fixed must be present. Other range +// event types (e.g, "last_affected" and "limit") are not supported in +// this implementation. +// +// See https://ossf.github.io/osv-schema/#affectedrangesevents-fields. +type RangeEvent struct { + // Introduced is a version that introduces the vulnerability. + // A special value, "0", represents a version that sorts before + // any other version, and should be used to indicate that the + // vulnerability exists from the "beginning of time". + Introduced string `json:"introduced,omitempty"` + // Fixed is a version that fixes the vulnerability. + Fixed string `json:"fixed,omitempty"` +} + +// Range describes the affected versions of the vulnerable module. +// +// See https://ossf.github.io/osv-schema/#affectedranges-field. +type Range struct { + // Type is the version type that should be used to interpret the + // versions in Events. Required. + // In this implementation, only the "SEMVER" type is supported. + Type RangeType `json:"type"` + // Events is a list of versions representing the ranges in which + // the module is vulnerable. Required. + // The events should be sorted, and MUST represent non-overlapping + // ranges. + // There must be at least one RangeEvent containing a value for + // Introduced. + // See https://ossf.github.io/osv-schema/#examples for examples. + Events []RangeEvent `json:"events"` +} + +// Reference type is a reference (link) type. +type ReferenceType string + +const ( + // ReferenceTypeAdvisory is a published security advisory for + // the vulnerability. + ReferenceTypeAdvisory = ReferenceType("ADVISORY") + // ReferenceTypeArticle is an article or blog post describing the vulnerability. + ReferenceTypeArticle = ReferenceType("ARTICLE") + // ReferenceTypeReport is a report, typically on a bug or issue tracker, of + // the vulnerability. + ReferenceTypeReport = ReferenceType("REPORT") + // ReferenceTypeFix is a source code browser link to the fix (e.g., a GitHub commit). + ReferenceTypeFix = ReferenceType("FIX") + // ReferenceTypePackage is a home web page for the package. + ReferenceTypePackage = ReferenceType("PACKAGE") + // ReferenceTypeEvidence is a demonstration of the validity of a vulnerability claim. + ReferenceTypeEvidence = ReferenceType("EVIDENCE") + // ReferenceTypeWeb is a web page of some unspecified kind. + ReferenceTypeWeb = ReferenceType("WEB") +) + +// Reference is a reference URL containing additional information, +// advisories, issue tracker entries, etc., about the vulnerability. +// +// See https://ossf.github.io/osv-schema/#references-field. +type Reference struct { + // The type of reference. Required. + Type ReferenceType `json:"type"` + // The fully-qualified URL of the reference. Required. + URL string `json:"url"` +} + +// Affected gives details about a module affected by the vulnerability. +// +// See https://ossf.github.io/osv-schema/#affected-fields. +type Affected struct { + // The affected Go module. Required. + // Note that this field is called "package" in the OSV specification. + Module Module `json:"package"` + // The module version ranges affected by the vulnerability. + Ranges []Range `json:"ranges,omitempty"` + // Details on the affected packages and symbols within the module. + EcosystemSpecific EcosystemSpecific `json:"ecosystem_specific"` +} + +// Package contains additional information about an affected package. +// This is an ecosystem-specific field for the Go ecosystem. +type Package struct { + // Path is the package import path. Required. + Path string `json:"path,omitempty"` + // GOOS is the execution operating system where the symbols appear, if + // known. + GOOS []string `json:"goos,omitempty"` + // GOARCH specifies the execution architecture where the symbols appear, if + // known. + GOARCH []string `json:"goarch,omitempty"` + // Symbols is a list of function and method names affected by + // this vulnerability. Methods are listed as <recv>.<method>. + // + // If included, only programs which use these symbols will be marked as + // vulnerable by `govulncheck`. If omitted, any program which imports this + // package will be marked vulnerable. + Symbols []string `json:"symbols,omitempty"` +} + +// EcosystemSpecific contains additional information about the vulnerable +// module for the Go ecosystem. +// +// See https://go.dev/security/vuln/database#schema. +type EcosystemSpecific struct { + // Packages is the list of affected packages within the module. + Packages []Package `json:"imports,omitempty"` +} + +// Entry represents a vulnerability in the Go OSV format, documented +// in https://go.dev/security/vuln/database#schema. +// It is a subset of the OSV schema (https://ossf.github.io/osv-schema). +// Only fields that are published in the Go Vulnerability Database +// are supported. +type Entry struct { + // SchemaVersion is the OSV schema version used to encode this + // vulnerability. + SchemaVersion string `json:"schema_version,omitempty"` + // ID is a unique identifier for the vulnerability. Required. + // The Go vulnerability database issues IDs of the form + // GO-<YEAR>-<ENTRYID>. + ID string `json:"id"` + // Modified is the time the entry was last modified. Required. + Modified time.Time `json:"modified,omitempty"` + // Published is the time the entry should be considered to have + // been published. + Published time.Time `json:"published,omitempty"` + // Withdrawn is the time the entry should be considered to have + // been withdrawn. If the field is missing, then the entry has + // not been withdrawn. + Withdrawn *time.Time `json:"withdrawn,omitempty"` + // Aliases is a list of IDs for the same vulnerability in other + // databases. + Aliases []string `json:"aliases,omitempty"` + // Summary gives a one-line, English textual summary of the vulnerability. + // It is recommended that this field be kept short, on the order of no more + // than 120 characters. + Summary string `json:"summary,omitempty"` + // Details contains additional English textual details about the vulnerability. + Details string `json:"details"` + // Affected contains information on the modules and versions + // affected by the vulnerability. + Affected []Affected `json:"affected"` + // References contains links to more information about the + // vulnerability. + References []Reference `json:"references,omitempty"` + // Credits contains credits to entities that helped find or fix the + // vulnerability. + Credits []Credit `json:"credits,omitempty"` + // DatabaseSpecific contains additional information about the + // vulnerability, specific to the Go vulnerability database. + DatabaseSpecific *DatabaseSpecific `json:"database_specific,omitempty"` +} + +// Credit represents a credit for the discovery, confirmation, patch, or +// other event in the life cycle of a vulnerability. +// +// See https://ossf.github.io/osv-schema/#credits-fields. +type Credit struct { + // Name is the name, label, or other identifier of the individual or + // entity being credited. Required. + Name string `json:"name"` +} + +// DatabaseSpecific contains additional information about the +// vulnerability, specific to the Go vulnerability database. +// +// See https://go.dev/security/vuln/database#schema. +type DatabaseSpecific struct { + // The URL of the Go advisory for this vulnerability, of the form + // "https://pkg.go.dev/GO-YYYY-XXXX". + URL string `json:"url,omitempty"` +} diff --git a/contribs/gnopls/internal/vulncheck/scan/command.go b/contribs/gnopls/internal/vulncheck/scan/command.go new file mode 100644 index 00000000000..4ef005010c9 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/scan/command.go @@ -0,0 +1,165 @@ +// 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 scan + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "sort" + "time" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/vulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/osv" + "golang.org/x/vuln/scan" +) + +// Main implements gopls vulncheck. +func Main(ctx context.Context, args ...string) error { + // wrapping govulncheck. + cmd := scan.Command(ctx, args...) + if err := cmd.Start(); err != nil { + return err + } + return cmd.Wait() +} + +// RunGovulncheck implements the codelens "Run Govulncheck" +// that runs 'gopls vulncheck' and converts the output to gopls's internal data +// used for diagnostics and hover message construction. +// +// TODO(rfindley): this should accept a *View (which exposes) Options, rather +// than a snapshot. +func RunGovulncheck(ctx context.Context, pattern string, snapshot *cache.Snapshot, dir string, log io.Writer) (*vulncheck.Result, error) { + vulncheckargs := []string{ + "vulncheck", "--", + "-json", + "-mode", "source", + "-scan", "symbol", + } + if dir != "" { + vulncheckargs = append(vulncheckargs, "-C", dir) + } + if db := cache.GetEnv(snapshot, "GOVULNDB"); db != "" { + vulncheckargs = append(vulncheckargs, "-db", db) + } + vulncheckargs = append(vulncheckargs, pattern) + // TODO: support -tags. need to compute tags args from opts.BuildFlags. + // TODO: support -test. + + ir, iw := io.Pipe() + handler := &govulncheckHandler{logger: log, osvs: map[string]*osv.Entry{}} + + stderr := new(bytes.Buffer) + var g errgroup.Group + // We run the govulncheck's analysis in a separate process as it can + // consume a lot of CPUs and memory, and terminates: a separate process + // is a perfect garbage collector and affords us ways to limit its resource usage. + g.Go(func() error { + defer iw.Close() + + cmd := exec.CommandContext(ctx, os.Args[0], vulncheckargs...) + cmd.Env = getEnvSlices(snapshot) + if goversion := cache.GetEnv(snapshot, cache.GoVersionForVulnTest); goversion != "" { + // Let govulncheck API use a different Go version using the (undocumented) hook + // in https://go.googlesource.com/vuln/+/v1.0.1/internal/scan/run.go#76 + cmd.Env = append(cmd.Env, "GOVERSION="+goversion) + } + cmd.Stderr = stderr // stream vulncheck's STDERR as progress reports + cmd.Stdout = iw // let the other goroutine parses the result. + + if err := cmd.Start(); err != nil { + return fmt.Errorf("failed to start govulncheck: %v", err) + } + if err := cmd.Wait(); err != nil { + return fmt.Errorf("failed to run govulncheck: %v", err) + } + return nil + }) + g.Go(func() error { + return govulncheck.HandleJSON(ir, handler) + }) + if err := g.Wait(); err != nil { + if stderr.Len() > 0 { + log.Write(stderr.Bytes()) + } + return nil, fmt.Errorf("failed to read govulncheck output: %v", err) + } + + findings := handler.findings // sort so the findings in the result is deterministic. + sort.Slice(findings, func(i, j int) bool { + x, y := findings[i], findings[j] + if x.OSV != y.OSV { + return x.OSV < y.OSV + } + return x.Trace[0].Package < y.Trace[0].Package + }) + result := &vulncheck.Result{ + Mode: vulncheck.ModeGovulncheck, + AsOf: time.Now(), + Entries: handler.osvs, + Findings: findings, + } + return result, nil +} + +type govulncheckHandler struct { + logger io.Writer // forward progress reports to logger. + + osvs map[string]*osv.Entry + findings []*govulncheck.Finding +} + +// Config implements vulncheck.Handler. +func (h *govulncheckHandler) Config(config *govulncheck.Config) error { + if config.GoVersion != "" { + fmt.Fprintf(h.logger, "Go: %v\n", config.GoVersion) + } + if config.ScannerName != "" { + scannerName := fmt.Sprintf("Scanner: %v", config.ScannerName) + if config.ScannerVersion != "" { + scannerName += "@" + config.ScannerVersion + } + fmt.Fprintln(h.logger, scannerName) + } + if config.DB != "" { + dbInfo := fmt.Sprintf("DB: %v", config.DB) + if config.DBLastModified != nil { + dbInfo += fmt.Sprintf(" (DB updated: %v)", config.DBLastModified.String()) + } + fmt.Fprintln(h.logger, dbInfo) + } + return nil +} + +// Finding implements vulncheck.Handler. +func (h *govulncheckHandler) Finding(finding *govulncheck.Finding) error { + h.findings = append(h.findings, finding) + return nil +} + +// OSV implements vulncheck.Handler. +func (h *govulncheckHandler) OSV(entry *osv.Entry) error { + h.osvs[entry.ID] = entry + return nil +} + +// Progress implements vulncheck.Handler. +func (h *govulncheckHandler) Progress(progress *govulncheck.Progress) error { + if progress.Message != "" { + fmt.Fprintf(h.logger, "%v\n", progress.Message) + } + return nil +} + +func getEnvSlices(snapshot *cache.Snapshot) []string { + return append(os.Environ(), snapshot.Options().EnvSlice()...) +} diff --git a/contribs/gnopls/internal/vulncheck/semver/semver.go b/contribs/gnopls/internal/vulncheck/semver/semver.go new file mode 100644 index 00000000000..ade710d0573 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/semver/semver.go @@ -0,0 +1,45 @@ +// 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 semver provides shared utilities for manipulating +// Go semantic versions. +package semver + +import ( + "strings" + + "golang.org/x/mod/semver" +) + +// addSemverPrefix adds a 'v' prefix to s if it isn't already prefixed +// with 'v' or 'go'. This allows us to easily test go-style SEMVER +// strings against normal SEMVER strings. +func addSemverPrefix(s string) string { + if !strings.HasPrefix(s, "v") && !strings.HasPrefix(s, "go") { + return "v" + s + } + return s +} + +// removeSemverPrefix removes the 'v' or 'go' prefixes from go-style +// SEMVER strings, for usage in the public vulnerability format. +func removeSemverPrefix(s string) string { + s = strings.TrimPrefix(s, "v") + s = strings.TrimPrefix(s, "go") + return s +} + +// CanonicalizeSemverPrefix turns a SEMVER string into the canonical +// representation using the 'v' prefix, as used by the OSV format. +// Input may be a bare SEMVER ("1.2.3"), Go prefixed SEMVER ("go1.2.3"), +// or already canonical SEMVER ("v1.2.3"). +func CanonicalizeSemverPrefix(s string) string { + return addSemverPrefix(removeSemverPrefix(s)) +} + +// Valid returns whether v is valid semver, allowing +// either a "v", "go" or no prefix. +func Valid(v string) bool { + return semver.IsValid(CanonicalizeSemverPrefix(v)) +} diff --git a/contribs/gnopls/internal/vulncheck/semver/semver_test.go b/contribs/gnopls/internal/vulncheck/semver/semver_test.go new file mode 100644 index 00000000000..8a462287fa4 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/semver/semver_test.go @@ -0,0 +1,25 @@ +// 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 semver + +import ( + "testing" +) + +func TestCanonicalize(t *testing.T) { + for _, test := range []struct { + v string + want string + }{ + {"v1.2.3", "v1.2.3"}, + {"1.2.3", "v1.2.3"}, + {"go1.2.3", "v1.2.3"}, + } { + got := CanonicalizeSemverPrefix(test.v) + if got != test.want { + t.Errorf("want %s; got %s", test.want, got) + } + } +} diff --git a/contribs/gnopls/internal/vulncheck/types.go b/contribs/gnopls/internal/vulncheck/types.go new file mode 100644 index 00000000000..450cd961797 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/types.go @@ -0,0 +1,47 @@ +// 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:generate go run copier.go + +package vulncheck + +import ( + "time" + + gvc "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" + "golang.org/x/tools/gopls/internal/vulncheck/osv" +) + +// Result is the result of vulnerability scanning. +type Result struct { + // Entries contains all vulnerabilities that are called or imported by + // the analyzed module. Keys are Entry.IDs. + Entries map[string]*osv.Entry + // Findings are vulnerabilities found by vulncheck or import-based analysis. + // Ordered by the OSV IDs and the package names. + Findings []*gvc.Finding + + // Mode contains the source of the vulnerability info. + // Clients of the gopls.fetch_vulncheck_result command may need + // to interpret the vulnerabilities differently based on the + // analysis mode. For example, Vuln without callstack traces + // indicate a vulnerability that is not used if the result was + // from 'govulncheck' analysis mode. On the other hand, Vuln + // without callstack traces just implies the package with the + // vulnerability is known to the workspace and we do not know + // whether the vulnerable symbols are actually used or not. + Mode AnalysisMode `json:",omitempty"` + + // AsOf describes when this Result was computed using govulncheck. + // It is valid only with the govulncheck analysis mode. + AsOf time.Time `json:",omitempty"` +} + +type AnalysisMode string + +const ( + ModeInvalid AnalysisMode = "" // zero value + ModeGovulncheck AnalysisMode = "govulncheck" + ModeImports AnalysisMode = "imports" +) diff --git a/contribs/gnopls/internal/vulncheck/vulntest/db.go b/contribs/gnopls/internal/vulncheck/vulntest/db.go new file mode 100644 index 00000000000..ee2a6923264 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/vulntest/db.go @@ -0,0 +1,233 @@ +// 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 vulntest provides helpers for vulncheck functionality testing. +package vulntest + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/vulncheck/osv" + "golang.org/x/tools/txtar" +) + +// NewDatabase returns a read-only DB containing the provided +// txtar-format collection of vulnerability reports. +// Each vulnerability report is a YAML file whose format +// is defined in golang.org/x/vulndb/doc/format.md. +// A report file name must have the id as its base name, +// and have .yaml as its extension. +// +// db, err := NewDatabase(ctx, reports) +// ... +// defer db.Clean() +// client, err := NewClient(db) +// ... +// +// The returned DB's Clean method must be called to clean up the +// generated database. +func NewDatabase(ctx context.Context, txtarReports []byte) (*DB, error) { + disk, err := os.MkdirTemp("", "vulndb-test") + if err != nil { + return nil, err + } + if err := generateDB(ctx, txtarReports, disk, false); err != nil { + os.RemoveAll(disk) + return nil, err + } + + return &DB{disk: disk}, nil +} + +// DB is a read-only vulnerability database on disk. +// Users can use this database with golang.org/x/vuln APIs +// by setting the `VULNDB` environment variable. +type DB struct { + disk string +} + +// URI returns the file URI that can be used for VULNDB environment +// variable. +func (db *DB) URI() string { + u := protocol.URIFromPath(filepath.Join(db.disk, "ID")) + return string(u) +} + +// Clean deletes the database. +func (db *DB) Clean() error { + return os.RemoveAll(db.disk) +} + +// +// The following was selectively copied from golang.org/x/vulndb/internal/database +// + +const ( + dbURL = "https://pkg.go.dev/vuln/" + + // idDirectory is the name of the directory that contains entries + // listed by their IDs. + idDirectory = "ID" + + // cmdModule is the name of the module containing Go toolchain + // binaries. + cmdModule = "cmd" + + // stdModule is the name of the module containing Go std packages. + stdModule = "std" +) + +// generateDB generates the file-based vuln DB in the directory jsonDir. +func generateDB(ctx context.Context, txtarData []byte, jsonDir string, indent bool) error { + archive := txtar.Parse(txtarData) + + entries, err := generateEntries(ctx, archive) + if err != nil { + return err + } + return writeEntriesByID(filepath.Join(jsonDir, idDirectory), entries, indent) +} + +func generateEntries(_ context.Context, archive *txtar.Archive) ([]osv.Entry, error) { + now := time.Now() + var entries []osv.Entry + for _, f := range archive.Files { + if !strings.HasSuffix(f.Name, ".yaml") { + continue + } + r, err := readReport(bytes.NewReader(f.Data)) + if err != nil { + return nil, err + } + name := strings.TrimSuffix(filepath.Base(f.Name), filepath.Ext(f.Name)) + linkName := fmt.Sprintf("%s%s", dbURL, name) + entry := generateOSVEntry(name, linkName, now, *r) + entries = append(entries, entry) + } + return entries, nil +} + +func writeEntriesByID(idDir string, entries []osv.Entry, indent bool) error { + // Write a directory containing entries by ID. + if err := os.MkdirAll(idDir, 0755); err != nil { + return fmt.Errorf("failed to create directory %q: %v", idDir, err) + } + for _, e := range entries { + outPath := filepath.Join(idDir, e.ID+".json") + if err := writeJSON(outPath, e, indent); err != nil { + return err + } + } + return nil +} + +func writeJSON(filename string, value any, indent bool) (err error) { + j, err := jsonMarshal(value, indent) + if err != nil { + return err + } + return os.WriteFile(filename, j, 0644) +} + +func jsonMarshal(v any, indent bool) ([]byte, error) { + if indent { + return json.MarshalIndent(v, "", " ") + } + return json.Marshal(v) +} + +// generateOSVEntry create an osv.Entry for a report. In addition to the report, it +// takes the ID for the vuln and a URL that will point to the entry in the vuln DB. +// It returns the osv.Entry and a list of module paths that the vuln affects. +func generateOSVEntry(id, url string, lastModified time.Time, r Report) osv.Entry { + entry := osv.Entry{ + ID: id, + Published: r.Published, + Modified: lastModified, + Withdrawn: r.Withdrawn, + Summary: r.Summary, + Details: r.Description, + DatabaseSpecific: &osv.DatabaseSpecific{URL: url}, + } + + moduleMap := make(map[string]bool) + for _, m := range r.Modules { + switch m.Module { + case stdModule: + moduleMap[osv.GoStdModulePath] = true + case cmdModule: + moduleMap[osv.GoCmdModulePath] = true + default: + moduleMap[m.Module] = true + } + entry.Affected = append(entry.Affected, toAffected(m)) + } + for _, ref := range r.References { + entry.References = append(entry.References, osv.Reference{ + Type: ref.Type, + URL: ref.URL, + }) + } + return entry +} + +func AffectedRanges(versions []VersionRange) []osv.Range { + a := osv.Range{Type: osv.RangeTypeSemver} + if len(versions) == 0 || versions[0].Introduced == "" { + a.Events = append(a.Events, osv.RangeEvent{Introduced: "0"}) + } + for _, v := range versions { + if v.Introduced != "" { + a.Events = append(a.Events, osv.RangeEvent{Introduced: v.Introduced.Canonical()}) + } + if v.Fixed != "" { + a.Events = append(a.Events, osv.RangeEvent{Fixed: v.Fixed.Canonical()}) + } + } + return []osv.Range{a} +} + +func toOSVPackages(pkgs []*Package) (imps []osv.Package) { + for _, p := range pkgs { + syms := append([]string{}, p.Symbols...) + syms = append(syms, p.DerivedSymbols...) + sort.Strings(syms) + imps = append(imps, osv.Package{ + Path: p.Package, + GOOS: p.GOOS, + GOARCH: p.GOARCH, + Symbols: syms, + }) + } + return imps +} + +func toAffected(m *Module) osv.Affected { + name := m.Module + switch name { + case stdModule: + name = osv.GoStdModulePath + case cmdModule: + name = osv.GoCmdModulePath + } + return osv.Affected{ + Module: osv.Module{ + Path: name, + Ecosystem: osv.GoEcosystem, + }, + Ranges: AffectedRanges(m.Versions), + EcosystemSpecific: osv.EcosystemSpecific{ + Packages: toOSVPackages(m.Packages), + }, + } +} diff --git a/contribs/gnopls/internal/vulncheck/vulntest/db_test.go b/contribs/gnopls/internal/vulncheck/vulntest/db_test.go new file mode 100644 index 00000000000..3c3407105ac --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/vulntest/db_test.go @@ -0,0 +1,76 @@ +// 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 vulntest + +import ( + "context" + "encoding/json" + "flag" + "os" + "path/filepath" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/gopls/internal/vulncheck/osv" +) + +var update = flag.Bool("update", false, "update golden files in testdata/") + +func TestNewDatabase(t *testing.T) { + ctx := context.Background() + + in, err := os.ReadFile("testdata/report.yaml") + if err != nil { + t.Fatal(err) + } + in = append([]byte("-- GO-2020-0001.yaml --\n"), in...) + + db, err := NewDatabase(ctx, in) + if err != nil { + t.Fatal(err) + } + defer db.Clean() + dbpath := protocol.DocumentURI(db.URI()).Path() + + // The generated JSON file will be in DB/GO-2022-0001.json. + got := readOSVEntry(t, filepath.Join(dbpath, "GO-2020-0001.json")) + got.Modified = time.Time{} + + if *update { + updateTestData(t, got, "testdata/GO-2020-0001.json") + } + + want := readOSVEntry(t, "testdata/GO-2020-0001.json") + want.Modified = time.Time{} + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } +} + +func updateTestData(t *testing.T, got *osv.Entry, fname string) { + content, err := json.MarshalIndent(got, "", "\t") + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(fname, content, 0666); err != nil { + t.Fatal(err) + } + t.Logf("updated %v", fname) +} + +func readOSVEntry(t *testing.T, filename string) *osv.Entry { + t.Helper() + content, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + var entry osv.Entry + if err := json.Unmarshal(content, &entry); err != nil { + t.Fatal(err) + } + return &entry +} diff --git a/contribs/gnopls/internal/vulncheck/vulntest/report.go b/contribs/gnopls/internal/vulncheck/vulntest/report.go new file mode 100644 index 00000000000..b67986cf8c2 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/vulntest/report.go @@ -0,0 +1,176 @@ +// 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 vulntest + +import ( + "fmt" + "io" + "os" + "strings" + "time" + + "golang.org/x/mod/semver" + "golang.org/x/tools/gopls/internal/vulncheck/osv" + "gopkg.in/yaml.v3" +) + +// +// The following was selectively copied from golang.org/x/vulndb/internal/report +// + +// readReport reads a Report in YAML format. +func readReport(in io.Reader) (*Report, error) { + d := yaml.NewDecoder(in) + // Require that all fields in the file are in the struct. + // This corresponds to v2's UnmarshalStrict. + d.KnownFields(true) + var r Report + if err := d.Decode(&r); err != nil { + return nil, fmt.Errorf("yaml.Decode: %v", err) + } + return &r, nil +} + +// Report represents a vulnerability report in the vulndb. +// See https://go.googlesource.com/vulndb/+/refs/heads/master/doc/format.md +type Report struct { + ID string `yaml:",omitempty"` + + Modules []*Module `yaml:",omitempty"` + + // Summary is a short phrase describing the vulnerability. + Summary string `yaml:",omitempty"` + + // Description is the CVE description from an existing CVE. If we are + // assigning a CVE ID ourselves, use CVEMetadata.Description instead. + Description string `yaml:",omitempty"` + Published time.Time `yaml:",omitempty"` + Withdrawn *time.Time `yaml:",omitempty"` + + References []*Reference `yaml:",omitempty"` +} + +// Write writes r to filename in YAML format. +func (r *Report) Write(filename string) (err error) { + f, err := os.Create(filename) + if err != nil { + return err + } + err = r.encode(f) + err2 := f.Close() + if err == nil { + err = err2 + } + return err +} + +// ToString encodes r to a YAML string. +func (r *Report) ToString() (string, error) { + var b strings.Builder + if err := r.encode(&b); err != nil { + return "", err + } + return b.String(), nil +} + +func (r *Report) encode(w io.Writer) error { + e := yaml.NewEncoder(w) + defer e.Close() + e.SetIndent(4) + return e.Encode(r) +} + +type VersionRange struct { + Introduced Version `yaml:"introduced,omitempty"` + Fixed Version `yaml:"fixed,omitempty"` +} + +type Module struct { + Module string `yaml:",omitempty"` + Versions []VersionRange `yaml:",omitempty"` + Packages []*Package `yaml:",omitempty"` +} + +type Package struct { + Package string `yaml:",omitempty"` + GOOS []string `yaml:"goos,omitempty"` + GOARCH []string `yaml:"goarch,omitempty"` + // Symbols originally identified as vulnerable. + Symbols []string `yaml:",omitempty"` + // Additional vulnerable symbols, computed from Symbols via static analysis + // or other technique. + DerivedSymbols []string `yaml:"derived_symbols,omitempty"` +} + +// Version is an SemVer 2.0.0 semantic version with no leading "v" prefix, +// as used by OSV. +type Version string + +// V returns the version with a "v" prefix. +func (v Version) V() string { + return "v" + string(v) +} + +// IsValid reports whether v is a valid semantic version string. +func (v Version) IsValid() bool { + return semver.IsValid(v.V()) +} + +// Before reports whether v < v2. +func (v Version) Before(v2 Version) bool { + return semver.Compare(v.V(), v2.V()) < 0 +} + +// Canonical returns the canonical formatting of the version. +func (v Version) Canonical() string { + return strings.TrimPrefix(semver.Canonical(v.V()), "v") +} + +// Reference type is a reference (link) type. +type ReferenceType string + +const ( + ReferenceTypeAdvisory = ReferenceType("ADVISORY") + ReferenceTypeArticle = ReferenceType("ARTICLE") + ReferenceTypeReport = ReferenceType("REPORT") + ReferenceTypeFix = ReferenceType("FIX") + ReferenceTypePackage = ReferenceType("PACKAGE") + ReferenceTypeEvidence = ReferenceType("EVIDENCE") + ReferenceTypeWeb = ReferenceType("WEB") +) + +// ReferenceTypes is the set of reference types defined in OSV. +var ReferenceTypes = []ReferenceType{ + ReferenceTypeAdvisory, + ReferenceTypeArticle, + ReferenceTypeReport, + ReferenceTypeFix, + ReferenceTypePackage, + ReferenceTypeEvidence, + ReferenceTypeWeb, +} + +// A Reference is a link to some external resource. +// +// For ease of typing, References are represented in the YAML as a +// single-element mapping of type to URL. +type Reference osv.Reference + +func (r *Reference) MarshalYAML() (interface{}, error) { + return map[string]string{ + strings.ToLower(string(r.Type)): r.URL, + }, nil +} + +func (r *Reference) UnmarshalYAML(n *yaml.Node) (err error) { + if n.Kind != yaml.MappingNode || len(n.Content) != 2 || n.Content[0].Kind != yaml.ScalarNode || n.Content[1].Kind != yaml.ScalarNode { + return &yaml.TypeError{Errors: []string{ + fmt.Sprintf("line %d: report.Reference must contain a mapping with one value", n.Line), + }} + } + r.Type = osv.ReferenceType(strings.ToUpper(n.Content[0].Value)) + r.URL = n.Content[1].Value + return nil +} diff --git a/contribs/gnopls/internal/vulncheck/vulntest/report_test.go b/contribs/gnopls/internal/vulncheck/vulntest/report_test.go new file mode 100644 index 00000000000..b88633c2f1c --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/vulntest/report_test.go @@ -0,0 +1,48 @@ +// 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 vulntest + +import ( + "bytes" + "io" + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func readAll(t *testing.T, filename string) io.Reader { + d, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + return bytes.NewReader(d) +} + +func TestRoundTrip(t *testing.T) { + // A report shouldn't change after being read and then written. + in := filepath.Join("testdata", "report.yaml") + r, err := readReport(readAll(t, in)) + if err != nil { + t.Fatal(err) + } + out := filepath.Join(t.TempDir(), "report.yaml") + if err := r.Write(out); err != nil { + t.Fatal(err) + } + + want, err := os.ReadFile(in) + if err != nil { + t.Fatal(err) + } + got, err := os.ReadFile(out) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("mismatch (-want, +got):\n%s", diff) + } +} diff --git a/contribs/gnopls/internal/vulncheck/vulntest/stdlib.go b/contribs/gnopls/internal/vulncheck/vulntest/stdlib.go new file mode 100644 index 00000000000..57194f71688 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/vulntest/stdlib.go @@ -0,0 +1,23 @@ +// 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 vulntest + +import ( + "strings" + + "golang.org/x/mod/module" +) + +// maybeStdlib reports whether the given import path could be part of the Go +// standard library, by reporting whether the first component lacks a '.'. +func maybeStdlib(path string) bool { + if err := module.CheckImportPath(path); err != nil { + return false + } + if i := strings.IndexByte(path, '/'); i != -1 { + path = path[:i] + } + return !strings.Contains(path, ".") +} diff --git a/contribs/gnopls/internal/vulncheck/vulntest/stdlib_test.go b/contribs/gnopls/internal/vulncheck/vulntest/stdlib_test.go new file mode 100644 index 00000000000..7b212976350 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/vulntest/stdlib_test.go @@ -0,0 +1,24 @@ +// 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 vulntest + +import "testing" + +func TestMaybeStdlib(t *testing.T) { + for _, test := range []struct { + in string + want bool + }{ + {"", false}, + {"math/crypto", true}, + {"github.com/pkg/errors", false}, + {"Path is unknown", false}, + } { + got := maybeStdlib(test.in) + if got != test.want { + t.Errorf("%q: got %t, want %t", test.in, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/vulncheck/vulntest/testdata/GO-2020-0001.json b/contribs/gnopls/internal/vulncheck/vulntest/testdata/GO-2020-0001.json new file mode 100644 index 00000000000..db371bd6930 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/vulntest/testdata/GO-2020-0001.json @@ -0,0 +1,50 @@ +{ + "id": "GO-2020-0001", + "modified": "0001-01-01T00:00:00Z", + "published": "0001-01-01T00:00:00Z", + "details": "The default Formatter for the Logger middleware (LoggerConfig.Formatter),\nwhich is included in the Default engine, allows attackers to inject arbitrary\nlog entries by manipulating the request path.\n", + "affected": [ + { + "package": { + "name": "github.com/gin-gonic/gin", + "ecosystem": "Go" + }, + "ranges": [ + { + "type": "SEMVER", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.6.0" + } + ] + } + ], + "ecosystem_specific": { + "imports": [ + { + "path": "github.com/gin-gonic/gin", + "symbols": [ + "defaultLogFormatter" + ] + } + ] + } + } + ], + "references": [ + { + "type": "FIX", + "url": "https://github.com/gin-gonic/gin/pull/1234" + }, + { + "type": "FIX", + "url": "https://github.com/gin-gonic/gin/commit/abcdefg" + } + ], + "database_specific": { + "url": "https://pkg.go.dev/vuln/GO-2020-0001" + } +} \ No newline at end of file diff --git a/contribs/gnopls/internal/vulncheck/vulntest/testdata/report.yaml b/contribs/gnopls/internal/vulncheck/vulntest/testdata/report.yaml new file mode 100644 index 00000000000..48384b543b2 --- /dev/null +++ b/contribs/gnopls/internal/vulncheck/vulntest/testdata/report.yaml @@ -0,0 +1,15 @@ +modules: + - module: github.com/gin-gonic/gin + versions: + - fixed: 1.6.0 + packages: + - package: github.com/gin-gonic/gin + symbols: + - defaultLogFormatter +description: | + The default Formatter for the Logger middleware (LoggerConfig.Formatter), + which is included in the Default engine, allows attackers to inject arbitrary + log entries by manipulating the request path. +references: + - fix: https://github.com/gin-gonic/gin/pull/1234 + - fix: https://github.com/gin-gonic/gin/commit/abcdefg diff --git a/contribs/gnopls/internal/work/completion.go b/contribs/gnopls/internal/work/completion.go new file mode 100644 index 00000000000..194721ef36d --- /dev/null +++ b/contribs/gnopls/internal/work/completion.go @@ -0,0 +1,161 @@ +// 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 work + +import ( + "context" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "sort" + "strings" + + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.CompletionList, error) { + ctx, done := event.Start(ctx, "work.Completion") + defer done() + + // Get the position of the cursor. + pw, err := snapshot.ParseWork(ctx, fh) + if err != nil { + return nil, fmt.Errorf("getting go.work file handle: %w", err) + } + cursor, err := pw.Mapper.PositionOffset(position) + if err != nil { + return nil, fmt.Errorf("computing cursor offset: %w", err) + } + + // Find the use statement the user is in. + use, pathStart, _ := usePath(pw, cursor) + if use == nil { + return &protocol.CompletionList{}, nil + } + completingFrom := use.Path[:cursor-pathStart] + + // We're going to find the completions of the user input + // (completingFrom) by doing a walk on the innermost directory + // of the given path, and comparing the found paths to make sure + // that they match the component of the path after the + // innermost directory. + // + // We'll maintain two paths when doing this: pathPrefixSlash + // is essentially the path the user typed in, and pathPrefixAbs + // is the path made absolute from the go.work directory. + + pathPrefixSlash := completingFrom + pathPrefixAbs := filepath.FromSlash(pathPrefixSlash) + if !filepath.IsAbs(pathPrefixAbs) { + pathPrefixAbs = filepath.Join(filepath.Dir(pw.URI.Path()), pathPrefixAbs) + } + + // pathPrefixDir is the directory that will be walked to find matches. + // If pathPrefixSlash is not explicitly a directory boundary (is either equivalent to "." or + // ends in a separator) we need to examine its parent directory to find sibling files that + // match. + depthBound := 5 + pathPrefixDir, pathPrefixBase := pathPrefixAbs, "" + pathPrefixSlashDir := pathPrefixSlash + if filepath.Clean(pathPrefixSlash) != "." && !strings.HasSuffix(pathPrefixSlash, "/") { + depthBound++ + pathPrefixDir, pathPrefixBase = filepath.Split(pathPrefixAbs) + pathPrefixSlashDir = dirNonClean(pathPrefixSlash) + } + + var completions []string + // Stop traversing deeper once we've hit 10k files to try to stay generally under 100ms. + const numSeenBound = 10000 + var numSeen int + stopWalking := errors.New("hit numSeenBound") + err = filepath.WalkDir(pathPrefixDir, func(wpath string, entry fs.DirEntry, err error) error { + if err != nil { + // golang/go#64225: an error reading a dir is expected, as the user may + // be typing out a use directive for a directory that doesn't exist. + return nil + } + if numSeen > numSeenBound { + // Stop traversing if we hit bound. + return stopWalking + } + numSeen++ + + // rel is the path relative to pathPrefixDir. + // Make sure that it has pathPrefixBase as a prefix + // otherwise it won't match the beginning of the + // base component of the path the user typed in. + rel := strings.TrimPrefix(wpath[len(pathPrefixDir):], string(filepath.Separator)) + if entry.IsDir() && wpath != pathPrefixDir && !strings.HasPrefix(rel, pathPrefixBase) { + return filepath.SkipDir + } + + // Check for a match (a module directory). + if filepath.Base(rel) == "go.mod" { + relDir := strings.TrimSuffix(dirNonClean(rel), string(os.PathSeparator)) + completionPath := join(pathPrefixSlashDir, filepath.ToSlash(relDir)) + + if !strings.HasPrefix(completionPath, completingFrom) { + return nil + } + if strings.HasSuffix(completionPath, "/") { + // Don't suggest paths that end in "/". This happens + // when the input is a path that ends in "/" and + // the completion is empty. + return nil + } + completion := completionPath[len(completingFrom):] + if completingFrom == "" && !strings.HasPrefix(completion, "./") { + // Bias towards "./" prefixes. + completion = join(".", completion) + } + + completions = append(completions, completion) + } + + if depth := strings.Count(rel, string(filepath.Separator)); depth >= depthBound { + return filepath.SkipDir + } + return nil + }) + if err != nil && !errors.Is(err, stopWalking) { + return nil, fmt.Errorf("walking to find completions: %w", err) + } + + sort.Strings(completions) + + items := []protocol.CompletionItem{} // must be a slice + for _, c := range completions { + items = append(items, protocol.CompletionItem{ + Label: c, + InsertText: c, + }) + } + return &protocol.CompletionList{Items: items}, nil +} + +// dirNonClean is filepath.Dir, without the Clean at the end. +func dirNonClean(path string) string { + vol := filepath.VolumeName(path) + i := len(path) - 1 + for i >= len(vol) && !os.IsPathSeparator(path[i]) { + i-- + } + return path[len(vol) : i+1] +} + +func join(a, b string) string { + if a == "" { + return b + } + if b == "" { + return a + } + return strings.TrimSuffix(a, "/") + "/" + b +} diff --git a/contribs/gnopls/internal/work/diagnostics.go b/contribs/gnopls/internal/work/diagnostics.go new file mode 100644 index 00000000000..f1acd4d27c7 --- /dev/null +++ b/contribs/gnopls/internal/work/diagnostics.go @@ -0,0 +1,92 @@ +// 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 work + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func Diagnostics(ctx context.Context, snapshot *cache.Snapshot) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { + ctx, done := event.Start(ctx, "work.Diagnostics", snapshot.Labels()...) + defer done() + + reports := map[protocol.DocumentURI][]*cache.Diagnostic{} + uri := snapshot.View().GoWork() + if uri == "" { + return nil, nil + } + fh, err := snapshot.ReadFile(ctx, uri) + if err != nil { + return nil, err + } + reports[fh.URI()] = []*cache.Diagnostic{} + diagnostics, err := diagnoseOne(ctx, snapshot, fh) + if err != nil { + return nil, err + } + for _, d := range diagnostics { + fh, err := snapshot.ReadFile(ctx, d.URI) + if err != nil { + return nil, err + } + reports[fh.URI()] = append(reports[fh.URI()], d) + } + + return reports, nil +} + +func diagnoseOne(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]*cache.Diagnostic, error) { + pw, err := snapshot.ParseWork(ctx, fh) + if err != nil { + if pw == nil || len(pw.ParseErrors) == 0 { + return nil, err + } + return pw.ParseErrors, nil + } + + // Add diagnostic if a directory does not contain a module. + var diagnostics []*cache.Diagnostic + for _, use := range pw.File.Use { + rng, err := pw.Mapper.OffsetRange(use.Syntax.Start.Byte, use.Syntax.End.Byte) + if err != nil { + return nil, err + } + + modfh, err := snapshot.ReadFile(ctx, modFileURI(pw, use)) + if err != nil { + return nil, err + } + if _, err := modfh.Content(); err != nil && os.IsNotExist(err) { + diagnostics = append(diagnostics, &cache.Diagnostic{ + URI: fh.URI(), + Range: rng, + Severity: protocol.SeverityError, + Source: cache.WorkFileError, + Message: fmt.Sprintf("directory %v does not contain a module", use.Path), + }) + } + } + return diagnostics, nil +} + +func modFileURI(pw *cache.ParsedWorkFile, use *modfile.Use) protocol.DocumentURI { + workdir := filepath.Dir(pw.URI.Path()) + + modroot := filepath.FromSlash(use.Path) + if !filepath.IsAbs(modroot) { + modroot = filepath.Join(workdir, modroot) + } + + return protocol.URIFromPath(filepath.Join(modroot, "go.mod")) +} diff --git a/contribs/gnopls/internal/work/format.go b/contribs/gnopls/internal/work/format.go new file mode 100644 index 00000000000..162bc8c0004 --- /dev/null +++ b/contribs/gnopls/internal/work/format.go @@ -0,0 +1,30 @@ +// 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 work + +import ( + "context" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/event" +) + +func Format(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.TextEdit, error) { + ctx, done := event.Start(ctx, "work.Format") + defer done() + + pw, err := snapshot.ParseWork(ctx, fh) + if err != nil { + return nil, err + } + formatted := modfile.Format(pw.File.Syntax) + // Calculate the edits to be made due to the change. + diffs := diff.Bytes(pw.Mapper.Content, formatted) + return protocol.EditsFromDiffEdits(pw.Mapper, diffs) +} diff --git a/contribs/gnopls/internal/work/hover.go b/contribs/gnopls/internal/work/hover.go new file mode 100644 index 00000000000..c59c14789be --- /dev/null +++ b/contribs/gnopls/internal/work/hover.go @@ -0,0 +1,93 @@ +// 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 work + +import ( + "bytes" + "context" + "fmt" + + "golang.org/x/mod/modfile" + "golang.org/x/tools/gopls/internal/cache" + "golang.org/x/tools/gopls/internal/file" + "golang.org/x/tools/gopls/internal/protocol" + "golang.org/x/tools/internal/event" +) + +func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) { + // We only provide hover information for the view's go.work file. + if fh.URI() != snapshot.View().GoWork() { + return nil, nil + } + + ctx, done := event.Start(ctx, "work.Hover") + defer done() + + // Get the position of the cursor. + pw, err := snapshot.ParseWork(ctx, fh) + if err != nil { + return nil, fmt.Errorf("getting go.work file handle: %w", err) + } + offset, err := pw.Mapper.PositionOffset(position) + if err != nil { + return nil, fmt.Errorf("computing cursor offset: %w", err) + } + + // Confirm that the cursor is inside a use statement, and then find + // the position of the use statement's directory path. + use, pathStart, pathEnd := usePath(pw, offset) + + // The cursor position is not on a use statement. + if use == nil { + return nil, nil + } + + // Get the mod file denoted by the use. + modfh, err := snapshot.ReadFile(ctx, modFileURI(pw, use)) + if err != nil { + return nil, fmt.Errorf("getting modfile handle: %w", err) + } + pm, err := snapshot.ParseMod(ctx, modfh) + if err != nil { + return nil, fmt.Errorf("getting modfile handle: %w", err) + } + if pm.File.Module == nil { + return nil, fmt.Errorf("modfile has no module declaration") + } + mod := pm.File.Module.Mod + + // Get the range to highlight for the hover. + rng, err := pw.Mapper.OffsetRange(pathStart, pathEnd) + if err != nil { + return nil, err + } + options := snapshot.Options() + return &protocol.Hover{ + Contents: protocol.MarkupContent{ + Kind: options.PreferredContentFormat, + Value: mod.Path, + }, + Range: rng, + }, nil +} + +func usePath(pw *cache.ParsedWorkFile, offset int) (use *modfile.Use, pathStart, pathEnd int) { + for _, u := range pw.File.Use { + path := []byte(u.Path) + s, e := u.Syntax.Start.Byte, u.Syntax.End.Byte + i := bytes.Index(pw.Mapper.Content[s:e], path) + if i == -1 { + // This should not happen. + continue + } + // Shift the start position to the location of the + // module directory within the use statement. + pathStart, pathEnd = s+i, s+i+len(path) + if pathStart <= offset && offset <= pathEnd { + return u, pathStart, pathEnd + } + } + return nil, 0, 0 +} diff --git a/contribs/gnopls/main.go b/contribs/gnopls/main.go new file mode 100644 index 00000000000..083c4efd8de --- /dev/null +++ b/contribs/gnopls/main.go @@ -0,0 +1,36 @@ +// 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. + +// Gopls (pronounced “go please”) is an LSP server for Go. +// The Language Server Protocol allows any text editor +// to be extended with IDE-like features; +// see https://langserver.org/ for details. +// +// See https://github.com/golang/tools/blob/master/gopls/README.md +// for the most up-to-date documentation. +package main // import "golang.org/x/tools/gopls" + +import ( + "context" + "os" + + "golang.org/x/telemetry" + "golang.org/x/tools/gopls/internal/cmd" + versionpkg "golang.org/x/tools/gopls/internal/version" + "golang.org/x/tools/internal/tool" +) + +var version = "" // if set by the linker, overrides the gopls version + +func main() { + versionpkg.VersionOverride = version + + telemetry.Start(telemetry.Config{ + ReportCrashes: true, + Upload: true, + }) + + ctx := context.Background() + tool.Main(ctx, cmd.New(), os.Args[1:]) +} diff --git a/contribs/gnopls/release/release.go b/contribs/gnopls/release/release.go new file mode 100644 index 00000000000..26ce5f7870a --- /dev/null +++ b/contribs/gnopls/release/release.go @@ -0,0 +1,106 @@ +// 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 release checks that the a given version of gopls is ready for +// release. It can also tag and publish the release. +// +// To run: +// +// $ cd $GOPATH/src/golang.org/x/tools/gopls +// $ go run release/release.go -version=<version> +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + + "golang.org/x/mod/modfile" + "golang.org/x/mod/semver" +) + +var versionFlag = flag.String("version", "", "version to tag") + +func main() { + flag.Parse() + + if *versionFlag == "" { + log.Fatalf("must provide -version flag") + } + if !semver.IsValid(*versionFlag) { + log.Fatalf("invalid version %s", *versionFlag) + } + if semver.Major(*versionFlag) != "v0" { + log.Fatalf("expected major version v0, got %s", semver.Major(*versionFlag)) + } + if semver.Build(*versionFlag) != "" { + log.Fatalf("unexpected build suffix: %s", *versionFlag) + } + // Validate that the user is running the program from the gopls module. + wd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + if filepath.Base(wd) != "gopls" { + log.Fatalf("must run from the gopls module") + } + // Confirm that the versions in the go.mod file are correct. + if err := validateGoModFile(wd); err != nil { + log.Fatal(err) + } + fmt.Println("Validated that the release is ready.") + os.Exit(0) +} + +func validateGoModFile(goplsDir string) error { + filename := filepath.Join(goplsDir, "go.mod") + data, err := os.ReadFile(filename) + if err != nil { + return err + } + gomod, err := modfile.Parse(filename, data, nil) + if err != nil { + return err + } + // Confirm that there is no replace directive in the go.mod file. + if len(gomod.Replace) > 0 { + return fmt.Errorf("expected no replace directives, got %v", len(gomod.Replace)) + } + // Confirm that the version of x/tools in the gopls/go.mod file points to + // the second-to-last commit. (The last commit will be the one to update the + // go.mod file.) + cmd := exec.Command("git", "rev-parse", "@~") + stdout, err := cmd.Output() + if err != nil { + return err + } + hash := string(stdout) + // Find the golang.org/x/tools require line and compare the versions. + var version string + for _, req := range gomod.Require { + if req.Mod.Path == "golang.org/x/tools" { + version = req.Mod.Version + break + } + } + if version == "" { + return fmt.Errorf("no require for golang.org/x/tools") + } + split := strings.Split(version, "-") + if len(split) != 3 { + return fmt.Errorf("unexpected pseudoversion format %s", version) + } + last := split[len(split)-1] + if last == "" { + return fmt.Errorf("unexpected pseudoversion format %s", version) + } + if !strings.HasPrefix(hash, last) { + return fmt.Errorf("golang.org/x/tools pseudoversion should be at commit %s, instead got %s", hash, last) + } + return nil +} From 6ead166d5583d8c7638533ebf9814bf7f3231a76 Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:29:08 +0200 Subject: [PATCH 2/5] wip: gnopls deps Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- contribs/gnopls/README.md | 4 +- contribs/gnopls/doc/advanced.md | 6 +- contribs/gnopls/doc/analyzers.md | 28 +- contribs/gnopls/doc/assets/go.mod | 4 +- contribs/gnopls/doc/contributing.md | 8 +- contribs/gnopls/doc/design/implementation.md | 40 +- contribs/gnopls/doc/design/integrating.md | 6 +- contribs/gnopls/doc/features/diagnostics.md | 2 +- .../gnopls/doc/features/transformation.md | 2 +- contribs/gnopls/doc/generate/generate.go | 16 +- contribs/gnopls/doc/generate/generate_test.go | 2 +- contribs/gnopls/doc/refactor-inline.md | 2 +- contribs/gnopls/doc/release/v0.16.0.md | 2 +- contribs/gnopls/doc/workspace.md | 2 +- contribs/gnopls/go.mod | 8 +- contribs/gnopls/go.sum | 22 +- contribs/gnopls/internal/aliases/aliases.go | 38 + .../gnopls/internal/aliases/aliases_go121.go | 37 + .../gnopls/internal/aliases/aliases_go122.go | 98 + .../gnopls/internal/aliases/aliases_test.go | 156 + .../analysis/deprecated/deprecated.go | 4 +- .../analysis/embeddirective/embeddirective.go | 6 +- .../analysis/fillreturns/fillreturns.go | 6 +- .../analysis/fillreturns/fillreturns_test.go | 4 +- .../analysis/fillstruct/fillstruct.go | 12 +- .../analysis/fillstruct/fillstruct_test.go | 4 +- .../analysis/fillswitch/fillswitch.go | 2 +- .../analysis/fillswitch/fillswitch_test.go | 4 +- .../analysis/infertypeargs/infertypeargs.go | 6 +- .../infertypeargs/infertypeargs_test.go | 2 +- .../internal/analysis/nonewvars/nonewvars.go | 4 +- .../analysis/nonewvars/nonewvars_test.go | 2 +- .../analysis/noresultvalues/noresultvalues.go | 4 +- .../noresultvalues/noresultvalues_test.go | 2 +- .../simplifycompositelit.go | 4 +- .../simplifycompositelit_test.go | 2 +- .../analysis/simplifyrange/simplifyrange.go | 4 +- .../simplifyrange/simplifyrange_test.go | 2 +- .../analysis/simplifyslice/simplifyslice.go | 4 +- .../simplifyslice/simplifyslice_test.go | 2 +- .../analysis/stubmethods/stubmethods.go | 10 +- .../analysis/stubmethods/stubmethods_test.go | 2 +- .../analysis/undeclaredname/undeclared.go | 8 +- .../undeclaredname/undeclared_test.go | 2 +- .../analysis/unusedparams/cmd/main.go | 2 +- .../analysis/unusedparams/unusedparams.go | 6 +- .../unusedparams/unusedparams_test.go | 2 +- .../analysis/unusedvariable/unusedvariable.go | 2 +- .../unusedvariable/unusedvariable_test.go | 2 +- .../gnopls/internal/analysis/useany/useany.go | 2 +- .../internal/analysis/useany/useany_test.go | 2 +- .../analysisinternal/addimport_test.go | 243 + .../internal/analysisinternal/analysis.go | 513 + .../internal/analysisinternal/extractdoc.go | 113 + .../analysisinternal/extractdoc_test.go | 80 + contribs/gnopls/internal/astutil/clone.go | 71 + contribs/gnopls/internal/cache/analysis.go | 36 +- contribs/gnopls/internal/cache/cache.go | 6 +- contribs/gnopls/internal/cache/check.go | 32 +- contribs/gnopls/internal/cache/diagnostics.go | 4 +- contribs/gnopls/internal/cache/errors.go | 18 +- contribs/gnopls/internal/cache/errors_test.go | 2 +- contribs/gnopls/internal/cache/filemap.go | 6 +- .../gnopls/internal/cache/filemap_test.go | 4 +- contribs/gnopls/internal/cache/fs_memoized.go | 10 +- contribs/gnopls/internal/cache/fs_overlay.go | 4 +- contribs/gnopls/internal/cache/imports.go | 10 +- contribs/gnopls/internal/cache/keys.go | 2 +- contribs/gnopls/internal/cache/load.go | 24 +- .../internal/cache/metadata/cycle_test.go | 2 +- .../gnopls/internal/cache/metadata/graph.go | 4 +- .../internal/cache/metadata/metadata.go | 4 +- .../internal/cache/methodsets/methodsets.go | 6 +- contribs/gnopls/internal/cache/mod.go | 14 +- contribs/gnopls/internal/cache/mod_tidy.go | 18 +- contribs/gnopls/internal/cache/mod_vuln.go | 14 +- contribs/gnopls/internal/cache/package.go | 12 +- contribs/gnopls/internal/cache/parse.go | 4 +- contribs/gnopls/internal/cache/parse_cache.go | 10 +- .../gnopls/internal/cache/parse_cache_test.go | 6 +- .../gnopls/internal/cache/parsego/file.go | 4 +- .../gnopls/internal/cache/parsego/parse.go | 12 +- .../internal/cache/parsego/parse_test.go | 6 +- contribs/gnopls/internal/cache/port.go | 2 +- contribs/gnopls/internal/cache/port_test.go | 8 +- contribs/gnopls/internal/cache/session.go | 32 +- .../gnopls/internal/cache/session_test.go | 8 +- contribs/gnopls/internal/cache/snapshot.go | 56 +- contribs/gnopls/internal/cache/symbols.go | 8 +- .../gnopls/internal/cache/testfuncs/tests.go | 8 +- .../gnopls/internal/cache/typerefs/doc.go | 2 +- .../internal/cache/typerefs/packageset.go | 2 +- .../internal/cache/typerefs/pkggraph_test.go | 8 +- .../internal/cache/typerefs/pkgrefs_test.go | 14 +- .../gnopls/internal/cache/typerefs/refs.go | 8 +- .../internal/cache/typerefs/refs_test.go | 8 +- contribs/gnopls/internal/cache/view.go | 24 +- contribs/gnopls/internal/cache/view_test.go | 2 +- contribs/gnopls/internal/cache/workspace.go | 4 +- contribs/gnopls/internal/cache/xrefs/xrefs.go | 8 +- .../internal/clonetest/clonetest_test.go | 2 +- .../gnopls/internal/cmd/call_hierarchy.go | 4 +- .../gnopls/internal/cmd/capabilities_test.go | 10 +- contribs/gnopls/internal/cmd/check.go | 4 +- contribs/gnopls/internal/cmd/cmd.go | 26 +- contribs/gnopls/internal/cmd/codeaction.go | 4 +- contribs/gnopls/internal/cmd/codelens.go | 6 +- contribs/gnopls/internal/cmd/definition.go | 6 +- contribs/gnopls/internal/cmd/execute.go | 10 +- contribs/gnopls/internal/cmd/folding_range.go | 4 +- contribs/gnopls/internal/cmd/format.go | 2 +- contribs/gnopls/internal/cmd/help_test.go | 6 +- contribs/gnopls/internal/cmd/highlight.go | 4 +- .../gnopls/internal/cmd/implementation.go | 4 +- contribs/gnopls/internal/cmd/imports.go | 4 +- contribs/gnopls/internal/cmd/info.go | 16 +- .../gnopls/internal/cmd/integration_test.go | 14 +- contribs/gnopls/internal/cmd/links.go | 4 +- contribs/gnopls/internal/cmd/parsespan.go | 2 +- .../gnopls/internal/cmd/prepare_rename.go | 4 +- contribs/gnopls/internal/cmd/references.go | 4 +- contribs/gnopls/internal/cmd/remote.go | 4 +- contribs/gnopls/internal/cmd/rename.go | 4 +- .../gnopls/internal/cmd/semantictokens.go | 4 +- contribs/gnopls/internal/cmd/serve.go | 14 +- contribs/gnopls/internal/cmd/signature.go | 4 +- contribs/gnopls/internal/cmd/span.go | 2 +- contribs/gnopls/internal/cmd/stats.go | 14 +- contribs/gnopls/internal/cmd/subcommands.go | 2 +- contribs/gnopls/internal/cmd/symbols.go | 4 +- .../gnopls/internal/cmd/usage/api-json.hlp | 2 +- .../gnopls/internal/cmd/usage/execute.hlp | 2 +- .../gnopls/internal/cmd/usage/vulncheck.hlp | 2 +- contribs/gnopls/internal/cmd/vulncheck.go | 4 +- .../gnopls/internal/cmd/workspace_symbol.go | 6 +- contribs/gnopls/internal/debug/info.go | 2 +- contribs/gnopls/internal/debug/info_test.go | 2 +- contribs/gnopls/internal/debug/log/log.go | 6 +- contribs/gnopls/internal/debug/metrics.go | 6 +- contribs/gnopls/internal/debug/rpc.go | 10 +- contribs/gnopls/internal/debug/serve.go | 26 +- .../gnopls/internal/debug/template_test.go | 10 +- contribs/gnopls/internal/debug/trace.go | 8 +- contribs/gnopls/internal/diff/diff.go | 176 + contribs/gnopls/internal/diff/diff_test.go | 207 + .../gnopls/internal/diff/difftest/difftest.go | 324 + .../internal/diff/difftest/difftest_test.go | 88 + contribs/gnopls/internal/diff/export_test.go | 9 + contribs/gnopls/internal/diff/lcs/common.go | 179 + .../gnopls/internal/diff/lcs/common_test.go | 140 + contribs/gnopls/internal/diff/lcs/doc.go | 156 + contribs/gnopls/internal/diff/lcs/git.sh | 33 + contribs/gnopls/internal/diff/lcs/labels.go | 55 + contribs/gnopls/internal/diff/lcs/old.go | 480 + contribs/gnopls/internal/diff/lcs/old_test.go | 251 + contribs/gnopls/internal/diff/lcs/sequence.go | 113 + contribs/gnopls/internal/diff/myers/diff.go | 246 + .../gnopls/internal/diff/myers/diff_test.go | 16 + contribs/gnopls/internal/diff/ndiff.go | 99 + contribs/gnopls/internal/diff/unified.go | 251 + contribs/gnopls/internal/doc/api.json | 28 +- contribs/gnopls/internal/drivertest/driver.go | 92 + .../gnopls/internal/drivertest/driver_test.go | 153 + contribs/gnopls/internal/event/bench_test.go | 158 + contribs/gnopls/internal/event/core/event.go | 85 + contribs/gnopls/internal/event/core/export.go | 70 + contribs/gnopls/internal/event/core/fast.go | 77 + contribs/gnopls/internal/event/doc.go | 7 + contribs/gnopls/internal/event/event.go | 127 + .../event/export/eventtest/eventtest.go | 64 + contribs/gnopls/internal/event/export/id.go | 71 + .../gnopls/internal/event/export/labels.go | 37 + contribs/gnopls/internal/event/export/log.go | 57 + .../gnopls/internal/event/export/log_test.go | 40 + .../internal/event/export/metric/data.go | 304 + .../internal/event/export/metric/exporter.go | 58 + .../internal/event/export/metric/info.go | 100 + .../internal/event/export/ocagent/README.md | 139 + .../internal/event/export/ocagent/metrics.go | 213 + .../event/export/ocagent/metrics_test.go | 144 + .../internal/event/export/ocagent/ocagent.go | 358 + .../event/export/ocagent/ocagent_test.go | 210 + .../event/export/ocagent/trace_test.go | 158 + .../event/export/ocagent/wire/common.go | 101 + .../event/export/ocagent/wire/core.go | 17 + .../event/export/ocagent/wire/metrics.go | 204 + .../event/export/ocagent/wire/metrics_test.go | 80 + .../event/export/ocagent/wire/trace.go | 112 + .../gnopls/internal/event/export/printer.go | 43 + .../event/export/prometheus/prometheus.go | 129 + .../gnopls/internal/event/export/trace.go | 117 + contribs/gnopls/internal/event/keys/keys.go | 564 + .../gnopls/internal/event/keys/standard.go | 22 + contribs/gnopls/internal/event/keys/util.go | 21 + .../gnopls/internal/event/keys/util_test.go | 29 + contribs/gnopls/internal/event/label/label.go | 215 + .../gnopls/internal/event/label/label_test.go | 285 + contribs/gnopls/internal/facts/facts.go | 389 + contribs/gnopls/internal/facts/facts_test.go | 560 + contribs/gnopls/internal/facts/imports.go | 136 + contribs/gnopls/internal/fakenet/conn.go | 129 + contribs/gnopls/internal/file/file.go | 2 +- contribs/gnopls/internal/file/kind.go | 24 +- contribs/gnopls/internal/file/modification.go | 2 +- .../gnopls/internal/filecache/filecache.go | 4 +- .../internal/filecache/filecache_test.go | 4 +- contribs/gnopls/internal/fuzzy/input_test.go | 2 +- .../gnopls/internal/fuzzy/matcher_test.go | 2 +- contribs/gnopls/internal/fuzzy/self_test.go | 2 +- contribs/gnopls/internal/fuzzy/symbol_test.go | 2 +- .../internal/gcimporter/bexport_test.go | 378 + .../gnopls/internal/gcimporter/bimport.go | 150 + .../gnopls/internal/gcimporter/exportdata.go | 99 + .../gnopls/internal/gcimporter/gcimporter.go | 266 + .../internal/gcimporter/gcimporter_test.go | 1050 + .../gnopls/internal/gcimporter/iexport.go | 1569 ++ .../gcimporter/iexport_common_test.go | 16 + .../internal/gcimporter/iexport_go118_test.go | 257 + .../internal/gcimporter/iexport_test.go | 601 + .../gnopls/internal/gcimporter/iimport.go | 1100 + .../gnopls/internal/gcimporter/israce_test.go | 12 + contribs/gnopls/internal/gcimporter/main.go | 117 + .../internal/gcimporter/newInterface10.go | 22 + .../internal/gcimporter/newInterface11.go | 14 + .../internal/gcimporter/shallow_test.go | 231 + .../gnopls/internal/gcimporter/stdlib_test.go | 94 + .../internal/gcimporter/support_go118.go | 34 + .../gnopls/internal/gcimporter/testdata/a.go | 14 + .../internal/gcimporter/testdata/a/a.go | 14 + .../gcimporter/testdata/aliases/a/a.go | 14 + .../gcimporter/testdata/aliases/b/b.go | 11 + .../gcimporter/testdata/aliases/c/c.go | 26 + .../gnopls/internal/gcimporter/testdata/b.go | 11 + .../internal/gcimporter/testdata/exports.go | 89 + .../gcimporter/testdata/issue15920.go | 11 + .../gcimporter/testdata/issue20046.go | 9 + .../gcimporter/testdata/issue25301.go | 17 + .../gcimporter/testdata/issue51836/a.go | 8 + .../gcimporter/testdata/issue51836/a/a.go | 8 + .../gcimporter/testdata/issue51836/aa.go | 13 + .../gcimporter/testdata/issue57015.go | 16 + .../gcimporter/testdata/issue58296/a/a.go | 9 + .../gcimporter/testdata/issue58296/b/b.go | 11 + .../gcimporter/testdata/issue58296/c/c.go | 11 + .../gnopls/internal/gcimporter/testdata/p.go | 13 + .../gcimporter/testdata/versions/test.go | 27 + .../testdata/versions/test_go1.16_i.a | Bin 0 -> 3464 bytes .../testdata/versions/test_go1.17_i.a | Bin 0 -> 3638 bytes .../testdata/versions/test_go1.18.5_i.a | Bin 0 -> 4162 bytes .../testdata/versions/test_go1.19_i.a | Bin 0 -> 4026 bytes .../testdata/versions/test_go1.20_u.a | Bin 0 -> 4288 bytes .../gnopls/internal/gcimporter/unified_no.go | 10 + .../gnopls/internal/gcimporter/unified_yes.go | 10 + .../gnopls/internal/gcimporter/ureader_yes.go | 740 + contribs/gnopls/internal/gocommand/invoke.go | 555 + .../gnopls/internal/gocommand/invoke_test.go | 25 + contribs/gnopls/internal/gocommand/vendor.go | 163 + contribs/gnopls/internal/gocommand/version.go | 71 + .../gnopls/internal/gocommand/version_test.go | 31 + contribs/gnopls/internal/golang/add_import.go | 10 +- contribs/gnopls/internal/golang/assembly.go | 4 +- .../gnopls/internal/golang/call_hierarchy.go | 14 +- .../gnopls/internal/golang/change_quote.go | 14 +- .../internal/golang/change_signature.go | 24 +- contribs/gnopls/internal/golang/code_lens.go | 12 +- contribs/gnopls/internal/golang/codeaction.go | 26 +- contribs/gnopls/internal/golang/comment.go | 10 +- .../internal/golang/completion/completion.go | 36 +- .../internal/golang/completion/definition.go | 6 +- .../internal/golang/completion/format.go | 14 +- .../gnopls/internal/golang/completion/fuzz.go | 2 +- .../internal/golang/completion/keywords.go | 4 +- .../internal/golang/completion/literal.go | 12 +- .../internal/golang/completion/package.go | 14 +- .../golang/completion/package_test.go | 2 +- .../golang/completion/postfix_snippets.go | 18 +- .../internal/golang/completion/snippet.go | 4 +- .../internal/golang/completion/statements.go | 8 +- .../gnopls/internal/golang/completion/util.go | 12 +- contribs/gnopls/internal/golang/definition.go | 16 +- .../gnopls/internal/golang/diagnostics.go | 12 +- .../gnopls/internal/golang/embeddirective.go | 2 +- contribs/gnopls/internal/golang/extract.go | 6 +- .../gnopls/internal/golang/extracttofile.go | 12 +- contribs/gnopls/internal/golang/fix.go | 22 +- .../gnopls/internal/golang/folding_range.go | 12 +- contribs/gnopls/internal/golang/format.go | 18 +- .../gnopls/internal/golang/format_test.go | 2 +- .../gnopls/internal/golang/freesymbols.go | 12 +- .../gnopls/internal/golang/gc_annotations.go | 12 +- contribs/gnopls/internal/golang/highlight.go | 8 +- contribs/gnopls/internal/golang/hover.go | 32 +- contribs/gnopls/internal/golang/identifier.go | 4 +- .../gnopls/internal/golang/identifier_test.go | 2 +- .../gnopls/internal/golang/implementation.go | 16 +- contribs/gnopls/internal/golang/inlay_hint.go | 16 +- contribs/gnopls/internal/golang/inline.go | 14 +- contribs/gnopls/internal/golang/inline_all.go | 10 +- .../internal/golang/invertifcondition.go | 2 +- .../gnopls/internal/golang/known_packages.go | 10 +- contribs/gnopls/internal/golang/lines.go | 2 +- contribs/gnopls/internal/golang/linkname.go | 10 +- contribs/gnopls/internal/golang/pkgdoc.go | 16 +- contribs/gnopls/internal/golang/references.go | 16 +- contribs/gnopls/internal/golang/rename.go | 22 +- .../gnopls/internal/golang/rename_check.go | 10 +- contribs/gnopls/internal/golang/semtok.go | 20 +- .../gnopls/internal/golang/signature_help.go | 14 +- contribs/gnopls/internal/golang/snapshot.go | 8 +- contribs/gnopls/internal/golang/stub.go | 16 +- contribs/gnopls/internal/golang/symbols.go | 10 +- .../gnopls/internal/golang/type_definition.go | 8 +- .../gnopls/internal/golang/types_format.go | 14 +- contribs/gnopls/internal/golang/util.go | 16 +- .../internal/golang/workspace_symbol.go | 12 +- .../internal/golang/workspace_symbol_test.go | 2 +- contribs/gnopls/internal/gopathwalk/walk.go | 337 + .../gnopls/internal/gopathwalk/walk_test.go | 252 + contribs/gnopls/internal/goroot/importcfg.go | 71 + contribs/gnopls/internal/imports/fix.go | 1963 ++ contribs/gnopls/internal/imports/fix_test.go | 3087 +++ contribs/gnopls/internal/imports/imports.go | 354 + .../gnopls/internal/imports/imports_test.go | 17 + contribs/gnopls/internal/imports/mkindex.go | 173 + contribs/gnopls/internal/imports/mod.go | 840 + contribs/gnopls/internal/imports/mod_cache.go | 331 + .../gnopls/internal/imports/mod_cache_test.go | 144 + .../gnopls/internal/imports/mod_go122_test.go | 58 + contribs/gnopls/internal/imports/mod_test.go | 1325 ++ .../gnopls/internal/imports/sortimports.go | 297 + .../testdata/mod/example.com_v1.0.0.txt | 9 + ...ext_v0.0.0-20170915032832-14c0d48ead0c.txt | 47 + .../testdata/mod/rsc.io_!q!u!o!t!e_v1.5.2.txt | 88 + .../mod/rsc.io_!q!u!o!t!e_v1.5.3-!p!r!e.txt | 88 + .../testdata/mod/rsc.io_quote_v1.5.1.txt | 86 + .../testdata/mod/rsc.io_quote_v1.5.2.txt | 98 + .../testdata/mod/rsc.io_quote_v2_v2.0.1.txt | 86 + .../testdata/mod/rsc.io_quote_v3_v3.0.0.txt | 45 + .../testdata/mod/rsc.io_sampler_v1.3.0.txt | 202 + .../testdata/mod/rsc.io_sampler_v1.3.1.txt | 201 + contribs/gnopls/internal/jsonrpc2/conn.go | 261 + contribs/gnopls/internal/jsonrpc2/handler.go | 109 + contribs/gnopls/internal/jsonrpc2/jsonrpc2.go | 17 + .../gnopls/internal/jsonrpc2/jsonrpc2_test.go | 148 + contribs/gnopls/internal/jsonrpc2/labels.go | 24 + contribs/gnopls/internal/jsonrpc2/messages.go | 238 + contribs/gnopls/internal/jsonrpc2/serve.go | 163 + .../gnopls/internal/jsonrpc2/serve_test.go | 65 + .../jsonrpc2/servertest/servertest.go | 119 + .../jsonrpc2/servertest/servertest_test.go | 53 + contribs/gnopls/internal/jsonrpc2/stream.go | 170 + contribs/gnopls/internal/jsonrpc2/wire.go | 159 + .../gnopls/internal/jsonrpc2/wire_test.go | 126 + contribs/gnopls/internal/jsonrpc2_v2/conn.go | 812 + contribs/gnopls/internal/jsonrpc2_v2/frame.go | 183 + .../gnopls/internal/jsonrpc2_v2/jsonrpc2.go | 137 + .../internal/jsonrpc2_v2/jsonrpc2_test.go | 388 + .../gnopls/internal/jsonrpc2_v2/messages.go | 181 + contribs/gnopls/internal/jsonrpc2_v2/net.go | 138 + contribs/gnopls/internal/jsonrpc2_v2/serve.go | 328 + .../internal/jsonrpc2_v2/serve_go116.go | 19 + .../internal/jsonrpc2_v2/serve_pre116.go | 30 + .../gnopls/internal/jsonrpc2_v2/serve_test.go | 347 + contribs/gnopls/internal/jsonrpc2_v2/wire.go | 86 + .../gnopls/internal/jsonrpc2_v2/wire_test.go | 118 + contribs/gnopls/internal/label/keys.go | 2 +- .../gnopls/internal/licenses/gen-licenses.sh | 2 +- .../gnopls/internal/licenses/licenses_test.go | 2 +- .../gnopls/internal/lsprpc/binder_test.go | 6 +- .../lsprpc/commandinterceptor_test.go | 6 +- contribs/gnopls/internal/lsprpc/dialer.go | 2 +- .../gnopls/internal/lsprpc/export_test.go | 8 +- contribs/gnopls/internal/lsprpc/goenv.go | 2 +- contribs/gnopls/internal/lsprpc/goenv_test.go | 10 +- contribs/gnopls/internal/lsprpc/lsprpc.go | 18 +- .../gnopls/internal/lsprpc/lsprpc_test.go | 16 +- .../gnopls/internal/lsprpc/middleware_test.go | 6 +- contribs/gnopls/internal/memoize/memoize.go | 335 + .../gnopls/internal/memoize/memoize_test.go | 166 + contribs/gnopls/internal/mod/code_lens.go | 10 +- contribs/gnopls/internal/mod/diagnostics.go | 14 +- contribs/gnopls/internal/mod/format.go | 10 +- contribs/gnopls/internal/mod/hover.go | 16 +- contribs/gnopls/internal/mod/inlayhint.go | 6 +- .../internal/packagesinternal/packages.go | 22 + contribs/gnopls/internal/pkgbits/codes.go | 77 + contribs/gnopls/internal/pkgbits/decoder.go | 519 + contribs/gnopls/internal/pkgbits/doc.go | 32 + contribs/gnopls/internal/pkgbits/encoder.go | 392 + contribs/gnopls/internal/pkgbits/flags.go | 9 + .../gnopls/internal/pkgbits/pkgbits_test.go | 77 + contribs/gnopls/internal/pkgbits/reloc.go | 42 + contribs/gnopls/internal/pkgbits/support.go | 17 + contribs/gnopls/internal/pkgbits/sync.go | 136 + .../internal/pkgbits/syncmarker_string.go | 92 + contribs/gnopls/internal/pkgbits/version.go | 85 + contribs/gnopls/internal/pprof/main.go | 36 + contribs/gnopls/internal/pprof/pprof.go | 89 + contribs/gnopls/internal/pprof/pprof_test.go | 46 + .../internal/pprof/testdata/sample.pprof | Bin 0 -> 50325 bytes contribs/gnopls/internal/progress/progress.go | 8 +- .../gnopls/internal/progress/progress_test.go | 2 +- .../internal/protocol/command/command_gen.go | 2 +- .../protocol/command/commandmeta/meta.go | 4 +- .../internal/protocol/command/gen/gen.go | 8 +- .../internal/protocol/command/generate.go | 2 +- .../internal/protocol/command/interface.go | 4 +- .../protocol/command/interface_test.go | 4 +- contribs/gnopls/internal/protocol/context.go | 10 +- contribs/gnopls/internal/protocol/edits.go | 2 +- .../gnopls/internal/protocol/generate/main.go | 4 +- .../gnopls/internal/protocol/json_test.go | 2 +- contribs/gnopls/internal/protocol/log.go | 2 +- contribs/gnopls/internal/protocol/mapper.go | 4 +- .../gnopls/internal/protocol/mapper_test.go | 2 +- contribs/gnopls/internal/protocol/protocol.go | 10 +- contribs/gnopls/internal/protocol/tsclient.go | 2 +- contribs/gnopls/internal/protocol/tsserver.go | 2 +- contribs/gnopls/internal/protocol/uri.go | 2 +- contribs/gnopls/internal/protocol/uri_test.go | 2 +- .../internal/protocol/uri_windows_test.go | 2 +- contribs/gnopls/internal/proxydir/proxydir.go | 99 + .../gnopls/internal/proxydir/proxydir_test.go | 112 + .../refactor/inline/analyzer/analyzer.go | 166 + .../refactor/inline/analyzer/analyzer_test.go | 18 + .../internal/refactor/inline/analyzer/main.go | 19 + .../inline/analyzer/testdata/src/a/a.go | 17 + .../analyzer/testdata/src/a/a.go.golden | 17 + .../inline/analyzer/testdata/src/b/b.go | 9 + .../analyzer/testdata/src/b/b.go.golden | 9 + .../gnopls/internal/refactor/inline/callee.go | 538 + .../internal/refactor/inline/calleefx.go | 347 + .../internal/refactor/inline/calleefx_test.go | 159 + .../gnopls/internal/refactor/inline/doc.go | 288 + .../gnopls/internal/refactor/inline/escape.go | 99 + .../refactor/inline/everything_test.go | 240 + .../internal/refactor/inline/export_test.go | 9 + .../gnopls/internal/refactor/inline/falcon.go | 893 + .../internal/refactor/inline/falcon_test.go | 381 + .../gnopls/internal/refactor/inline/inline.go | 3289 +++ .../internal/refactor/inline/inline_test.go | 1763 ++ .../refactor/inline/testdata/basic-err.txtar | 24 + .../inline/testdata/basic-literal.txtar | 29 + .../inline/testdata/basic-reduce.txtar | 50 + .../refactor/inline/testdata/cgo.txtar | 45 + .../refactor/inline/testdata/comments.txtar | 57 + .../inline/testdata/crosspkg-selfref.txtar | 32 + .../refactor/inline/testdata/crosspkg.txtar | 105 + .../refactor/inline/testdata/dotimport.txtar | 37 + .../refactor/inline/testdata/embed.txtar | 28 + .../refactor/inline/testdata/empty-body.txtar | 103 + .../refactor/inline/testdata/err-basic.txtar | 30 + .../inline/testdata/err-shadow-builtin.txtar | 36 + .../inline/testdata/err-shadow-pkg.txtar | 36 + .../inline/testdata/err-unexported.txtar | 31 + .../refactor/inline/testdata/exprstmt.txtar | 99 + .../inline/testdata/import-rename.txtar | 40 + .../inline/testdata/import-shadow.txtar | 123 + .../refactor/inline/testdata/internal.txtar | 29 + .../refactor/inline/testdata/issue62667.txtar | 44 + .../refactor/inline/testdata/issue63298.txtar | 50 + .../inline/testdata/line-directives.txtar | 35 + .../refactor/inline/testdata/method.txtar | 127 + .../inline/testdata/multistmt-body.txtar | 85 + .../refactor/inline/testdata/n-ary.txtar | 79 + .../inline/testdata/param-subst.txtar | 19 + .../inline/testdata/revdotimport.txtar | 42 + .../inline/testdata/std-internal.txtar | 15 + .../refactor/inline/testdata/tailcall.txtar | 120 + .../gnopls/internal/refactor/inline/util.go | 197 + .../gnopls/internal/robustio/copyfiles.go | 117 + .../gnopls/internal/robustio/gopls_windows.go | 16 + contribs/gnopls/internal/robustio/robustio.go | 69 + .../internal/robustio/robustio_darwin.go | 21 + .../internal/robustio/robustio_flaky.go | 92 + .../internal/robustio/robustio_other.go | 28 + .../internal/robustio/robustio_plan9.go | 26 + .../internal/robustio/robustio_posix.go | 26 + .../gnopls/internal/robustio/robustio_test.go | 101 + .../internal/robustio/robustio_windows.go | 51 + .../gnopls/internal/server/call_hierarchy.go | 14 +- .../gnopls/internal/server/code_action.go | 18 +- contribs/gnopls/internal/server/code_lens.go | 18 +- contribs/gnopls/internal/server/command.go | 38 +- contribs/gnopls/internal/server/completion.go | 26 +- contribs/gnopls/internal/server/definition.go | 18 +- .../gnopls/internal/server/diagnostics.go | 26 +- .../gnopls/internal/server/folding_range.go | 12 +- contribs/gnopls/internal/server/format.go | 16 +- contribs/gnopls/internal/server/general.go | 22 +- contribs/gnopls/internal/server/highlight.go | 14 +- contribs/gnopls/internal/server/hover.go | 22 +- .../gnopls/internal/server/implementation.go | 14 +- contribs/gnopls/internal/server/inlay_hint.go | 14 +- contribs/gnopls/internal/server/link.go | 20 +- contribs/gnopls/internal/server/prompt.go | 4 +- contribs/gnopls/internal/server/references.go | 16 +- contribs/gnopls/internal/server/rename.go | 14 +- .../gnopls/internal/server/selection_range.go | 10 +- contribs/gnopls/internal/server/semantic.go | 14 +- contribs/gnopls/internal/server/server.go | 16 +- .../gnopls/internal/server/signature_help.go | 12 +- contribs/gnopls/internal/server/symbols.go | 14 +- .../internal/server/text_synchronization.go | 16 +- .../gnopls/internal/server/unimplemented.go | 4 +- contribs/gnopls/internal/server/workspace.go | 8 +- .../internal/server/workspace_symbol.go | 10 +- contribs/gnopls/internal/settings/analysis.go | 32 +- .../internal/settings/codeactionkind.go | 2 +- contribs/gnopls/internal/settings/default.go | 8 +- contribs/gnopls/internal/settings/settings.go | 6 +- .../gnopls/internal/settings/settings_test.go | 4 +- .../gnopls/internal/settings/staticcheck.go | 2 +- contribs/gnopls/internal/settings/vet_test.go | 4 +- .../internal/stack/gostacks/gostacks.go | 23 + contribs/gnopls/internal/stack/parse.go | 175 + contribs/gnopls/internal/stack/process.go | 112 + contribs/gnopls/internal/stack/stack.go | 170 + contribs/gnopls/internal/stack/stack_test.go | 193 + .../internal/stack/stacktest/stacktest.go | 50 + contribs/gnopls/internal/stdlib/generate.go | 226 + contribs/gnopls/internal/stdlib/manifest.go | 17431 ++++++++++++++++ contribs/gnopls/internal/stdlib/stdlib.go | 97 + .../internal/telemetry/cmd/stacks/stacks.go | 16 +- .../internal/telemetry/telemetry_test.go | 18 +- .../gnopls/internal/template/completion.go | 6 +- .../internal/template/completion_test.go | 2 +- .../gnopls/internal/template/highlight.go | 6 +- .../internal/template/implementations.go | 8 +- contribs/gnopls/internal/template/parse.go | 6 +- contribs/gnopls/internal/template/symbols.go | 8 +- contribs/gnopls/internal/test/compare/text.go | 2 +- .../gnopls/internal/test/compare/text_test.go | 2 +- .../test/integration/bench/bench_test.go | 22 +- .../test/integration/bench/codeaction_test.go | 2 +- .../test/integration/bench/completion_test.go | 6 +- .../test/integration/bench/didchange_test.go | 4 +- .../test/integration/bench/imports_test.go | 8 +- .../test/integration/bench/iwl_test.go | 8 +- .../test/integration/bench/reload_test.go | 2 +- .../test/integration/bench/repo_test.go | 4 +- .../test/integration/bench/stress_test.go | 10 +- .../test/integration/bench/tests_test.go | 6 +- .../test/integration/bench/typing_test.go | 2 +- .../integration/codelens/codelens_test.go | 18 +- .../integration/codelens/gcdetails_test.go | 14 +- .../completion/completion18_test.go | 4 +- .../integration/completion/completion_test.go | 12 +- .../integration/completion/fixedbugs_test.go | 2 +- .../completion/postfix_snippet_test.go | 2 +- .../test/integration/debug/debug_test.go | 8 +- .../integration/diagnostics/analysis_test.go | 6 +- .../integration/diagnostics/builtin_test.go | 2 +- .../diagnostics/diagnostics_test.go | 12 +- .../integration/diagnostics/golist_test.go | 6 +- .../diagnostics/gopackagesdriver_test.go | 2 +- .../diagnostics/invalidation_test.go | 4 +- .../diagnostics/undeclared_test.go | 4 +- .../gnopls/internal/test/integration/doc.go | 4 +- .../gnopls/internal/test/integration/env.go | 6 +- .../internal/test/integration/env_test.go | 2 +- .../internal/test/integration/expectation.go | 4 +- .../internal/test/integration/fake/client.go | 4 +- .../internal/test/integration/fake/edit.go | 4 +- .../test/integration/fake/edit_test.go | 2 +- .../internal/test/integration/fake/editor.go | 16 +- .../test/integration/fake/editor_test.go | 2 +- .../test/integration/fake/glob/glob_test.go | 2 +- .../internal/test/integration/fake/proxy.go | 2 +- .../internal/test/integration/fake/sandbox.go | 6 +- .../internal/test/integration/fake/workdir.go | 4 +- .../test/integration/fake/workdir_test.go | 2 +- .../integration/inlayhints/inlayhints_test.go | 6 +- .../integration/misc/call_hierarchy_test.go | 4 +- .../test/integration/misc/codeactions_test.go | 6 +- .../integration/misc/configuration_test.go | 4 +- .../test/integration/misc/debugserver_test.go | 6 +- .../test/integration/misc/definition_test.go | 6 +- .../test/integration/misc/embed_test.go | 2 +- .../test/integration/misc/extract_test.go | 8 +- .../test/integration/misc/failures_test.go | 4 +- .../test/integration/misc/fix_test.go | 8 +- .../test/integration/misc/formatting_test.go | 6 +- .../test/integration/misc/generate_test.go | 2 +- .../test/integration/misc/highlight_test.go | 4 +- .../test/integration/misc/hover_test.go | 8 +- .../test/integration/misc/import_test.go | 8 +- .../test/integration/misc/imports_test.go | 6 +- .../test/integration/misc/link_test.go | 2 +- .../test/integration/misc/misc_test.go | 6 +- .../integration/misc/multiple_adhoc_test.go | 2 +- .../test/integration/misc/prompt_test.go | 8 +- .../test/integration/misc/references_test.go | 6 +- .../test/integration/misc/rename_test.go | 6 +- .../integration/misc/semantictokens_test.go | 6 +- .../test/integration/misc/settings_test.go | 2 +- .../test/integration/misc/shared_test.go | 2 +- .../integration/misc/signature_help_test.go | 4 +- .../test/integration/misc/staticcheck_test.go | 6 +- .../test/integration/misc/vendor_test.go | 4 +- .../test/integration/misc/vuln_test.go | 14 +- .../test/integration/misc/webserver_test.go | 10 +- .../integration/misc/workspace_symbol_test.go | 4 +- .../test/integration/modfile/modfile_test.go | 8 +- .../integration/modfile/tempmodfile_test.go | 2 +- .../internal/test/integration/options.go | 6 +- .../internal/test/integration/regtest.go | 14 +- .../internal/test/integration/runner.go | 20 +- .../integration/template/template_test.go | 6 +- .../test/integration/watch/setting_test.go | 2 +- .../test/integration/watch/watch_test.go | 8 +- .../test/integration/workspace/adhoc_test.go | 2 +- .../test/integration/workspace/broken_test.go | 6 +- .../workspace/directoryfilters_test.go | 2 +- .../integration/workspace/fromenv_test.go | 2 +- .../integration/workspace/goversion_test.go | 4 +- .../integration/workspace/metadata_test.go | 2 +- .../integration/workspace/misspelling_test.go | 4 +- .../integration/workspace/modules_test.go | 6 +- .../workspace/multi_folder_test.go | 2 +- .../integration/workspace/packages_test.go | 6 +- .../integration/workspace/quickfix_test.go | 6 +- .../integration/workspace/standalone_test.go | 4 +- .../test/integration/workspace/std_test.go | 2 +- .../test/integration/workspace/vendor_test.go | 2 +- .../integration/workspace/workspace_test.go | 20 +- .../integration/workspace/zero_config_test.go | 6 +- .../internal/test/integration/wrappers.go | 8 +- .../internal/test/marker/marker_test.go | 28 +- contribs/gnopls/internal/testenv/exec.go | 192 + contribs/gnopls/internal/testenv/testenv.go | 492 + .../internal/testenv/testenv_notunix.go | 14 + .../gnopls/internal/testenv/testenv_unix.go | 14 + .../internal/testfiles/testdata/somefile.txt | 1 + .../testfiles/testdata/versions/go.mod.test | 5 + .../testfiles/testdata/versions/mod.go | 3 + .../testfiles/testdata/versions/post.go | 3 + .../testfiles/testdata/versions/pre.go | 3 + .../testdata/versions/sub.test/sub.go.test | 1 + .../gnopls/internal/testfiles/testfiles.go | 106 + .../internal/testfiles/testfiles_test.go | 95 + .../internal/tokeninternal/tokeninternal.go | 137 + .../tokeninternal/tokeninternal_test.go | 55 + contribs/gnopls/internal/tool/tool.go | 277 + contribs/gnopls/internal/typeparams/common.go | 142 + .../gnopls/internal/typeparams/common_test.go | 277 + .../internal/typeparams/copytermlist.go | 98 + .../gnopls/internal/typeparams/coretype.go | 150 + .../internal/typeparams/coretype_test.go | 101 + contribs/gnopls/internal/typeparams/free.go | 120 + .../gnopls/internal/typeparams/free_test.go | 73 + .../typeparams/genericfeatures/features.go | 104 + .../gnopls/internal/typeparams/normalize.go | 218 + .../internal/typeparams/normalize_test.go | 101 + .../gnopls/internal/typeparams/termlist.go | 163 + .../gnopls/internal/typeparams/typeterm.go | 169 + .../gnopls/internal/typesinternal/element.go | 134 + .../internal/typesinternal/element_test.go | 154 + .../internal/typesinternal/errorcode.go | 1560 ++ .../typesinternal/errorcode_string.go | 179 + .../internal/typesinternal/errorcode_test.go | 105 + .../gnopls/internal/typesinternal/recv.go | 43 + .../gnopls/internal/typesinternal/toonew.go | 89 + .../gnopls/internal/typesinternal/types.go | 65 + .../gnopls/internal/util/astutil/purge.go | 2 +- .../internal/util/astutil/purge_test.go | 4 +- contribs/gnopls/internal/util/astutil/util.go | 2 +- .../gnopls/internal/util/frob/frob_test.go | 2 +- .../internal/util/goversion/goversion_test.go | 2 +- .../gnopls/internal/util/lru/lru_fuzz_test.go | 2 +- .../gnopls/internal/util/lru/lru_nil_test.go | 2 +- contribs/gnopls/internal/util/lru/lru_test.go | 2 +- .../gnopls/internal/util/persistent/map.go | 2 +- .../gnopls/internal/util/persistent/set.go | 2 +- .../internal/util/persistent/set_test.go | 4 +- .../internal/util/safetoken/safetoken_test.go | 8 +- .../gnopls/internal/versions/constraint.go | 13 + .../internal/versions/constraint_go121.go | 14 + contribs/gnopls/internal/versions/features.go | 43 + contribs/gnopls/internal/versions/gover.go | 172 + .../gnopls/internal/versions/toolchain.go | 14 + .../internal/versions/toolchain_go119.go | 14 + .../internal/versions/toolchain_go120.go | 14 + .../internal/versions/toolchain_go121.go | 14 + contribs/gnopls/internal/versions/types.go | 19 + .../gnopls/internal/versions/types_go121.go | 30 + .../gnopls/internal/versions/types_go122.go | 41 + .../gnopls/internal/versions/types_test.go | 87 + contribs/gnopls/internal/versions/versions.go | 57 + .../gnopls/internal/versions/versions_test.go | 256 + contribs/gnopls/internal/vulncheck/copier.go | 2 +- .../vulncheck/govulncheck/govulncheck.go | 2 +- .../internal/vulncheck/govulncheck/handler.go | 2 +- .../vulncheck/govulncheck/jsonhandler.go | 2 +- .../gnopls/internal/vulncheck/scan/command.go | 8 +- contribs/gnopls/internal/vulncheck/types.go | 4 +- .../gnopls/internal/vulncheck/vulntest/db.go | 4 +- .../internal/vulncheck/vulntest/db_test.go | 4 +- .../internal/vulncheck/vulntest/report.go | 2 +- contribs/gnopls/internal/work/completion.go | 8 +- contribs/gnopls/internal/work/diagnostics.go | 8 +- contribs/gnopls/internal/work/format.go | 10 +- contribs/gnopls/internal/work/hover.go | 8 +- contribs/gnopls/internal/xcontext/xcontext.go | 23 + contribs/gnopls/main.go | 8 +- contribs/gnopls/release/release.go | 2 +- 706 files changed, 71895 insertions(+), 1632 deletions(-) create mode 100644 contribs/gnopls/internal/aliases/aliases.go create mode 100644 contribs/gnopls/internal/aliases/aliases_go121.go create mode 100644 contribs/gnopls/internal/aliases/aliases_go122.go create mode 100644 contribs/gnopls/internal/aliases/aliases_test.go create mode 100644 contribs/gnopls/internal/analysisinternal/addimport_test.go create mode 100644 contribs/gnopls/internal/analysisinternal/analysis.go create mode 100644 contribs/gnopls/internal/analysisinternal/extractdoc.go create mode 100644 contribs/gnopls/internal/analysisinternal/extractdoc_test.go create mode 100644 contribs/gnopls/internal/astutil/clone.go create mode 100644 contribs/gnopls/internal/diff/diff.go create mode 100644 contribs/gnopls/internal/diff/diff_test.go create mode 100644 contribs/gnopls/internal/diff/difftest/difftest.go create mode 100644 contribs/gnopls/internal/diff/difftest/difftest_test.go create mode 100644 contribs/gnopls/internal/diff/export_test.go create mode 100644 contribs/gnopls/internal/diff/lcs/common.go create mode 100644 contribs/gnopls/internal/diff/lcs/common_test.go create mode 100644 contribs/gnopls/internal/diff/lcs/doc.go create mode 100644 contribs/gnopls/internal/diff/lcs/git.sh create mode 100644 contribs/gnopls/internal/diff/lcs/labels.go create mode 100644 contribs/gnopls/internal/diff/lcs/old.go create mode 100644 contribs/gnopls/internal/diff/lcs/old_test.go create mode 100644 contribs/gnopls/internal/diff/lcs/sequence.go create mode 100644 contribs/gnopls/internal/diff/myers/diff.go create mode 100644 contribs/gnopls/internal/diff/myers/diff_test.go create mode 100644 contribs/gnopls/internal/diff/ndiff.go create mode 100644 contribs/gnopls/internal/diff/unified.go create mode 100644 contribs/gnopls/internal/drivertest/driver.go create mode 100644 contribs/gnopls/internal/drivertest/driver_test.go create mode 100644 contribs/gnopls/internal/event/bench_test.go create mode 100644 contribs/gnopls/internal/event/core/event.go create mode 100644 contribs/gnopls/internal/event/core/export.go create mode 100644 contribs/gnopls/internal/event/core/fast.go create mode 100644 contribs/gnopls/internal/event/doc.go create mode 100644 contribs/gnopls/internal/event/event.go create mode 100644 contribs/gnopls/internal/event/export/eventtest/eventtest.go create mode 100644 contribs/gnopls/internal/event/export/id.go create mode 100644 contribs/gnopls/internal/event/export/labels.go create mode 100644 contribs/gnopls/internal/event/export/log.go create mode 100644 contribs/gnopls/internal/event/export/log_test.go create mode 100644 contribs/gnopls/internal/event/export/metric/data.go create mode 100644 contribs/gnopls/internal/event/export/metric/exporter.go create mode 100644 contribs/gnopls/internal/event/export/metric/info.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/README.md create mode 100644 contribs/gnopls/internal/event/export/ocagent/metrics.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/metrics_test.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/ocagent.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/ocagent_test.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/trace_test.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/wire/common.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/wire/core.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/wire/metrics.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/wire/metrics_test.go create mode 100644 contribs/gnopls/internal/event/export/ocagent/wire/trace.go create mode 100644 contribs/gnopls/internal/event/export/printer.go create mode 100644 contribs/gnopls/internal/event/export/prometheus/prometheus.go create mode 100644 contribs/gnopls/internal/event/export/trace.go create mode 100644 contribs/gnopls/internal/event/keys/keys.go create mode 100644 contribs/gnopls/internal/event/keys/standard.go create mode 100644 contribs/gnopls/internal/event/keys/util.go create mode 100644 contribs/gnopls/internal/event/keys/util_test.go create mode 100644 contribs/gnopls/internal/event/label/label.go create mode 100644 contribs/gnopls/internal/event/label/label_test.go create mode 100644 contribs/gnopls/internal/facts/facts.go create mode 100644 contribs/gnopls/internal/facts/facts_test.go create mode 100644 contribs/gnopls/internal/facts/imports.go create mode 100644 contribs/gnopls/internal/fakenet/conn.go create mode 100644 contribs/gnopls/internal/gcimporter/bexport_test.go create mode 100644 contribs/gnopls/internal/gcimporter/bimport.go create mode 100644 contribs/gnopls/internal/gcimporter/exportdata.go create mode 100644 contribs/gnopls/internal/gcimporter/gcimporter.go create mode 100644 contribs/gnopls/internal/gcimporter/gcimporter_test.go create mode 100644 contribs/gnopls/internal/gcimporter/iexport.go create mode 100644 contribs/gnopls/internal/gcimporter/iexport_common_test.go create mode 100644 contribs/gnopls/internal/gcimporter/iexport_go118_test.go create mode 100644 contribs/gnopls/internal/gcimporter/iexport_test.go create mode 100644 contribs/gnopls/internal/gcimporter/iimport.go create mode 100644 contribs/gnopls/internal/gcimporter/israce_test.go create mode 100644 contribs/gnopls/internal/gcimporter/main.go create mode 100644 contribs/gnopls/internal/gcimporter/newInterface10.go create mode 100644 contribs/gnopls/internal/gcimporter/newInterface11.go create mode 100644 contribs/gnopls/internal/gcimporter/shallow_test.go create mode 100644 contribs/gnopls/internal/gcimporter/stdlib_test.go create mode 100644 contribs/gnopls/internal/gcimporter/support_go118.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/a.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/a/a.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/aliases/a/a.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/aliases/b/b.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/aliases/c/c.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/b.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/exports.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue15920.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue20046.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue25301.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue51836/a.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue51836/a/a.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue51836/aa.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue57015.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue58296/a/a.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue58296/b/b.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/issue58296/c/c.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/p.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/versions/test.go create mode 100644 contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.16_i.a create mode 100644 contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.17_i.a create mode 100644 contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.18.5_i.a create mode 100644 contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.19_i.a create mode 100644 contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.20_u.a create mode 100644 contribs/gnopls/internal/gcimporter/unified_no.go create mode 100644 contribs/gnopls/internal/gcimporter/unified_yes.go create mode 100644 contribs/gnopls/internal/gcimporter/ureader_yes.go create mode 100644 contribs/gnopls/internal/gocommand/invoke.go create mode 100644 contribs/gnopls/internal/gocommand/invoke_test.go create mode 100644 contribs/gnopls/internal/gocommand/vendor.go create mode 100644 contribs/gnopls/internal/gocommand/version.go create mode 100644 contribs/gnopls/internal/gocommand/version_test.go create mode 100644 contribs/gnopls/internal/gopathwalk/walk.go create mode 100644 contribs/gnopls/internal/gopathwalk/walk_test.go create mode 100644 contribs/gnopls/internal/goroot/importcfg.go create mode 100644 contribs/gnopls/internal/imports/fix.go create mode 100644 contribs/gnopls/internal/imports/fix_test.go create mode 100644 contribs/gnopls/internal/imports/imports.go create mode 100644 contribs/gnopls/internal/imports/imports_test.go create mode 100644 contribs/gnopls/internal/imports/mkindex.go create mode 100644 contribs/gnopls/internal/imports/mod.go create mode 100644 contribs/gnopls/internal/imports/mod_cache.go create mode 100644 contribs/gnopls/internal/imports/mod_cache_test.go create mode 100644 contribs/gnopls/internal/imports/mod_go122_test.go create mode 100644 contribs/gnopls/internal/imports/mod_test.go create mode 100644 contribs/gnopls/internal/imports/sortimports.go create mode 100644 contribs/gnopls/internal/imports/testdata/mod/example.com_v1.0.0.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/rsc.io_!q!u!o!t!e_v1.5.2.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/rsc.io_!q!u!o!t!e_v1.5.3-!p!r!e.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v1.5.1.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v1.5.2.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v2_v2.0.1.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v3_v3.0.0.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/rsc.io_sampler_v1.3.0.txt create mode 100644 contribs/gnopls/internal/imports/testdata/mod/rsc.io_sampler_v1.3.1.txt create mode 100644 contribs/gnopls/internal/jsonrpc2/conn.go create mode 100644 contribs/gnopls/internal/jsonrpc2/handler.go create mode 100644 contribs/gnopls/internal/jsonrpc2/jsonrpc2.go create mode 100644 contribs/gnopls/internal/jsonrpc2/jsonrpc2_test.go create mode 100644 contribs/gnopls/internal/jsonrpc2/labels.go create mode 100644 contribs/gnopls/internal/jsonrpc2/messages.go create mode 100644 contribs/gnopls/internal/jsonrpc2/serve.go create mode 100644 contribs/gnopls/internal/jsonrpc2/serve_test.go create mode 100644 contribs/gnopls/internal/jsonrpc2/servertest/servertest.go create mode 100644 contribs/gnopls/internal/jsonrpc2/servertest/servertest_test.go create mode 100644 contribs/gnopls/internal/jsonrpc2/stream.go create mode 100644 contribs/gnopls/internal/jsonrpc2/wire.go create mode 100644 contribs/gnopls/internal/jsonrpc2/wire_test.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/conn.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/frame.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/jsonrpc2.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/jsonrpc2_test.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/messages.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/net.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/serve.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/serve_go116.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/serve_pre116.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/serve_test.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/wire.go create mode 100644 contribs/gnopls/internal/jsonrpc2_v2/wire_test.go create mode 100644 contribs/gnopls/internal/memoize/memoize.go create mode 100644 contribs/gnopls/internal/memoize/memoize_test.go create mode 100644 contribs/gnopls/internal/packagesinternal/packages.go create mode 100644 contribs/gnopls/internal/pkgbits/codes.go create mode 100644 contribs/gnopls/internal/pkgbits/decoder.go create mode 100644 contribs/gnopls/internal/pkgbits/doc.go create mode 100644 contribs/gnopls/internal/pkgbits/encoder.go create mode 100644 contribs/gnopls/internal/pkgbits/flags.go create mode 100644 contribs/gnopls/internal/pkgbits/pkgbits_test.go create mode 100644 contribs/gnopls/internal/pkgbits/reloc.go create mode 100644 contribs/gnopls/internal/pkgbits/support.go create mode 100644 contribs/gnopls/internal/pkgbits/sync.go create mode 100644 contribs/gnopls/internal/pkgbits/syncmarker_string.go create mode 100644 contribs/gnopls/internal/pkgbits/version.go create mode 100644 contribs/gnopls/internal/pprof/main.go create mode 100644 contribs/gnopls/internal/pprof/pprof.go create mode 100644 contribs/gnopls/internal/pprof/pprof_test.go create mode 100644 contribs/gnopls/internal/pprof/testdata/sample.pprof create mode 100644 contribs/gnopls/internal/proxydir/proxydir.go create mode 100644 contribs/gnopls/internal/proxydir/proxydir_test.go create mode 100644 contribs/gnopls/internal/refactor/inline/analyzer/analyzer.go create mode 100644 contribs/gnopls/internal/refactor/inline/analyzer/analyzer_test.go create mode 100644 contribs/gnopls/internal/refactor/inline/analyzer/main.go create mode 100644 contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/a/a.go create mode 100644 contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/a/a.go.golden create mode 100644 contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/b/b.go create mode 100644 contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/b/b.go.golden create mode 100644 contribs/gnopls/internal/refactor/inline/callee.go create mode 100644 contribs/gnopls/internal/refactor/inline/calleefx.go create mode 100644 contribs/gnopls/internal/refactor/inline/calleefx_test.go create mode 100644 contribs/gnopls/internal/refactor/inline/doc.go create mode 100644 contribs/gnopls/internal/refactor/inline/escape.go create mode 100644 contribs/gnopls/internal/refactor/inline/everything_test.go create mode 100644 contribs/gnopls/internal/refactor/inline/export_test.go create mode 100644 contribs/gnopls/internal/refactor/inline/falcon.go create mode 100644 contribs/gnopls/internal/refactor/inline/falcon_test.go create mode 100644 contribs/gnopls/internal/refactor/inline/inline.go create mode 100644 contribs/gnopls/internal/refactor/inline/inline_test.go create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/basic-err.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/basic-literal.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/basic-reduce.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/cgo.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/comments.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/crosspkg-selfref.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/crosspkg.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/dotimport.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/embed.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/empty-body.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/err-basic.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/err-shadow-builtin.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/err-shadow-pkg.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/err-unexported.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/exprstmt.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/import-rename.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/import-shadow.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/internal.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/issue62667.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/issue63298.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/line-directives.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/method.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/multistmt-body.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/n-ary.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/param-subst.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/revdotimport.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/std-internal.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/testdata/tailcall.txtar create mode 100644 contribs/gnopls/internal/refactor/inline/util.go create mode 100644 contribs/gnopls/internal/robustio/copyfiles.go create mode 100644 contribs/gnopls/internal/robustio/gopls_windows.go create mode 100644 contribs/gnopls/internal/robustio/robustio.go create mode 100644 contribs/gnopls/internal/robustio/robustio_darwin.go create mode 100644 contribs/gnopls/internal/robustio/robustio_flaky.go create mode 100644 contribs/gnopls/internal/robustio/robustio_other.go create mode 100644 contribs/gnopls/internal/robustio/robustio_plan9.go create mode 100644 contribs/gnopls/internal/robustio/robustio_posix.go create mode 100644 contribs/gnopls/internal/robustio/robustio_test.go create mode 100644 contribs/gnopls/internal/robustio/robustio_windows.go create mode 100644 contribs/gnopls/internal/stack/gostacks/gostacks.go create mode 100644 contribs/gnopls/internal/stack/parse.go create mode 100644 contribs/gnopls/internal/stack/process.go create mode 100644 contribs/gnopls/internal/stack/stack.go create mode 100644 contribs/gnopls/internal/stack/stack_test.go create mode 100644 contribs/gnopls/internal/stack/stacktest/stacktest.go create mode 100644 contribs/gnopls/internal/stdlib/generate.go create mode 100644 contribs/gnopls/internal/stdlib/manifest.go create mode 100644 contribs/gnopls/internal/stdlib/stdlib.go create mode 100644 contribs/gnopls/internal/testenv/exec.go create mode 100644 contribs/gnopls/internal/testenv/testenv.go create mode 100644 contribs/gnopls/internal/testenv/testenv_notunix.go create mode 100644 contribs/gnopls/internal/testenv/testenv_unix.go create mode 100644 contribs/gnopls/internal/testfiles/testdata/somefile.txt create mode 100644 contribs/gnopls/internal/testfiles/testdata/versions/go.mod.test create mode 100644 contribs/gnopls/internal/testfiles/testdata/versions/mod.go create mode 100644 contribs/gnopls/internal/testfiles/testdata/versions/post.go create mode 100644 contribs/gnopls/internal/testfiles/testdata/versions/pre.go create mode 100644 contribs/gnopls/internal/testfiles/testdata/versions/sub.test/sub.go.test create mode 100644 contribs/gnopls/internal/testfiles/testfiles.go create mode 100644 contribs/gnopls/internal/testfiles/testfiles_test.go create mode 100644 contribs/gnopls/internal/tokeninternal/tokeninternal.go create mode 100644 contribs/gnopls/internal/tokeninternal/tokeninternal_test.go create mode 100644 contribs/gnopls/internal/tool/tool.go create mode 100644 contribs/gnopls/internal/typeparams/common.go create mode 100644 contribs/gnopls/internal/typeparams/common_test.go create mode 100644 contribs/gnopls/internal/typeparams/copytermlist.go create mode 100644 contribs/gnopls/internal/typeparams/coretype.go create mode 100644 contribs/gnopls/internal/typeparams/coretype_test.go create mode 100644 contribs/gnopls/internal/typeparams/free.go create mode 100644 contribs/gnopls/internal/typeparams/free_test.go create mode 100644 contribs/gnopls/internal/typeparams/genericfeatures/features.go create mode 100644 contribs/gnopls/internal/typeparams/normalize.go create mode 100644 contribs/gnopls/internal/typeparams/normalize_test.go create mode 100644 contribs/gnopls/internal/typeparams/termlist.go create mode 100644 contribs/gnopls/internal/typeparams/typeterm.go create mode 100644 contribs/gnopls/internal/typesinternal/element.go create mode 100644 contribs/gnopls/internal/typesinternal/element_test.go create mode 100644 contribs/gnopls/internal/typesinternal/errorcode.go create mode 100644 contribs/gnopls/internal/typesinternal/errorcode_string.go create mode 100644 contribs/gnopls/internal/typesinternal/errorcode_test.go create mode 100644 contribs/gnopls/internal/typesinternal/recv.go create mode 100644 contribs/gnopls/internal/typesinternal/toonew.go create mode 100644 contribs/gnopls/internal/typesinternal/types.go create mode 100644 contribs/gnopls/internal/versions/constraint.go create mode 100644 contribs/gnopls/internal/versions/constraint_go121.go create mode 100644 contribs/gnopls/internal/versions/features.go create mode 100644 contribs/gnopls/internal/versions/gover.go create mode 100644 contribs/gnopls/internal/versions/toolchain.go create mode 100644 contribs/gnopls/internal/versions/toolchain_go119.go create mode 100644 contribs/gnopls/internal/versions/toolchain_go120.go create mode 100644 contribs/gnopls/internal/versions/toolchain_go121.go create mode 100644 contribs/gnopls/internal/versions/types.go create mode 100644 contribs/gnopls/internal/versions/types_go121.go create mode 100644 contribs/gnopls/internal/versions/types_go122.go create mode 100644 contribs/gnopls/internal/versions/types_test.go create mode 100644 contribs/gnopls/internal/versions/versions.go create mode 100644 contribs/gnopls/internal/versions/versions_test.go create mode 100644 contribs/gnopls/internal/xcontext/xcontext.go diff --git a/contribs/gnopls/README.md b/contribs/gnopls/README.md index 6602e0c27a7..05ce70b9239 100644 --- a/contribs/gnopls/README.md +++ b/contribs/gnopls/README.md @@ -1,6 +1,6 @@ # `gopls`, the Go language server -[![PkgGoDev](https://pkg.go.dev/badge/golang.org/x/tools/gopls)](https://pkg.go.dev/golang.org/x/tools/gopls) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/gnolang/gno/contribs/gnopls)](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls) `gopls` (pronounced "Go please") is the official Go [language server] developed by the Go team. @@ -43,7 +43,7 @@ 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 +go install github.com/gnolang/gno/contribs/gnopls@latest ``` Learn more in the diff --git a/contribs/gnopls/doc/advanced.md b/contribs/gnopls/doc/advanced.md index 4c5e6015fd7..9274609af3a 100644 --- a/contribs/gnopls/doc/advanced.md +++ b/contribs/gnopls/doc/advanced.md @@ -9,7 +9,7 @@ To get a specific version of `gopls` (for example, to test a prerelease version), run: ```sh -$ go install golang.org/x/tools/gopls@vX.Y.Z +$ go install github.com/gnolang/gno/contribs/gnopls@vX.Y.Z ``` Where `vX.Y.Z` is the desired version. @@ -25,9 +25,9 @@ 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 get -d github.com/gnolang/gno/contribs/gnopls@master golang.org/x/tools@master -go install golang.org/x/tools/gopls +go install github.com/gnolang/gno/contribs/gnopls ``` ## Working on the Go source distribution diff --git a/contribs/gnopls/doc/analyzers.md b/contribs/gnopls/doc/analyzers.md index f78f1bdf732..b6d10d48438 100644 --- a/contribs/gnopls/doc/analyzers.md +++ b/contribs/gnopls/doc/analyzers.md @@ -208,7 +208,7 @@ for documenting and signaling deprecated identifiers. Default: on. -Package documentation: [deprecated](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated) +Package documentation: [deprecated](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/deprecated) <a id='directive'></a> ## `directive`: check Go toolchain directives such as //go:debug @@ -245,7 +245,7 @@ declaration of a single variable. Default: on. -Package documentation: [embed](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective) +Package documentation: [embed](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/embeddirective) <a id='errorsas'></a> ## `errorsas`: report passing non-pointer or non-error values to errors.As @@ -279,7 +279,7 @@ This functionality is similar to https://github.com/sqs/goreturns. Default: on. -Package documentation: [fillreturns](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns) +Package documentation: [fillreturns](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillreturns) <a id='framepointer'></a> ## `framepointer`: report assembly that clobbers the frame pointer before saving it @@ -349,7 +349,7 @@ inferred from function arguments, or from other type arguments: Default: on. -Package documentation: [infertypeargs](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs) +Package documentation: [infertypeargs](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/infertypeargs) <a id='loopclosure'></a> ## `loopclosure`: check references to loop variables from within nested functions @@ -531,7 +531,7 @@ will turn into Default: on. -Package documentation: [nonewvars](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars) +Package documentation: [nonewvars](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/nonewvars) <a id='noresultvalues'></a> ## `noresultvalues`: suggested fixes for unexpected return values @@ -549,7 +549,7 @@ will turn into Default: on. -Package documentation: [noresultvalues](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues) +Package documentation: [noresultvalues](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/noresultvalues) <a id='printf'></a> ## `printf`: check consistency of Printf format strings and arguments @@ -640,7 +640,7 @@ This analyzer ignores generated code. Default: on. -Package documentation: [simplifycompositelit](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit) +Package documentation: [simplifycompositelit](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifycompositelit) <a id='simplifyrange'></a> ## `simplifyrange`: check for range statement simplifications @@ -668,7 +668,7 @@ This analyzer ignores generated code. Default: on. -Package documentation: [simplifyrange](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange) +Package documentation: [simplifyrange](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyrange) <a id='simplifyslice'></a> ## `simplifyslice`: check for slice simplifications @@ -688,7 +688,7 @@ This analyzer ignores generated code. Default: on. -Package documentation: [simplifyslice](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice) +Package documentation: [simplifyslice](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyslice) <a id='slog'></a> ## `slog`: check for invalid structured logging calls @@ -830,7 +830,7 @@ logic in gopls's golang.stub function.) Default: on. -Package documentation: [stubmethods](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods) +Package documentation: [stubmethods](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/stubmethods) <a id='testinggoroutine'></a> ## `testinggoroutine`: report calls to (*testing.T).Fatal from goroutines started by a test @@ -896,7 +896,7 @@ or a new function declaration, such as: Default: on. -Package documentation: [undeclaredname](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/undeclaredname) +Package documentation: [undeclaredname](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/undeclaredname) <a id='unmarshal'></a> ## `unmarshal`: report passing non-pointer or non-interface values to unmarshal @@ -964,7 +964,7 @@ https://github.com/golang/tools/releases/tag/gopls%2Fv0.14. Default: on. -Package documentation: [unusedparams](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams) +Package documentation: [unusedparams](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedparams) <a id='unusedresult'></a> ## `unusedresult`: check for unused results of calls to some functions @@ -989,7 +989,7 @@ Package documentation: [unusedresult](https://pkg.go.dev/golang.org/x/tools/go/a Default: off. Enable by setting `"analyses": {"unusedvariable": true}`. -Package documentation: [unusedvariable](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedvariable) +Package documentation: [unusedvariable](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedvariable) <a id='unusedwrite'></a> ## `unusedwrite`: checks for unused writes @@ -1030,6 +1030,6 @@ Package documentation: [unusedwrite](https://pkg.go.dev/golang.org/x/tools/go/an Default: off. Enable by setting `"analyses": {"useany": true}`. -Package documentation: [useany](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/useany) +Package documentation: [useany](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/useany) <!-- END Analyzers: DO NOT MANUALLY EDIT THIS SECTION --> diff --git a/contribs/gnopls/doc/assets/go.mod b/contribs/gnopls/doc/assets/go.mod index 73f49695726..dd67b7042f5 100644 --- a/contribs/gnopls/doc/assets/go.mod +++ b/contribs/gnopls/doc/assets/go.mod @@ -1,7 +1,7 @@ // This module contains no Go code, but serves to carve out a hole in // its parent module to avoid bloating it with large image files that -// would otherwise be dowloaded by "go install golang.org/x/tools/gopls@latest". +// would otherwise be dowloaded by "go install github.com/gnolang/gno/contribs/gnopls@latest". -module golang.org/x/tools/gopls/doc/assets +module github.com/gnolang/gno/contribs/gnopls/doc/assets go 1.19 diff --git a/contribs/gnopls/doc/contributing.md b/contribs/gnopls/doc/contributing.md index 007c5793073..735a0780877 100644 --- a/contribs/gnopls/doc/contributing.md +++ b/contribs/gnopls/doc/contributing.md @@ -18,7 +18,7 @@ claiming it. ## Getting started -Most of the `gopls` logic is in the `golang.org/x/tools/gopls/internal` +Most of the `gopls` logic is in the `github.com/gnolang/gno/contribs/gnopls/internal` directory. See [design/implementation.md] for an overview of the code organization. ## Build @@ -35,8 +35,8 @@ your `gopls` version looks like this: ```bash $ gopls version -golang.org/x/tools/gopls master - golang.org/x/tools/gopls@(devel) +github.com/gnolang/gno/contribs/gnopls master + github.com/gnolang/gno/contribs/gnopls@(devel) ``` ## Getting help @@ -137,7 +137,7 @@ need help. 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 +both the `golang.org/x/tools` and `github.com/gnolang/gno/contribs/gnopls` modules, as described above. Furthermore, an additional "gopls-CI" pass will be run by _Kokoro_, which is a diff --git a/contribs/gnopls/doc/design/implementation.md b/contribs/gnopls/doc/design/implementation.md index 12d655c0b5e..706d86428e9 100644 --- a/contribs/gnopls/doc/design/implementation.md +++ b/contribs/gnopls/doc/design/implementation.md @@ -145,28 +145,28 @@ 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 +[cache]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/cache +[cmd]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/cmd +[command]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/protocol/command +[debug]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/debug +[file]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/file +[filecache]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@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 +[gopls]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@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 +[lsprpc]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@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 +[metadata]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/cache/metadata +[methodsets]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/cache/methodsets +[mod]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/mod +[parsego]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/cache/parsego +[protocol]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/protocol +[server]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/server +[settings]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/settings +[golang]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/golang +[template]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/template +[typerefs]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/cache/typerefs +[work]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@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 +[xrefs]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls@master/internal/cache/xrefs diff --git a/contribs/gnopls/doc/design/integrating.md b/contribs/gnopls/doc/design/integrating.md index 2d8e01a76c0..b0b0dbfd487 100644 --- a/contribs/gnopls/doc/design/integrating.md +++ b/contribs/gnopls/doc/design/integrating.md @@ -59,9 +59,9 @@ For instance, files that are needed to do correct type checking are modified by 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 +[InitializeResult]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/protocol#InitializeResult +[ServerCapabilities]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/protocol#ServerCapabilities +[`github.com/gnolang/gno/contribs/gnopls/internal/protocol`]: https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/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 diff --git a/contribs/gnopls/doc/features/diagnostics.md b/contribs/gnopls/doc/features/diagnostics.md index f58a6465d1d..c14b5b6df97 100644 --- a/contribs/gnopls/doc/features/diagnostics.md +++ b/contribs/gnopls/doc/features/diagnostics.md @@ -131,7 +131,7 @@ dorky details and deletia: simple problems. For example, when a return statement has too few operands, the - [fillreturns](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns) analyzer can heuristically fill in the missing ones + [fillreturns](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillreturns) analyzer can heuristically fill in the missing ones with suitable values. The actual diagnostics are produced by the type checker, diff --git a/contribs/gnopls/doc/features/transformation.md b/contribs/gnopls/doc/features/transformation.md index bf5df29c01b..d13906628c4 100644 --- a/contribs/gnopls/doc/features/transformation.md +++ b/contribs/gnopls/doc/features/transformation.md @@ -496,7 +496,7 @@ Here are some of the technical challenges involved in sound inlining: 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 +documentation for [github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/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. diff --git a/contribs/gnopls/doc/generate/generate.go b/contribs/gnopls/doc/generate/generate.go index 994933a3681..3ded658244b 100644 --- a/contribs/gnopls/doc/generate/generate.go +++ b/contribs/gnopls/doc/generate/generate.go @@ -38,12 +38,12 @@ import ( "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/settings" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/doc" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/mod" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) func main() { @@ -62,7 +62,7 @@ func doMain(write bool) (bool, error) { return false, err } - goplsDir, err := pkgDir("golang.org/x/tools/gopls") + goplsDir, err := pkgDir("github.com/gnolang/gno/contribs/gnopls") if err != nil { return false, err } @@ -127,7 +127,7 @@ func loadAPI() (*doc.API, error) { &packages.Config{ Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedDeps, }, - "golang.org/x/tools/gopls/internal/settings", + "github.com/gnolang/gno/contribs/gnopls/internal/settings", ) if err != nil { return nil, err diff --git a/contribs/gnopls/doc/generate/generate_test.go b/contribs/gnopls/doc/generate/generate_test.go index da3c6792d8f..a6d79e3adce 100644 --- a/contribs/gnopls/doc/generate/generate_test.go +++ b/contribs/gnopls/doc/generate/generate_test.go @@ -7,7 +7,7 @@ package main import ( "testing" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestGenerated(t *testing.T) { diff --git a/contribs/gnopls/doc/refactor-inline.md b/contribs/gnopls/doc/refactor-inline.md index cdddeb29e6e..21f9d6decf9 100644 --- a/contribs/gnopls/doc/refactor-inline.md +++ b/contribs/gnopls/doc/refactor-inline.md @@ -153,7 +153,7 @@ challenges involved in sound inlining: 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 +documentation for [github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/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. diff --git a/contribs/gnopls/doc/release/v0.16.0.md b/contribs/gnopls/doc/release/v0.16.0.md index 1bcb5ec3d06..c69cef16bb6 100644 --- a/contribs/gnopls/doc/release/v0.16.0.md +++ b/contribs/gnopls/doc/release/v0.16.0.md @@ -6,7 +6,7 @@ https://raw.githubusercontent.com/golang/v0.16.0/gopls/doc/ etc --> ``` -go install golang.org/x/tools/gopls@v0.16.0-pre.2 +go install github.com/gnolang/gno/contribs/gnopls@v0.16.0-pre.2 ``` This release includes several features and bug fixes, and is the first diff --git a/contribs/gnopls/doc/workspace.md b/contribs/gnopls/doc/workspace.md index 766175dd3b1..8dc017d21a2 100644 --- a/contribs/gnopls/doc/workspace.md +++ b/contribs/gnopls/doc/workspace.md @@ -101,7 +101,7 @@ 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` +You can work on both `github.com/gnolang/gno/contribs/gnopls` and `golang.org/x/mod` simultaneously by creating a `go.work` file: ```sh diff --git a/contribs/gnopls/go.mod b/contribs/gnopls/go.mod index 759670af3cd..54db08d4f35 100644 --- a/contribs/gnopls/go.mod +++ b/contribs/gnopls/go.mod @@ -1,4 +1,4 @@ -module golang.org/x/tools/gopls +module github.com/gnolang/gno/contribs/gnopls // go 1.23.1 fixes some bugs in go/types Alias support. // (golang/go#68894 and golang/go#68905). @@ -9,9 +9,10 @@ require ( github.com/jba/templatecheck v0.7.0 golang.org/x/mod v0.21.0 golang.org/x/sync v0.8.0 + golang.org/x/sys v0.25.0 golang.org/x/telemetry v0.0.0-20240829154258-f29ab539cc98 golang.org/x/text v0.18.0 - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d + golang.org/x/tools v0.25.0 golang.org/x/vuln v1.0.4 gopkg.in/yaml.v3 v3.0.1 honnef.co/go/tools v0.4.7 @@ -23,9 +24,6 @@ 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.25.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) - -replace golang.org/x/tools => ../ diff --git a/contribs/gnopls/go.sum b/contribs/gnopls/go.sum index 2819e487d71..2d9a767b85f 100644 --- a/contribs/gnopls/go.sum +++ b/contribs/gnopls/go.sum @@ -14,38 +14,22 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= 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/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= 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= diff --git a/contribs/gnopls/internal/aliases/aliases.go b/contribs/gnopls/internal/aliases/aliases.go new file mode 100644 index 00000000000..f7798e3354e --- /dev/null +++ b/contribs/gnopls/internal/aliases/aliases.go @@ -0,0 +1,38 @@ +// 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 aliases + +import ( + "go/token" + "go/types" +) + +// Package aliases defines backward compatible shims +// for the types.Alias type representation added in 1.22. +// This defines placeholders for x/tools until 1.26. + +// NewAlias creates a new TypeName in Package pkg that +// is an alias for the type rhs. +// +// The enabled parameter determines whether the resulting [TypeName]'s +// type is an [types.Alias]. Its value must be the result of a call to +// [Enabled], which computes the effective value of +// GODEBUG=gotypesalias=... by invoking the type checker. The Enabled +// function is expensive and should be called once per task (e.g. +// package import), not once per call to NewAlias. +// +// Precondition: enabled || len(tparams)==0. +// If materialized aliases are disabled, there must not be any type parameters. +func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName { + if enabled { + tname := types.NewTypeName(pos, pkg, name, nil) + newAlias(tname, rhs, tparams) + return tname + } + if len(tparams) > 0 { + panic("cannot create an alias with type parameters when gotypesalias is not enabled") + } + return types.NewTypeName(pos, pkg, name, rhs) +} diff --git a/contribs/gnopls/internal/aliases/aliases_go121.go b/contribs/gnopls/internal/aliases/aliases_go121.go new file mode 100644 index 00000000000..a775fcc4bed --- /dev/null +++ b/contribs/gnopls/internal/aliases/aliases_go121.go @@ -0,0 +1,37 @@ +// 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. + +//go:build !go1.22 +// +build !go1.22 + +package aliases + +import ( + "go/types" +) + +// Alias is a placeholder for a go/types.Alias for <=1.21. +// It will never be created by go/types. +type Alias struct{} + +func (*Alias) String() string { panic("unreachable") } +func (*Alias) Underlying() types.Type { panic("unreachable") } +func (*Alias) Obj() *types.TypeName { panic("unreachable") } +func Rhs(alias *Alias) types.Type { panic("unreachable") } +func TypeParams(alias *Alias) *types.TypeParamList { panic("unreachable") } +func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { panic("unreachable") } +func TypeArgs(alias *Alias) *types.TypeList { panic("unreachable") } +func Origin(alias *Alias) *Alias { panic("unreachable") } + +// Unalias returns the type t for go <=1.21. +func Unalias(t types.Type) types.Type { return t } + +func newAlias(name *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias { + panic("unreachable") +} + +// Enabled reports whether [NewAlias] should create [types.Alias] types. +// +// Before go1.22, this function always returns false. +func Enabled() bool { return false } diff --git a/contribs/gnopls/internal/aliases/aliases_go122.go b/contribs/gnopls/internal/aliases/aliases_go122.go new file mode 100644 index 00000000000..31c159e42e6 --- /dev/null +++ b/contribs/gnopls/internal/aliases/aliases_go122.go @@ -0,0 +1,98 @@ +// 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. + +//go:build go1.22 +// +build go1.22 + +package aliases + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" +) + +// Alias is an alias of types.Alias. +type Alias = types.Alias + +// Rhs returns the type on the right-hand side of the alias declaration. +func Rhs(alias *Alias) types.Type { + if alias, ok := any(alias).(interface{ Rhs() types.Type }); ok { + return alias.Rhs() // go1.23+ + } + + // go1.22's Alias didn't have the Rhs method, + // so Unalias is the best we can do. + return Unalias(alias) +} + +// TypeParams returns the type parameter list of the alias. +func TypeParams(alias *Alias) *types.TypeParamList { + if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok { + return alias.TypeParams() // go1.23+ + } + return nil +} + +// SetTypeParams sets the type parameters of the alias type. +func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { + if alias, ok := any(alias).(interface { + SetTypeParams(tparams []*types.TypeParam) + }); ok { + alias.SetTypeParams(tparams) // go1.23+ + } else if len(tparams) > 0 { + panic("cannot set type parameters of an Alias type in go1.22") + } +} + +// TypeArgs returns the type arguments used to instantiate the Alias type. +func TypeArgs(alias *Alias) *types.TypeList { + if alias, ok := any(alias).(interface{ TypeArgs() *types.TypeList }); ok { + return alias.TypeArgs() // go1.23+ + } + return nil // empty (go1.22) +} + +// Origin returns the generic Alias type of which alias is an instance. +// If alias is not an instance of a generic alias, Origin returns alias. +func Origin(alias *Alias) *Alias { + if alias, ok := any(alias).(interface{ Origin() *types.Alias }); ok { + return alias.Origin() // go1.23+ + } + return alias // not an instance of a generic alias (go1.22) +} + +// Unalias is a wrapper of types.Unalias. +func Unalias(t types.Type) types.Type { return types.Unalias(t) } + +// newAlias is an internal alias around types.NewAlias. +// Direct usage is discouraged as the moment. +// Try to use NewAlias instead. +func newAlias(tname *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias { + a := types.NewAlias(tname, rhs) + SetTypeParams(a, tparams) + return a +} + +// Enabled reports whether [NewAlias] should create [types.Alias] types. +// +// This function is expensive! Call it sparingly. +func Enabled() bool { + // The only reliable way to compute the answer is to invoke go/types. + // We don't parse the GODEBUG environment variable, because + // (a) it's tricky to do so in a manner that is consistent + // with the godebug package; in particular, a simple + // substring check is not good enough. The value is a + // rightmost-wins list of options. But more importantly: + // (b) it is impossible to detect changes to the effective + // setting caused by os.Setenv("GODEBUG"), as happens in + // many tests. Therefore any attempt to cache the result + // is just incorrect. + fset := token.NewFileSet() + f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0) + pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil) + _, enabled := pkg.Scope().Lookup("A").Type().(*types.Alias) + return enabled +} diff --git a/contribs/gnopls/internal/aliases/aliases_test.go b/contribs/gnopls/internal/aliases/aliases_test.go new file mode 100644 index 00000000000..89ca49f89de --- /dev/null +++ b/contribs/gnopls/internal/aliases/aliases_test.go @@ -0,0 +1,156 @@ +// 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 aliases_test + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +// Assert that Obj exists on Alias. +var _ func(*aliases.Alias) *types.TypeName = (*aliases.Alias).Obj + +// TestNewAlias tests that alias.NewAlias creates an alias of a type +// whose underlying and Unaliased type is *Named. +// When gotypesalias=1 (or unset) and GoVersion >= 1.22, the type will +// be an *aliases.Alias. +func TestNewAlias(t *testing.T) { + const source = ` + package p + + type Named int + ` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hello.go", source, 0) + if err != nil { + t.Fatal(err) + } + + var conf types.Config + pkg, err := conf.Check("p", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + expr := `*Named` + tv, err := types.Eval(fset, pkg, 0, expr) + if err != nil { + t.Fatalf("Eval(%s) failed: %v", expr, err) + } + + for _, godebug := range []string{ + // The default gotypesalias value follows the x/tools/go.mod version + // The go.mod is at 1.19 so the default is gotypesalias=0. + // "", // Use the default GODEBUG value. + "gotypesalias=0", + "gotypesalias=1", + } { + t.Run(godebug, func(t *testing.T) { + t.Setenv("GODEBUG", godebug) + + enabled := aliases.Enabled() + + A := aliases.NewAlias(enabled, token.NoPos, pkg, "A", tv.Type, nil) + if got, want := A.Name(), "A"; got != want { + t.Errorf("Expected A.Name()==%q. got %q", want, got) + } + + if got, want := A.Type().Underlying(), tv.Type; got != want { + t.Errorf("Expected A.Type().Underlying()==%q. got %q", want, got) + } + if got, want := aliases.Unalias(A.Type()), tv.Type; got != want { + t.Errorf("Expected Unalias(A)==%q. got %q", want, got) + } + + if testenv.Go1Point() >= 22 && godebug != "gotypesalias=0" { + if _, ok := A.Type().(*aliases.Alias); !ok { + t.Errorf("Expected A.Type() to be a types.Alias(). got %q", A.Type()) + } + } + }) + } +} + +// TestNewAlias tests that alias.NewAlias can create a parameterized alias +// A[T] of a type whose underlying and Unaliased type is *T. The test then +// instantiates A[Named] and checks that the underlying and Unaliased type +// of A[Named] is *Named. +// +// Requires gotypesalias GODEBUG and aliastypeparams GOEXPERIMENT. +func TestNewParameterizedAlias(t *testing.T) { + testenv.NeedsGoExperiment(t, "aliastypeparams") + + t.Setenv("GODEBUG", "gotypesalias=1") // needed until gotypesalias is removed (1.27). + enabled := aliases.Enabled() + if !enabled { + t.Fatal("Need materialized aliases enabled") + } + + const source = ` + package p + + type Named int + ` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hello.go", source, 0) + if err != nil { + t.Fatal(err) + } + + var conf types.Config + pkg, err := conf.Check("p", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + // type A[T ~int] = *T + tparam := types.NewTypeParam( + types.NewTypeName(token.NoPos, pkg, "T", nil), + types.NewUnion([]*types.Term{types.NewTerm(true, types.Typ[types.Int])}), + ) + ptrT := types.NewPointer(tparam) + A := aliases.NewAlias(enabled, token.NoPos, pkg, "A", ptrT, []*types.TypeParam{tparam}) + if got, want := A.Name(), "A"; got != want { + t.Errorf("NewAlias: got %q, want %q", got, want) + } + + if got, want := A.Type().Underlying(), ptrT; !types.Identical(got, want) { + t.Errorf("A.Type().Underlying (%q) is not identical to %q", got, want) + } + if got, want := aliases.Unalias(A.Type()), ptrT; !types.Identical(got, want) { + t.Errorf("Unalias(A)==%q is not identical to %q", got, want) + } + + if _, ok := A.Type().(*aliases.Alias); !ok { + t.Errorf("Expected A.Type() to be a types.Alias(). got %q", A.Type()) + } + + pkg.Scope().Insert(A) // Add A to pkg so it is available to types.Eval. + + named, ok := pkg.Scope().Lookup("Named").(*types.TypeName) + if !ok { + t.Fatalf("Failed to Lookup(%q) in package %s", "Named", pkg) + } + ptrNamed := types.NewPointer(named.Type()) + + const expr = `A[Named]` + tv, err := types.Eval(fset, pkg, 0, expr) + if err != nil { + t.Fatalf("Eval(%s) failed: %v", expr, err) + } + + if got, want := tv.Type.Underlying(), ptrNamed; !types.Identical(got, want) { + t.Errorf("A[Named].Type().Underlying (%q) is not identical to %q", got, want) + } + if got, want := aliases.Unalias(tv.Type), ptrNamed; !types.Identical(got, want) { + t.Errorf("Unalias(A[Named])==%q is not identical to %q", got, want) + } +} diff --git a/contribs/gnopls/internal/analysis/deprecated/deprecated.go b/contribs/gnopls/internal/analysis/deprecated/deprecated.go index 1a8c4c56766..7378051f53b 100644 --- a/contribs/gnopls/internal/analysis/deprecated/deprecated.go +++ b/contribs/gnopls/internal/analysis/deprecated/deprecated.go @@ -18,7 +18,7 @@ import ( "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" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -31,7 +31,7 @@ var Analyzer = &analysis.Analyzer{ Run: checkDeprecated, FactTypes: []analysis.Fact{(*deprecationFact)(nil)}, RunDespiteErrors: true, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/deprecated", } // checkDeprecated is a simplified copy of staticcheck.CheckDeprecated. diff --git a/contribs/gnopls/internal/analysis/embeddirective/embeddirective.go b/contribs/gnopls/internal/analysis/embeddirective/embeddirective.go index 1b0b89711c2..8f0211a3b12 100644 --- a/contribs/gnopls/internal/analysis/embeddirective/embeddirective.go +++ b/contribs/gnopls/internal/analysis/embeddirective/embeddirective.go @@ -12,8 +12,8 @@ import ( "strings" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/analysisinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -24,7 +24,7 @@ var Analyzer = &analysis.Analyzer{ Doc: analysisinternal.MustExtractDoc(doc, "embed"), Run: run, RunDespiteErrors: true, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/embeddirective", } const FixCategory = "addembedimport" // recognized by gopls ApplyFix diff --git a/contribs/gnopls/internal/analysis/fillreturns/fillreturns.go b/contribs/gnopls/internal/analysis/fillreturns/fillreturns.go index 5e18c1c6642..4d32c58ae2f 100644 --- a/contribs/gnopls/internal/analysis/fillreturns/fillreturns.go +++ b/contribs/gnopls/internal/analysis/fillreturns/fillreturns.go @@ -16,8 +16,8 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/fuzzy" - "golang.org/x/tools/internal/analysisinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -28,7 +28,7 @@ var Analyzer = &analysis.Analyzer{ Doc: analysisinternal.MustExtractDoc(doc, "fillreturns"), Run: run, RunDespiteErrors: true, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillreturns", } func run(pass *analysis.Pass) (interface{}, error) { diff --git a/contribs/gnopls/internal/analysis/fillreturns/fillreturns_test.go b/contribs/gnopls/internal/analysis/fillreturns/fillreturns_test.go index 45c7846a7dc..0213d4f61ba 100644 --- a/contribs/gnopls/internal/analysis/fillreturns/fillreturns_test.go +++ b/contribs/gnopls/internal/analysis/fillreturns/fillreturns_test.go @@ -8,8 +8,8 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/fillreturns" - "golang.org/x/tools/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillreturns" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/fillstruct/fillstruct.go b/contribs/gnopls/internal/analysis/fillstruct/fillstruct.go index b42b8232f22..c0573b4e0d6 100644 --- a/contribs/gnopls/internal/analysis/fillstruct/fillstruct.go +++ b/contribs/gnopls/internal/analysis/fillstruct/fillstruct.go @@ -24,12 +24,12 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/fuzzy" - "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/typeparams" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // Diagnose computes diagnostics for fillable struct literals overlapping with diff --git a/contribs/gnopls/internal/analysis/fillstruct/fillstruct_test.go b/contribs/gnopls/internal/analysis/fillstruct/fillstruct_test.go index e0ad83de83b..8fa2849ccd3 100644 --- a/contribs/gnopls/internal/analysis/fillstruct/fillstruct_test.go +++ b/contribs/gnopls/internal/analysis/fillstruct/fillstruct_test.go @@ -10,7 +10,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/fillstruct" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillstruct" ) // analyzer allows us to test the fillstruct code action using the analysistest @@ -26,7 +26,7 @@ var analyzer = &analysis.Analyzer{ } return nil, nil }, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillstruct", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillstruct", RunDespiteErrors: true, } diff --git a/contribs/gnopls/internal/analysis/fillswitch/fillswitch.go b/contribs/gnopls/internal/analysis/fillswitch/fillswitch.go index 7b1a7e8cbe5..3baece66046 100644 --- a/contribs/gnopls/internal/analysis/fillswitch/fillswitch.go +++ b/contribs/gnopls/internal/analysis/fillswitch/fillswitch.go @@ -12,7 +12,7 @@ import ( "go/types" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // Diagnose computes diagnostics for switch statements with missing cases diff --git a/contribs/gnopls/internal/analysis/fillswitch/fillswitch_test.go b/contribs/gnopls/internal/analysis/fillswitch/fillswitch_test.go index bf70aa39648..3b9991811cc 100644 --- a/contribs/gnopls/internal/analysis/fillswitch/fillswitch_test.go +++ b/contribs/gnopls/internal/analysis/fillswitch/fillswitch_test.go @@ -10,7 +10,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/fillswitch" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillswitch" ) // analyzer allows us to test the fillswitch code action using the analysistest @@ -26,7 +26,7 @@ var analyzer = &analysis.Analyzer{ } return nil, nil }, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillswitch", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillswitch", RunDespiteErrors: true, } diff --git a/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs.go b/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs.go index 9a514ad620c..5330d8ef4da 100644 --- a/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs.go +++ b/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs.go @@ -12,8 +12,8 @@ import ( "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" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" ) const Doc = `check for unnecessary type arguments in call expressions @@ -33,7 +33,7 @@ var Analyzer = &analysis.Analyzer{ Doc: Doc, Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/infertypeargs", } func run(pass *analysis.Pass) (any, error) { diff --git a/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs_test.go b/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs_test.go index 25c88e84f29..b81fa120e3e 100644 --- a/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs_test.go +++ b/contribs/gnopls/internal/analysis/infertypeargs/infertypeargs_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/infertypeargs" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/infertypeargs" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/nonewvars/nonewvars.go b/contribs/gnopls/internal/analysis/nonewvars/nonewvars.go index b9c9b4d6f48..a98d51a7afc 100644 --- a/contribs/gnopls/internal/analysis/nonewvars/nonewvars.go +++ b/contribs/gnopls/internal/analysis/nonewvars/nonewvars.go @@ -16,7 +16,7 @@ import ( "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" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -28,7 +28,7 @@ var Analyzer = &analysis.Analyzer{ Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, RunDespiteErrors: true, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/nonewvars", } func run(pass *analysis.Pass) (interface{}, error) { diff --git a/contribs/gnopls/internal/analysis/nonewvars/nonewvars_test.go b/contribs/gnopls/internal/analysis/nonewvars/nonewvars_test.go index 49e19db2f0c..dbab41b1539 100644 --- a/contribs/gnopls/internal/analysis/nonewvars/nonewvars_test.go +++ b/contribs/gnopls/internal/analysis/nonewvars/nonewvars_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/nonewvars" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/nonewvars" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues.go b/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues.go index a5cd424a762..afd464d49b9 100644 --- a/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues.go +++ b/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues.go @@ -15,7 +15,7 @@ import ( "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" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -27,7 +27,7 @@ var Analyzer = &analysis.Analyzer{ Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, RunDespiteErrors: true, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/noresultvalues", } func run(pass *analysis.Pass) (interface{}, error) { diff --git a/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues_test.go b/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues_test.go index e9f1a36ab6f..9fd4f5e9b1f 100644 --- a/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues_test.go +++ b/contribs/gnopls/internal/analysis/noresultvalues/noresultvalues_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/noresultvalues" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/noresultvalues" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit.go b/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit.go index 6511477d254..d67e28ccde7 100644 --- a/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit.go +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit.go @@ -19,7 +19,7 @@ import ( "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" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -30,7 +30,7 @@ var Analyzer = &analysis.Analyzer{ 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", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifycompositelit", } func run(pass *analysis.Pass) (interface{}, error) { diff --git a/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit_test.go b/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit_test.go index 4445a0cbb2f..c0072708540 100644 --- a/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit_test.go +++ b/contribs/gnopls/internal/analysis/simplifycompositelit/simplifycompositelit_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/simplifycompositelit" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifycompositelit" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange.go b/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange.go index 4071d1b6e8a..0efe0860520 100644 --- a/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange.go +++ b/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange.go @@ -14,7 +14,7 @@ import ( "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" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -25,7 +25,7 @@ var Analyzer = &analysis.Analyzer{ Doc: analysisinternal.MustExtractDoc(doc, "simplifyrange"), Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyrange", } func run(pass *analysis.Pass) (interface{}, error) { diff --git a/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange_test.go b/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange_test.go index 50a600e03bf..e73e5c6200a 100644 --- a/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange_test.go +++ b/contribs/gnopls/internal/analysis/simplifyrange/simplifyrange_test.go @@ -10,7 +10,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/simplifyrange" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyrange" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice.go b/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice.go index dc99580b07e..cc61d66747c 100644 --- a/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice.go +++ b/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice.go @@ -15,7 +15,7 @@ import ( "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" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -26,7 +26,7 @@ var Analyzer = &analysis.Analyzer{ Doc: analysisinternal.MustExtractDoc(doc, "simplifyslice"), Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyslice", } // Note: We could also simplify slice expressions of the form s[0:b] to s[:b] diff --git a/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice_test.go b/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice_test.go index 7fc5f9af451..f2cbd040836 100644 --- a/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice_test.go +++ b/contribs/gnopls/internal/analysis/simplifyslice/simplifyslice_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/simplifyslice" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyslice" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/stubmethods/stubmethods.go b/contribs/gnopls/internal/analysis/stubmethods/stubmethods.go index f4c30aadd7d..cb12162b0e3 100644 --- a/contribs/gnopls/internal/analysis/stubmethods/stubmethods.go +++ b/contribs/gnopls/internal/analysis/stubmethods/stubmethods.go @@ -16,10 +16,10 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/util/typesutil" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/analysisinternal" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/util/typesutil" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) //go:embed doc.go @@ -30,7 +30,7 @@ var Analyzer = &analysis.Analyzer{ Doc: analysisinternal.MustExtractDoc(doc, "stubmethods"), Run: run, RunDespiteErrors: true, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/stubmethods", } // TODO(rfindley): remove this thin wrapper around the stubmethods refactoring, diff --git a/contribs/gnopls/internal/analysis/stubmethods/stubmethods_test.go b/contribs/gnopls/internal/analysis/stubmethods/stubmethods_test.go index 9c744c9b7a3..260693a6f7c 100644 --- a/contribs/gnopls/internal/analysis/stubmethods/stubmethods_test.go +++ b/contribs/gnopls/internal/analysis/stubmethods/stubmethods_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/stubmethods" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/stubmethods" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/undeclaredname/undeclared.go b/contribs/gnopls/internal/analysis/undeclaredname/undeclared.go index afd9b652b97..9a0829b1f41 100644 --- a/contribs/gnopls/internal/analysis/undeclaredname/undeclared.go +++ b/contribs/gnopls/internal/analysis/undeclaredname/undeclared.go @@ -17,9 +17,9 @@ import ( "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" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -31,7 +31,7 @@ var Analyzer = &analysis.Analyzer{ Requires: []*analysis.Analyzer{}, Run: run, RunDespiteErrors: true, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/undeclaredname", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/undeclaredname", } // The prefix for this error message changed in Go 1.20. diff --git a/contribs/gnopls/internal/analysis/undeclaredname/undeclared_test.go b/contribs/gnopls/internal/analysis/undeclaredname/undeclared_test.go index ea3d724515b..970cf2bf0b4 100644 --- a/contribs/gnopls/internal/analysis/undeclaredname/undeclared_test.go +++ b/contribs/gnopls/internal/analysis/undeclaredname/undeclared_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/undeclaredname" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/undeclaredname" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/unusedparams/cmd/main.go b/contribs/gnopls/internal/analysis/unusedparams/cmd/main.go index 2f35fb06083..133981e95df 100644 --- a/contribs/gnopls/internal/analysis/unusedparams/cmd/main.go +++ b/contribs/gnopls/internal/analysis/unusedparams/cmd/main.go @@ -7,7 +7,7 @@ package main import ( "golang.org/x/tools/go/analysis/singlechecker" - "golang.org/x/tools/gopls/internal/analysis/unusedparams" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedparams" ) func main() { singlechecker.Main(unusedparams.Analyzer) } diff --git a/contribs/gnopls/internal/analysis/unusedparams/unusedparams.go b/contribs/gnopls/internal/analysis/unusedparams/unusedparams.go index ca808a740d3..c8ed17ccd39 100644 --- a/contribs/gnopls/internal/analysis/unusedparams/unusedparams.go +++ b/contribs/gnopls/internal/analysis/unusedparams/unusedparams.go @@ -13,8 +13,8 @@ import ( "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/gopls/internal/util/moreslices" - "golang.org/x/tools/internal/analysisinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/util/moreslices" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) //go:embed doc.go @@ -25,7 +25,7 @@ var Analyzer = &analysis.Analyzer{ Doc: analysisinternal.MustExtractDoc(doc, "unusedparams"), Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedparams", } const FixCategory = "unusedparams" // recognized by gopls ApplyFix diff --git a/contribs/gnopls/internal/analysis/unusedparams/unusedparams_test.go b/contribs/gnopls/internal/analysis/unusedparams/unusedparams_test.go index 1e2d8851b8b..f0de9f77d08 100644 --- a/contribs/gnopls/internal/analysis/unusedparams/unusedparams_test.go +++ b/contribs/gnopls/internal/analysis/unusedparams/unusedparams_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/unusedparams" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedparams" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable.go b/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable.go index 8019cfe9eca..6e0aaa76f70 100644 --- a/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable.go +++ b/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable.go @@ -27,7 +27,7 @@ var Analyzer = &analysis.Analyzer{ Requires: []*analysis.Analyzer{}, Run: run, RunDespiteErrors: true, // an unusedvariable diagnostic is a compile error - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedvariable", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedvariable", } // The suffix for this error message changed in Go 1.20 and Go 1.23. diff --git a/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable_test.go b/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable_test.go index 5dcca007a98..8a0cefc6989 100644 --- a/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable_test.go +++ b/contribs/gnopls/internal/analysis/unusedvariable/unusedvariable_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/unusedvariable" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedvariable" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysis/useany/useany.go b/contribs/gnopls/internal/analysis/useany/useany.go index ff25e5945d3..90d109ddd0a 100644 --- a/contribs/gnopls/internal/analysis/useany/useany.go +++ b/contribs/gnopls/internal/analysis/useany/useany.go @@ -24,7 +24,7 @@ var Analyzer = &analysis.Analyzer{ Doc: Doc, Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: run, - URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/useany", + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/useany", } func run(pass *analysis.Pass) (interface{}, error) { diff --git a/contribs/gnopls/internal/analysis/useany/useany_test.go b/contribs/gnopls/internal/analysis/useany/useany_test.go index a8cb692f359..0a16c555171 100644 --- a/contribs/gnopls/internal/analysis/useany/useany_test.go +++ b/contribs/gnopls/internal/analysis/useany/useany_test.go @@ -8,7 +8,7 @@ import ( "testing" "golang.org/x/tools/go/analysis/analysistest" - "golang.org/x/tools/gopls/internal/analysis/useany" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/useany" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/analysisinternal/addimport_test.go b/contribs/gnopls/internal/analysisinternal/addimport_test.go new file mode 100644 index 00000000000..0f20532c6a0 --- /dev/null +++ b/contribs/gnopls/internal/analysisinternal/addimport_test.go @@ -0,0 +1,243 @@ +// 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 analysisinternal_test + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "runtime" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/tools/go/analysis" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" +) + +func TestAddImport(t *testing.T) { + descr := func(s string) string { + if _, _, line, ok := runtime.Caller(1); ok { + return fmt.Sprintf("L%d %s", line, s) + } + panic("runtime.Caller failed") + } + + // Each test case contains a «name pkgpath» + // section to be replaced with a reference + // to a valid import of pkgpath, + // ideally of the specified name. + for _, test := range []struct { + descr, src, want string + }{ + { + descr: descr("simple add import"), + src: `package a +func _() { + «fmt fmt» +}`, + want: `package a +import "fmt" + +func _() { + fmt +}`, + }, + { + descr: descr("existing import"), + src: `package a + +import "fmt" + +func _(fmt.Stringer) { + «fmt fmt» +}`, + want: `package a + +import "fmt" + +func _(fmt.Stringer) { + fmt +}`, + }, + { + descr: descr("existing blank import"), + src: `package a + +import _ "fmt" + +func _() { + «fmt fmt» +}`, + want: `package a + +import "fmt" + +import _ "fmt" + +func _() { + fmt +}`, + }, + { + descr: descr("existing renaming import"), + src: `package a + +import fmtpkg "fmt" + +var fmt int + +func _(fmtpkg.Stringer) { + «fmt fmt» +}`, + want: `package a + +import fmtpkg "fmt" + +var fmt int + +func _(fmtpkg.Stringer) { + fmtpkg +}`, + }, + { + descr: descr("existing import is shadowed"), + src: `package a + +import "fmt" + +var _ fmt.Stringer + +func _(fmt int) { + «fmt fmt» +}`, + want: `package a + +import fmt0 "fmt" + +import "fmt" + +var _ fmt.Stringer + +func _(fmt int) { + fmt0 +}`, + }, + { + descr: descr("preferred name is shadowed"), + src: `package a + +import "fmt" + +func _(fmt fmt.Stringer) { + «fmt fmt» +}`, + want: `package a + +import fmt0 "fmt" + +import "fmt" + +func _(fmt fmt.Stringer) { + fmt0 +}`, + }, + { + descr: descr("import inserted before doc comments"), + src: `package a + +// hello +import () + +// world +func _() { + «fmt fmt» +}`, + want: `package a + +import "fmt" + +// hello +import () + +// world +func _() { + fmt +}`, + }, + { + descr: descr("arbitrary preferred name => renaming import"), + src: `package a + +func _() { + «foo encoding/json» +}`, + want: `package a + +import foo "encoding/json" + +func _() { + foo +}`, + }, + } { + t.Run(test.descr, func(t *testing.T) { + // splice marker + before, mid, ok1 := strings.Cut(test.src, "«") + mid, after, ok2 := strings.Cut(mid, "»") + if !ok1 || !ok2 { + t.Fatal("no «name path» marker") + } + src := before + "/*!*/" + after + name, path, _ := strings.Cut(mid, " ") + + // parse + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "a.go", src, parser.ParseComments) + if err != nil { + t.Log(err) + } + pos := fset.File(f.Pos()).Pos(len(before)) + + // type-check + info := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Scopes: make(map[ast.Node]*types.Scope), + Defs: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + } + conf := &types.Config{ + Error: func(err error) { t.Log(err) }, + Importer: importer.Default(), + } + conf.Check(f.Name.Name, fset, []*ast.File{f}, info) + + // add import + name, edits := analysisinternal.AddImport(info, f, pos, path, name) + + var edit analysis.TextEdit + switch len(edits) { + case 0: + case 1: + edit = edits[0] + default: + t.Fatalf("expected at most one edit, got %d", len(edits)) + } + + // apply patch + start := fset.Position(edit.Pos) + end := fset.Position(edit.End) + output := src[:start.Offset] + string(edit.NewText) + src[end.Offset:] + output = strings.ReplaceAll(output, "/*!*/", name) + if output != test.want { + t.Errorf("\n--got--\n%s\n--want--\n%s\n--diff--\n%s", + output, test.want, cmp.Diff(test.want, output)) + } + }) + } +} diff --git a/contribs/gnopls/internal/analysisinternal/analysis.go b/contribs/gnopls/internal/analysisinternal/analysis.go new file mode 100644 index 00000000000..4b4a1cd3a99 --- /dev/null +++ b/contribs/gnopls/internal/analysisinternal/analysis.go @@ -0,0 +1,513 @@ +// 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 analysisinternal provides gopls' internal analyses with a +// number of helper functions that operate on typed syntax trees. +package analysisinternal + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "go/types" + "os" + pathpkg "path" + "strconv" + + "golang.org/x/tools/go/analysis" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" +) + +func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { + // Get the end position for the type error. + offset, end := fset.PositionFor(start, false).Offset, start + if offset >= len(src) { + return end + } + if width := bytes.IndexAny(src[offset:], " \n,():;[]+-*"); width > 0 { + end = start + token.Pos(width) + } + return end +} + +func ZeroValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { + // TODO(adonovan): think about generics, and also generic aliases. + under := aliases.Unalias(typ) + // Don't call Underlying unconditionally: although it removes + // Named and Alias, it also removes TypeParam. + if n, ok := under.(*types.Named); ok { + under = n.Underlying() + } + switch under := under.(type) { + case *types.Basic: + switch { + case under.Info()&types.IsNumeric != 0: + return &ast.BasicLit{Kind: token.INT, Value: "0"} + case under.Info()&types.IsBoolean != 0: + return &ast.Ident{Name: "false"} + case under.Info()&types.IsString != 0: + return &ast.BasicLit{Kind: token.STRING, Value: `""`} + default: + panic(fmt.Sprintf("unknown basic type %v", under)) + } + case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice, *types.Array: + return ast.NewIdent("nil") + case *types.Struct: + texpr := TypeExpr(f, pkg, typ) // typ because we want the name here. + if texpr == nil { + return nil + } + return &ast.CompositeLit{ + Type: texpr, + } + } + return nil +} + +// IsZeroValue checks whether the given expression is a 'zero value' (as determined by output of +// analysisinternal.ZeroValue) +func IsZeroValue(expr ast.Expr) bool { + switch e := expr.(type) { + case *ast.BasicLit: + return e.Value == "0" || e.Value == `""` + case *ast.Ident: + return e.Name == "nil" || e.Name == "false" + default: + return false + } +} + +// TypeExpr returns syntax for the specified type. References to +// named types from packages other than pkg are qualified by an appropriate +// package name, as defined by the import environment of file. +func TypeExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { + switch t := typ.(type) { + case *types.Basic: + switch t.Kind() { + case types.UnsafePointer: + return &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")} + default: + return ast.NewIdent(t.Name()) + } + case *types.Pointer: + x := TypeExpr(f, pkg, t.Elem()) + if x == nil { + return nil + } + return &ast.UnaryExpr{ + Op: token.MUL, + X: x, + } + case *types.Array: + elt := TypeExpr(f, pkg, t.Elem()) + if elt == nil { + return nil + } + return &ast.ArrayType{ + Len: &ast.BasicLit{ + Kind: token.INT, + Value: fmt.Sprintf("%d", t.Len()), + }, + Elt: elt, + } + case *types.Slice: + elt := TypeExpr(f, pkg, t.Elem()) + if elt == nil { + return nil + } + return &ast.ArrayType{ + Elt: elt, + } + case *types.Map: + key := TypeExpr(f, pkg, t.Key()) + value := TypeExpr(f, pkg, t.Elem()) + if key == nil || value == nil { + return nil + } + return &ast.MapType{ + Key: key, + Value: value, + } + case *types.Chan: + dir := ast.ChanDir(t.Dir()) + if t.Dir() == types.SendRecv { + dir = ast.SEND | ast.RECV + } + value := TypeExpr(f, pkg, t.Elem()) + if value == nil { + return nil + } + return &ast.ChanType{ + Dir: dir, + Value: value, + } + case *types.Signature: + var params []*ast.Field + for i := 0; i < t.Params().Len(); i++ { + p := TypeExpr(f, pkg, t.Params().At(i).Type()) + if p == nil { + return nil + } + params = append(params, &ast.Field{ + Type: p, + Names: []*ast.Ident{ + { + Name: t.Params().At(i).Name(), + }, + }, + }) + } + if t.Variadic() { + last := params[len(params)-1] + last.Type = &ast.Ellipsis{Elt: last.Type.(*ast.ArrayType).Elt} + } + var returns []*ast.Field + for i := 0; i < t.Results().Len(); i++ { + r := TypeExpr(f, pkg, t.Results().At(i).Type()) + if r == nil { + return nil + } + returns = append(returns, &ast.Field{ + Type: r, + }) + } + return &ast.FuncType{ + Params: &ast.FieldList{ + List: params, + }, + Results: &ast.FieldList{ + List: returns, + }, + } + case interface{ Obj() *types.TypeName }: // *types.{Alias,Named,TypeParam} + if t.Obj().Pkg() == nil { + return ast.NewIdent(t.Obj().Name()) + } + if t.Obj().Pkg() == pkg { + return ast.NewIdent(t.Obj().Name()) + } + pkgName := t.Obj().Pkg().Name() + + // If the file already imports the package under another name, use that. + for _, cand := range f.Imports { + if path, _ := strconv.Unquote(cand.Path.Value); path == t.Obj().Pkg().Path() { + if cand.Name != nil && cand.Name.Name != "" { + pkgName = cand.Name.Name + } + } + } + if pkgName == "." { + return ast.NewIdent(t.Obj().Name()) + } + return &ast.SelectorExpr{ + X: ast.NewIdent(pkgName), + Sel: ast.NewIdent(t.Obj().Name()), + } + case *types.Struct: + return ast.NewIdent(t.String()) + case *types.Interface: + return ast.NewIdent(t.String()) + default: + return nil + } +} + +// StmtToInsertVarBefore returns the ast.Stmt before which we can safely insert a new variable. +// Some examples: +// +// Basic Example: +// z := 1 +// y := z + x +// If x is undeclared, then this function would return `y := z + x`, so that we +// can insert `x := ` on the line before `y := z + x`. +// +// If stmt example: +// if z == 1 { +// } else if z == y {} +// If y is undeclared, then this function would return `if z == 1 {`, because we cannot +// insert a statement between an if and an else if statement. As a result, we need to find +// the top of the if chain to insert `y := ` before. +func StmtToInsertVarBefore(path []ast.Node) ast.Stmt { + enclosingIndex := -1 + for i, p := range path { + if _, ok := p.(ast.Stmt); ok { + enclosingIndex = i + break + } + } + if enclosingIndex == -1 { + return nil + } + enclosingStmt := path[enclosingIndex] + switch enclosingStmt.(type) { + case *ast.IfStmt: + // The enclosingStmt is inside of the if declaration, + // We need to check if we are in an else-if stmt and + // get the base if statement. + return baseIfStmt(path, enclosingIndex) + case *ast.CaseClause: + // Get the enclosing switch stmt if the enclosingStmt is + // inside of the case statement. + for i := enclosingIndex + 1; i < len(path); i++ { + if node, ok := path[i].(*ast.SwitchStmt); ok { + return node + } else if node, ok := path[i].(*ast.TypeSwitchStmt); ok { + return node + } + } + } + if len(path) <= enclosingIndex+1 { + return enclosingStmt.(ast.Stmt) + } + // Check if the enclosing statement is inside another node. + switch expr := path[enclosingIndex+1].(type) { + case *ast.IfStmt: + // Get the base if statement. + return baseIfStmt(path, enclosingIndex+1) + case *ast.ForStmt: + if expr.Init == enclosingStmt || expr.Post == enclosingStmt { + return expr + } + case *ast.SwitchStmt, *ast.TypeSwitchStmt: + return expr.(ast.Stmt) + } + return enclosingStmt.(ast.Stmt) +} + +// baseIfStmt walks up the if/else-if chain until we get to +// the top of the current if chain. +func baseIfStmt(path []ast.Node, index int) ast.Stmt { + stmt := path[index] + for i := index + 1; i < len(path); i++ { + if node, ok := path[i].(*ast.IfStmt); ok && node.Else == stmt { + stmt = node + continue + } + break + } + return stmt.(ast.Stmt) +} + +// WalkASTWithParent walks the AST rooted at n. The semantics are +// similar to ast.Inspect except it does not call f(nil). +func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) { + var ancestors []ast.Node + ast.Inspect(n, func(n ast.Node) (recurse bool) { + if n == nil { + ancestors = ancestors[:len(ancestors)-1] + return false + } + + var parent ast.Node + if len(ancestors) > 0 { + parent = ancestors[len(ancestors)-1] + } + ancestors = append(ancestors, n) + return f(n, parent) + }) +} + +// MatchingIdents finds the names of all identifiers in 'node' that match any of the given types. +// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within +// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that +// is unrecognized. +func MatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]string { + + // Initialize matches to contain the variable types we are searching for. + matches := make(map[types.Type][]string) + for _, typ := range typs { + if typ == nil { + continue // TODO(adonovan): is this reachable? + } + matches[typ] = nil // create entry + } + + seen := map[types.Object]struct{}{} + ast.Inspect(node, func(n ast.Node) bool { + if n == nil { + return false + } + // Prevent circular definitions. If 'pos' is within an assignment statement, do not + // allow any identifiers in that assignment statement to be selected. Otherwise, + // we could do the following, where 'x' satisfies the type of 'f0': + // + // x := fakeStruct{f0: x} + // + if assign, ok := n.(*ast.AssignStmt); ok && pos > assign.Pos() && pos <= assign.End() { + return false + } + if n.End() > pos { + return n.Pos() <= pos + } + ident, ok := n.(*ast.Ident) + if !ok || ident.Name == "_" { + return true + } + obj := info.Defs[ident] + if obj == nil || obj.Type() == nil { + return true + } + if _, ok := obj.(*types.TypeName); ok { + return true + } + // Prevent duplicates in matches' values. + if _, ok = seen[obj]; ok { + return true + } + seen[obj] = struct{}{} + // Find the scope for the given position. Then, check whether the object + // exists within the scope. + innerScope := pkg.Scope().Innermost(pos) + if innerScope == nil { + return true + } + _, foundObj := innerScope.LookupParent(ident.Name, pos) + if foundObj != obj { + return true + } + // The object must match one of the types that we are searching for. + // TODO(adonovan): opt: use typeutil.Map? + if names, ok := matches[obj.Type()]; ok { + matches[obj.Type()] = append(names, ident.Name) + } else { + // If the object type does not exactly match + // any of the target types, greedily find the first + // target type that the object type can satisfy. + for typ := range matches { + if equivalentTypes(obj.Type(), typ) { + matches[typ] = append(matches[typ], ident.Name) + } + } + } + return true + }) + return matches +} + +func equivalentTypes(want, got types.Type) bool { + if 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) +} + +// MakeReadFile returns a simple implementation of the Pass.ReadFile function. +func MakeReadFile(pass *analysis.Pass) func(filename string) ([]byte, error) { + return func(filename string) ([]byte, error) { + if err := CheckReadable(pass, filename); err != nil { + return nil, err + } + return os.ReadFile(filename) + } +} + +// CheckReadable enforces the access policy defined by the ReadFile field of [analysis.Pass]. +func CheckReadable(pass *analysis.Pass, filename string) error { + if slicesContains(pass.OtherFiles, filename) || + slicesContains(pass.IgnoredFiles, filename) { + return nil + } + for _, f := range pass.Files { + // TODO(adonovan): use go1.20 f.FileStart + if pass.Fset.File(f.Pos()).Name() == filename { + return nil + } + } + return fmt.Errorf("Pass.ReadFile: %s is not among OtherFiles, IgnoredFiles, or names of Files", filename) +} + +// TODO(adonovan): use go1.21 slices.Contains. +func slicesContains[S ~[]E, E comparable](slice S, x E) bool { + for _, elem := range slice { + if elem == x { + return true + } + } + return false +} + +// AddImport checks whether this file already imports pkgpath and +// that import is in scope at pos. If so, it returns the name under +// which it was imported and a zero edit. Otherwise, it adds a new +// import of pkgpath, using a name derived from the preferred name, +// and returns the chosen name along with the edit for the new import. +// +// It does not mutate its arguments. +func AddImport(info *types.Info, file *ast.File, pos token.Pos, pkgpath, preferredName string) (name string, newImport []analysis.TextEdit) { + // Find innermost enclosing lexical block. + scope := info.Scopes[file].Innermost(pos) + if scope == nil { + panic("no enclosing lexical block") + } + + // Is there an existing import of this package? + // If so, are we in its scope? (not shadowed) + for _, spec := range file.Imports { + pkgname, ok := importedPkgName(info, spec) + if ok && pkgname.Imported().Path() == pkgpath { + if _, obj := scope.LookupParent(pkgname.Name(), pos); obj == pkgname { + return pkgname.Name(), nil + } + } + } + + // We must add a new import. + // Ensure we have a fresh name. + newName := preferredName + for i := 0; ; i++ { + if _, obj := scope.LookupParent(newName, pos); obj == nil { + break // fresh + } + newName = fmt.Sprintf("%s%d", preferredName, i) + } + + // For now, keep it real simple: create a new import + // declaration before the first existing declaration (which + // must exist), including its comments, and let goimports tidy it up. + // + // Use a renaming import whenever the preferred name is not + // available, or the chosen name does not match the last + // segment of its path. + newText := fmt.Sprintf("import %q\n\n", pkgpath) + if newName != preferredName || newName != pathpkg.Base(pkgpath) { + newText = fmt.Sprintf("import %s %q\n\n", newName, pkgpath) + } + decl0 := file.Decls[0] + var before ast.Node = decl0 + switch decl0 := decl0.(type) { + case *ast.GenDecl: + if decl0.Doc != nil { + before = decl0.Doc + } + case *ast.FuncDecl: + if decl0.Doc != nil { + before = decl0.Doc + } + } + return newName, []analysis.TextEdit{{ + Pos: before.Pos(), + End: before.Pos(), + NewText: []byte(newText), + }} +} + +// importedPkgName returns the PkgName object declared by an ImportSpec. +// TODO(adonovan): use go1.22's Info.PkgNameOf. +func importedPkgName(info *types.Info, imp *ast.ImportSpec) (*types.PkgName, bool) { + var obj types.Object + if imp.Name != nil { + obj = info.Defs[imp.Name] + } else { + obj = info.Implicits[imp] + } + pkgname, ok := obj.(*types.PkgName) + return pkgname, ok +} diff --git a/contribs/gnopls/internal/analysisinternal/extractdoc.go b/contribs/gnopls/internal/analysisinternal/extractdoc.go new file mode 100644 index 00000000000..39507723d3d --- /dev/null +++ b/contribs/gnopls/internal/analysisinternal/extractdoc.go @@ -0,0 +1,113 @@ +// 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 analysisinternal + +import ( + "fmt" + "go/parser" + "go/token" + "strings" +) + +// MustExtractDoc is like [ExtractDoc] but it panics on error. +// +// To use, define a doc.go file such as: +// +// // Package halting defines an analyzer of program termination. +// // +// // # Analyzer halting +// // +// // halting: reports whether execution will halt. +// // +// // The halting analyzer reports a diagnostic for functions +// // that run forever. To suppress the diagnostics, try inserting +// // a 'break' statement into each loop. +// package halting +// +// import _ "embed" +// +// //go:embed doc.go +// var doc string +// +// And declare your analyzer as: +// +// var Analyzer = &analysis.Analyzer{ +// Name: "halting", +// Doc: analysisutil.MustExtractDoc(doc, "halting"), +// ... +// } +func MustExtractDoc(content, name string) string { + doc, err := ExtractDoc(content, name) + if err != nil { + panic(err) + } + return doc +} + +// ExtractDoc extracts a section of a package doc comment from the +// provided contents of an analyzer package's doc.go file. +// +// A section is a portion of the comment between one heading and +// the next, using this form: +// +// # Analyzer NAME +// +// NAME: SUMMARY +// +// Full description... +// +// where NAME matches the name argument, and SUMMARY is a brief +// verb-phrase that describes the analyzer. The following lines, up +// until the next heading or the end of the comment, contain the full +// description. ExtractDoc returns the portion following the colon, +// which is the form expected by Analyzer.Doc. +// +// Example: +// +// # Analyzer printf +// +// printf: checks consistency of calls to printf +// +// The printf analyzer checks consistency of calls to printf. +// Here is the complete description... +// +// This notation allows a single doc comment to provide documentation +// for multiple analyzers, each in its own section. +// The HTML anchors generated for each heading are predictable. +// +// It returns an error if the content was not a valid Go source file +// containing a package doc comment with a heading of the required +// form. +// +// This machinery enables the package documentation (typically +// accessible via the web at https://pkg.go.dev/) and the command +// documentation (typically printed to a terminal) to be derived from +// the same source and formatted appropriately. +func ExtractDoc(content, name string) (string, error) { + if content == "" { + return "", fmt.Errorf("empty Go source file") + } + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", content, parser.ParseComments|parser.PackageClauseOnly) + if err != nil { + return "", fmt.Errorf("not a Go source file") + } + if f.Doc == nil { + return "", fmt.Errorf("Go source file has no package doc comment") + } + for _, section := range strings.Split(f.Doc.Text(), "\n# ") { + if body := strings.TrimPrefix(section, "Analyzer "+name); body != section && + body != "" && + body[0] == '\r' || body[0] == '\n' { + body = strings.TrimSpace(body) + rest := strings.TrimPrefix(body, name+":") + if rest == body { + return "", fmt.Errorf("'Analyzer %s' heading not followed by '%s: summary...' line", name, name) + } + return strings.TrimSpace(rest), nil + } + } + return "", fmt.Errorf("package doc comment contains no 'Analyzer %s' heading", name) +} diff --git a/contribs/gnopls/internal/analysisinternal/extractdoc_test.go b/contribs/gnopls/internal/analysisinternal/extractdoc_test.go new file mode 100644 index 00000000000..53cea97169e --- /dev/null +++ b/contribs/gnopls/internal/analysisinternal/extractdoc_test.go @@ -0,0 +1,80 @@ +// 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 analysisinternal_test + +import ( + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" +) + +func TestExtractDoc(t *testing.T) { + const multi = `// Copyright + +//+build tag + +// Package foo +// +// # Irrelevant heading +// +// This is irrelevant doc. +// +// # Analyzer nocolon +// +// This one has the wrong form for this line. +// +// # Analyzer food +// +// food: reports dining opportunities +// +// This is the doc for analyzer 'food'. +// +// # Analyzer foo +// +// foo: reports diagnostics +// +// This is the doc for analyzer 'foo'. +// +// # Analyzer bar +// +// bar: reports drinking opportunities +// +// This is the doc for analyzer 'bar'. +package blah + +var x = syntax error +` + + for _, test := range []struct { + content, name string + want string // doc or "error: %w" string + }{ + {"", "foo", + "error: empty Go source file"}, + {"//foo", "foo", + "error: not a Go source file"}, + {"//foo\npackage foo", "foo", + "error: package doc comment contains no 'Analyzer foo' heading"}, + {multi, "foo", + "reports diagnostics\n\nThis is the doc for analyzer 'foo'."}, + {multi, "bar", + "reports drinking opportunities\n\nThis is the doc for analyzer 'bar'."}, + {multi, "food", + "reports dining opportunities\n\nThis is the doc for analyzer 'food'."}, + {multi, "nope", + "error: package doc comment contains no 'Analyzer nope' heading"}, + {multi, "nocolon", + "error: 'Analyzer nocolon' heading not followed by 'nocolon: summary...' line"}, + } { + got, err := analysisinternal.ExtractDoc(test.content, test.name) + if err != nil { + got = "error: " + err.Error() + } + if test.want != got { + t.Errorf("ExtractDoc(%q) returned <<%s>>, want <<%s>>, given input <<%s>>", + test.name, got, test.want, test.content) + } + } +} diff --git a/contribs/gnopls/internal/astutil/clone.go b/contribs/gnopls/internal/astutil/clone.go new file mode 100644 index 00000000000..d5ee82c58b2 --- /dev/null +++ b/contribs/gnopls/internal/astutil/clone.go @@ -0,0 +1,71 @@ +// 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 astutil + +import ( + "go/ast" + "reflect" +) + +// CloneNode returns a deep copy of a Node. +// It omits pointers to ast.{Scope,Object} variables. +func CloneNode[T ast.Node](n T) T { + return cloneNode(n).(T) +} + +func cloneNode(n ast.Node) ast.Node { + var clone func(x reflect.Value) reflect.Value + set := func(dst, src reflect.Value) { + src = clone(src) + if src.IsValid() { + dst.Set(src) + } + } + clone = func(x reflect.Value) reflect.Value { + switch x.Kind() { + case reflect.Ptr: + if x.IsNil() { + return x + } + // Skip fields of types potentially involved in cycles. + switch x.Interface().(type) { + case *ast.Object, *ast.Scope: + return reflect.Zero(x.Type()) + } + y := reflect.New(x.Type().Elem()) + set(y.Elem(), x.Elem()) + return y + + case reflect.Struct: + y := reflect.New(x.Type()).Elem() + for i := 0; i < x.Type().NumField(); i++ { + set(y.Field(i), x.Field(i)) + } + return y + + case reflect.Slice: + if x.IsNil() { + return x + } + y := reflect.MakeSlice(x.Type(), x.Len(), x.Cap()) + for i := 0; i < x.Len(); i++ { + set(y.Index(i), x.Index(i)) + } + return y + + case reflect.Interface: + y := reflect.New(x.Type()).Elem() + set(y, x.Elem()) + return y + + case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer: + panic(x) // unreachable in AST + + default: + return x // bool, string, number + } + } + return clone(reflect.ValueOf(n)).Interface().(ast.Node) +} diff --git a/contribs/gnopls/internal/cache/analysis.go b/contribs/gnopls/internal/cache/analysis.go index 0d7fc46237d..274c4da30f8 100644 --- a/contribs/gnopls/internal/cache/analysis.go +++ b/contribs/gnopls/internal/cache/analysis.go @@ -33,24 +33,24 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/filecache" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/progress" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/frob" - "golang.org/x/tools/gopls/internal/util/moremaps" - "golang.org/x/tools/internal/analysisinternal" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/facts" - "golang.org/x/tools/internal/gcimporter" - "golang.org/x/tools/internal/typesinternal" - "golang.org/x/tools/internal/versions" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/filecache" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/progress" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/frob" + "github.com/gnolang/gno/contribs/gnopls/internal/util/moremaps" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/facts" + "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" ) /* diff --git a/contribs/gnopls/internal/cache/cache.go b/contribs/gnopls/internal/cache/cache.go index 9f85846165f..3e38da5fc6f 100644 --- a/contribs/gnopls/internal/cache/cache.go +++ b/contribs/gnopls/internal/cache/cache.go @@ -9,9 +9,9 @@ import ( "strconv" "sync/atomic" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" ) // ballast is a 100MB unused byte slice that exists only to reduce garbage diff --git a/contribs/gnopls/internal/cache/check.go b/contribs/gnopls/internal/cache/check.go index 4923c92db8d..b6a334a131e 100644 --- a/contribs/gnopls/internal/cache/check.go +++ b/contribs/gnopls/internal/cache/check.go @@ -24,22 +24,22 @@ import ( "golang.org/x/mod/module" "golang.org/x/sync/errgroup" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/cache/typerefs" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/filecache" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/analysisinternal" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gcimporter" - "golang.org/x/tools/internal/packagesinternal" - "golang.org/x/tools/internal/tokeninternal" - "golang.org/x/tools/internal/typesinternal" - "golang.org/x/tools/internal/versions" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/typerefs" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/filecache" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" + "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" ) // Various optimizations that should not affect correctness. diff --git a/contribs/gnopls/internal/cache/diagnostics.go b/contribs/gnopls/internal/cache/diagnostics.go index 797ce961cd8..04a6ef3d3c6 100644 --- a/contribs/gnopls/internal/cache/diagnostics.go +++ b/contribs/gnopls/internal/cache/diagnostics.go @@ -8,8 +8,8 @@ import ( "encoding/json" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) // A InitializationError is an error that causes snapshot initialization to fail. diff --git a/contribs/gnopls/internal/cache/errors.go b/contribs/gnopls/internal/cache/errors.go index 4423fb69e3e..22871342ac6 100644 --- a/contribs/gnopls/internal/cache/errors.go +++ b/contribs/gnopls/internal/cache/errors.go @@ -20,14 +20,14 @@ import ( "strings" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // goPackagesErrorDiagnostics translates the given go/packages Error into a @@ -371,7 +371,7 @@ func onlyDeletions(fixes []SuggestedFix) bool { } func typesCodeHref(linkTarget string, code typesinternal.ErrorCode) string { - return BuildLink(linkTarget, "golang.org/x/tools/internal/typesinternal", code.String()) + return BuildLink(linkTarget, "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal", code.String()) } // BuildLink constructs a URL with the given target, path, and anchor. diff --git a/contribs/gnopls/internal/cache/errors_test.go b/contribs/gnopls/internal/cache/errors_test.go index 664135a8826..240452ab8d1 100644 --- a/contribs/gnopls/internal/cache/errors_test.go +++ b/contribs/gnopls/internal/cache/errors_test.go @@ -11,7 +11,7 @@ import ( "github.com/google/go-cmp/cmp" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func TestParseErrorMessage(t *testing.T) { diff --git a/contribs/gnopls/internal/cache/filemap.go b/contribs/gnopls/internal/cache/filemap.go index ee64d7c32c3..838070e6dbc 100644 --- a/contribs/gnopls/internal/cache/filemap.go +++ b/contribs/gnopls/internal/cache/filemap.go @@ -7,9 +7,9 @@ package cache import ( "path/filepath" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/persistent" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/persistent" ) // A fileMap maps files in the snapshot, with some additional bookkeeping: diff --git a/contribs/gnopls/internal/cache/filemap_test.go b/contribs/gnopls/internal/cache/filemap_test.go index 13f2c1a9ccd..f585ac1428e 100644 --- a/contribs/gnopls/internal/cache/filemap_test.go +++ b/contribs/gnopls/internal/cache/filemap_test.go @@ -10,8 +10,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func TestFileMap(t *testing.T) { diff --git a/contribs/gnopls/internal/cache/fs_memoized.go b/contribs/gnopls/internal/cache/fs_memoized.go index 9f156e3e153..5b36f3dc5e7 100644 --- a/contribs/gnopls/internal/cache/fs_memoized.go +++ b/contribs/gnopls/internal/cache/fs_memoized.go @@ -10,11 +10,11 @@ import ( "sync" "time" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/robustio" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/robustio" ) // A memoizedFS is a file source that memoizes reads, to reduce IO. diff --git a/contribs/gnopls/internal/cache/fs_overlay.go b/contribs/gnopls/internal/cache/fs_overlay.go index 265598bb967..e3f8c1021a0 100644 --- a/contribs/gnopls/internal/cache/fs_overlay.go +++ b/contribs/gnopls/internal/cache/fs_overlay.go @@ -8,8 +8,8 @@ import ( "context" "sync" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // An overlayFS is a file.Source that keeps track of overlays on top of a diff --git a/contribs/gnopls/internal/cache/imports.go b/contribs/gnopls/internal/cache/imports.go index c467a851f8f..7f3bcf31af2 100644 --- a/contribs/gnopls/internal/cache/imports.go +++ b/contribs/gnopls/internal/cache/imports.go @@ -10,11 +10,11 @@ import ( "sync" "time" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/keys" - "golang.org/x/tools/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" ) // refreshTimer implements delayed asynchronous refreshing of state. diff --git a/contribs/gnopls/internal/cache/keys.go b/contribs/gnopls/internal/cache/keys.go index 664e539edbc..0c2bc223566 100644 --- a/contribs/gnopls/internal/cache/keys.go +++ b/contribs/gnopls/internal/cache/keys.go @@ -9,7 +9,7 @@ package cache import ( "io" - "golang.org/x/tools/internal/event/label" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" ) var ( diff --git a/contribs/gnopls/internal/cache/load.go b/contribs/gnopls/internal/cache/load.go index 9373766b413..7952ad4bbab 100644 --- a/contribs/gnopls/internal/cache/load.go +++ b/contribs/gnopls/internal/cache/load.go @@ -17,17 +17,17 @@ import ( "time" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/immutable" - "golang.org/x/tools/gopls/internal/util/pathutil" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/packagesinternal" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/immutable" + "github.com/gnolang/gno/contribs/gnopls/internal/util/pathutil" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) var loadID uint64 // atomic identifier for loads @@ -75,7 +75,7 @@ func (s *Snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc panic(fmt.Sprintf("internal error: load called with multiple scopes when a file scope is present (file: %s)", uri)) } fh := s.FindFile(uri) - if fh == nil || s.FileKind(fh) != file.Go { + if fh == nil || s.FileKind(fh) != file.Gno { // Don't try to load a file that doesn't exist, or isn't a go file. continue } diff --git a/contribs/gnopls/internal/cache/metadata/cycle_test.go b/contribs/gnopls/internal/cache/metadata/cycle_test.go index 09628d881e9..42b256ceae3 100644 --- a/contribs/gnopls/internal/cache/metadata/cycle_test.go +++ b/contribs/gnopls/internal/cache/metadata/cycle_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) func init() { diff --git a/contribs/gnopls/internal/cache/metadata/graph.go b/contribs/gnopls/internal/cache/metadata/graph.go index f09822d3575..7d0a4762d71 100644 --- a/contribs/gnopls/internal/cache/metadata/graph.go +++ b/contribs/gnopls/internal/cache/metadata/graph.go @@ -8,8 +8,8 @@ import ( "sort" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) // A Graph is an immutable and transitively closed graph of [Package] data. diff --git a/contribs/gnopls/internal/cache/metadata/metadata.go b/contribs/gnopls/internal/cache/metadata/metadata.go index e42aac304f6..ba67c18e51d 100644 --- a/contribs/gnopls/internal/cache/metadata/metadata.go +++ b/contribs/gnopls/internal/cache/metadata/metadata.go @@ -20,8 +20,8 @@ import ( "strings" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/packagesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" ) // Declare explicit types for package paths, names, and IDs to ensure that we diff --git a/contribs/gnopls/internal/cache/methodsets/methodsets.go b/contribs/gnopls/internal/cache/methodsets/methodsets.go index f6a2ba96b33..6830a435a97 100644 --- a/contribs/gnopls/internal/cache/methodsets/methodsets.go +++ b/contribs/gnopls/internal/cache/methodsets/methodsets.go @@ -52,9 +52,9 @@ import ( "strings" "golang.org/x/tools/go/types/objectpath" - "golang.org/x/tools/gopls/internal/util/frob" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/util/frob" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" ) // An Index records the non-empty method sets of all package-level diff --git a/contribs/gnopls/internal/cache/mod.go b/contribs/gnopls/internal/cache/mod.go index 6837ec3257c..521ad2137bf 100644 --- a/contribs/gnopls/internal/cache/mod.go +++ b/contribs/gnopls/internal/cache/mod.go @@ -12,15 +12,15 @@ import ( "regexp" "strings" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" "golang.org/x/mod/modfile" "golang.org/x/mod/module" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/memoize" ) // A ParsedModule contains the results of parsing a go.mod file. diff --git a/contribs/gnopls/internal/cache/mod_tidy.go b/contribs/gnopls/internal/cache/mod_tidy.go index 8532d1c7497..cbf99930f04 100644 --- a/contribs/gnopls/internal/cache/mod_tidy.go +++ b/contribs/gnopls/internal/cache/mod_tidy.go @@ -16,15 +16,15 @@ import ( "strings" "golang.org/x/mod/modfile" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" ) // This error is sought by mod diagnostics. diff --git a/contribs/gnopls/internal/cache/mod_vuln.go b/contribs/gnopls/internal/cache/mod_vuln.go index a92f5b5abe1..4b73c2ac859 100644 --- a/contribs/gnopls/internal/cache/mod_vuln.go +++ b/contribs/gnopls/internal/cache/mod_vuln.go @@ -16,13 +16,13 @@ import ( "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/vulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/osv" - isem "golang.org/x/tools/gopls/internal/vulncheck/semver" - "golang.org/x/tools/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/govulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" + isem "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/semver" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" "golang.org/x/vuln/scan" ) diff --git a/contribs/gnopls/internal/cache/package.go b/contribs/gnopls/internal/cache/package.go index 5c0da7e6af0..0974bf755f2 100644 --- a/contribs/gnopls/internal/cache/package.go +++ b/contribs/gnopls/internal/cache/package.go @@ -12,12 +12,12 @@ import ( "go/types" "sync" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/methodsets" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/cache/testfuncs" - "golang.org/x/tools/gopls/internal/cache/xrefs" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/methodsets" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/testfuncs" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/xrefs" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // Convenient aliases for very heavily used types. diff --git a/contribs/gnopls/internal/cache/parse.go b/contribs/gnopls/internal/cache/parse.go index 56130c6e1fb..de538addbfc 100644 --- a/contribs/gnopls/internal/cache/parse.go +++ b/contribs/gnopls/internal/cache/parse.go @@ -11,8 +11,8 @@ import ( "go/token" "path/filepath" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" ) // ParseGo parses the file whose contents are provided by fh. diff --git a/contribs/gnopls/internal/cache/parse_cache.go b/contribs/gnopls/internal/cache/parse_cache.go index 8586f655d28..b331d13f2b1 100644 --- a/contribs/gnopls/internal/cache/parse_cache.go +++ b/contribs/gnopls/internal/cache/parse_cache.go @@ -17,11 +17,11 @@ import ( "time" "golang.org/x/sync/errgroup" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/memoize" - "golang.org/x/tools/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" ) // This file contains an implementation of an LRU parse cache, that offsets the diff --git a/contribs/gnopls/internal/cache/parse_cache_test.go b/contribs/gnopls/internal/cache/parse_cache_test.go index 7aefac77c38..dc6e43570d2 100644 --- a/contribs/gnopls/internal/cache/parse_cache_test.go +++ b/contribs/gnopls/internal/cache/parse_cache_test.go @@ -12,9 +12,9 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func skipIfNoParseCache(t *testing.T) { diff --git a/contribs/gnopls/internal/cache/parsego/file.go b/contribs/gnopls/internal/cache/parsego/file.go index b03929e6c86..469643e4e64 100644 --- a/contribs/gnopls/internal/cache/parsego/file.go +++ b/contribs/gnopls/internal/cache/parsego/file.go @@ -10,8 +10,8 @@ import ( "go/scanner" "go/token" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // A File contains the results of parsing a Go file. diff --git a/contribs/gnopls/internal/cache/parsego/parse.go b/contribs/gnopls/internal/cache/parsego/parse.go index 0143a36ab71..4a3a28035ce 100644 --- a/contribs/gnopls/internal/cache/parsego/parse.go +++ b/contribs/gnopls/internal/cache/parsego/parse.go @@ -14,12 +14,12 @@ import ( "go/token" "reflect" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // Common parse modes; these should be reused wherever possible to increase diff --git a/contribs/gnopls/internal/cache/parsego/parse_test.go b/contribs/gnopls/internal/cache/parsego/parse_test.go index c64125427b1..12e29c46261 100644 --- a/contribs/gnopls/internal/cache/parsego/parse_test.go +++ b/contribs/gnopls/internal/cache/parsego/parse_test.go @@ -10,9 +10,9 @@ import ( "go/token" "testing" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" ) // TODO(golang/go#64335): we should have many more tests for fixed syntax. diff --git a/contribs/gnopls/internal/cache/port.go b/contribs/gnopls/internal/cache/port.go index e62ebe29903..fc9e8a98cba 100644 --- a/contribs/gnopls/internal/cache/port.go +++ b/contribs/gnopls/internal/cache/port.go @@ -13,7 +13,7 @@ import ( "path/filepath" "strings" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) type port struct{ GOOS, GOARCH string } diff --git a/contribs/gnopls/internal/cache/port_test.go b/contribs/gnopls/internal/cache/port_test.go index a92056a9c22..0a917bc2a24 100644 --- a/contribs/gnopls/internal/cache/port_test.go +++ b/contribs/gnopls/internal/cache/port_test.go @@ -11,10 +11,10 @@ import ( "github.com/google/go-cmp/cmp" "golang.org/x/sync/errgroup" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/cache/session.go b/contribs/gnopls/internal/cache/session.go index c5e9aab98a5..90602a4e57b 100644 --- a/contribs/gnopls/internal/cache/session.go +++ b/contribs/gnopls/internal/cache/session.go @@ -18,20 +18,20 @@ import ( "sync/atomic" "time" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/typerefs" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/persistent" - "golang.org/x/tools/gopls/internal/vulncheck" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/keys" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/memoize" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/typerefs" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/persistent" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) // NewSession creates a new gopls session with the given cache. @@ -690,7 +690,7 @@ func matchingView[V viewDefiner](fh file.Handle, relevantViews []V) V { // Port matching doesn't apply to non-go files, or files that no longer exist. // Note that the behavior here on non-existent files shouldn't matter much, // since there will be a subsequent failure. - if fileKind(fh) != file.Go || err != nil { + if fileKind(fh) != file.Gno || err != nil { return relevantViews[0] } @@ -805,7 +805,7 @@ func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modif // pay this cost when e.g. processing a bunch of on-disk changes due to a // branch change. Be careful to only do this if both files are open Go // files. - if old, ok := replaced[c.URI]; ok && !checkViews && fileKind(fh) == file.Go { + if old, ok := replaced[c.URI]; ok && !checkViews && fileKind(fh) == file.Gno { if new, ok := fh.(*overlay); ok { if buildComment(old.content) != buildComment(new.content) { checkViews = true diff --git a/contribs/gnopls/internal/cache/session_test.go b/contribs/gnopls/internal/cache/session_test.go index fe4e55e3d74..744bb7f19f9 100644 --- a/contribs/gnopls/internal/cache/session_test.go +++ b/contribs/gnopls/internal/cache/session_test.go @@ -12,10 +12,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestZeroConfigAlgorithm(t *testing.T) { diff --git a/contribs/gnopls/internal/cache/snapshot.go b/contribs/gnopls/internal/cache/snapshot.go index 95f222c0eec..4a5020dafdb 100644 --- a/contribs/gnopls/internal/cache/snapshot.go +++ b/contribs/gnopls/internal/cache/snapshot.go @@ -25,33 +25,33 @@ import ( "strings" "sync" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/methodsets" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/testfuncs" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/typerefs" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/xrefs" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/filecache" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + label1 "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/constraints" + "github.com/gnolang/gno/contribs/gnopls/internal/util/immutable" + "github.com/gnolang/gno/contribs/gnopls/internal/util/pathutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/persistent" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" "golang.org/x/sync/errgroup" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/types/objectpath" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/methodsets" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/cache/testfuncs" - "golang.org/x/tools/gopls/internal/cache/typerefs" - "golang.org/x/tools/gopls/internal/cache/xrefs" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/filecache" - label1 "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/constraints" - "golang.org/x/tools/gopls/internal/util/immutable" - "golang.org/x/tools/gopls/internal/util/pathutil" - "golang.org/x/tools/gopls/internal/util/persistent" - "golang.org/x/tools/gopls/internal/vulncheck" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/label" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/memoize" - "golang.org/x/tools/internal/packagesinternal" - "golang.org/x/tools/internal/typesinternal" ) // A Snapshot represents the current state for a given view. @@ -310,7 +310,7 @@ func (s *Snapshot) FileKind(fh file.Handle) file.Kind { // TODO(rfindley): this doesn't look right. We should default to UnknownKind. // Also, I don't understand the comment above, though I'd guess before go1.15 // we encountered cgo files without the .go extension. - return file.Go + return file.Gno } // fileKind returns the default file kind for a file, before considering @@ -327,8 +327,8 @@ func fileKind(fh file.Handle) file.Kind { fext := filepath.Ext(fh.URI().Path()) switch fext { - case ".go": - return file.Go + case ".gno": + return file.Gno case ".mod": return file.Mod case ".sum": @@ -1387,7 +1387,7 @@ func (s *Snapshot) orphanedFileDiagnostics(ctx context.Context, overlays []*over searchOverlays: for _, o := range overlays { uri := o.URI() - if s.IsBuiltin(uri) || s.FileKind(o) != file.Go { + if s.IsBuiltin(uri) || s.FileKind(o) != file.Gno { continue } mps, err := s.MetadataForFile(ctx, uri) diff --git a/contribs/gnopls/internal/cache/symbols.go b/contribs/gnopls/internal/cache/symbols.go index 9954c747798..85b930b4738 100644 --- a/contribs/gnopls/internal/cache/symbols.go +++ b/contribs/gnopls/internal/cache/symbols.go @@ -11,10 +11,10 @@ import ( "go/types" "strings" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" ) // Symbol holds a precomputed symbol value. Note: we avoid using the diff --git a/contribs/gnopls/internal/cache/testfuncs/tests.go b/contribs/gnopls/internal/cache/testfuncs/tests.go index cfef3c54164..484a9733f5c 100644 --- a/contribs/gnopls/internal/cache/testfuncs/tests.go +++ b/contribs/gnopls/internal/cache/testfuncs/tests.go @@ -11,10 +11,10 @@ import ( "regexp" "strings" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/frob" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/frob" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // An Index records the test set of a package. diff --git a/contribs/gnopls/internal/cache/typerefs/doc.go b/contribs/gnopls/internal/cache/typerefs/doc.go index 18042c623bc..fae7f118935 100644 --- a/contribs/gnopls/internal/cache/typerefs/doc.go +++ b/contribs/gnopls/internal/cache/typerefs/doc.go @@ -111,7 +111,7 @@ // The [BuildPackageGraph] constructor implements a whole-graph analysis similar // to that which will be implemented by gopls, but for various reasons the // logic for this analysis will eventually live in the -// [golang.org/x/tools/gopls/internal/cache] package. Nevertheless, +// [github.com/gnolang/gno/contribs/gnopls/internal/cache] package. Nevertheless, // BuildPackageGraph and its test serve to verify the syntactic analysis, and // may serve as a proving ground for new optimizations of the whole-graph analysis. // diff --git a/contribs/gnopls/internal/cache/typerefs/packageset.go b/contribs/gnopls/internal/cache/typerefs/packageset.go index 29c37cd1c4c..443be59afb5 100644 --- a/contribs/gnopls/internal/cache/typerefs/packageset.go +++ b/contribs/gnopls/internal/cache/typerefs/packageset.go @@ -11,7 +11,7 @@ import ( "strings" "sync" - "golang.org/x/tools/gopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" ) // PackageIndex stores common data to enable efficient representation of diff --git a/contribs/gnopls/internal/cache/typerefs/pkggraph_test.go b/contribs/gnopls/internal/cache/typerefs/pkggraph_test.go index 01cd1a86f0f..89add2eba77 100644 --- a/contribs/gnopls/internal/cache/typerefs/pkggraph_test.go +++ b/contribs/gnopls/internal/cache/typerefs/pkggraph_test.go @@ -17,10 +17,10 @@ import ( "sync" "golang.org/x/sync/errgroup" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/cache/typerefs" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/typerefs" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) const ( diff --git a/contribs/gnopls/internal/cache/typerefs/pkgrefs_test.go b/contribs/gnopls/internal/cache/typerefs/pkgrefs_test.go index 9d4b5c011d3..e0abe478f30 100644 --- a/contribs/gnopls/internal/cache/typerefs/pkgrefs_test.go +++ b/contribs/gnopls/internal/cache/typerefs/pkgrefs_test.go @@ -20,13 +20,13 @@ import ( "golang.org/x/tools/go/gcexportdata" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/cache/typerefs" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/internal/packagesinternal" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/typerefs" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) var ( diff --git a/contribs/gnopls/internal/cache/typerefs/refs.go b/contribs/gnopls/internal/cache/typerefs/refs.go index b389667ae7f..84d18b94f1d 100644 --- a/contribs/gnopls/internal/cache/typerefs/refs.go +++ b/contribs/gnopls/internal/cache/typerefs/refs.go @@ -11,10 +11,10 @@ import ( "sort" "strings" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/gopls/internal/util/frob" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/frob" ) // Encode analyzes the Go syntax trees of a package, constructs a diff --git a/contribs/gnopls/internal/cache/typerefs/refs_test.go b/contribs/gnopls/internal/cache/typerefs/refs_test.go index 1e98fb585ed..f27a269cb97 100644 --- a/contribs/gnopls/internal/cache/typerefs/refs_test.go +++ b/contribs/gnopls/internal/cache/typerefs/refs_test.go @@ -12,10 +12,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/cache/typerefs" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/typerefs" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // TestRefs checks that the analysis reports, for each exported member diff --git a/contribs/gnopls/internal/cache/view.go b/contribs/gnopls/internal/cache/view.go index 8a5a701d890..bc6491417c2 100644 --- a/contribs/gnopls/internal/cache/view.go +++ b/contribs/gnopls/internal/cache/view.go @@ -26,17 +26,17 @@ import ( "sync" "time" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/moremaps" - "golang.org/x/tools/gopls/internal/util/pathutil" - "golang.org/x/tools/gopls/internal/vulncheck" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/moremaps" + "github.com/gnolang/gno/contribs/gnopls/internal/util/pathutil" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) // A Folder represents an LSP workspace folder, together with its per-folder @@ -817,7 +817,7 @@ func defineView(ctx context.Context, fs file.Source, folder *Folder, forFile fil def := new(viewDefinition) def.folder = folder - if forFile != nil && fileKind(forFile) == file.Go { + if forFile != nil && fileKind(forFile) == file.Gno { // If the file has GOOS/GOARCH build constraints that // don't match the folder's environment (which comes from // 'go env' in the folder, plus user options), diff --git a/contribs/gnopls/internal/cache/view_test.go b/contribs/gnopls/internal/cache/view_test.go index 992a3d61828..db8c316289f 100644 --- a/contribs/gnopls/internal/cache/view_test.go +++ b/contribs/gnopls/internal/cache/view_test.go @@ -8,7 +8,7 @@ import ( "path/filepath" "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func TestCaseInsensitiveFilesystem(t *testing.T) { diff --git a/contribs/gnopls/internal/cache/workspace.go b/contribs/gnopls/internal/cache/workspace.go index 07134b3da00..98b5ca150bc 100644 --- a/contribs/gnopls/internal/cache/workspace.go +++ b/contribs/gnopls/internal/cache/workspace.go @@ -11,8 +11,8 @@ import ( "path/filepath" "golang.org/x/mod/modfile" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // isGoWork reports if uri is a go.work file. diff --git a/contribs/gnopls/internal/cache/xrefs/xrefs.go b/contribs/gnopls/internal/cache/xrefs/xrefs.go index 4113e08716e..3c5bff90e0f 100644 --- a/contribs/gnopls/internal/cache/xrefs/xrefs.go +++ b/contribs/gnopls/internal/cache/xrefs/xrefs.go @@ -14,10 +14,10 @@ import ( "sort" "golang.org/x/tools/go/types/objectpath" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/frob" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/frob" ) // Index constructs a serializable index of outbound cross-references diff --git a/contribs/gnopls/internal/clonetest/clonetest_test.go b/contribs/gnopls/internal/clonetest/clonetest_test.go index bbb803f2447..18320798607 100644 --- a/contribs/gnopls/internal/clonetest/clonetest_test.go +++ b/contribs/gnopls/internal/clonetest/clonetest_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/clonetest" + "github.com/gnolang/gno/contribs/gnopls/internal/clonetest" ) func Test(t *testing.T) { diff --git a/contribs/gnopls/internal/cmd/call_hierarchy.go b/contribs/gnopls/internal/cmd/call_hierarchy.go index 0ac6956144e..7d5cefa50f2 100644 --- a/contribs/gnopls/internal/cmd/call_hierarchy.go +++ b/contribs/gnopls/internal/cmd/call_hierarchy.go @@ -10,8 +10,8 @@ import ( "fmt" "strings" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // callHierarchy implements the callHierarchy verb for gopls. diff --git a/contribs/gnopls/internal/cmd/capabilities_test.go b/contribs/gnopls/internal/cmd/capabilities_test.go index 97eb49652d0..da97c7ba126 100644 --- a/contribs/gnopls/internal/cmd/capabilities_test.go +++ b/contribs/gnopls/internal/cmd/capabilities_test.go @@ -11,11 +11,11 @@ import ( "path/filepath" "testing" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/server" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) // TestCapabilities does some minimal validation of the server's adherence to the LSP. diff --git a/contribs/gnopls/internal/cmd/check.go b/contribs/gnopls/internal/cmd/check.go index d256fa9de2a..605574a91ac 100644 --- a/contribs/gnopls/internal/cmd/check.go +++ b/contribs/gnopls/internal/cmd/check.go @@ -10,8 +10,8 @@ import ( "fmt" "slices" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" ) // check implements the check verb for gopls. diff --git a/contribs/gnopls/internal/cmd/cmd.go b/contribs/gnopls/internal/cmd/cmd.go index 9ec5b630d6c..3ba50681ef1 100644 --- a/contribs/gnopls/internal/cmd/cmd.go +++ b/contribs/gnopls/internal/cmd/cmd.go @@ -22,19 +22,19 @@ import ( "text/tabwriter" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/filecache" - "golang.org/x/tools/gopls/internal/lsprpc" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/server" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/browser" - bugpkg "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/filecache" + "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/browser" + bugpkg "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // Application is the main application as passed to tool.Main diff --git a/contribs/gnopls/internal/cmd/codeaction.go b/contribs/gnopls/internal/cmd/codeaction.go index 84d7d181b88..2f2751ca3ec 100644 --- a/contribs/gnopls/internal/cmd/codeaction.go +++ b/contribs/gnopls/internal/cmd/codeaction.go @@ -12,8 +12,8 @@ import ( "slices" "strings" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // codeaction implements the codeaction verb for gopls. diff --git a/contribs/gnopls/internal/cmd/codelens.go b/contribs/gnopls/internal/cmd/codelens.go index 452e978094f..0c87f92a0dd 100644 --- a/contribs/gnopls/internal/cmd/codelens.go +++ b/contribs/gnopls/internal/cmd/codelens.go @@ -9,9 +9,9 @@ import ( "flag" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // codelens implements the codelens verb for gopls. diff --git a/contribs/gnopls/internal/cmd/definition.go b/contribs/gnopls/internal/cmd/definition.go index d9cd98554e3..15e2f532793 100644 --- a/contribs/gnopls/internal/cmd/definition.go +++ b/contribs/gnopls/internal/cmd/definition.go @@ -12,9 +12,9 @@ import ( "os" "strings" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // A Definition is the result of a 'definition' query. diff --git a/contribs/gnopls/internal/cmd/execute.go b/contribs/gnopls/internal/cmd/execute.go index 96b3cf3b81d..a9e48c928cd 100644 --- a/contribs/gnopls/internal/cmd/execute.go +++ b/contribs/gnopls/internal/cmd/execute.go @@ -13,10 +13,10 @@ import ( "os" "slices" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/server" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // execute implements the LSP ExecuteCommand verb for gopls. @@ -36,7 +36,7 @@ with a set of optional JSON argument values. Some commands return a result, also JSON. Gopls' command set is defined by the command.Interface type; see -https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol/command#Interface. +https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/protocol/command#Interface. It is not a stable interface: commands may change or disappear without notice. Examples: diff --git a/contribs/gnopls/internal/cmd/folding_range.go b/contribs/gnopls/internal/cmd/folding_range.go index f48feee5b2c..916864e8f26 100644 --- a/contribs/gnopls/internal/cmd/folding_range.go +++ b/contribs/gnopls/internal/cmd/folding_range.go @@ -9,8 +9,8 @@ import ( "flag" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // foldingRanges implements the folding_ranges verb for gopls diff --git a/contribs/gnopls/internal/cmd/format.go b/contribs/gnopls/internal/cmd/format.go index eb68d73d527..65a4001b3e6 100644 --- a/contribs/gnopls/internal/cmd/format.go +++ b/contribs/gnopls/internal/cmd/format.go @@ -9,7 +9,7 @@ import ( "flag" "fmt" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // format implements the format verb for gopls. diff --git a/contribs/gnopls/internal/cmd/help_test.go b/contribs/gnopls/internal/cmd/help_test.go index 74fb07fbe75..983426c5f49 100644 --- a/contribs/gnopls/internal/cmd/help_test.go +++ b/contribs/gnopls/internal/cmd/help_test.go @@ -21,9 +21,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/cmd" - "golang.org/x/tools/internal/testenv" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/cmd" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) var updateHelpFiles = flag.Bool("update-help-files", false, "Write out the help files instead of checking them") diff --git a/contribs/gnopls/internal/cmd/highlight.go b/contribs/gnopls/internal/cmd/highlight.go index 43af063f53f..5448604ee13 100644 --- a/contribs/gnopls/internal/cmd/highlight.go +++ b/contribs/gnopls/internal/cmd/highlight.go @@ -9,8 +9,8 @@ import ( "flag" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // highlight implements the highlight verb for gopls. diff --git a/contribs/gnopls/internal/cmd/implementation.go b/contribs/gnopls/internal/cmd/implementation.go index 858026540ad..3ccdc51b0a5 100644 --- a/contribs/gnopls/internal/cmd/implementation.go +++ b/contribs/gnopls/internal/cmd/implementation.go @@ -10,8 +10,8 @@ import ( "fmt" "sort" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // implementation implements the implementation verb for gopls diff --git a/contribs/gnopls/internal/cmd/imports.go b/contribs/gnopls/internal/cmd/imports.go index c64c871e390..5cc4c062901 100644 --- a/contribs/gnopls/internal/cmd/imports.go +++ b/contribs/gnopls/internal/cmd/imports.go @@ -9,8 +9,8 @@ import ( "flag" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // imports implements the import verb for gopls. diff --git a/contribs/gnopls/internal/cmd/info.go b/contribs/gnopls/internal/cmd/info.go index 93a66880234..b94b99845e9 100644 --- a/contribs/gnopls/internal/cmd/info.go +++ b/contribs/gnopls/internal/cmd/info.go @@ -16,13 +16,13 @@ import ( "sort" "strings" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/doc" - "golang.org/x/tools/gopls/internal/filecache" - licensespkg "golang.org/x/tools/gopls/internal/licenses" - "golang.org/x/tools/gopls/internal/util/browser" - goplsbug "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/doc" + "github.com/gnolang/gno/contribs/gnopls/internal/filecache" + licensespkg "github.com/gnolang/gno/contribs/gnopls/internal/licenses" + "github.com/gnolang/gno/contribs/gnopls/internal/util/browser" + goplsbug "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // help implements the help command. @@ -223,7 +223,7 @@ func (j *apiJSON) DetailedHelp(f *flag.FlagSet) { fmt.Fprint(f.Output(), ` The api-json command prints a JSON value that describes and documents all gopls' public interfaces. -Its schema is defined by golang.org/x/tools/gopls/internal/doc.API. +Its schema is defined by github.com/gnolang/gno/contribs/gnopls/internal/doc.API. `) printFlagDefaults(f) } diff --git a/contribs/gnopls/internal/cmd/integration_test.go b/contribs/gnopls/internal/cmd/integration_test.go index dfb2a164a42..3e5344f5a2d 100644 --- a/contribs/gnopls/internal/cmd/integration_test.go +++ b/contribs/gnopls/internal/cmd/integration_test.go @@ -38,13 +38,13 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/cmd" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/version" - "golang.org/x/tools/internal/testenv" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/cmd" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/version" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" "golang.org/x/tools/txtar" ) diff --git a/contribs/gnopls/internal/cmd/links.go b/contribs/gnopls/internal/cmd/links.go index 3c14f4e6608..ab9869779ed 100644 --- a/contribs/gnopls/internal/cmd/links.go +++ b/contribs/gnopls/internal/cmd/links.go @@ -11,8 +11,8 @@ import ( "fmt" "os" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // links implements the links verb for gopls. diff --git a/contribs/gnopls/internal/cmd/parsespan.go b/contribs/gnopls/internal/cmd/parsespan.go index 556beb9730e..1cee793cee7 100644 --- a/contribs/gnopls/internal/cmd/parsespan.go +++ b/contribs/gnopls/internal/cmd/parsespan.go @@ -9,7 +9,7 @@ import ( "strings" "unicode/utf8" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // parseSpan returns the location represented by the input. diff --git a/contribs/gnopls/internal/cmd/prepare_rename.go b/contribs/gnopls/internal/cmd/prepare_rename.go index 3ff38356d55..c3712cf18a2 100644 --- a/contribs/gnopls/internal/cmd/prepare_rename.go +++ b/contribs/gnopls/internal/cmd/prepare_rename.go @@ -10,8 +10,8 @@ import ( "flag" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // prepareRename implements the prepare_rename verb for gopls. diff --git a/contribs/gnopls/internal/cmd/references.go b/contribs/gnopls/internal/cmd/references.go index 1483bf12db0..c2fbd007df4 100644 --- a/contribs/gnopls/internal/cmd/references.go +++ b/contribs/gnopls/internal/cmd/references.go @@ -10,8 +10,8 @@ import ( "fmt" "sort" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // references implements the references verb for gopls diff --git a/contribs/gnopls/internal/cmd/remote.go b/contribs/gnopls/internal/cmd/remote.go index ae4aa55ab61..82bb253f70a 100644 --- a/contribs/gnopls/internal/cmd/remote.go +++ b/contribs/gnopls/internal/cmd/remote.go @@ -13,8 +13,8 @@ import ( "log" "os" - "golang.org/x/tools/gopls/internal/lsprpc" - "golang.org/x/tools/gopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" ) type remote struct { diff --git a/contribs/gnopls/internal/cmd/rename.go b/contribs/gnopls/internal/cmd/rename.go index e96850cd1c8..0848048a054 100644 --- a/contribs/gnopls/internal/cmd/rename.go +++ b/contribs/gnopls/internal/cmd/rename.go @@ -9,8 +9,8 @@ import ( "flag" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // rename implements the rename verb for gopls. diff --git a/contribs/gnopls/internal/cmd/semantictokens.go b/contribs/gnopls/internal/cmd/semantictokens.go index 77e8a03939c..8400466855a 100644 --- a/contribs/gnopls/internal/cmd/semantictokens.go +++ b/contribs/gnopls/internal/cmd/semantictokens.go @@ -13,8 +13,8 @@ import ( "os" "unicode/utf8" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" ) // generate semantic tokens and interpolate them in the file diff --git a/contribs/gnopls/internal/cmd/serve.go b/contribs/gnopls/internal/cmd/serve.go index 16f3b160a73..9d8dc87e06f 100644 --- a/contribs/gnopls/internal/cmd/serve.go +++ b/contribs/gnopls/internal/cmd/serve.go @@ -14,13 +14,13 @@ import ( "os" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/lsprpc" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/fakenet" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/fakenet" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // Serve is a struct that exposes the configurable parts of the LSP server as diff --git a/contribs/gnopls/internal/cmd/signature.go b/contribs/gnopls/internal/cmd/signature.go index 601cfaa13fa..98c72660d28 100644 --- a/contribs/gnopls/internal/cmd/signature.go +++ b/contribs/gnopls/internal/cmd/signature.go @@ -9,8 +9,8 @@ import ( "flag" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // signature implements the signature verb for gopls diff --git a/contribs/gnopls/internal/cmd/span.go b/contribs/gnopls/internal/cmd/span.go index 4753d534350..1bbb74fce35 100644 --- a/contribs/gnopls/internal/cmd/span.go +++ b/contribs/gnopls/internal/cmd/span.go @@ -13,7 +13,7 @@ import ( "sort" "strings" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // A span represents a range of text within a source file. The start diff --git a/contribs/gnopls/internal/cmd/stats.go b/contribs/gnopls/internal/cmd/stats.go index cc19a94fb84..2a708d9ebce 100644 --- a/contribs/gnopls/internal/cmd/stats.go +++ b/contribs/gnopls/internal/cmd/stats.go @@ -18,13 +18,13 @@ import ( "strings" "time" - "golang.org/x/tools/gopls/internal/filecache" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" - bugpkg "golang.org/x/tools/gopls/internal/util/bug" - versionpkg "golang.org/x/tools/gopls/internal/version" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/filecache" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + bugpkg "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + versionpkg "github.com/gnolang/gno/contribs/gnopls/internal/version" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) type stats struct { diff --git a/contribs/gnopls/internal/cmd/subcommands.go b/contribs/gnopls/internal/cmd/subcommands.go index e30c42b85f9..43752b4eb34 100644 --- a/contribs/gnopls/internal/cmd/subcommands.go +++ b/contribs/gnopls/internal/cmd/subcommands.go @@ -10,7 +10,7 @@ import ( "fmt" "text/tabwriter" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // subcommands is a helper that may be embedded for commands that delegate to diff --git a/contribs/gnopls/internal/cmd/symbols.go b/contribs/gnopls/internal/cmd/symbols.go index 663a08f4be1..03c36d45cc5 100644 --- a/contribs/gnopls/internal/cmd/symbols.go +++ b/contribs/gnopls/internal/cmd/symbols.go @@ -11,8 +11,8 @@ import ( "fmt" "sort" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // symbols implements the symbols verb for gopls diff --git a/contribs/gnopls/internal/cmd/usage/api-json.hlp b/contribs/gnopls/internal/cmd/usage/api-json.hlp index 304c43d3b47..3067da6b015 100644 --- a/contribs/gnopls/internal/cmd/usage/api-json.hlp +++ b/contribs/gnopls/internal/cmd/usage/api-json.hlp @@ -5,4 +5,4 @@ Usage: The api-json command prints a JSON value that describes and documents all gopls' public interfaces. -Its schema is defined by golang.org/x/tools/gopls/internal/doc.API. +Its schema is defined by github.com/gnolang/gno/contribs/gnopls/internal/doc.API. diff --git a/contribs/gnopls/internal/cmd/usage/execute.hlp b/contribs/gnopls/internal/cmd/usage/execute.hlp index b5a7b1cefbc..b8221452b35 100644 --- a/contribs/gnopls/internal/cmd/usage/execute.hlp +++ b/contribs/gnopls/internal/cmd/usage/execute.hlp @@ -8,7 +8,7 @@ with a set of optional JSON argument values. Some commands return a result, also JSON. Gopls' command set is defined by the command.Interface type; see -https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol/command#Interface. +https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/protocol/command#Interface. It is not a stable interface: commands may change or disappear without notice. Examples: diff --git a/contribs/gnopls/internal/cmd/usage/vulncheck.hlp b/contribs/gnopls/internal/cmd/usage/vulncheck.hlp index 7f2818dd40c..d68d6159070 100644 --- a/contribs/gnopls/internal/cmd/usage/vulncheck.hlp +++ b/contribs/gnopls/internal/cmd/usage/vulncheck.hlp @@ -6,7 +6,7 @@ Usage: WARNING: this command is for internal-use only. By default, the command outputs a JSON-encoded - golang.org/x/tools/gopls/internal/protocol/command.VulncheckResult + github.com/gnolang/gno/contribs/gnopls/internal/protocol/command.VulncheckResult message. Example: $ gopls vulncheck <packages> diff --git a/contribs/gnopls/internal/cmd/vulncheck.go b/contribs/gnopls/internal/cmd/vulncheck.go index 7babf0d14d7..d8b8ca3401c 100644 --- a/contribs/gnopls/internal/cmd/vulncheck.go +++ b/contribs/gnopls/internal/cmd/vulncheck.go @@ -10,7 +10,7 @@ import ( "fmt" "os" - "golang.org/x/tools/gopls/internal/vulncheck/scan" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/scan" ) // vulncheck implements the vulncheck command. @@ -30,7 +30,7 @@ func (v *vulncheck) DetailedHelp(f *flag.FlagSet) { WARNING: this command is for internal-use only. By default, the command outputs a JSON-encoded - golang.org/x/tools/gopls/internal/protocol/command.VulncheckResult + github.com/gnolang/gno/contribs/gnopls/internal/protocol/command.VulncheckResult message. Example: $ gopls vulncheck <packages> diff --git a/contribs/gnopls/internal/cmd/workspace_symbol.go b/contribs/gnopls/internal/cmd/workspace_symbol.go index aba33fa9d2a..cd592c203ce 100644 --- a/contribs/gnopls/internal/cmd/workspace_symbol.go +++ b/contribs/gnopls/internal/cmd/workspace_symbol.go @@ -10,9 +10,9 @@ import ( "fmt" "strings" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // workspaceSymbol implements the workspace_symbol verb for gopls. diff --git a/contribs/gnopls/internal/debug/info.go b/contribs/gnopls/internal/debug/info.go index b2824d86f38..2b860d7a01f 100644 --- a/contribs/gnopls/internal/debug/info.go +++ b/contribs/gnopls/internal/debug/info.go @@ -15,7 +15,7 @@ import ( "runtime/debug" "strings" - "golang.org/x/tools/gopls/internal/version" + "github.com/gnolang/gno/contribs/gnopls/internal/version" ) type PrintMode int diff --git a/contribs/gnopls/internal/debug/info_test.go b/contribs/gnopls/internal/debug/info_test.go index 7f24b696682..9faf5940704 100644 --- a/contribs/gnopls/internal/debug/info_test.go +++ b/contribs/gnopls/internal/debug/info_test.go @@ -12,7 +12,7 @@ import ( "runtime" "testing" - "golang.org/x/tools/gopls/internal/version" + "github.com/gnolang/gno/contribs/gnopls/internal/version" ) func TestPrintVersionInfoJSON(t *testing.T) { diff --git a/contribs/gnopls/internal/debug/log/log.go b/contribs/gnopls/internal/debug/log/log.go index d015f9bfdd3..bbb90c83efb 100644 --- a/contribs/gnopls/internal/debug/log/log.go +++ b/contribs/gnopls/internal/debug/log/log.go @@ -10,9 +10,9 @@ import ( "context" "fmt" - label1 "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/label" + label1 "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" ) // Level parameterizes log severity. diff --git a/contribs/gnopls/internal/debug/metrics.go b/contribs/gnopls/internal/debug/metrics.go index d8bfe52f106..cdc169f345a 100644 --- a/contribs/gnopls/internal/debug/metrics.go +++ b/contribs/gnopls/internal/debug/metrics.go @@ -5,9 +5,9 @@ package debug import ( - "golang.org/x/tools/internal/event/export/metric" - "golang.org/x/tools/internal/event/label" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/metric" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) var ( diff --git a/contribs/gnopls/internal/debug/rpc.go b/contribs/gnopls/internal/debug/rpc.go index 8a696f848d0..4ae41d9c262 100644 --- a/contribs/gnopls/internal/debug/rpc.go +++ b/contribs/gnopls/internal/debug/rpc.go @@ -13,11 +13,11 @@ import ( "sync" "time" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/core" - "golang.org/x/tools/internal/event/export" - "golang.org/x/tools/internal/event/label" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) var RPCTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(` diff --git a/contribs/gnopls/internal/debug/serve.go b/contribs/gnopls/internal/debug/serve.go index 058254b755b..97289645aa3 100644 --- a/contribs/gnopls/internal/debug/serve.go +++ b/contribs/gnopls/internal/debug/serve.go @@ -24,19 +24,19 @@ import ( "sync" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug/log" - label1 "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/core" - "golang.org/x/tools/internal/event/export" - "golang.org/x/tools/internal/event/export/metric" - "golang.org/x/tools/internal/event/export/ocagent" - "golang.org/x/tools/internal/event/export/prometheus" - "golang.org/x/tools/internal/event/keys" - "golang.org/x/tools/internal/event/label" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug/log" + label1 "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/metric" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/ocagent" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/prometheus" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" ) type contextKeyType int diff --git a/contribs/gnopls/internal/debug/template_test.go b/contribs/gnopls/internal/debug/template_test.go index db940efc602..4dd05454c95 100644 --- a/contribs/gnopls/internal/debug/template_test.go +++ b/contribs/gnopls/internal/debug/template_test.go @@ -21,10 +21,10 @@ import ( "github.com/jba/templatecheck" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) var templates = map[string]struct { @@ -62,7 +62,7 @@ func TestTemplates(t *testing.T) { "GOFLAGS=-mod=mod", ) - pkgs, err := packages.Load(cfg, "golang.org/x/tools/gopls/internal/debug") + pkgs, err := packages.Load(cfg, "github.com/gnolang/gno/contribs/gnopls/internal/debug") if err != nil { t.Fatal(err) } diff --git a/contribs/gnopls/internal/debug/trace.go b/contribs/gnopls/internal/debug/trace.go index 9314a04d241..64914c3f58a 100644 --- a/contribs/gnopls/internal/debug/trace.go +++ b/contribs/gnopls/internal/debug/trace.go @@ -16,10 +16,10 @@ import ( "sync" "time" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/core" - "golang.org/x/tools/internal/event/export" - "golang.org/x/tools/internal/event/label" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" ) // TraceTmpl extends BaseTemplate and renders a TraceResults, e.g. from getData(). diff --git a/contribs/gnopls/internal/diff/diff.go b/contribs/gnopls/internal/diff/diff.go new file mode 100644 index 00000000000..a13547b7a7e --- /dev/null +++ b/contribs/gnopls/internal/diff/diff.go @@ -0,0 +1,176 @@ +// 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. + +// Package diff computes differences between text files or strings. +package diff + +import ( + "fmt" + "sort" + "strings" +) + +// An Edit describes the replacement of a portion of a text file. +type Edit struct { + Start, End int // byte offsets of the region to replace + New string // the replacement +} + +func (e Edit) String() string { + return fmt.Sprintf("{Start:%d,End:%d,New:%q}", e.Start, e.End, e.New) +} + +// Apply applies a sequence of edits to the src buffer and returns the +// result. Edits are applied in order of start offset; edits with the +// same start offset are applied in they order they were provided. +// +// Apply returns an error if any edit is out of bounds, +// or if any pair of edits is overlapping. +func Apply(src string, edits []Edit) (string, error) { + edits, size, err := validate(src, edits) + if err != nil { + return "", err + } + + // Apply edits. + out := make([]byte, 0, size) + lastEnd := 0 + for _, edit := range edits { + if lastEnd < edit.Start { + out = append(out, src[lastEnd:edit.Start]...) + } + out = append(out, edit.New...) + lastEnd = edit.End + } + out = append(out, src[lastEnd:]...) + + if len(out) != size { + panic("wrong size") + } + + return string(out), nil +} + +// ApplyBytes is like Apply, but it accepts a byte slice. +// The result is always a new array. +func ApplyBytes(src []byte, edits []Edit) ([]byte, error) { + res, err := Apply(string(src), edits) + return []byte(res), err +} + +// validate checks that edits are consistent with src, +// and returns the size of the patched output. +// It may return a different slice. +func validate(src string, edits []Edit) ([]Edit, int, error) { + if !sort.IsSorted(editsSort(edits)) { + edits = append([]Edit(nil), edits...) + SortEdits(edits) + } + + // Check validity of edits and compute final size. + size := len(src) + lastEnd := 0 + for _, edit := range edits { + if !(0 <= edit.Start && edit.Start <= edit.End && edit.End <= len(src)) { + return nil, 0, fmt.Errorf("diff has out-of-bounds edits") + } + if edit.Start < lastEnd { + return nil, 0, fmt.Errorf("diff has overlapping edits") + } + size += len(edit.New) + edit.Start - edit.End + lastEnd = edit.End + } + + return edits, size, nil +} + +// SortEdits orders a slice of Edits by (start, end) offset. +// This ordering puts insertions (end = start) before deletions +// (end > start) at the same point, but uses a stable sort to preserve +// the order of multiple insertions at the same point. +// (Apply detects multiple deletions at the same point as an error.) +func SortEdits(edits []Edit) { + sort.Stable(editsSort(edits)) +} + +type editsSort []Edit + +func (a editsSort) Len() int { return len(a) } +func (a editsSort) Less(i, j int) bool { + if cmp := a[i].Start - a[j].Start; cmp != 0 { + return cmp < 0 + } + return a[i].End < a[j].End +} +func (a editsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// lineEdits expands and merges a sequence of edits so that each +// resulting edit replaces one or more complete lines. +// See ApplyEdits for preconditions. +func lineEdits(src string, edits []Edit) ([]Edit, error) { + edits, _, err := validate(src, edits) + if err != nil { + return nil, err + } + + // Do all deletions begin and end at the start of a line, + // and all insertions end with a newline? + // (This is merely a fast path.) + for _, edit := range edits { + if edit.Start >= len(src) || // insertion at EOF + edit.Start > 0 && src[edit.Start-1] != '\n' || // not at line start + edit.End > 0 && src[edit.End-1] != '\n' || // not at line start + edit.New != "" && edit.New[len(edit.New)-1] != '\n' { // partial insert + goto expand // slow path + } + } + return edits, nil // aligned + +expand: + if len(edits) == 0 { + return edits, nil // no edits (unreachable due to fast path) + } + expanded := make([]Edit, 0, len(edits)) // a guess + prev := edits[0] + // TODO(adonovan): opt: start from the first misaligned edit. + // TODO(adonovan): opt: avoid quadratic cost of string += string. + for _, edit := range edits[1:] { + between := src[prev.End:edit.Start] + if !strings.Contains(between, "\n") { + // overlapping lines: combine with previous edit. + prev.New += between + edit.New + prev.End = edit.End + } else { + // non-overlapping lines: flush previous edit. + expanded = append(expanded, expandEdit(prev, src)) + prev = edit + } + } + return append(expanded, expandEdit(prev, src)), nil // flush final edit +} + +// expandEdit returns edit expanded to complete whole lines. +func expandEdit(edit Edit, src string) Edit { + // Expand start left to start of line. + // (delta is the zero-based column number of start.) + start := edit.Start + if delta := start - 1 - strings.LastIndex(src[:start], "\n"); delta > 0 { + edit.Start -= delta + edit.New = src[start-delta:start] + edit.New + } + + // Expand end right to end of line. + end := edit.End + if end > 0 && src[end-1] != '\n' || + edit.New != "" && edit.New[len(edit.New)-1] != '\n' { + if nl := strings.IndexByte(src[end:], '\n'); nl < 0 { + edit.End = len(src) // extend to EOF + } else { + edit.End = end + nl + 1 // extend beyond \n + } + } + edit.New += src[end:edit.End] + + return edit +} diff --git a/contribs/gnopls/internal/diff/diff_test.go b/contribs/gnopls/internal/diff/diff_test.go new file mode 100644 index 00000000000..310ecd932d5 --- /dev/null +++ b/contribs/gnopls/internal/diff/diff_test.go @@ -0,0 +1,207 @@ +// 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. + +package diff_test + +import ( + "bytes" + "math/rand" + "os" + "os/exec" + "path/filepath" + "reflect" + "strings" + "testing" + "unicode/utf8" + + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/diff/difftest" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +func TestApply(t *testing.T) { + for _, tc := range difftest.TestCases { + t.Run(tc.Name, func(t *testing.T) { + got, err := diff.Apply(tc.In, tc.Edits) + if err != nil { + t.Fatalf("Apply(Edits) failed: %v", err) + } + if got != tc.Out { + t.Errorf("Apply(Edits): got %q, want %q", got, tc.Out) + } + if tc.LineEdits != nil { + got, err := diff.Apply(tc.In, tc.LineEdits) + if err != nil { + t.Fatalf("Apply(LineEdits) failed: %v", err) + } + if got != tc.Out { + t.Errorf("Apply(LineEdits): got %q, want %q", got, tc.Out) + } + } + }) + } +} + +func TestNEdits(t *testing.T) { + for _, tc := range difftest.TestCases { + edits := diff.Strings(tc.In, tc.Out) + got, err := diff.Apply(tc.In, edits) + if err != nil { + t.Fatalf("Apply failed: %v", err) + } + if got != tc.Out { + t.Fatalf("%s: got %q wanted %q", tc.Name, got, tc.Out) + } + if len(edits) < len(tc.Edits) { // should find subline edits + t.Errorf("got %v, expected %v for %#v", edits, tc.Edits, tc) + } + } +} + +func TestNRandom(t *testing.T) { + rand.Seed(1) + for i := 0; i < 1000; i++ { + a := randstr("abω", 16) + b := randstr("abωc", 16) + edits := diff.Strings(a, b) + got, err := diff.Apply(a, edits) + if err != nil { + t.Fatalf("Apply failed: %v", err) + } + if got != b { + t.Fatalf("%d: got %q, wanted %q, starting with %q", i, got, b, a) + } + } +} + +// $ go test -fuzz=FuzzRoundTrip ./internal/diff +func FuzzRoundTrip(f *testing.F) { + f.Fuzz(func(t *testing.T, a, b string) { + if !utf8.ValidString(a) || !utf8.ValidString(b) { + return // inputs must be text + } + edits := diff.Strings(a, b) + got, err := diff.Apply(a, edits) + if err != nil { + t.Fatalf("Apply failed: %v", err) + } + if got != b { + t.Fatalf("applying diff(%q, %q) gives %q; edits=%v", a, b, got, edits) + } + }) +} + +func TestLineEdits(t *testing.T) { + for _, tc := range difftest.TestCases { + t.Run(tc.Name, func(t *testing.T) { + want := tc.LineEdits + if want == nil { + want = tc.Edits // already line-aligned + } + got, err := diff.LineEdits(tc.In, tc.Edits) + if err != nil { + t.Fatalf("LineEdits: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("in=<<%s>>\nout=<<%s>>\nraw edits=%s\nline edits=%s\nwant: %s", + tc.In, tc.Out, tc.Edits, got, want) + } + // make sure that applying the edits gives the expected result + fixed, err := diff.Apply(tc.In, got) + if err != nil { + t.Error(err) + } + if fixed != tc.Out { + t.Errorf("Apply(LineEdits): got %q, want %q", fixed, tc.Out) + } + }) + } +} + +func TestToUnified(t *testing.T) { + testenv.NeedsTool(t, "patch") + for _, tc := range difftest.TestCases { + t.Run(tc.Name, func(t *testing.T) { + unified, err := diff.ToUnified(difftest.FileA, difftest.FileB, tc.In, tc.Edits, diff.DefaultContextLines) + if err != nil { + t.Fatal(err) + } + if unified == "" { + return + } + orig := filepath.Join(t.TempDir(), "original") + err = os.WriteFile(orig, []byte(tc.In), 0644) + if err != nil { + t.Fatal(err) + } + temp := filepath.Join(t.TempDir(), "patched") + err = os.WriteFile(temp, []byte(tc.In), 0644) + if err != nil { + t.Fatal(err) + } + cmd := exec.Command("patch", "-p0", "-u", "-s", "-o", temp, orig) + cmd.Stdin = strings.NewReader(unified) + cmd.Stdout = new(bytes.Buffer) + cmd.Stderr = new(bytes.Buffer) + if err = cmd.Run(); err != nil { + t.Fatalf("%v: %q (%q) (%q)", err, cmd.String(), + cmd.Stderr, cmd.Stdout) + } + got, err := os.ReadFile(temp) + if err != nil { + t.Fatal(err) + } + if string(got) != tc.Out { + t.Errorf("applying unified failed: got\n%q, wanted\n%q unified\n%q", + got, tc.Out, unified) + } + + }) + } +} + +func TestRegressionOld001(t *testing.T) { + a := "// Copyright 2019 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage diff_test\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"golang.org/x/tools/gopls/internal/lsp/diff\"\n\t\"golang.org/x/tools/internal/diff/difftest\"\n\t\"golang.org/x/tools/gopls/internal/span\"\n)\n" + + b := "// Copyright 2019 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage diff_test\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/safehtml/template\"\n\t\"golang.org/x/tools/gopls/internal/lsp/diff\"\n\t\"golang.org/x/tools/internal/diff/difftest\"\n\t\"golang.org/x/tools/gopls/internal/span\"\n)\n" + diffs := diff.Strings(a, b) + got, err := diff.Apply(a, diffs) + if err != nil { + t.Fatalf("Apply failed: %v", err) + } + if got != b { + i := 0 + for ; i < len(a) && i < len(b) && got[i] == b[i]; i++ { + } + t.Errorf("oops %vd\n%q\n%q", diffs, got, b) + t.Errorf("\n%q\n%q", got[i:], b[i:]) + } +} + +func TestRegressionOld002(t *testing.T) { + a := "n\"\n)\n" + b := "n\"\n\t\"golang.org/x//nnal/stack\"\n)\n" + diffs := diff.Strings(a, b) + got, err := diff.Apply(a, diffs) + if err != nil { + t.Fatalf("Apply failed: %v", err) + } + if got != b { + i := 0 + for ; i < len(a) && i < len(b) && got[i] == b[i]; i++ { + } + t.Errorf("oops %vd\n%q\n%q", diffs, got, b) + t.Errorf("\n%q\n%q", got[i:], b[i:]) + } +} + +// return a random string of length n made of characters from s +func randstr(s string, n int) string { + src := []rune(s) + x := make([]rune, n) + for i := 0; i < n; i++ { + x[i] = src[rand.Intn(len(src))] + } + return string(x) +} diff --git a/contribs/gnopls/internal/diff/difftest/difftest.go b/contribs/gnopls/internal/diff/difftest/difftest.go new file mode 100644 index 00000000000..07195e706e0 --- /dev/null +++ b/contribs/gnopls/internal/diff/difftest/difftest.go @@ -0,0 +1,324 @@ +// 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. + +// Package difftest supplies a set of tests that will operate on any +// implementation of a diff algorithm as exposed by +// "github.com/gnolang/gno/contribs/gnopls/internal/diff" +package difftest + +// There are two kinds of tests, semantic tests, and 'golden data' tests. +// The semantic tests check that the computed diffs transform the input to +// the output, and that 'patch' accepts the computed unified diffs. +// The other tests just check that Edits and LineEdits haven't changed +// unexpectedly. These fields may need to be changed when the diff algorithm +// changes. + +import ( + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/diff" +) + +const ( + FileA = "from" + FileB = "to" + UnifiedPrefix = "--- " + FileA + "\n+++ " + FileB + "\n" +) + +var TestCases = []struct { + Name, In, Out, Unified string + Edits, LineEdits []diff.Edit // expectation (LineEdits=nil => already line-aligned) + NoDiff bool +}{{ + Name: "empty", + In: "", + Out: "", +}, { + Name: "no_diff", + In: "gargantuan\n", + Out: "gargantuan\n", +}, { + Name: "replace_all", + In: "fruit\n", + Out: "cheese\n", + Unified: UnifiedPrefix + ` +@@ -1 +1 @@ +-fruit ++cheese +`[1:], + Edits: []diff.Edit{{Start: 0, End: 5, New: "cheese"}}, + LineEdits: []diff.Edit{{Start: 0, End: 6, New: "cheese\n"}}, +}, { + Name: "insert_rune", + In: "gord\n", + Out: "gourd\n", + Unified: UnifiedPrefix + ` +@@ -1 +1 @@ +-gord ++gourd +`[1:], + Edits: []diff.Edit{{Start: 2, End: 2, New: "u"}}, + LineEdits: []diff.Edit{{Start: 0, End: 5, New: "gourd\n"}}, +}, { + Name: "delete_rune", + In: "groat\n", + Out: "goat\n", + Unified: UnifiedPrefix + ` +@@ -1 +1 @@ +-groat ++goat +`[1:], + Edits: []diff.Edit{{Start: 1, End: 2, New: ""}}, + LineEdits: []diff.Edit{{Start: 0, End: 6, New: "goat\n"}}, +}, { + Name: "replace_rune", + In: "loud\n", + Out: "lord\n", + Unified: UnifiedPrefix + ` +@@ -1 +1 @@ +-loud ++lord +`[1:], + Edits: []diff.Edit{{Start: 2, End: 3, New: "r"}}, + LineEdits: []diff.Edit{{Start: 0, End: 5, New: "lord\n"}}, +}, { + Name: "replace_partials", + In: "blanket\n", + Out: "bunker\n", + Unified: UnifiedPrefix + ` +@@ -1 +1 @@ +-blanket ++bunker +`[1:], + Edits: []diff.Edit{ + {Start: 1, End: 3, New: "u"}, + {Start: 6, End: 7, New: "r"}, + }, + LineEdits: []diff.Edit{{Start: 0, End: 8, New: "bunker\n"}}, +}, { + Name: "insert_line", + In: "1: one\n3: three\n", + Out: "1: one\n2: two\n3: three\n", + Unified: UnifiedPrefix + ` +@@ -1,2 +1,3 @@ + 1: one ++2: two + 3: three +`[1:], + Edits: []diff.Edit{{Start: 7, End: 7, New: "2: two\n"}}, +}, { + Name: "replace_no_newline", + In: "A", + Out: "B", + Unified: UnifiedPrefix + ` +@@ -1 +1 @@ +-A +\ No newline at end of file ++B +\ No newline at end of file +`[1:], + Edits: []diff.Edit{{Start: 0, End: 1, New: "B"}}, +}, { + Name: "delete_empty", + In: "meow", + Out: "", // GNU diff -u special case: +0,0 + Unified: UnifiedPrefix + ` +@@ -1 +0,0 @@ +-meow +\ No newline at end of file +`[1:], + Edits: []diff.Edit{{Start: 0, End: 4, New: ""}}, + LineEdits: []diff.Edit{{Start: 0, End: 4, New: ""}}, +}, { + Name: "append_empty", + In: "", // GNU diff -u special case: -0,0 + Out: "AB\nC", + Unified: UnifiedPrefix + ` +@@ -0,0 +1,2 @@ ++AB ++C +\ No newline at end of file +`[1:], + Edits: []diff.Edit{{Start: 0, End: 0, New: "AB\nC"}}, + LineEdits: []diff.Edit{{Start: 0, End: 0, New: "AB\nC"}}, +}, + // TODO(adonovan): fix this test: GNU diff -u prints "+1,2", Unifies prints "+1,3". + // { + // Name: "add_start", + // In: "A", + // Out: "B\nCA", + // Unified: UnifiedPrefix + ` + // @@ -1 +1,2 @@ + // -A + // \ No newline at end of file + // +B + // +CA + // \ No newline at end of file + // `[1:], + // Edits: []diff.TextEdit{{Span: newSpan(0, 0), NewText: "B\nC"}}, + // LineEdits: []diff.TextEdit{{Span: newSpan(0, 0), NewText: "B\nC"}}, + // }, + { + Name: "add_end", + In: "A", + Out: "AB", + Unified: UnifiedPrefix + ` +@@ -1 +1 @@ +-A +\ No newline at end of file ++AB +\ No newline at end of file +`[1:], + Edits: []diff.Edit{{Start: 1, End: 1, New: "B"}}, + LineEdits: []diff.Edit{{Start: 0, End: 1, New: "AB"}}, + }, { + Name: "add_empty", + In: "", + Out: "AB\nC", + Unified: UnifiedPrefix + ` +@@ -0,0 +1,2 @@ ++AB ++C +\ No newline at end of file +`[1:], + Edits: []diff.Edit{{Start: 0, End: 0, New: "AB\nC"}}, + LineEdits: []diff.Edit{{Start: 0, End: 0, New: "AB\nC"}}, + }, { + Name: "add_newline", + In: "A", + Out: "A\n", + Unified: UnifiedPrefix + ` +@@ -1 +1 @@ +-A +\ No newline at end of file ++A +`[1:], + Edits: []diff.Edit{{Start: 1, End: 1, New: "\n"}}, + LineEdits: []diff.Edit{{Start: 0, End: 1, New: "A\n"}}, + }, { + Name: "delete_front", + In: "A\nB\nC\nA\nB\nB\nA\n", + Out: "C\nB\nA\nB\nA\nC\n", + Unified: UnifiedPrefix + ` +@@ -1,7 +1,6 @@ +-A +-B + C ++B + A + B +-B + A ++C +`[1:], + NoDiff: true, // unified diff is different but valid + Edits: []diff.Edit{ + {Start: 0, End: 4, New: ""}, + {Start: 6, End: 6, New: "B\n"}, + {Start: 10, End: 12, New: ""}, + {Start: 14, End: 14, New: "C\n"}, + }, + LineEdits: []diff.Edit{ + {Start: 0, End: 4, New: ""}, + {Start: 6, End: 6, New: "B\n"}, + {Start: 10, End: 12, New: ""}, + {Start: 14, End: 14, New: "C\n"}, + }, + }, { + Name: "replace_last_line", + In: "A\nB\n", + Out: "A\nC\n\n", + Unified: UnifiedPrefix + ` +@@ -1,2 +1,3 @@ + A +-B ++C ++ +`[1:], + Edits: []diff.Edit{{Start: 2, End: 3, New: "C\n"}}, + LineEdits: []diff.Edit{{Start: 2, End: 4, New: "C\n\n"}}, + }, + { + Name: "multiple_replace", + In: "A\nB\nC\nD\nE\nF\nG\n", + Out: "A\nH\nI\nJ\nE\nF\nK\n", + Unified: UnifiedPrefix + ` +@@ -1,7 +1,7 @@ + A +-B +-C +-D ++H ++I ++J + E + F +-G ++K +`[1:], + Edits: []diff.Edit{ + {Start: 2, End: 8, New: "H\nI\nJ\n"}, + {Start: 12, End: 14, New: "K\n"}, + }, + NoDiff: true, // diff algorithm produces different delete/insert pattern + }, + { + Name: "extra_newline", + In: "\nA\n", + Out: "A\n", + Edits: []diff.Edit{{Start: 0, End: 1, New: ""}}, + Unified: UnifiedPrefix + `@@ -1,2 +1 @@ +- + A +`, + }, { + Name: "unified_lines", + In: "aaa\nccc\n", + Out: "aaa\nbbb\nccc\n", + Edits: []diff.Edit{{Start: 3, End: 3, New: "\nbbb"}}, + LineEdits: []diff.Edit{{Start: 0, End: 4, New: "aaa\nbbb\n"}}, + Unified: UnifiedPrefix + "@@ -1,2 +1,3 @@\n aaa\n+bbb\n ccc\n", + }, { + Name: "60379", + In: `package a + +type S struct { +s fmt.Stringer +} +`, + Out: `package a + +type S struct { + s fmt.Stringer +} +`, + Edits: []diff.Edit{{Start: 27, End: 27, New: "\t"}}, + LineEdits: []diff.Edit{{Start: 27, End: 42, New: "\ts fmt.Stringer\n"}}, + Unified: UnifiedPrefix + "@@ -1,5 +1,5 @@\n package a\n \n type S struct {\n-s fmt.Stringer\n+\ts fmt.Stringer\n }\n", + }, +} + +func DiffTest(t *testing.T, compute func(before, after string) []diff.Edit) { + for _, test := range TestCases { + t.Run(test.Name, func(t *testing.T) { + edits := compute(test.In, test.Out) + got, err := diff.Apply(test.In, edits) + if err != nil { + t.Fatalf("Apply failed: %v", err) + } + unified, err := diff.ToUnified(FileA, FileB, test.In, edits, diff.DefaultContextLines) + if err != nil { + t.Fatalf("ToUnified: %v", err) + } + if got != test.Out { + t.Errorf("Apply: got patched:\n%v\nfrom diff:\n%v\nexpected:\n%v", + got, unified, test.Out) + } + if !test.NoDiff && unified != test.Unified { + t.Errorf("Unified: got diff:\n%q\nexpected:\n%q diffs:%v", + unified, test.Unified, edits) + } + }) + } +} diff --git a/contribs/gnopls/internal/diff/difftest/difftest_test.go b/contribs/gnopls/internal/diff/difftest/difftest_test.go new file mode 100644 index 00000000000..b5184dcf748 --- /dev/null +++ b/contribs/gnopls/internal/diff/difftest/difftest_test.go @@ -0,0 +1,88 @@ +// 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. + +// Package difftest supplies a set of tests that will operate on any +// implementation of a diff algorithm as exposed by +// "github.com/gnolang/gno/contribs/gnopls/internal/diff" +package difftest_test + +import ( + "fmt" + "os" + "os/exec" + "strings" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/diff/difftest" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +func TestVerifyUnified(t *testing.T) { + testenv.NeedsTool(t, "diff") + for _, test := range difftest.TestCases { + t.Run(test.Name, func(t *testing.T) { + if test.NoDiff { + t.Skip("diff tool produces expected different results") + } + diff, err := getDiffOutput(test.In, test.Out) + if err != nil { + t.Fatal(err) + } + if len(diff) > 0 { + diff = difftest.UnifiedPrefix + diff + } + if diff != test.Unified { + t.Errorf("unified:\n%s\ndiff -u:\n%s", test.Unified, diff) + } + }) + } +} + +func getDiffOutput(a, b string) (string, error) { + fileA, err := os.CreateTemp("", "myers.in") + if err != nil { + return "", err + } + defer os.Remove(fileA.Name()) + if _, err := fileA.Write([]byte(a)); err != nil { + return "", err + } + if err := fileA.Close(); err != nil { + return "", err + } + fileB, err := os.CreateTemp("", "myers.in") + if err != nil { + return "", err + } + defer os.Remove(fileB.Name()) + if _, err := fileB.Write([]byte(b)); err != nil { + return "", err + } + if err := fileB.Close(); err != nil { + return "", err + } + cmd := exec.Command("diff", "-u", fileA.Name(), fileB.Name()) + cmd.Env = append(cmd.Env, "LANG=en_US.UTF-8") + out, err := cmd.Output() + if err != nil { + exit, ok := err.(*exec.ExitError) + if !ok { + return "", fmt.Errorf("can't exec %s: %v", cmd, err) + } + if len(out) == 0 { + // Nonzero exit with no output: terminated by signal? + return "", fmt.Errorf("%s failed: %v; stderr:\n%s", cmd, err, exit.Stderr) + } + // nonzero exit + output => files differ + } + diff := string(out) + if len(diff) <= 0 { + return diff, nil + } + bits := strings.SplitN(diff, "\n", 3) + if len(bits) != 3 { + return "", fmt.Errorf("diff output did not have file prefix:\n%s", diff) + } + return bits[2], nil +} diff --git a/contribs/gnopls/internal/diff/export_test.go b/contribs/gnopls/internal/diff/export_test.go new file mode 100644 index 00000000000..eedf0dd77ba --- /dev/null +++ b/contribs/gnopls/internal/diff/export_test.go @@ -0,0 +1,9 @@ +// 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 diff + +// This file exports some private declarations to tests. + +var LineEdits = lineEdits diff --git a/contribs/gnopls/internal/diff/lcs/common.go b/contribs/gnopls/internal/diff/lcs/common.go new file mode 100644 index 00000000000..c3e82dd2683 --- /dev/null +++ b/contribs/gnopls/internal/diff/lcs/common.go @@ -0,0 +1,179 @@ +// 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 lcs + +import ( + "log" + "sort" +) + +// lcs is a longest common sequence +type lcs []diag + +// A diag is a piece of the edit graph where A[X+i] == B[Y+i], for 0<=i<Len. +// All computed diagonals are parts of a longest common subsequence. +type diag struct { + X, Y int + Len int +} + +// sort sorts in place, by lowest X, and if tied, inversely by Len +func (l lcs) sort() lcs { + sort.Slice(l, func(i, j int) bool { + if l[i].X != l[j].X { + return l[i].X < l[j].X + } + return l[i].Len > l[j].Len + }) + return l +} + +// validate that the elements of the lcs do not overlap +// (can only happen when the two-sided algorithm ends early) +// expects the lcs to be sorted +func (l lcs) valid() bool { + for i := 1; i < len(l); i++ { + if l[i-1].X+l[i-1].Len > l[i].X { + return false + } + if l[i-1].Y+l[i-1].Len > l[i].Y { + return false + } + } + return true +} + +// repair overlapping lcs +// only called if two-sided stops early +func (l lcs) fix() lcs { + // from the set of diagonals in l, find a maximal non-conflicting set + // this problem may be NP-complete, but we use a greedy heuristic, + // which is quadratic, but with a better data structure, could be D log D. + // indepedent is not enough: {0,3,1} and {3,0,2} can't both occur in an lcs + // which has to have monotone x and y + if len(l) == 0 { + return nil + } + sort.Slice(l, func(i, j int) bool { return l[i].Len > l[j].Len }) + tmp := make(lcs, 0, len(l)) + tmp = append(tmp, l[0]) + for i := 1; i < len(l); i++ { + var dir direction + nxt := l[i] + for _, in := range tmp { + if dir, nxt = overlap(in, nxt); dir == empty || dir == bad { + break + } + } + if nxt.Len > 0 && dir != bad { + tmp = append(tmp, nxt) + } + } + tmp.sort() + if false && !tmp.valid() { // debug checking + log.Fatalf("here %d", len(tmp)) + } + return tmp +} + +type direction int + +const ( + empty direction = iota // diag is empty (so not in lcs) + leftdown // proposed acceptably to the left and below + rightup // proposed diag is acceptably to the right and above + bad // proposed diag is inconsistent with the lcs so far +) + +// overlap trims the proposed diag prop so it doesn't overlap with +// the existing diag that has already been added to the lcs. +func overlap(exist, prop diag) (direction, diag) { + if prop.X <= exist.X && exist.X < prop.X+prop.Len { + // remove the end of prop where it overlaps with the X end of exist + delta := prop.X + prop.Len - exist.X + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + } + if exist.X <= prop.X && prop.X < exist.X+exist.Len { + // remove the beginning of prop where overlaps with exist + delta := exist.X + exist.Len - prop.X + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + prop.X += delta + prop.Y += delta + } + if prop.Y <= exist.Y && exist.Y < prop.Y+prop.Len { + // remove the end of prop that overlaps (in Y) with exist + delta := prop.Y + prop.Len - exist.Y + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + } + if exist.Y <= prop.Y && prop.Y < exist.Y+exist.Len { + // remove the beginning of peop that overlaps with exist + delta := exist.Y + exist.Len - prop.Y + prop.Len -= delta + if prop.Len <= 0 { + return empty, prop + } + prop.X += delta // no test reaches this code + prop.Y += delta + } + if prop.X+prop.Len <= exist.X && prop.Y+prop.Len <= exist.Y { + return leftdown, prop + } + if exist.X+exist.Len <= prop.X && exist.Y+exist.Len <= prop.Y { + return rightup, prop + } + // prop can't be in an lcs that contains exist + return bad, prop +} + +// manipulating Diag and lcs + +// prepend a diagonal (x,y)-(x+1,y+1) segment either to an empty lcs +// or to its first Diag. prepend is only called to extend diagonals +// the backward direction. +func (lcs lcs) prepend(x, y int) lcs { + if len(lcs) > 0 { + d := &lcs[0] + if int(d.X) == x+1 && int(d.Y) == y+1 { + // extend the diagonal down and to the left + d.X, d.Y = int(x), int(y) + d.Len++ + return lcs + } + } + + r := diag{X: int(x), Y: int(y), Len: 1} + lcs = append([]diag{r}, lcs...) + return lcs +} + +// append appends a diagonal, or extends the existing one. +// by adding the edge (x,y)-(x+1.y+1). append is only called +// to extend diagonals in the forward direction. +func (lcs lcs) append(x, y int) lcs { + if len(lcs) > 0 { + last := &lcs[len(lcs)-1] + // Expand last element if adjoining. + if last.X+last.Len == x && last.Y+last.Len == y { + last.Len++ + return lcs + } + } + + return append(lcs, diag{X: x, Y: y, Len: 1}) +} + +// enforce constraint on d, k +func ok(d, k int) bool { + return d >= 0 && -d <= k && k <= d +} diff --git a/contribs/gnopls/internal/diff/lcs/common_test.go b/contribs/gnopls/internal/diff/lcs/common_test.go new file mode 100644 index 00000000000..f19245e404c --- /dev/null +++ b/contribs/gnopls/internal/diff/lcs/common_test.go @@ -0,0 +1,140 @@ +// 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 lcs + +import ( + "log" + "math/rand" + "strings" + "testing" +) + +type Btest struct { + a, b string + lcs []string +} + +var Btests = []Btest{ + {"aaabab", "abaab", []string{"abab", "aaab"}}, + {"aabbba", "baaba", []string{"aaba"}}, + {"cabbx", "cbabx", []string{"cabx", "cbbx"}}, + {"c", "cb", []string{"c"}}, + {"aaba", "bbb", []string{"b"}}, + {"bbaabb", "b", []string{"b"}}, + {"baaabb", "bbaba", []string{"bbb", "baa", "bab"}}, + {"baaabb", "abbab", []string{"abb", "bab", "aab"}}, + {"baaba", "aaabba", []string{"aaba"}}, + {"ca", "cba", []string{"ca"}}, + {"ccbcbc", "abba", []string{"bb"}}, + {"ccbcbc", "aabba", []string{"bb"}}, + {"ccb", "cba", []string{"cb"}}, + {"caef", "axe", []string{"ae"}}, + {"bbaabb", "baabb", []string{"baabb"}}, + // Example from Myers: + {"abcabba", "cbabac", []string{"caba", "baba", "cbba"}}, + {"3456aaa", "aaa", []string{"aaa"}}, + {"aaa", "aaa123", []string{"aaa"}}, + {"aabaa", "aacaa", []string{"aaaa"}}, + {"1a", "a", []string{"a"}}, + {"abab", "bb", []string{"bb"}}, + {"123", "ab", []string{""}}, + {"a", "b", []string{""}}, + {"abc", "123", []string{""}}, + {"aa", "aa", []string{"aa"}}, + {"abcde", "12345", []string{""}}, + {"aaa3456", "aaa", []string{"aaa"}}, + {"abcde", "12345a", []string{"a"}}, + {"ab", "123", []string{""}}, + {"1a2", "a", []string{"a"}}, + // for two-sided + {"babaab", "cccaba", []string{"aba"}}, + {"aabbab", "cbcabc", []string{"bab"}}, + {"abaabb", "bcacab", []string{"baab"}}, + {"abaabb", "abaaaa", []string{"abaa"}}, + {"bababb", "baaabb", []string{"baabb"}}, + {"abbbaa", "cabacc", []string{"aba"}}, + {"aabbaa", "aacaba", []string{"aaaa", "aaba"}}, +} + +func init() { + log.SetFlags(log.Lshortfile) +} + +func check(t *testing.T, str string, lcs lcs, want []string) { + t.Helper() + if !lcs.valid() { + t.Errorf("bad lcs %v", lcs) + } + var got strings.Builder + for _, dd := range lcs { + got.WriteString(str[dd.X : dd.X+dd.Len]) + } + ans := got.String() + for _, w := range want { + if ans == w { + return + } + } + t.Fatalf("str=%q lcs=%v want=%q got=%q", str, lcs, want, ans) +} + +func checkDiffs(t *testing.T, before string, diffs []Diff, after string) { + t.Helper() + var ans strings.Builder + sofar := 0 // index of position in before + for _, d := range diffs { + if sofar < d.Start { + ans.WriteString(before[sofar:d.Start]) + } + ans.WriteString(after[d.ReplStart:d.ReplEnd]) + sofar = d.End + } + ans.WriteString(before[sofar:]) + if ans.String() != after { + t.Fatalf("diff %v took %q to %q, not to %q", diffs, before, ans.String(), after) + } +} + +func lcslen(l lcs) int { + ans := 0 + for _, d := range l { + ans += int(d.Len) + } + return ans +} + +// return a random string of length n made of characters from s +func randstr(s string, n int) string { + src := []rune(s) + x := make([]rune, n) + for i := 0; i < n; i++ { + x[i] = src[rand.Intn(len(src))] + } + return string(x) +} + +func TestLcsFix(t *testing.T) { + tests := []struct{ before, after lcs }{ + {lcs{diag{0, 0, 3}, diag{2, 2, 5}, diag{3, 4, 5}, diag{8, 9, 4}}, lcs{diag{0, 0, 2}, diag{2, 2, 1}, diag{3, 4, 5}, diag{8, 9, 4}}}, + {lcs{diag{1, 1, 6}, diag{6, 12, 3}}, lcs{diag{1, 1, 5}, diag{6, 12, 3}}}, + {lcs{diag{0, 0, 4}, diag{3, 5, 4}}, lcs{diag{0, 0, 3}, diag{3, 5, 4}}}, + {lcs{diag{0, 20, 1}, diag{0, 0, 3}, diag{1, 20, 4}}, lcs{diag{0, 0, 3}, diag{3, 22, 2}}}, + {lcs{diag{0, 0, 4}, diag{1, 1, 2}}, lcs{diag{0, 0, 4}}}, + {lcs{diag{0, 0, 4}}, lcs{diag{0, 0, 4}}}, + {lcs{}, lcs{}}, + {lcs{diag{0, 0, 4}, diag{1, 1, 6}, diag{3, 3, 2}}, lcs{diag{0, 0, 1}, diag{1, 1, 6}}}, + } + for n, x := range tests { + got := x.before.fix() + if len(got) != len(x.after) { + t.Errorf("got %v, expected %v, for %v", got, x.after, x.before) + } + olen := lcslen(x.after) + glen := lcslen(got) + if olen != glen { + t.Errorf("%d: lens(%d,%d) differ, %v, %v, %v", n, glen, olen, got, x.after, x.before) + } + } +} diff --git a/contribs/gnopls/internal/diff/lcs/doc.go b/contribs/gnopls/internal/diff/lcs/doc.go new file mode 100644 index 00000000000..9029dd20b3d --- /dev/null +++ b/contribs/gnopls/internal/diff/lcs/doc.go @@ -0,0 +1,156 @@ +// 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 lcs contains code to find longest-common-subsequences +// (and diffs) +package lcs + +/* +Compute longest-common-subsequences of two slices A, B using +algorithms from Myers' paper. A longest-common-subsequence +(LCS from now on) of A and B is a maximal set of lexically increasing +pairs of subscripts (x,y) with A[x]==B[y]. There may be many LCS, but +they all have the same length. An LCS determines a sequence of edits +that changes A into B. + +The key concept is the edit graph of A and B. +If A has length N and B has length M, then the edit graph has +vertices v[i][j] for 0 <= i <= N, 0 <= j <= M. There is a +horizontal edge from v[i][j] to v[i+1][j] whenever both are in +the graph, and a vertical edge from v[i][j] to f[i][j+1] similarly. +When A[i] == B[j] there is a diagonal edge from v[i][j] to v[i+1][j+1]. + +A path between in the graph between (0,0) and (N,M) determines a sequence +of edits converting A into B: each horizontal edge corresponds to removing +an element of A, and each vertical edge corresponds to inserting an +element of B. + +A vertex (x,y) is on (forward) diagonal k if x-y=k. A path in the graph +is of length D if it has D non-diagonal edges. The algorithms generate +forward paths (in which at least one of x,y increases at each edge), +or backward paths (in which at least one of x,y decreases at each edge), +or a combination. (Note that the orientation is the traditional mathematical one, +with the origin in the lower-left corner.) + +Here is the edit graph for A:"aabbaa", B:"aacaba". (I know the diagonals look weird.) + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + b | | | ___/‾‾‾ | ___/‾‾‾ | | | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + c | | | | | | | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a a b b a a + + +The algorithm labels a vertex (x,y) with D,k if it is on diagonal k and at +the end of a maximal path of length D. (Because x-y=k it suffices to remember +only the x coordinate of the vertex.) + +The forward algorithm: Find the longest diagonal starting at (0,0) and +label its end with D=0,k=0. From that vertex take a vertical step and +then follow the longest diagonal (up and to the right), and label that vertex +with D=1,k=-1. From the D=0,k=0 point take a horizontal step and the follow +the longest diagonal (up and to the right) and label that vertex +D=1,k=1. In the same way, having labelled all the D vertices, +from a vertex labelled D,k find two vertices +tentatively labelled D+1,k-1 and D+1,k+1. There may be two on the same +diagonal, in which case take the one with the larger x. + +Eventually the path gets to (N,M), and the diagonals on it are the LCS. + +Here is the edit graph with the ends of D-paths labelled. (So, for instance, +0/2,2 indicates that x=2,y=2 is labelled with 0, as it should be, since the first +step is to go up the longest diagonal from (0,0).) +A:"aabbaa", B:"aacaba" + ⊙ ------- ⊙ ------- ⊙ -------(3/3,6)------- ⊙ -------(3/5,6)-------(4/6,6) + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ -------(2/3,5)------- ⊙ ------- ⊙ ------- ⊙ + b | | | ___/‾‾‾ | ___/‾‾‾ | | | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ -------(3/5,4)------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ -------(1/2,3)-------(2/3,3)------- ⊙ ------- ⊙ ------- ⊙ + c | | | | | | | + ⊙ ------- ⊙ -------(0/2,2)-------(1/3,2)-------(2/4,2)-------(3/5,2)-------(4/6,2) + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a | ___/‾‾‾ | ___/‾‾‾ | | | ___/‾‾‾ | ___/‾‾‾ | + ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ ------- ⊙ + a a b b a a + +The 4-path is reconstructed starting at (4/6,6), horizontal to (3/5,6), diagonal to (3,4), vertical +to (2/3,3), horizontal to (1/2,3), vertical to (0/2,2), and diagonal to (0,0). As expected, +there are 4 non-diagonal steps, and the diagonals form an LCS. + +There is a symmetric backward algorithm, which gives (backwards labels are prefixed with a colon): +A:"aabbaa", B:"aacaba" + ⊙ -------- ⊙ -------- ⊙ -------- ⊙ -------- ⊙ -------- ⊙ -------- ⊙ + a | ____/‾‾‾ | ____/‾‾‾ | | | ____/‾‾‾ | ____/‾‾‾ | + ⊙ -------- ⊙ -------- ⊙ -------- ⊙ -------- ⊙ --------(:0/5,5)-------- ⊙ + b | | | ____/‾‾‾ | ____/‾‾‾ | | | + ⊙ -------- ⊙ -------- ⊙ --------(:1/3,4)-------- ⊙ -------- ⊙ -------- ⊙ + a | ____/‾‾‾ | ____/‾‾‾ | | | ____/‾‾‾ | ____/‾‾‾ | + (:3/0,3)--------(:2/1,3)-------- ⊙ --------(:2/3,3)--------(:1/4,3)-------- ⊙ -------- ⊙ + c | | | | | | | + ⊙ -------- ⊙ -------- ⊙ --------(:3/3,2)--------(:2/4,2)-------- ⊙ -------- ⊙ + a | ____/‾‾‾ | ____/‾‾‾ | | | ____/‾‾‾ | ____/‾‾‾ | + (:3/0,1)-------- ⊙ -------- ⊙ -------- ⊙ --------(:3/4,1)-------- ⊙ -------- ⊙ + a | ____/‾‾‾ | ____/‾‾‾ | | | ____/‾‾‾ | ____/‾‾‾ | + (:4/0,0)-------- ⊙ -------- ⊙ -------- ⊙ --------(:4/4,0)-------- ⊙ -------- ⊙ + a a b b a a + +Neither of these is ideal for use in an editor, where it is undesirable to send very long diffs to the +front end. It's tricky to decide exactly what 'very long diffs' means, as "replace A by B" is very short. +We want to control how big D can be, by stopping when it gets too large. The forward algorithm then +privileges common prefixes, and the backward algorithm privileges common suffixes. Either is an undesirable +asymmetry. + +Fortunately there is a two-sided algorithm, implied by results in Myers' paper. Here's what the labels in +the edit graph look like. +A:"aabbaa", B:"aacaba" + ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ + a | ____/‾‾‾‾ | ____/‾‾‾‾ | | | ____/‾‾‾‾ | ____/‾‾‾‾ | + ⊙ --------- ⊙ --------- ⊙ --------- (2/3,5) --------- ⊙ --------- (:0/5,5)--------- ⊙ + b | | | ____/‾‾‾‾ | ____/‾‾‾‾ | | | + ⊙ --------- ⊙ --------- ⊙ --------- (:1/3,4)--------- ⊙ --------- ⊙ --------- ⊙ + a | ____/‾‾‾‾ | ____/‾‾‾‾ | | | ____/‾‾‾‾ | ____/‾‾‾‾ | + ⊙ --------- (:2/1,3)--------- (1/2,3) ---------(2:2/3,3)--------- (:1/4,3)--------- ⊙ --------- ⊙ + c | | | | | | | + ⊙ --------- ⊙ --------- (0/2,2) --------- (1/3,2) ---------(2:2/4,2)--------- ⊙ --------- ⊙ + a | ____/‾‾‾‾ | ____/‾‾‾‾ | | | ____/‾‾‾‾ | ____/‾‾‾‾ | + ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ + a | ____/‾‾‾‾ | ____/‾‾‾‾ | | | ____/‾‾‾‾ | ____/‾‾‾‾ | + ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ --------- ⊙ + a a b b a a + +The algorithm stopped when it saw the backwards 2-path ending at (1,3) and the forwards 2-path ending at (3,5). The criterion +is a backwards path ending at (u,v) and a forward path ending at (x,y), where u <= x and the two points are on the same +diagonal. (Here the edgegraph has a diagonal, but the criterion is x-y=u-v.) Myers proves there is a forward +2-path from (0,0) to (1,3), and that together with the backwards 2-path ending at (1,3) gives the expected 4-path. +Unfortunately the forward path has to be constructed by another run of the forward algorithm; it can't be found from the +computed labels. That is the worst case. Had the code noticed (x,y)=(u,v)=(3,3) the whole path could be reconstructed +from the edgegraph. The implementation looks for a number of special cases to try to avoid computing an extra forward path. + +If the two-sided algorithm has stop early (because D has become too large) it will have found a forward LCS and a +backwards LCS. Ideally these go with disjoint prefixes and suffixes of A and B, but disjointness may fail and the two +computed LCS may conflict. (An easy example is where A is a suffix of B, and shares a short prefix. The backwards LCS +is all of A, and the forward LCS is a prefix of A.) The algorithm combines the two +to form a best-effort LCS. In the worst case the forward partial LCS may have to +be recomputed. +*/ + +/* Eugene Myers paper is titled +"An O(ND) Difference Algorithm and Its Variations" +and can be found at +http://www.xmailserver.org/diff2.pdf + +(There is a generic implementation of the algorithm the repository with git hash +b9ad7e4ade3a686d608e44475390ad428e60e7fc) +*/ diff --git a/contribs/gnopls/internal/diff/lcs/git.sh b/contribs/gnopls/internal/diff/lcs/git.sh new file mode 100644 index 00000000000..b25ba4aac74 --- /dev/null +++ b/contribs/gnopls/internal/diff/lcs/git.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# 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. +# +# Creates a zip file containing all numbered versions +# of the commit history of a large source file, for use +# as input data for the tests of the diff algorithm. +# +# Run script from root of the x/tools repo. + +set -eu + +# WARNING: This script will install the latest version of $file +# The largest real source file in the x/tools repo. +# file=internal/golang/completion/completion.go +# file=internal/golang/diagnostics.go +file=internal/protocol/tsprotocol.go + +tmp=$(mktemp -d) +git log $file | + awk '/^commit / {print $2}' | + nl -ba -nrz | + while read n hash; do + git checkout --quiet $hash $file + cp -f $file $tmp/$n + done +(cd $tmp && zip -q - *) > testdata.zip +rm -fr $tmp +git restore --staged $file +git restore $file +echo "Created testdata.zip" diff --git a/contribs/gnopls/internal/diff/lcs/labels.go b/contribs/gnopls/internal/diff/lcs/labels.go new file mode 100644 index 00000000000..504913d1da3 --- /dev/null +++ b/contribs/gnopls/internal/diff/lcs/labels.go @@ -0,0 +1,55 @@ +// 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 lcs + +import ( + "fmt" +) + +// For each D, vec[D] has length D+1, +// and the label for (D, k) is stored in vec[D][(D+k)/2]. +type label struct { + vec [][]int +} + +// Temporary checking DO NOT COMMIT true TO PRODUCTION CODE +const debug = false + +// debugging. check that the (d,k) pair is valid +// (that is, -d<=k<=d and d+k even) +func checkDK(D, k int) { + if k >= -D && k <= D && (D+k)%2 == 0 { + return + } + panic(fmt.Sprintf("out of range, d=%d,k=%d", D, k)) +} + +func (t *label) set(D, k, x int) { + if debug { + checkDK(D, k) + } + for len(t.vec) <= D { + t.vec = append(t.vec, nil) + } + if t.vec[D] == nil { + t.vec[D] = make([]int, D+1) + } + t.vec[D][(D+k)/2] = x // known that D+k is even +} + +func (t *label) get(d, k int) int { + if debug { + checkDK(d, k) + } + return int(t.vec[d][(d+k)/2]) +} + +func newtriang(limit int) label { + if limit < 100 { + // Preallocate if limit is not large. + return label{vec: make([][]int, limit)} + } + return label{} +} diff --git a/contribs/gnopls/internal/diff/lcs/old.go b/contribs/gnopls/internal/diff/lcs/old.go new file mode 100644 index 00000000000..4353da15ba9 --- /dev/null +++ b/contribs/gnopls/internal/diff/lcs/old.go @@ -0,0 +1,480 @@ +// 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 lcs + +// TODO(adonovan): remove unclear references to "old" in this package. + +import ( + "fmt" +) + +// A Diff is a replacement of a portion of A by a portion of B. +type Diff struct { + Start, End int // offsets of portion to delete in A + ReplStart, ReplEnd int // offset of replacement text in B +} + +// DiffStrings returns the differences between two strings. +// It does not respect rune boundaries. +func DiffStrings(a, b string) []Diff { return diff(stringSeqs{a, b}) } + +// DiffBytes returns the differences between two byte sequences. +// It does not respect rune boundaries. +func DiffBytes(a, b []byte) []Diff { return diff(bytesSeqs{a, b}) } + +// DiffRunes returns the differences between two rune sequences. +func DiffRunes(a, b []rune) []Diff { return diff(runesSeqs{a, b}) } + +func diff(seqs sequences) []Diff { + // A limit on how deeply the LCS algorithm should search. The value is just a guess. + const maxDiffs = 100 + diff, _ := compute(seqs, twosided, maxDiffs/2) + return diff +} + +// compute computes the list of differences between two sequences, +// along with the LCS. It is exercised directly by tests. +// The algorithm is one of {forward, backward, twosided}. +func compute(seqs sequences, algo func(*editGraph) lcs, limit int) ([]Diff, lcs) { + if limit <= 0 { + limit = 1 << 25 // effectively infinity + } + alen, blen := seqs.lengths() + g := &editGraph{ + seqs: seqs, + vf: newtriang(limit), + vb: newtriang(limit), + limit: limit, + ux: alen, + uy: blen, + delta: alen - blen, + } + lcs := algo(g) + diffs := lcs.toDiffs(alen, blen) + return diffs, lcs +} + +// editGraph carries the information for computing the lcs of two sequences. +type editGraph struct { + seqs sequences + vf, vb label // forward and backward labels + + limit int // maximal value of D + // the bounding rectangle of the current edit graph + lx, ly, ux, uy int + delta int // common subexpression: (ux-lx)-(uy-ly) +} + +// toDiffs converts an LCS to a list of edits. +func (lcs lcs) toDiffs(alen, blen int) []Diff { + var diffs []Diff + var pa, pb int // offsets in a, b + for _, l := range lcs { + if pa < l.X || pb < l.Y { + diffs = append(diffs, Diff{pa, l.X, pb, l.Y}) + } + pa = l.X + l.Len + pb = l.Y + l.Len + } + if pa < alen || pb < blen { + diffs = append(diffs, Diff{pa, alen, pb, blen}) + } + return diffs +} + +// --- FORWARD --- + +// fdone decides if the forward path has reached the upper right +// corner of the rectangle. If so, it also returns the computed lcs. +func (e *editGraph) fdone(D, k int) (bool, lcs) { + // x, y, k are relative to the rectangle + x := e.vf.get(D, k) + y := x - k + if x == e.ux && y == e.uy { + return true, e.forwardlcs(D, k) + } + return false, nil +} + +// run the forward algorithm, until success or up to the limit on D. +func forward(e *editGraph) lcs { + e.setForward(0, 0, e.lx) + if ok, ans := e.fdone(0, 0); ok { + return ans + } + // from D to D+1 + for D := 0; D < e.limit; D++ { + e.setForward(D+1, -(D + 1), e.getForward(D, -D)) + if ok, ans := e.fdone(D+1, -(D + 1)); ok { + return ans + } + e.setForward(D+1, D+1, e.getForward(D, D)+1) + if ok, ans := e.fdone(D+1, D+1); ok { + return ans + } + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get backwards + lookv := e.lookForward(k, e.getForward(D, k-1)+1) + lookh := e.lookForward(k, e.getForward(D, k+1)) + if lookv > lookh { + e.setForward(D+1, k, lookv) + } else { + e.setForward(D+1, k, lookh) + } + if ok, ans := e.fdone(D+1, k); ok { + return ans + } + } + } + // D is too large + // find the D path with maximal x+y inside the rectangle and + // use that to compute the found part of the lcs + kmax := -e.limit - 1 + diagmax := -1 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getForward(e.limit, k) + y := x - k + if x+y > diagmax && x <= e.ux && y <= e.uy { + diagmax, kmax = x+y, k + } + } + return e.forwardlcs(e.limit, kmax) +} + +// recover the lcs by backtracking from the farthest point reached +func (e *editGraph) forwardlcs(D, k int) lcs { + var ans lcs + for x := e.getForward(D, k); x != 0 || x-k != 0; { + if ok(D-1, k-1) && x-1 == e.getForward(D-1, k-1) { + // if (x-1,y) is labelled D-1, x--,D--,k--,continue + D, k, x = D-1, k-1, x-1 + continue + } else if ok(D-1, k+1) && x == e.getForward(D-1, k+1) { + // if (x,y-1) is labelled D-1, x, D--,k++, continue + D, k = D-1, k+1 + continue + } + // if (x-1,y-1)--(x,y) is a diagonal, prepend,x--,y--, continue + y := x - k + ans = ans.prepend(x+e.lx-1, y+e.ly-1) + x-- + } + return ans +} + +// start at (x,y), go up the diagonal as far as possible, +// and label the result with d +func (e *editGraph) lookForward(k, relx int) int { + rely := relx - k + x, y := relx+e.lx, rely+e.ly + if x < e.ux && y < e.uy { + x += e.seqs.commonPrefixLen(x, e.ux, y, e.uy) + } + return x +} + +func (e *editGraph) setForward(d, k, relx int) { + x := e.lookForward(k, relx) + e.vf.set(d, k, x-e.lx) +} + +func (e *editGraph) getForward(d, k int) int { + x := e.vf.get(d, k) + return x +} + +// --- BACKWARD --- + +// bdone decides if the backward path has reached the lower left corner +func (e *editGraph) bdone(D, k int) (bool, lcs) { + // x, y, k are relative to the rectangle + x := e.vb.get(D, k) + y := x - (k + e.delta) + if x == 0 && y == 0 { + return true, e.backwardlcs(D, k) + } + return false, nil +} + +// run the backward algorithm, until success or up to the limit on D. +func backward(e *editGraph) lcs { + e.setBackward(0, 0, e.ux) + if ok, ans := e.bdone(0, 0); ok { + return ans + } + // from D to D+1 + for D := 0; D < e.limit; D++ { + e.setBackward(D+1, -(D + 1), e.getBackward(D, -D)-1) + if ok, ans := e.bdone(D+1, -(D + 1)); ok { + return ans + } + e.setBackward(D+1, D+1, e.getBackward(D, D)) + if ok, ans := e.bdone(D+1, D+1); ok { + return ans + } + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get wrong + lookv := e.lookBackward(k, e.getBackward(D, k-1)) + lookh := e.lookBackward(k, e.getBackward(D, k+1)-1) + if lookv < lookh { + e.setBackward(D+1, k, lookv) + } else { + e.setBackward(D+1, k, lookh) + } + if ok, ans := e.bdone(D+1, k); ok { + return ans + } + } + } + + // D is too large + // find the D path with minimal x+y inside the rectangle and + // use that to compute the part of the lcs found + kmax := -e.limit - 1 + diagmin := 1 << 25 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getBackward(e.limit, k) + y := x - (k + e.delta) + if x+y < diagmin && x >= 0 && y >= 0 { + diagmin, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no paths when limit=%d?", e.limit)) + } + return e.backwardlcs(e.limit, kmax) +} + +// recover the lcs by backtracking +func (e *editGraph) backwardlcs(D, k int) lcs { + var ans lcs + for x := e.getBackward(D, k); x != e.ux || x-(k+e.delta) != e.uy; { + if ok(D-1, k-1) && x == e.getBackward(D-1, k-1) { + // D--, k--, x unchanged + D, k = D-1, k-1 + continue + } else if ok(D-1, k+1) && x+1 == e.getBackward(D-1, k+1) { + // D--, k++, x++ + D, k, x = D-1, k+1, x+1 + continue + } + y := x - (k + e.delta) + ans = ans.append(x+e.lx, y+e.ly) + x++ + } + return ans +} + +// start at (x,y), go down the diagonal as far as possible, +func (e *editGraph) lookBackward(k, relx int) int { + rely := relx - (k + e.delta) // forward k = k + e.delta + x, y := relx+e.lx, rely+e.ly + if x > 0 && y > 0 { + x -= e.seqs.commonSuffixLen(0, x, 0, y) + } + return x +} + +// convert to rectangle, and label the result with d +func (e *editGraph) setBackward(d, k, relx int) { + x := e.lookBackward(k, relx) + e.vb.set(d, k, x-e.lx) +} + +func (e *editGraph) getBackward(d, k int) int { + x := e.vb.get(d, k) + return x +} + +// -- TWOSIDED --- + +func twosided(e *editGraph) lcs { + // The termination condition could be improved, as either the forward + // or backward pass could succeed before Myers' Lemma applies. + // Aside from questions of efficiency (is the extra testing cost-effective) + // this is more likely to matter when e.limit is reached. + e.setForward(0, 0, e.lx) + e.setBackward(0, 0, e.ux) + + // from D to D+1 + for D := 0; D < e.limit; D++ { + // just finished a backwards pass, so check + if got, ok := e.twoDone(D, D); ok { + return e.twolcs(D, D, got) + } + // do a forwards pass (D to D+1) + e.setForward(D+1, -(D + 1), e.getForward(D, -D)) + e.setForward(D+1, D+1, e.getForward(D, D)+1) + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get backwards + lookv := e.lookForward(k, e.getForward(D, k-1)+1) + lookh := e.lookForward(k, e.getForward(D, k+1)) + if lookv > lookh { + e.setForward(D+1, k, lookv) + } else { + e.setForward(D+1, k, lookh) + } + } + // just did a forward pass, so check + if got, ok := e.twoDone(D+1, D); ok { + return e.twolcs(D+1, D, got) + } + // do a backward pass, D to D+1 + e.setBackward(D+1, -(D + 1), e.getBackward(D, -D)-1) + e.setBackward(D+1, D+1, e.getBackward(D, D)) + for k := -D + 1; k <= D-1; k += 2 { + // these are tricky and easy to get wrong + lookv := e.lookBackward(k, e.getBackward(D, k-1)) + lookh := e.lookBackward(k, e.getBackward(D, k+1)-1) + if lookv < lookh { + e.setBackward(D+1, k, lookv) + } else { + e.setBackward(D+1, k, lookh) + } + } + } + + // D too large. combine a forward and backward partial lcs + // first, a forward one + kmax := -e.limit - 1 + diagmax := -1 + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getForward(e.limit, k) + y := x - k + if x+y > diagmax && x <= e.ux && y <= e.uy { + diagmax, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no forward paths when limit=%d?", e.limit)) + } + lcs := e.forwardlcs(e.limit, kmax) + // now a backward one + // find the D path with minimal x+y inside the rectangle and + // use that to compute the lcs + diagmin := 1 << 25 // infinity + for k := -e.limit; k <= e.limit; k += 2 { + x := e.getBackward(e.limit, k) + y := x - (k + e.delta) + if x+y < diagmin && x >= 0 && y >= 0 { + diagmin, kmax = x+y, k + } + } + if kmax < -e.limit { + panic(fmt.Sprintf("no backward paths when limit=%d?", e.limit)) + } + lcs = append(lcs, e.backwardlcs(e.limit, kmax)...) + // These may overlap (e.forwardlcs and e.backwardlcs return sorted lcs) + ans := lcs.fix() + return ans +} + +// Does Myers' Lemma apply? +func (e *editGraph) twoDone(df, db int) (int, bool) { + if (df+db+e.delta)%2 != 0 { + return 0, false // diagonals cannot overlap + } + kmin := -db + e.delta + if -df > kmin { + kmin = -df + } + kmax := db + e.delta + if df < kmax { + kmax = df + } + for k := kmin; k <= kmax; k += 2 { + x := e.vf.get(df, k) + u := e.vb.get(db, k-e.delta) + if u <= x { + // is it worth looking at all the other k? + for l := k; l <= kmax; l += 2 { + x := e.vf.get(df, l) + y := x - l + u := e.vb.get(db, l-e.delta) + v := u - l + if x == u || u == 0 || v == 0 || y == e.uy || x == e.ux { + return l, true + } + } + return k, true + } + } + return 0, false +} + +func (e *editGraph) twolcs(df, db, kf int) lcs { + // db==df || db+1==df + x := e.vf.get(df, kf) + y := x - kf + kb := kf - e.delta + u := e.vb.get(db, kb) + v := u - kf + + // Myers proved there is a df-path from (0,0) to (u,v) + // and a db-path from (x,y) to (N,M). + // In the first case the overall path is the forward path + // to (u,v) followed by the backward path to (N,M). + // In the second case the path is the backward path to (x,y) + // followed by the forward path to (x,y) from (0,0). + + // Look for some special cases to avoid computing either of these paths. + if x == u { + // "babaab" "cccaba" + // already patched together + lcs := e.forwardlcs(df, kf) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + + // is (u-1,v) or (u,v-1) labelled df-1? + // if so, that forward df-1-path plus a horizontal or vertical edge + // is the df-path to (u,v), then plus the db-path to (N,M) + if u > 0 && ok(df-1, u-1-v) && e.vf.get(df-1, u-1-v) == u-1 { + // "aabbab" "cbcabc" + lcs := e.forwardlcs(df-1, u-1-v) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + if v > 0 && ok(df-1, (u-(v-1))) && e.vf.get(df-1, u-(v-1)) == u { + // "abaabb" "bcacab" + lcs := e.forwardlcs(df-1, u-(v-1)) + lcs = append(lcs, e.backwardlcs(db, kb)...) + return lcs.sort() + } + + // The path can't possibly contribute to the lcs because it + // is all horizontal or vertical edges + if u == 0 || v == 0 || x == e.ux || y == e.uy { + // "abaabb" "abaaaa" + if u == 0 || v == 0 { + return e.backwardlcs(db, kb) + } + return e.forwardlcs(df, kf) + } + + // is (x+1,y) or (x,y+1) labelled db-1? + if x+1 <= e.ux && ok(db-1, x+1-y-e.delta) && e.vb.get(db-1, x+1-y-e.delta) == x+1 { + // "bababb" "baaabb" + lcs := e.backwardlcs(db-1, kb+1) + lcs = append(lcs, e.forwardlcs(df, kf)...) + return lcs.sort() + } + if y+1 <= e.uy && ok(db-1, x-(y+1)-e.delta) && e.vb.get(db-1, x-(y+1)-e.delta) == x { + // "abbbaa" "cabacc" + lcs := e.backwardlcs(db-1, kb-1) + lcs = append(lcs, e.forwardlcs(df, kf)...) + return lcs.sort() + } + + // need to compute another path + // "aabbaa" "aacaba" + lcs := e.backwardlcs(db, kb) + oldx, oldy := e.ux, e.uy + e.ux = u + e.uy = v + lcs = append(lcs, forward(e)...) + e.ux, e.uy = oldx, oldy + return lcs.sort() +} diff --git a/contribs/gnopls/internal/diff/lcs/old_test.go b/contribs/gnopls/internal/diff/lcs/old_test.go new file mode 100644 index 00000000000..ddc3bde0ed2 --- /dev/null +++ b/contribs/gnopls/internal/diff/lcs/old_test.go @@ -0,0 +1,251 @@ +// 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 lcs + +import ( + "fmt" + "log" + "math/rand" + "os" + "strings" + "testing" +) + +func TestAlgosOld(t *testing.T) { + for i, algo := range []func(*editGraph) lcs{forward, backward, twosided} { + t.Run(strings.Fields("forward backward twosided")[i], func(t *testing.T) { + for _, tx := range Btests { + lim := len(tx.a) + len(tx.b) + + diffs, lcs := compute(stringSeqs{tx.a, tx.b}, algo, lim) + check(t, tx.a, lcs, tx.lcs) + checkDiffs(t, tx.a, diffs, tx.b) + + diffs, lcs = compute(stringSeqs{tx.b, tx.a}, algo, lim) + check(t, tx.b, lcs, tx.lcs) + checkDiffs(t, tx.b, diffs, tx.a) + } + }) + } +} + +func TestIntOld(t *testing.T) { + // need to avoid any characters in btests + lfill, rfill := "AAAAAAAAAAAA", "BBBBBBBBBBBB" + for _, tx := range Btests { + if len(tx.a) < 2 || len(tx.b) < 2 { + continue + } + left := tx.a + lfill + right := tx.b + rfill + lim := len(tx.a) + len(tx.b) + diffs, lcs := compute(stringSeqs{left, right}, twosided, lim) + check(t, left, lcs, tx.lcs) + checkDiffs(t, left, diffs, right) + diffs, lcs = compute(stringSeqs{right, left}, twosided, lim) + check(t, right, lcs, tx.lcs) + checkDiffs(t, right, diffs, left) + + left = lfill + tx.a + right = rfill + tx.b + diffs, lcs = compute(stringSeqs{left, right}, twosided, lim) + check(t, left, lcs, tx.lcs) + checkDiffs(t, left, diffs, right) + diffs, lcs = compute(stringSeqs{right, left}, twosided, lim) + check(t, right, lcs, tx.lcs) + checkDiffs(t, right, diffs, left) + } +} + +func TestSpecialOld(t *testing.T) { // exercises lcs.fix + a := "golang.org/x/tools/intern" + b := "github.com/google/safehtml/template\"\n\t\"golang.org/x/tools/intern" + diffs, lcs := compute(stringSeqs{a, b}, twosided, 4) + if !lcs.valid() { + t.Errorf("%d,%v", len(diffs), lcs) + } +} + +func TestRegressionOld001(t *testing.T) { + a := "// Copyright 2019 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage diff_test\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"golang.org/x/tools/gopls/internal/lsp/diff\"\n\t\"golang.org/x/tools/internal/diff/difftest\"\n\t\"golang.org/x/tools/gopls/internal/span\"\n)\n" + + b := "// Copyright 2019 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\npackage diff_test\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/safehtml/template\"\n\t\"golang.org/x/tools/gopls/internal/lsp/diff\"\n\t\"golang.org/x/tools/internal/diff/difftest\"\n\t\"golang.org/x/tools/gopls/internal/span\"\n)\n" + for i := 1; i < len(b); i++ { + diffs, lcs := compute(stringSeqs{a, b}, twosided, i) // 14 from gopls + if !lcs.valid() { + t.Errorf("%d,%v", len(diffs), lcs) + } + checkDiffs(t, a, diffs, b) + } +} + +func TestRegressionOld002(t *testing.T) { + a := "n\"\n)\n" + b := "n\"\n\t\"golang.org/x//nnal/stack\"\n)\n" + for i := 1; i <= len(b); i++ { + diffs, lcs := compute(stringSeqs{a, b}, twosided, i) + if !lcs.valid() { + t.Errorf("%d,%v", len(diffs), lcs) + } + checkDiffs(t, a, diffs, b) + } +} + +func TestRegressionOld003(t *testing.T) { + a := "golang.org/x/hello v1.0.0\nrequire golang.org/x/unused v1" + b := "golang.org/x/hello v1" + for i := 1; i <= len(a); i++ { + diffs, lcs := compute(stringSeqs{a, b}, twosided, i) + if !lcs.valid() { + t.Errorf("%d,%v", len(diffs), lcs) + } + checkDiffs(t, a, diffs, b) + } +} + +func TestRandOld(t *testing.T) { + rand.Seed(1) + for i := 0; i < 1000; i++ { + // TODO(adonovan): use ASCII and bytesSeqs here? The use of + // non-ASCII isn't relevant to the property exercised by the test. + a := []rune(randstr("abω", 16)) + b := []rune(randstr("abωc", 16)) + seq := runesSeqs{a, b} + + const lim = 24 // large enough to get true lcs + _, forw := compute(seq, forward, lim) + _, back := compute(seq, backward, lim) + _, two := compute(seq, twosided, lim) + if lcslen(two) != lcslen(forw) || lcslen(forw) != lcslen(back) { + t.Logf("\n%v\n%v\n%v", forw, back, two) + t.Fatalf("%d forw:%d back:%d two:%d", i, lcslen(forw), lcslen(back), lcslen(two)) + } + if !two.valid() || !forw.valid() || !back.valid() { + t.Errorf("check failure") + } + } +} + +// TestDiffAPI tests the public API functions (Diff{Bytes,Strings,Runes}) +// to ensure at least minimal parity of the three representations. +func TestDiffAPI(t *testing.T) { + for _, test := range []struct { + a, b string + wantStrings, wantBytes, wantRunes string + }{ + {"abcXdef", "abcxdef", "[{3 4 3 4}]", "[{3 4 3 4}]", "[{3 4 3 4}]"}, // ASCII + {"abcωdef", "abcΩdef", "[{3 5 3 5}]", "[{3 5 3 5}]", "[{3 4 3 4}]"}, // non-ASCII + } { + + gotStrings := fmt.Sprint(DiffStrings(test.a, test.b)) + if gotStrings != test.wantStrings { + t.Errorf("DiffStrings(%q, %q) = %v, want %v", + test.a, test.b, gotStrings, test.wantStrings) + } + gotBytes := fmt.Sprint(DiffBytes([]byte(test.a), []byte(test.b))) + if gotBytes != test.wantBytes { + t.Errorf("DiffBytes(%q, %q) = %v, want %v", + test.a, test.b, gotBytes, test.wantBytes) + } + gotRunes := fmt.Sprint(DiffRunes([]rune(test.a), []rune(test.b))) + if gotRunes != test.wantRunes { + t.Errorf("DiffRunes(%q, %q) = %v, want %v", + test.a, test.b, gotRunes, test.wantRunes) + } + } +} + +func BenchmarkTwoOld(b *testing.B) { + tests := genBench("abc", 96) + for i := 0; i < b.N; i++ { + for _, tt := range tests { + _, two := compute(stringSeqs{tt.before, tt.after}, twosided, 100) + if !two.valid() { + b.Error("check failed") + } + } + } +} + +func BenchmarkForwOld(b *testing.B) { + tests := genBench("abc", 96) + for i := 0; i < b.N; i++ { + for _, tt := range tests { + _, two := compute(stringSeqs{tt.before, tt.after}, forward, 100) + if !two.valid() { + b.Error("check failed") + } + } + } +} + +func genBench(set string, n int) []struct{ before, after string } { + // before and after for benchmarks. 24 strings of length n with + // before and after differing at least once, and about 5% + rand.Seed(3) + var ans []struct{ before, after string } + for i := 0; i < 24; i++ { + // maybe b should have an approximately known number of diffs + a := randstr(set, n) + cnt := 0 + bb := make([]rune, 0, n) + for _, r := range a { + if rand.Float64() < .05 { + cnt++ + r = 'N' + } + bb = append(bb, r) + } + if cnt == 0 { + // avoid == shortcut + bb[n/2] = 'N' + } + ans = append(ans, struct{ before, after string }{a, string(bb)}) + } + return ans +} + +// This benchmark represents a common case for a diff command: +// large file with a single relatively small diff in the middle. +// (It's not clear whether this is representative of gopls workloads +// or whether it is important to gopls diff performance.) +// +// TODO(adonovan) opt: it could be much faster. For example, +// comparing a file against itself is about 10x faster than with the +// small deletion in the middle. Strangely, comparing a file against +// itself minus the last byte is faster still; I don't know why. +// There is much low-hanging fruit here for further improvement. +func BenchmarkLargeFileSmallDiff(b *testing.B) { + data, err := os.ReadFile("old.go") // large file + if err != nil { + log.Fatal(err) + } + + n := len(data) + + src := string(data) + dst := src[:n*49/100] + src[n*51/100:] // remove 2% from the middle + b.Run("string", func(b *testing.B) { + for i := 0; i < b.N; i++ { + compute(stringSeqs{src, dst}, twosided, len(src)+len(dst)) + } + }) + + srcBytes := []byte(src) + dstBytes := []byte(dst) + b.Run("bytes", func(b *testing.B) { + for i := 0; i < b.N; i++ { + compute(bytesSeqs{srcBytes, dstBytes}, twosided, len(srcBytes)+len(dstBytes)) + } + }) + + srcRunes := []rune(src) + dstRunes := []rune(dst) + b.Run("runes", func(b *testing.B) { + for i := 0; i < b.N; i++ { + compute(runesSeqs{srcRunes, dstRunes}, twosided, len(srcRunes)+len(dstRunes)) + } + }) +} diff --git a/contribs/gnopls/internal/diff/lcs/sequence.go b/contribs/gnopls/internal/diff/lcs/sequence.go new file mode 100644 index 00000000000..2d72d263043 --- /dev/null +++ b/contribs/gnopls/internal/diff/lcs/sequence.go @@ -0,0 +1,113 @@ +// 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 lcs + +// This file defines the abstract sequence over which the LCS algorithm operates. + +// sequences abstracts a pair of sequences, A and B. +type sequences interface { + lengths() (int, int) // len(A), len(B) + commonPrefixLen(ai, aj, bi, bj int) int // len(commonPrefix(A[ai:aj], B[bi:bj])) + commonSuffixLen(ai, aj, bi, bj int) int // len(commonSuffix(A[ai:aj], B[bi:bj])) +} + +type stringSeqs struct{ a, b string } + +func (s stringSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s stringSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenString(s.a[ai:aj], s.b[bi:bj]) +} +func (s stringSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenString(s.a[ai:aj], s.b[bi:bj]) +} + +// The explicit capacity in s[i:j:j] leads to more efficient code. + +type bytesSeqs struct{ a, b []byte } + +func (s bytesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s bytesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenBytes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} +func (s bytesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenBytes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} + +type runesSeqs struct{ a, b []rune } + +func (s runesSeqs) lengths() (int, int) { return len(s.a), len(s.b) } +func (s runesSeqs) commonPrefixLen(ai, aj, bi, bj int) int { + return commonPrefixLenRunes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} +func (s runesSeqs) commonSuffixLen(ai, aj, bi, bj int) int { + return commonSuffixLenRunes(s.a[ai:aj:aj], s.b[bi:bj:bj]) +} + +// TODO(adonovan): optimize these functions using ideas from: +// - https://go.dev/cl/408116 common.go +// - https://go.dev/cl/421435 xor_generic.go + +// TODO(adonovan): factor using generics when available, +// but measure performance impact. + +// commonPrefixLen* returns the length of the common prefix of a[ai:aj] and b[bi:bj]. +func commonPrefixLenBytes(a, b []byte) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} +func commonPrefixLenRunes(a, b []rune) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} +func commonPrefixLenString(a, b string) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[i] == b[i] { + i++ + } + return i +} + +// commonSuffixLen* returns the length of the common suffix of a[ai:aj] and b[bi:bj]. +func commonSuffixLenBytes(a, b []byte) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} +func commonSuffixLenRunes(a, b []rune) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} +func commonSuffixLenString(a, b string) int { + n := min(len(a), len(b)) + i := 0 + for i < n && a[len(a)-1-i] == b[len(b)-1-i] { + i++ + } + return i +} + +func min(x, y int) int { + if x < y { + return x + } else { + return y + } +} diff --git a/contribs/gnopls/internal/diff/myers/diff.go b/contribs/gnopls/internal/diff/myers/diff.go new file mode 100644 index 00000000000..ab1f73714c6 --- /dev/null +++ b/contribs/gnopls/internal/diff/myers/diff.go @@ -0,0 +1,246 @@ +// 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. + +// Package myers implements the Myers diff algorithm. +package myers + +import ( + "strings" + + "github.com/gnolang/gno/contribs/gnopls/internal/diff" +) + +// Sources: +// https://blog.jcoglan.com/2017/02/17/the-myers-diff-algorithm-part-3/ +// https://www.codeproject.com/Articles/42279/%2FArticles%2F42279%2FInvestigating-Myers-diff-algorithm-Part-1-of-2 + +// ComputeEdits returns the diffs of two strings using a simple +// line-based implementation, like [diff.Strings]. +// +// Deprecated: this implementation is moribund. However, when diffs +// appear in marker test expectations, they are the particular diffs +// produced by this implementation. The marker test framework +// asserts diff(orig, got)==wantDiff, but ideally it would compute +// got==apply(orig, wantDiff) so that the notation of the diff +// is immaterial. +func ComputeEdits(before, after string) []diff.Edit { + beforeLines := splitLines(before) + ops := operations(beforeLines, splitLines(after)) + + // Build a table mapping line number to offset. + lineOffsets := make([]int, 0, len(beforeLines)+1) + total := 0 + for i := range beforeLines { + lineOffsets = append(lineOffsets, total) + total += len(beforeLines[i]) + } + lineOffsets = append(lineOffsets, total) // EOF + + edits := make([]diff.Edit, 0, len(ops)) + for _, op := range ops { + start, end := lineOffsets[op.I1], lineOffsets[op.I2] + switch op.Kind { + case opDelete: + // Delete: before[I1:I2] is deleted. + edits = append(edits, diff.Edit{Start: start, End: end}) + case opInsert: + // Insert: after[J1:J2] is inserted at before[I1:I1]. + if content := strings.Join(op.Content, ""); content != "" { + edits = append(edits, diff.Edit{Start: start, End: end, New: content}) + } + } + } + return edits +} + +// opKind is used to denote the type of operation a line represents. +type opKind int + +const ( + opDelete opKind = iota // line deleted from input (-) + opInsert // line inserted into output (+) + opEqual // line present in input and output +) + +func (kind opKind) String() string { + switch kind { + case opDelete: + return "delete" + case opInsert: + return "insert" + case opEqual: + return "equal" + default: + panic("unknown opKind") + } +} + +type operation struct { + Kind opKind + Content []string // content from b + I1, I2 int // indices of the line in a + J1 int // indices of the line in b, J2 implied by len(Content) +} + +// operations returns the list of operations to convert a into b, consolidating +// operations for multiple lines and not including equal lines. +func operations(a, b []string) []*operation { + if len(a) == 0 && len(b) == 0 { + return nil + } + + trace, offset := shortestEditSequence(a, b) + snakes := backtrack(trace, len(a), len(b), offset) + + M, N := len(a), len(b) + + var i int + solution := make([]*operation, len(a)+len(b)) + + add := func(op *operation, i2, j2 int) { + if op == nil { + return + } + op.I2 = i2 + if op.Kind == opInsert { + op.Content = b[op.J1:j2] + } + solution[i] = op + i++ + } + x, y := 0, 0 + for _, snake := range snakes { + if len(snake) < 2 { + continue + } + var op *operation + // delete (horizontal) + for snake[0]-snake[1] > x-y { + if op == nil { + op = &operation{ + Kind: opDelete, + I1: x, + J1: y, + } + } + x++ + if x == M { + break + } + } + add(op, x, y) + op = nil + // insert (vertical) + for snake[0]-snake[1] < x-y { + if op == nil { + op = &operation{ + Kind: opInsert, + I1: x, + J1: y, + } + } + y++ + } + add(op, x, y) + op = nil + // equal (diagonal) + for x < snake[0] { + x++ + y++ + } + if x >= M && y >= N { + break + } + } + return solution[:i] +} + +// backtrack uses the trace for the edit sequence computation and returns the +// "snakes" that make up the solution. A "snake" is a single deletion or +// insertion followed by zero or diagonals. +func backtrack(trace [][]int, x, y, offset int) [][]int { + snakes := make([][]int, len(trace)) + d := len(trace) - 1 + for ; x > 0 && y > 0 && d > 0; d-- { + V := trace[d] + if len(V) == 0 { + continue + } + snakes[d] = []int{x, y} + + k := x - y + + var kPrev int + if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) { + kPrev = k + 1 + } else { + kPrev = k - 1 + } + + x = V[kPrev+offset] + y = x - kPrev + } + if x < 0 || y < 0 { + return snakes + } + snakes[d] = []int{x, y} + return snakes +} + +// shortestEditSequence returns the shortest edit sequence that converts a into b. +func shortestEditSequence(a, b []string) ([][]int, int) { + M, N := len(a), len(b) + V := make([]int, 2*(N+M)+1) + offset := N + M + trace := make([][]int, N+M+1) + + // Iterate through the maximum possible length of the SES (N+M). + for d := 0; d <= N+M; d++ { + copyV := make([]int, len(V)) + // k lines are represented by the equation y = x - k. We move in + // increments of 2 because end points for even d are on even k lines. + for k := -d; k <= d; k += 2 { + // At each point, we either go down or to the right. We go down if + // k == -d, and we go to the right if k == d. We also prioritize + // the maximum x value, because we prefer deletions to insertions. + var x int + if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) { + x = V[k+1+offset] // down + } else { + x = V[k-1+offset] + 1 // right + } + + y := x - k + + // Diagonal moves while we have equal contents. + for x < M && y < N && a[x] == b[y] { + x++ + y++ + } + + V[k+offset] = x + + // Return if we've exceeded the maximum values. + if x == M && y == N { + // Makes sure to save the state of the array before returning. + copy(copyV, V) + trace[d] = copyV + return trace, offset + } + } + + // Save the state of the array. + copy(copyV, V) + trace[d] = copyV + } + return nil, 0 +} + +func splitLines(text string) []string { + lines := strings.SplitAfter(text, "\n") + if lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + return lines +} diff --git a/contribs/gnopls/internal/diff/myers/diff_test.go b/contribs/gnopls/internal/diff/myers/diff_test.go new file mode 100644 index 00000000000..524e043a46c --- /dev/null +++ b/contribs/gnopls/internal/diff/myers/diff_test.go @@ -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. + +package myers_test + +import ( + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/diff/difftest" + "github.com/gnolang/gno/contribs/gnopls/internal/diff/myers" +) + +func TestDiff(t *testing.T) { + difftest.DiffTest(t, myers.ComputeEdits) +} diff --git a/contribs/gnopls/internal/diff/ndiff.go b/contribs/gnopls/internal/diff/ndiff.go new file mode 100644 index 00000000000..79ba0d10102 --- /dev/null +++ b/contribs/gnopls/internal/diff/ndiff.go @@ -0,0 +1,99 @@ +// 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 diff + +import ( + "bytes" + "unicode/utf8" + + "github.com/gnolang/gno/contribs/gnopls/internal/diff/lcs" +) + +// Strings computes the differences between two strings. +// The resulting edits respect rune boundaries. +func Strings(before, after string) []Edit { + if before == after { + return nil // common case + } + + if isASCII(before) && isASCII(after) { + // TODO(adonovan): opt: specialize diffASCII for strings. + return diffASCII([]byte(before), []byte(after)) + } + return diffRunes([]rune(before), []rune(after)) +} + +// Bytes computes the differences between two byte slices. +// The resulting edits respect rune boundaries. +func Bytes(before, after []byte) []Edit { + if bytes.Equal(before, after) { + return nil // common case + } + + if isASCII(before) && isASCII(after) { + return diffASCII(before, after) + } + return diffRunes(runes(before), runes(after)) +} + +func diffASCII(before, after []byte) []Edit { + diffs := lcs.DiffBytes(before, after) + + // Convert from LCS diffs. + res := make([]Edit, len(diffs)) + for i, d := range diffs { + res[i] = Edit{d.Start, d.End, string(after[d.ReplStart:d.ReplEnd])} + } + return res +} + +func diffRunes(before, after []rune) []Edit { + diffs := lcs.DiffRunes(before, after) + + // The diffs returned by the lcs package use indexes + // into whatever slice was passed in. + // Convert rune offsets to byte offsets. + res := make([]Edit, len(diffs)) + lastEnd := 0 + utf8Len := 0 + for i, d := range diffs { + utf8Len += runesLen(before[lastEnd:d.Start]) // text between edits + start := utf8Len + utf8Len += runesLen(before[d.Start:d.End]) // text deleted by this edit + res[i] = Edit{start, utf8Len, string(after[d.ReplStart:d.ReplEnd])} + lastEnd = d.End + } + return res +} + +// runes is like []rune(string(bytes)) without the duplicate allocation. +func runes(bytes []byte) []rune { + n := utf8.RuneCount(bytes) + runes := make([]rune, n) + for i := 0; i < n; i++ { + r, sz := utf8.DecodeRune(bytes) + bytes = bytes[sz:] + runes[i] = r + } + return runes +} + +// runesLen returns the length in bytes of the UTF-8 encoding of runes. +func runesLen(runes []rune) (len int) { + for _, r := range runes { + len += utf8.RuneLen(r) + } + return len +} + +// isASCII reports whether s contains only ASCII. +func isASCII[S string | []byte](s S) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} diff --git a/contribs/gnopls/internal/diff/unified.go b/contribs/gnopls/internal/diff/unified.go new file mode 100644 index 00000000000..cfbda61020a --- /dev/null +++ b/contribs/gnopls/internal/diff/unified.go @@ -0,0 +1,251 @@ +// 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. + +package diff + +import ( + "fmt" + "log" + "strings" +) + +// DefaultContextLines is the number of unchanged lines of surrounding +// context displayed by Unified. Use ToUnified to specify a different value. +const DefaultContextLines = 3 + +// Unified returns a unified diff of the old and new strings. +// The old and new labels are the names of the old and new files. +// If the strings are equal, it returns the empty string. +func Unified(oldLabel, newLabel, old, new string) string { + edits := Strings(old, new) + unified, err := ToUnified(oldLabel, newLabel, old, edits, DefaultContextLines) + if err != nil { + // Can't happen: edits are consistent. + log.Fatalf("internal error in diff.Unified: %v", err) + } + return unified +} + +// ToUnified applies the edits to content and returns a unified diff, +// with contextLines lines of (unchanged) context around each diff hunk. +// The old and new labels are the names of the content and result files. +// It returns an error if the edits are inconsistent; see ApplyEdits. +func ToUnified(oldLabel, newLabel, content string, edits []Edit, contextLines int) (string, error) { + u, err := toUnified(oldLabel, newLabel, content, edits, contextLines) + if err != nil { + return "", err + } + return u.String(), nil +} + +// unified represents a set of edits as a unified diff. +type unified struct { + // from is the name of the original file. + from string + // to is the name of the modified file. + to string + // hunks is the set of edit hunks needed to transform the file content. + hunks []*hunk +} + +// Hunk represents a contiguous set of line edits to apply. +type hunk struct { + // The line in the original source where the hunk starts. + fromLine int + // The line in the original source where the hunk finishes. + toLine int + // The set of line based edits to apply. + lines []line +} + +// Line represents a single line operation to apply as part of a Hunk. +type line struct { + // kind is the type of line this represents, deletion, insertion or copy. + kind opKind + // content is the content of this line. + // For deletion it is the line being removed, for all others it is the line + // to put in the output. + content string +} + +// opKind is used to denote the type of operation a line represents. +type opKind int + +const ( + // opDelete is the operation kind for a line that is present in the input + // but not in the output. + opDelete opKind = iota + // opInsert is the operation kind for a line that is new in the output. + opInsert + // opEqual is the operation kind for a line that is the same in the input and + // output, often used to provide context around edited lines. + opEqual +) + +// String returns a human readable representation of an OpKind. It is not +// intended for machine processing. +func (k opKind) String() string { + switch k { + case opDelete: + return "delete" + case opInsert: + return "insert" + case opEqual: + return "equal" + default: + panic("unknown operation kind") + } +} + +// toUnified takes a file contents and a sequence of edits, and calculates +// a unified diff that represents those edits. +func toUnified(fromName, toName string, content string, edits []Edit, contextLines int) (unified, error) { + gap := contextLines * 2 + u := unified{ + from: fromName, + to: toName, + } + if len(edits) == 0 { + return u, nil + } + var err error + edits, err = lineEdits(content, edits) // expand to whole lines + if err != nil { + return u, err + } + lines := splitLines(content) + var h *hunk + last := 0 + toLine := 0 + for _, edit := range edits { + // Compute the zero-based line numbers of the edit start and end. + // TODO(adonovan): opt: compute incrementally, avoid O(n^2). + start := strings.Count(content[:edit.Start], "\n") + end := strings.Count(content[:edit.End], "\n") + if edit.End == len(content) && len(content) > 0 && content[len(content)-1] != '\n' { + end++ // EOF counts as an implicit newline + } + + switch { + case h != nil && start == last: + //direct extension + case h != nil && start <= last+gap: + //within range of previous lines, add the joiners + addEqualLines(h, lines, last, start) + default: + //need to start a new hunk + if h != nil { + // add the edge to the previous hunk + addEqualLines(h, lines, last, last+contextLines) + u.hunks = append(u.hunks, h) + } + toLine += start - last + h = &hunk{ + fromLine: start + 1, + toLine: toLine + 1, + } + // add the edge to the new hunk + delta := addEqualLines(h, lines, start-contextLines, start) + h.fromLine -= delta + h.toLine -= delta + } + last = start + for i := start; i < end; i++ { + h.lines = append(h.lines, line{kind: opDelete, content: lines[i]}) + last++ + } + if edit.New != "" { + for _, content := range splitLines(edit.New) { + h.lines = append(h.lines, line{kind: opInsert, content: content}) + toLine++ + } + } + } + if h != nil { + // add the edge to the final hunk + addEqualLines(h, lines, last, last+contextLines) + u.hunks = append(u.hunks, h) + } + return u, nil +} + +func splitLines(text string) []string { + lines := strings.SplitAfter(text, "\n") + if lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + return lines +} + +func addEqualLines(h *hunk, lines []string, start, end int) int { + delta := 0 + for i := start; i < end; i++ { + if i < 0 { + continue + } + if i >= len(lines) { + return delta + } + h.lines = append(h.lines, line{kind: opEqual, content: lines[i]}) + delta++ + } + return delta +} + +// String converts a unified diff to the standard textual form for that diff. +// The output of this function can be passed to tools like patch. +func (u unified) String() string { + if len(u.hunks) == 0 { + return "" + } + b := new(strings.Builder) + fmt.Fprintf(b, "--- %s\n", u.from) + fmt.Fprintf(b, "+++ %s\n", u.to) + for _, hunk := range u.hunks { + fromCount, toCount := 0, 0 + for _, l := range hunk.lines { + switch l.kind { + case opDelete: + fromCount++ + case opInsert: + toCount++ + default: + fromCount++ + toCount++ + } + } + fmt.Fprint(b, "@@") + if fromCount > 1 { + fmt.Fprintf(b, " -%d,%d", hunk.fromLine, fromCount) + } else if hunk.fromLine == 1 && fromCount == 0 { + // Match odd GNU diff -u behavior adding to empty file. + fmt.Fprintf(b, " -0,0") + } else { + fmt.Fprintf(b, " -%d", hunk.fromLine) + } + if toCount > 1 { + fmt.Fprintf(b, " +%d,%d", hunk.toLine, toCount) + } else if hunk.toLine == 1 && toCount == 0 { + // Match odd GNU diff -u behavior adding to empty file. + fmt.Fprintf(b, " +0,0") + } else { + fmt.Fprintf(b, " +%d", hunk.toLine) + } + fmt.Fprint(b, " @@\n") + for _, l := range hunk.lines { + switch l.kind { + case opDelete: + fmt.Fprintf(b, "-%s", l.content) + case opInsert: + fmt.Fprintf(b, "+%s", l.content) + default: + fmt.Fprintf(b, " %s", l.content) + } + if !strings.HasSuffix(l.content, "\n") { + fmt.Fprintf(b, "\n\\ No newline at end of file\n") + } + } + } + return b.String() +} diff --git a/contribs/gnopls/internal/doc/api.json b/contribs/gnopls/internal/doc/api.json index d294ea0197d..abf753214fd 100644 --- a/contribs/gnopls/internal/doc/api.json +++ b/contribs/gnopls/internal/doc/api.json @@ -1073,7 +1073,7 @@ { "Name": "deprecated", "Doc": "check for use of deprecated identifiers\n\nThe deprecated analyzer looks for deprecated symbols and package\nimports.\n\nSee https://go.dev/wiki/Deprecated to learn about Go's convention\nfor documenting and signaling deprecated identifiers.", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/deprecated", "Default": true }, { @@ -1085,7 +1085,7 @@ { "Name": "embed", "Doc": "check //go:embed directive usage\n\nThis analyzer checks that the embed package is imported if //go:embed\ndirectives are present, providing a suggested fix to add the import if\nit is missing.\n\nThis analyzer also checks that //go:embed directives precede the\ndeclaration of a single variable.", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/embeddirective", "Default": true }, { @@ -1097,7 +1097,7 @@ { "Name": "fillreturns", "Doc": "suggest fixes for errors due to an incorrect number of return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\n\nwill turn into\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillreturns", "Default": true }, { @@ -1121,7 +1121,7 @@ { "Name": "infertypeargs", "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/infertypeargs", "Default": true }, { @@ -1151,13 +1151,13 @@ { "Name": "nonewvars", "Doc": "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\n\tz := 1\n\tz := 2\n\nwill turn into\n\n\tz := 1\n\tz = 2", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/nonewvars", "Default": true }, { "Name": "noresultvalues", "Doc": "suggested fixes for unexpected return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\" or \"too many return values\".\nFor example:\n\n\tfunc z() { return nil }\n\nwill turn into\n\n\tfunc z() { return }", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/noresultvalues", "Default": true }, { @@ -1187,19 +1187,19 @@ { "Name": "simplifycompositelit", "Doc": "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\n\t[]T{T{}, T{}}\n\nwill be simplified to:\n\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifycompositelit", "Default": true }, { "Name": "simplifyrange", "Doc": "check for range statement simplifications\n\nA range of the form:\n\n\tfor x, _ = range v {...}\n\nwill be simplified to:\n\n\tfor x = range v {...}\n\nA range of the form:\n\n\tfor _ = range v {...}\n\nwill be simplified to:\n\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyrange", "Default": true }, { "Name": "simplifyslice", "Doc": "check for slice simplifications\n\nA slice expression of the form:\n\n\ts[a:len(s)]\n\nwill be simplified to:\n\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyslice", "Default": true }, { @@ -1241,7 +1241,7 @@ { "Name": "stubmethods", "Doc": "detect missing methods and fix with stub implementations\n\nThis analyzer detects type-checking errors due to missing methods\nin assignments from concrete types to interface types, and offers\na suggested fix that will create a set of stub methods so that\nthe concrete type satisfies the interface.\n\nFor example, this function will not compile because the value\nNegativeErr{} does not implement the \"error\" interface:\n\n\tfunc sqrt(x float64) (float64, error) {\n\t\tif x \u003c 0 {\n\t\t\treturn 0, NegativeErr{} // error: missing method\n\t\t}\n\t\t...\n\t}\n\n\ttype NegativeErr struct{}\n\nThis analyzer will suggest a fix to declare this method:\n\n\t// Error implements error.Error.\n\tfunc (NegativeErr) Error() string {\n\t\tpanic(\"unimplemented\")\n\t}\n\n(At least, it appears to behave that way, but technically it\ndoesn't use the SuggestedFix mechanism and the stub is created by\nlogic in gopls's golang.stub function.)", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/stubmethods", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/stubmethods", "Default": true }, { @@ -1265,7 +1265,7 @@ { "Name": "undeclaredname", "Doc": "suggested fixes for \"undeclared name: \u003c\u003e\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"undeclared name: \u003c\u003e\". It will either insert a new statement,\nsuch as:\n\n\t\u003c\u003e :=\n\nor a new function declaration, such as:\n\n\tfunc \u003c\u003e(inferred parameters) {\n\t\tpanic(\"implement me!\")\n\t}", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/undeclaredname", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/undeclaredname", "Default": true }, { @@ -1289,7 +1289,7 @@ { "Name": "unusedparams", "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedparams", "Default": true }, { @@ -1301,7 +1301,7 @@ { "Name": "unusedvariable", "Doc": "check for unused variables and suggest fixes", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedvariable", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedvariable", "Default": false }, { @@ -1313,7 +1313,7 @@ { "Name": "useany", "Doc": "check for constraints that could be simplified to \"any\"", - "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/useany", + "URL": "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/analysis/useany", "Default": false } ], diff --git a/contribs/gnopls/internal/drivertest/driver.go b/contribs/gnopls/internal/drivertest/driver.go new file mode 100644 index 00000000000..cab6586ebc1 --- /dev/null +++ b/contribs/gnopls/internal/drivertest/driver.go @@ -0,0 +1,92 @@ +// 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 drivertest package provides a fake implementation of the go/packages +// driver protocol that delegates to the go list driver. It may be used to test +// programs such as gopls that specialize behavior when a go/packages driver is +// in use. +// +// The driver is run as a child of the current process, by calling [RunIfChild] +// at process start, and running go/packages with the environment variables set +// by [Env]. +package drivertest + +import ( + "encoding/json" + "flag" + "log" + "os" + + "golang.org/x/tools/go/packages" +) + +const runAsDriverEnv = "DRIVERTEST_RUN_AS_DRIVER" + +// RunIfChild runs the current process as a go/packages driver, if configured +// to do so by the current environment (see [Env]). +// +// Otherwise, RunIfChild is a no op. +func RunIfChild() { + if os.Getenv(runAsDriverEnv) != "" { + main() + os.Exit(0) + } +} + +// Env returns additional environment variables for use in [packages.Config] +// to enable the use of drivertest as the driver. +// +// t abstracts a *testing.T or log.Default(). +func Env(t interface{ Fatal(...any) }) []string { + exe, err := os.Executable() + if err != nil { + t.Fatal(err) + } + return []string{"GOPACKAGESDRIVER=" + exe, runAsDriverEnv + "=1"} +} + +func main() { + flag.Parse() + + dec := json.NewDecoder(os.Stdin) + var request packages.DriverRequest + if err := dec.Decode(&request); err != nil { + log.Fatalf("decoding request: %v", err) + } + + config := packages.Config{ + Mode: request.Mode, + Env: append(request.Env, "GOPACKAGESDRIVER=off"), // avoid recursive invocation + BuildFlags: request.BuildFlags, + Tests: request.Tests, + Overlay: request.Overlay, + } + pkgs, err := packages.Load(&config, flag.Args()...) + if err != nil { + log.Fatalf("load failed: %v", err) + } + + var roots []string + for _, pkg := range pkgs { + roots = append(roots, pkg.ID) + } + var allPackages []*packages.Package + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + newImports := make(map[string]*packages.Package) + for path, imp := range pkg.Imports { + newImports[path] = &packages.Package{ID: imp.ID} + } + pkg.Imports = newImports + allPackages = append(allPackages, pkg) + }) + + enc := json.NewEncoder(os.Stdout) + response := packages.DriverResponse{ + Roots: roots, + Packages: allPackages, + } + if err := enc.Encode(response); err != nil { + log.Fatalf("encoding response: %v", err) + } +} diff --git a/contribs/gnopls/internal/drivertest/driver_test.go b/contribs/gnopls/internal/drivertest/driver_test.go new file mode 100644 index 00000000000..5e91191a9da --- /dev/null +++ b/contribs/gnopls/internal/drivertest/driver_test.go @@ -0,0 +1,153 @@ +// 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 drivertest_test + +// This file is both a test of drivertest and an example of how to use it in your own tests. + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "golang.org/x/tools/go/packages" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/diff/myers" + "github.com/gnolang/gno/contribs/gnopls/internal/drivertest" + "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/testfiles" + "golang.org/x/tools/txtar" +) + +func TestMain(m *testing.M) { + drivertest.RunIfChild() + + os.Exit(m.Run()) +} + +func TestDriverConformance(t *testing.T) { + testenv.NeedsExec(t) + + const workspace = ` +-- go.mod -- +module example.com/m + +go 1.20 + +-- m.go -- +package m + +-- lib/lib.go -- +package lib +` + + fs, err := txtar.FS(txtar.Parse([]byte(workspace))) + if err != nil { + t.Fatal(err) + } + dir := testfiles.CopyToTmp(t, fs) + + // TODO(rfindley): on mac, this is required to fix symlink path mismatches. + // But why? Where is the symlink being evaluated in go/packages? + dir, err = filepath.EvalSymlinks(dir) + if err != nil { + t.Fatal(err) + } + + baseConfig := packages.Config{ + Dir: dir, + Mode: packages.NeedName | + packages.NeedFiles | + packages.NeedCompiledGoFiles | + packages.NeedImports | + packages.NeedDeps | + packages.NeedTypesSizes | + packages.NeedModule | + packages.NeedEmbedFiles | + packages.LoadMode(packagesinternal.DepsErrors) | + packages.LoadMode(packagesinternal.ForTest), + } + + tests := []struct { + name string + query string + overlay string + }{ + { + name: "load all", + query: "./...", + }, + { + name: "overlays", + query: "./...", + overlay: ` +-- m.go -- +package m + +import . "lib" +-- a/a.go -- +package a +`, + }, + { + name: "std", + query: "std", + }, + { + name: "builtin", + query: "builtin", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg := baseConfig + if test.overlay != "" { + cfg.Overlay = make(map[string][]byte) + for _, file := range txtar.Parse([]byte(test.overlay)).Files { + name := filepath.Join(dir, filepath.FromSlash(file.Name)) + cfg.Overlay[name] = file.Data + } + } + + // Compare JSON-encoded packages with and without GOPACKAGESDRIVER. + // + // Note that this does not guarantee that the go/packages results + // themselves are equivalent, only that their encoded JSON is equivalent. + // Certain fields such as Module are intentionally omitted from external + // drivers, because they don't make sense for an arbitrary build system. + var jsons []string + for _, env := range [][]string{ + {"GOPACKAGESDRIVER=off"}, + drivertest.Env(t), + } { + cfg.Env = append(os.Environ(), env...) + pkgs, err := packages.Load(&cfg, test.query) + if err != nil { + t.Fatalf("failed to load (env: %v): %v", env, err) + } + data, err := json.MarshalIndent(pkgs, "", "\t") + if err != nil { + t.Fatalf("failed to marshal (env: %v): %v", env, err) + } + jsons = append(jsons, string(data)) + } + + listJSON := jsons[0] + driverJSON := jsons[1] + + // Use the myers package for better line diffs. + edits := myers.ComputeEdits(listJSON, driverJSON) + d, err := diff.ToUnified("go list", "driver", listJSON, edits, 0) + if err != nil { + t.Fatal(err) + } + if d != "" { + t.Errorf("mismatching JSON:\n%s", d) + } + }) + } +} diff --git a/contribs/gnopls/internal/event/bench_test.go b/contribs/gnopls/internal/event/bench_test.go new file mode 100644 index 00000000000..c55ce3a708a --- /dev/null +++ b/contribs/gnopls/internal/event/bench_test.go @@ -0,0 +1,158 @@ +// 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 event_test + +import ( + "context" + "io" + "log" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +type Hooks struct { + A func(ctx context.Context, a int) (context.Context, func()) + B func(ctx context.Context, b string) (context.Context, func()) +} + +var ( + aValue = keys.NewInt("a", "") + bValue = keys.NewString("b", "") + aCount = keys.NewInt64("aCount", "Count of time A is called.") + aStat = keys.NewInt("aValue", "A value.") + bCount = keys.NewInt64("B", "Count of time B is called.") + bLength = keys.NewInt("BLen", "B length.") + + Baseline = Hooks{ + A: func(ctx context.Context, a int) (context.Context, func()) { + return ctx, func() {} + }, + B: func(ctx context.Context, b string) (context.Context, func()) { + return ctx, func() {} + }, + } + + StdLog = Hooks{ + A: func(ctx context.Context, a int) (context.Context, func()) { + log.Printf("A where a=%d", a) + return ctx, func() {} + }, + B: func(ctx context.Context, b string) (context.Context, func()) { + log.Printf("B where b=%q", b) + return ctx, func() {} + }, + } + + Log = Hooks{ + A: func(ctx context.Context, a int) (context.Context, func()) { + core.Log1(ctx, "A", aValue.Of(a)) + return ctx, func() {} + }, + B: func(ctx context.Context, b string) (context.Context, func()) { + core.Log1(ctx, "B", bValue.Of(b)) + return ctx, func() {} + }, + } + + Trace = Hooks{ + A: func(ctx context.Context, a int) (context.Context, func()) { + return core.Start1(ctx, "A", aValue.Of(a)) + }, + B: func(ctx context.Context, b string) (context.Context, func()) { + return core.Start1(ctx, "B", bValue.Of(b)) + }, + } + + Stats = Hooks{ + A: func(ctx context.Context, a int) (context.Context, func()) { + core.Metric1(ctx, aStat.Of(a)) + core.Metric1(ctx, aCount.Of(1)) + return ctx, func() {} + }, + B: func(ctx context.Context, b string) (context.Context, func()) { + core.Metric1(ctx, bLength.Of(len(b))) + core.Metric1(ctx, bCount.Of(1)) + return ctx, func() {} + }, + } + + initialList = []int{0, 1, 22, 333, 4444, 55555, 666666, 7777777} + stringList = []string{ + "A value", + "Some other value", + "A nice longer value but not too long", + "V", + "", + "ı", + "prime count of values", + } +) + +type namedBenchmark struct { + name string + test func(*testing.B) +} + +func Benchmark(b *testing.B) { + b.Run("Baseline", Baseline.runBenchmark) + b.Run("StdLog", StdLog.runBenchmark) + benchmarks := []namedBenchmark{ + {"Log", Log.runBenchmark}, + {"Trace", Trace.runBenchmark}, + {"Stats", Stats.runBenchmark}, + } + + event.SetExporter(nil) + for _, t := range benchmarks { + b.Run(t.name+"NoExporter", t.test) + } + + event.SetExporter(noopExporter) + for _, t := range benchmarks { + b.Run(t.name+"Noop", t.test) + } + + event.SetExporter(export.Spans(export.LogWriter(io.Discard, false))) + for _, t := range benchmarks { + b.Run(t.name, t.test) + } +} + +func A(ctx context.Context, hooks Hooks, a int) int { + ctx, done := hooks.A(ctx, a) + defer done() + return B(ctx, hooks, a, stringList[a%len(stringList)]) +} + +func B(ctx context.Context, hooks Hooks, a int, b string) int { + _, done := hooks.B(ctx, b) + defer done() + return a + len(b) +} + +func (hooks Hooks) runBenchmark(b *testing.B) { + ctx := context.Background() + b.ReportAllocs() + b.ResetTimer() + var acc int + for i := 0; i < b.N; i++ { + for _, value := range initialList { + acc += A(ctx, hooks, value) + } + } +} + +func init() { + log.SetOutput(io.Discard) +} + +func noopExporter(ctx context.Context, ev core.Event, lm label.Map) context.Context { + return ctx +} diff --git a/contribs/gnopls/internal/event/core/event.go b/contribs/gnopls/internal/event/core/event.go new file mode 100644 index 00000000000..cb97d3c56b7 --- /dev/null +++ b/contribs/gnopls/internal/event/core/event.go @@ -0,0 +1,85 @@ +// 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. + +// Package core provides support for event based telemetry. +package core + +import ( + "fmt" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Event holds the information about an event of note that occurred. +type Event struct { + at time.Time + + // As events are often on the stack, storing the first few labels directly + // in the event can avoid an allocation at all for the very common cases of + // simple events. + // The length needs to be large enough to cope with the majority of events + // but no so large as to cause undue stack pressure. + // A log message with two values will use 3 labels (one for each value and + // one for the message itself). + + static [3]label.Label // inline storage for the first few labels + dynamic []label.Label // dynamically sized storage for remaining labels +} + +// eventLabelMap implements label.Map for a the labels of an Event. +type eventLabelMap struct { + event Event +} + +func (ev Event) At() time.Time { return ev.at } + +func (ev Event) Format(f fmt.State, r rune) { + if !ev.at.IsZero() { + fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 ")) + } + for index := 0; ev.Valid(index); index++ { + if l := ev.Label(index); l.Valid() { + fmt.Fprintf(f, "\n\t%v", l) + } + } +} + +func (ev Event) Valid(index int) bool { + return index >= 0 && index < len(ev.static)+len(ev.dynamic) +} + +func (ev Event) Label(index int) label.Label { + if index < len(ev.static) { + return ev.static[index] + } + return ev.dynamic[index-len(ev.static)] +} + +func (ev Event) Find(key label.Key) label.Label { + for _, l := range ev.static { + if l.Key() == key { + return l + } + } + for _, l := range ev.dynamic { + if l.Key() == key { + return l + } + } + return label.Label{} +} + +func MakeEvent(static [3]label.Label, labels []label.Label) Event { + return Event{ + static: static, + dynamic: labels, + } +} + +// CloneEvent event returns a copy of the event with the time adjusted to at. +func CloneEvent(ev Event, at time.Time) Event { + ev.at = at + return ev +} diff --git a/contribs/gnopls/internal/event/core/export.go b/contribs/gnopls/internal/event/core/export.go new file mode 100644 index 00000000000..b185fbdcafa --- /dev/null +++ b/contribs/gnopls/internal/event/core/export.go @@ -0,0 +1,70 @@ +// 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. + +package core + +import ( + "context" + "sync/atomic" + "time" + "unsafe" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Exporter is a function that handles events. +// It may return a modified context and event. +type Exporter func(context.Context, Event, label.Map) context.Context + +var ( + exporter unsafe.Pointer +) + +// SetExporter sets the global exporter function that handles all events. +// The exporter is called synchronously from the event call site, so it should +// return quickly so as not to hold up user code. +func SetExporter(e Exporter) { + p := unsafe.Pointer(&e) + if e == nil { + // &e is always valid, and so p is always valid, but for the early abort + // of ProcessEvent to be efficient it needs to make the nil check on the + // pointer without having to dereference it, so we make the nil function + // also a nil pointer + p = nil + } + atomic.StorePointer(&exporter, p) +} + +// deliver is called to deliver an event to the supplied exporter. +// it will fill in the time. +func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context { + // add the current time to the event + ev.at = time.Now() + // hand the event off to the current exporter + return exporter(ctx, ev, ev) +} + +// Export is called to deliver an event to the global exporter if set. +func Export(ctx context.Context, ev Event) context.Context { + // get the global exporter and abort early if there is not one + exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) + if exporterPtr == nil { + return ctx + } + return deliver(ctx, *exporterPtr, ev) +} + +// ExportPair is called to deliver a start event to the supplied exporter. +// It also returns a function that will deliver the end event to the same +// exporter. +// It will fill in the time. +func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) { + // get the global exporter and abort early if there is not one + exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) + if exporterPtr == nil { + return ctx, func() {} + } + ctx = deliver(ctx, *exporterPtr, begin) + return ctx, func() { deliver(ctx, *exporterPtr, end) } +} diff --git a/contribs/gnopls/internal/event/core/fast.go b/contribs/gnopls/internal/event/core/fast.go new file mode 100644 index 00000000000..77eac914205 --- /dev/null +++ b/contribs/gnopls/internal/event/core/fast.go @@ -0,0 +1,77 @@ +// 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. + +package core + +import ( + "context" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Log1 takes a message and one label delivers a log event to the exporter. +// It is a customized version of Print that is faster and does no allocation. +func Log1(ctx context.Context, message string, t1 label.Label) { + Export(ctx, MakeEvent([3]label.Label{ + keys.Msg.Of(message), + t1, + }, nil)) +} + +// Log2 takes a message and two labels and delivers a log event to the exporter. +// It is a customized version of Print that is faster and does no allocation. +func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) { + Export(ctx, MakeEvent([3]label.Label{ + keys.Msg.Of(message), + t1, + t2, + }, nil)) +} + +// Metric1 sends a label event to the exporter with the supplied labels. +func Metric1(ctx context.Context, t1 label.Label) context.Context { + return Export(ctx, MakeEvent([3]label.Label{ + keys.Metric.New(), + t1, + }, nil)) +} + +// Metric2 sends a label event to the exporter with the supplied labels. +func Metric2(ctx context.Context, t1, t2 label.Label) context.Context { + return Export(ctx, MakeEvent([3]label.Label{ + keys.Metric.New(), + t1, + t2, + }, nil)) +} + +// Start1 sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) { + return ExportPair(ctx, + MakeEvent([3]label.Label{ + keys.Start.Of(name), + t1, + }, nil), + MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} + +// Start2 sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) { + return ExportPair(ctx, + MakeEvent([3]label.Label{ + keys.Start.Of(name), + t1, + t2, + }, nil), + MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} diff --git a/contribs/gnopls/internal/event/doc.go b/contribs/gnopls/internal/event/doc.go new file mode 100644 index 00000000000..5dc6e6babed --- /dev/null +++ b/contribs/gnopls/internal/event/doc.go @@ -0,0 +1,7 @@ +// 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. + +// Package event provides a set of packages that cover the main +// concepts of telemetry in an implementation agnostic way. +package event diff --git a/contribs/gnopls/internal/event/event.go b/contribs/gnopls/internal/event/event.go new file mode 100644 index 00000000000..fa5a150b7f0 --- /dev/null +++ b/contribs/gnopls/internal/event/event.go @@ -0,0 +1,127 @@ +// 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. + +package event + +import ( + "context" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Exporter is a function that handles events. +// It may return a modified context and event. +type Exporter func(context.Context, core.Event, label.Map) context.Context + +// SetExporter sets the global exporter function that handles all events. +// The exporter is called synchronously from the event call site, so it should +// return quickly so as not to hold up user code. +func SetExporter(e Exporter) { + core.SetExporter(core.Exporter(e)) +} + +// Log takes a message and a label list and combines them into a single event +// before delivering them to the exporter. +func Log(ctx context.Context, message string, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Msg.Of(message), + }, labels)) +} + +// IsLog returns true if the event was built by the Log function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsLog(ev core.Event) bool { + return ev.Label(0).Key() == keys.Msg +} + +// Error takes a message and a label list and combines them into a single event +// before delivering them to the exporter. It captures the error in the +// delivered event. +func Error(ctx context.Context, message string, err error, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Msg.Of(message), + keys.Err.Of(err), + }, labels)) +} + +// IsError returns true if the event was built by the Error function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsError(ev core.Event) bool { + return ev.Label(0).Key() == keys.Msg && + ev.Label(1).Key() == keys.Err +} + +// Metric sends a label event to the exporter with the supplied labels. +func Metric(ctx context.Context, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Metric.New(), + }, labels)) +} + +// IsMetric returns true if the event was built by the Metric function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsMetric(ev core.Event) bool { + return ev.Label(0).Key() == keys.Metric +} + +// Label sends a label event to the exporter with the supplied labels. +func Label(ctx context.Context, labels ...label.Label) context.Context { + return core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Label.New(), + }, labels)) +} + +// IsLabel returns true if the event was built by the Label function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsLabel(ev core.Event) bool { + return ev.Label(0).Key() == keys.Label +} + +// Start sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) { + return core.ExportPair(ctx, + core.MakeEvent([3]label.Label{ + keys.Start.Of(name), + }, labels), + core.MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} + +// IsStart returns true if the event was built by the Start function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsStart(ev core.Event) bool { + return ev.Label(0).Key() == keys.Start +} + +// IsEnd returns true if the event was built by the End function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsEnd(ev core.Event) bool { + return ev.Label(0).Key() == keys.End +} + +// Detach returns a context without an associated span. +// This allows the creation of spans that are not children of the current span. +func Detach(ctx context.Context) context.Context { + return core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Detach.New(), + }, nil)) +} + +// IsDetach returns true if the event was built by the Detach function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsDetach(ev core.Event) bool { + return ev.Label(0).Key() == keys.Detach +} diff --git a/contribs/gnopls/internal/event/export/eventtest/eventtest.go b/contribs/gnopls/internal/event/export/eventtest/eventtest.go new file mode 100644 index 00000000000..2cbce57ae86 --- /dev/null +++ b/contribs/gnopls/internal/event/export/eventtest/eventtest.go @@ -0,0 +1,64 @@ +// 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 eventtest supports logging events to a test. +// You can use NewContext to create a context that knows how to deliver +// telemetry events back to the test. +// You must use this context or a derived one anywhere you want telemetry to be +// correctly routed back to the test it was constructed with. +// Any events delivered to a background context will be dropped. +// +// Importing this package will cause it to register a new global telemetry +// exporter that understands the special contexts returned by NewContext. +// This means you should not import this package if you are not going to call +// NewContext. +package eventtest + +import ( + "bytes" + "context" + "sync" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +func init() { + e := &testExporter{buffer: &bytes.Buffer{}} + e.logger = export.LogWriter(e.buffer, false) + + event.SetExporter(export.Spans(e.processEvent)) +} + +type testingKeyType int + +const testingKey = testingKeyType(0) + +// NewContext returns a context you should use for the active test. +func NewContext(ctx context.Context, t testing.TB) context.Context { + return context.WithValue(ctx, testingKey, t) +} + +type testExporter struct { + mu sync.Mutex + buffer *bytes.Buffer + logger event.Exporter +} + +func (w *testExporter) processEvent(ctx context.Context, ev core.Event, tm label.Map) context.Context { + w.mu.Lock() + defer w.mu.Unlock() + // build our log message in buffer + result := w.logger(ctx, ev, tm) + v := ctx.Value(testingKey) + // get the testing.TB + if w.buffer.Len() > 0 && v != nil { + v.(testing.TB).Log(w.buffer) + } + w.buffer.Truncate(0) + return result +} diff --git a/contribs/gnopls/internal/event/export/id.go b/contribs/gnopls/internal/event/export/id.go new file mode 100644 index 00000000000..bf9938b38c1 --- /dev/null +++ b/contribs/gnopls/internal/event/export/id.go @@ -0,0 +1,71 @@ +// 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. + +package export + +import ( + crand "crypto/rand" + "encoding/binary" + "fmt" + "math/rand" + "sync" + "sync/atomic" +) + +type TraceID [16]byte +type SpanID [8]byte + +func (t TraceID) String() string { + return fmt.Sprintf("%02x", t[:]) +} + +func (s SpanID) String() string { + return fmt.Sprintf("%02x", s[:]) +} + +func (s SpanID) IsValid() bool { + return s != SpanID{} +} + +var ( + generationMu sync.Mutex + nextSpanID uint64 + spanIDInc uint64 + + traceIDAdd [2]uint64 + traceIDRand *rand.Rand +) + +func initGenerator() { + var rngSeed int64 + for _, p := range []interface{}{ + &rngSeed, &traceIDAdd, &nextSpanID, &spanIDInc, + } { + binary.Read(crand.Reader, binary.LittleEndian, p) + } + traceIDRand = rand.New(rand.NewSource(rngSeed)) + spanIDInc |= 1 +} + +func newTraceID() TraceID { + generationMu.Lock() + defer generationMu.Unlock() + if traceIDRand == nil { + initGenerator() + } + var tid [16]byte + binary.LittleEndian.PutUint64(tid[0:8], traceIDRand.Uint64()+traceIDAdd[0]) + binary.LittleEndian.PutUint64(tid[8:16], traceIDRand.Uint64()+traceIDAdd[1]) + return tid +} + +func newSpanID() SpanID { + var id uint64 + for id == 0 { + id = atomic.AddUint64(&nextSpanID, spanIDInc) + } + var sid [8]byte + binary.LittleEndian.PutUint64(sid[:], id) + return sid +} diff --git a/contribs/gnopls/internal/event/export/labels.go b/contribs/gnopls/internal/event/export/labels.go new file mode 100644 index 00000000000..20cd59dcc0a --- /dev/null +++ b/contribs/gnopls/internal/event/export/labels.go @@ -0,0 +1,37 @@ +// 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. + +package export + +import ( + "context" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Labels builds an exporter that manipulates the context using the event. +// If the event is type IsLabel or IsStartSpan then it returns a context updated +// with label values from the event. +// For all other event types the event labels will be updated with values from the +// context if they are missing. +func Labels(output event.Exporter) event.Exporter { + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + stored, _ := ctx.Value(labelContextKey).(label.Map) + if event.IsLabel(ev) || event.IsStart(ev) { + // update the label map stored in the context + fromEvent := label.Map(ev) + if stored == nil { + stored = fromEvent + } else { + stored = label.MergeMaps(fromEvent, stored) + } + ctx = context.WithValue(ctx, labelContextKey, stored) + } + // add the stored label context to the label map + lm = label.MergeMaps(lm, stored) + return output(ctx, ev, lm) + } +} diff --git a/contribs/gnopls/internal/event/export/log.go b/contribs/gnopls/internal/event/export/log.go new file mode 100644 index 00000000000..f293e2b0037 --- /dev/null +++ b/contribs/gnopls/internal/event/export/log.go @@ -0,0 +1,57 @@ +// 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. + +package export + +import ( + "context" + "fmt" + "io" + "sync" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// LogWriter returns an Exporter that logs events to the supplied writer. +// If onlyErrors is true it does not log any event that did not have an +// associated error. +// It ignores all telemetry other than log events. +func LogWriter(w io.Writer, onlyErrors bool) event.Exporter { + lw := &logWriter{writer: w, onlyErrors: onlyErrors} + return lw.ProcessEvent +} + +type logWriter struct { + mu sync.Mutex + printer Printer + writer io.Writer + onlyErrors bool +} + +func (w *logWriter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { + switch { + case event.IsLog(ev): + if w.onlyErrors && !event.IsError(ev) { + return ctx + } + w.mu.Lock() + defer w.mu.Unlock() + w.printer.WriteEvent(w.writer, ev, lm) + + case event.IsStart(ev): + if span := GetSpan(ctx); span != nil { + fmt.Fprintf(w.writer, "start: %v %v", span.Name, span.ID) + if span.ParentID.IsValid() { + fmt.Fprintf(w.writer, "[%v]", span.ParentID) + } + } + case event.IsEnd(ev): + if span := GetSpan(ctx); span != nil { + fmt.Fprintf(w.writer, "finish: %v %v", span.Name, span.ID) + } + } + return ctx +} diff --git a/contribs/gnopls/internal/event/export/log_test.go b/contribs/gnopls/internal/event/export/log_test.go new file mode 100644 index 00000000000..b4cf6161982 --- /dev/null +++ b/contribs/gnopls/internal/event/export/log_test.go @@ -0,0 +1,40 @@ +// 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 export_test + +import ( + "context" + "errors" + "os" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +func ExampleLog() { + ctx := context.Background() + event.SetExporter(timeFixer(export.LogWriter(os.Stdout, false))) + anInt := keys.NewInt("myInt", "an integer") + aString := keys.NewString("myString", "a string") + event.Log(ctx, "my event", anInt.Of(6)) + event.Error(ctx, "error event", errors.New("an error"), aString.Of("some string value")) + // Output: + // 2020/03/05 14:27:48 my event + // myInt=6 + // 2020/03/05 14:27:48 error event: an error + // myString="some string value" +} + +func timeFixer(output event.Exporter) event.Exporter { + at, _ := time.Parse(time.RFC3339Nano, "2020-03-05T14:27:48Z") + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + copy := core.CloneEvent(ev, at) + return output(ctx, copy, lm) + } +} diff --git a/contribs/gnopls/internal/event/export/metric/data.go b/contribs/gnopls/internal/event/export/metric/data.go new file mode 100644 index 00000000000..1a93d0623b3 --- /dev/null +++ b/contribs/gnopls/internal/event/export/metric/data.go @@ -0,0 +1,304 @@ +// 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. + +package metric + +import ( + "fmt" + "sort" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Data represents a single point in the time series of a metric. +// This provides the common interface to all metrics no matter their data +// format. +// To get the actual values for the metric you must type assert to a concrete +// metric type. +type Data interface { + // Handle returns the metric handle this data is for. + //TODO: rethink the concept of metric handles + Handle() string + // Groups reports the rows that currently exist for this metric. + Groups() [][]label.Label +} + +// Int64Data is a concrete implementation of Data for int64 scalar metrics. +type Int64Data struct { + // Info holds the original construction information. + Info *Scalar + // IsGauge is true for metrics that track values, rather than increasing over time. + IsGauge bool + // Rows holds the per group values for the metric. + Rows []int64 + // End is the last time this metric was updated. + EndTime time.Time + + groups [][]label.Label + key *keys.Int64 +} + +// Float64Data is a concrete implementation of Data for float64 scalar metrics. +type Float64Data struct { + // Info holds the original construction information. + Info *Scalar + // IsGauge is true for metrics that track values, rather than increasing over time. + IsGauge bool + // Rows holds the per group values for the metric. + Rows []float64 + // End is the last time this metric was updated. + EndTime time.Time + + groups [][]label.Label + key *keys.Float64 +} + +// HistogramInt64Data is a concrete implementation of Data for int64 histogram metrics. +type HistogramInt64Data struct { + // Info holds the original construction information. + Info *HistogramInt64 + // Rows holds the per group values for the metric. + Rows []*HistogramInt64Row + // End is the last time this metric was updated. + EndTime time.Time + + groups [][]label.Label + key *keys.Int64 +} + +// HistogramInt64Row holds the values for a single row of a HistogramInt64Data. +type HistogramInt64Row struct { + // Values is the counts per bucket. + Values []int64 + // Count is the total count. + Count int64 + // Sum is the sum of all the values recorded. + Sum int64 + // Min is the smallest recorded value. + Min int64 + // Max is the largest recorded value. + Max int64 +} + +// HistogramFloat64Data is a concrete implementation of Data for float64 histogram metrics. +type HistogramFloat64Data struct { + // Info holds the original construction information. + Info *HistogramFloat64 + // Rows holds the per group values for the metric. + Rows []*HistogramFloat64Row + // End is the last time this metric was updated. + EndTime time.Time + + groups [][]label.Label + key *keys.Float64 +} + +// HistogramFloat64Row holds the values for a single row of a HistogramFloat64Data. +type HistogramFloat64Row struct { + // Values is the counts per bucket. + Values []int64 + // Count is the total count. + Count int64 + // Sum is the sum of all the values recorded. + Sum float64 + // Min is the smallest recorded value. + Min float64 + // Max is the largest recorded value. + Max float64 +} + +func labelListEqual(a, b []label.Label) bool { + //TODO: make this more efficient + return fmt.Sprint(a) == fmt.Sprint(b) +} + +func labelListLess(a, b []label.Label) bool { + //TODO: make this more efficient + return fmt.Sprint(a) < fmt.Sprint(b) +} + +func getGroup(lm label.Map, g *[][]label.Label, keys []label.Key) (int, bool) { + group := make([]label.Label, len(keys)) + for i, key := range keys { + l := lm.Find(key) + if l.Valid() { + group[i] = l + } + } + old := *g + index := sort.Search(len(old), func(i int) bool { + return !labelListLess(old[i], group) + }) + if index < len(old) && labelListEqual(group, old[index]) { + // not a new group + return index, false + } + *g = make([][]label.Label, len(old)+1) + copy(*g, old[:index]) + copy((*g)[index+1:], old[index:]) + (*g)[index] = group + return index, true +} + +func (data *Int64Data) Handle() string { return data.Info.Name } +func (data *Int64Data) Groups() [][]label.Label { return data.groups } + +func (data *Int64Data) modify(at time.Time, lm label.Map, f func(v int64) int64) Data { + index, insert := getGroup(lm, &data.groups, data.Info.Keys) + old := data.Rows + if insert { + data.Rows = make([]int64, len(old)+1) + copy(data.Rows, old[:index]) + copy(data.Rows[index+1:], old[index:]) + } else { + data.Rows = make([]int64, len(old)) + copy(data.Rows, old) + } + data.Rows[index] = f(data.Rows[index]) + data.EndTime = at + frozen := *data + return &frozen +} + +func (data *Int64Data) count(at time.Time, lm label.Map, l label.Label) Data { + return data.modify(at, lm, func(v int64) int64 { + return v + 1 + }) +} + +func (data *Int64Data) sum(at time.Time, lm label.Map, l label.Label) Data { + return data.modify(at, lm, func(v int64) int64 { + return v + data.key.From(l) + }) +} + +func (data *Int64Data) latest(at time.Time, lm label.Map, l label.Label) Data { + return data.modify(at, lm, func(v int64) int64 { + return data.key.From(l) + }) +} + +func (data *Float64Data) Handle() string { return data.Info.Name } +func (data *Float64Data) Groups() [][]label.Label { return data.groups } + +func (data *Float64Data) modify(at time.Time, lm label.Map, f func(v float64) float64) Data { + index, insert := getGroup(lm, &data.groups, data.Info.Keys) + old := data.Rows + if insert { + data.Rows = make([]float64, len(old)+1) + copy(data.Rows, old[:index]) + copy(data.Rows[index+1:], old[index:]) + } else { + data.Rows = make([]float64, len(old)) + copy(data.Rows, old) + } + data.Rows[index] = f(data.Rows[index]) + data.EndTime = at + frozen := *data + return &frozen +} + +func (data *Float64Data) sum(at time.Time, lm label.Map, l label.Label) Data { + return data.modify(at, lm, func(v float64) float64 { + return v + data.key.From(l) + }) +} + +func (data *Float64Data) latest(at time.Time, lm label.Map, l label.Label) Data { + return data.modify(at, lm, func(v float64) float64 { + return data.key.From(l) + }) +} + +func (data *HistogramInt64Data) Handle() string { return data.Info.Name } +func (data *HistogramInt64Data) Groups() [][]label.Label { return data.groups } + +func (data *HistogramInt64Data) modify(at time.Time, lm label.Map, f func(v *HistogramInt64Row)) Data { + index, insert := getGroup(lm, &data.groups, data.Info.Keys) + old := data.Rows + var v HistogramInt64Row + if insert { + data.Rows = make([]*HistogramInt64Row, len(old)+1) + copy(data.Rows, old[:index]) + copy(data.Rows[index+1:], old[index:]) + } else { + data.Rows = make([]*HistogramInt64Row, len(old)) + copy(data.Rows, old) + v = *data.Rows[index] + } + oldValues := v.Values + v.Values = make([]int64, len(data.Info.Buckets)) + copy(v.Values, oldValues) + f(&v) + data.Rows[index] = &v + data.EndTime = at + frozen := *data + return &frozen +} + +func (data *HistogramInt64Data) record(at time.Time, lm label.Map, l label.Label) Data { + return data.modify(at, lm, func(v *HistogramInt64Row) { + value := data.key.From(l) + v.Sum += value + if v.Min > value || v.Count == 0 { + v.Min = value + } + if v.Max < value || v.Count == 0 { + v.Max = value + } + v.Count++ + for i, b := range data.Info.Buckets { + if value <= b { + v.Values[i]++ + } + } + }) +} + +func (data *HistogramFloat64Data) Handle() string { return data.Info.Name } +func (data *HistogramFloat64Data) Groups() [][]label.Label { return data.groups } + +func (data *HistogramFloat64Data) modify(at time.Time, lm label.Map, f func(v *HistogramFloat64Row)) Data { + index, insert := getGroup(lm, &data.groups, data.Info.Keys) + old := data.Rows + var v HistogramFloat64Row + if insert { + data.Rows = make([]*HistogramFloat64Row, len(old)+1) + copy(data.Rows, old[:index]) + copy(data.Rows[index+1:], old[index:]) + } else { + data.Rows = make([]*HistogramFloat64Row, len(old)) + copy(data.Rows, old) + v = *data.Rows[index] + } + oldValues := v.Values + v.Values = make([]int64, len(data.Info.Buckets)) + copy(v.Values, oldValues) + f(&v) + data.Rows[index] = &v + data.EndTime = at + frozen := *data + return &frozen +} + +func (data *HistogramFloat64Data) record(at time.Time, lm label.Map, l label.Label) Data { + return data.modify(at, lm, func(v *HistogramFloat64Row) { + value := data.key.From(l) + v.Sum += value + if v.Min > value || v.Count == 0 { + v.Min = value + } + if v.Max < value || v.Count == 0 { + v.Max = value + } + v.Count++ + for i, b := range data.Info.Buckets { + if value <= b { + v.Values[i]++ + } + } + }) +} diff --git a/contribs/gnopls/internal/event/export/metric/exporter.go b/contribs/gnopls/internal/event/export/metric/exporter.go new file mode 100644 index 00000000000..344c59c3ad3 --- /dev/null +++ b/contribs/gnopls/internal/event/export/metric/exporter.go @@ -0,0 +1,58 @@ +// 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. + +// Package metric aggregates events into metrics that can be exported. +package metric + +import ( + "context" + "sync" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +var Entries = keys.New("metric_entries", "The set of metrics calculated for an event") + +type Config struct { + subscribers map[interface{}][]subscriber +} + +type subscriber func(time.Time, label.Map, label.Label) Data + +func (e *Config) subscribe(key label.Key, s subscriber) { + if e.subscribers == nil { + e.subscribers = make(map[interface{}][]subscriber) + } + e.subscribers[key] = append(e.subscribers[key], s) +} + +func (e *Config) Exporter(output event.Exporter) event.Exporter { + var mu sync.Mutex + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + if !event.IsMetric(ev) { + return output(ctx, ev, lm) + } + mu.Lock() + defer mu.Unlock() + var metrics []Data + for index := 0; ev.Valid(index); index++ { + l := ev.Label(index) + if !l.Valid() { + continue + } + id := l.Key() + if list := e.subscribers[id]; len(list) > 0 { + for _, s := range list { + metrics = append(metrics, s(ev.At(), lm, l)) + } + } + } + lm = label.MergeMaps(label.NewMap(Entries.Of(metrics)), lm) + return output(ctx, ev, lm) + } +} diff --git a/contribs/gnopls/internal/event/export/metric/info.go b/contribs/gnopls/internal/event/export/metric/info.go new file mode 100644 index 00000000000..d196ac8923f --- /dev/null +++ b/contribs/gnopls/internal/event/export/metric/info.go @@ -0,0 +1,100 @@ +// 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. + +package metric + +import ( + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Scalar represents the construction information for a scalar metric. +type Scalar struct { + // Name is the unique name of this metric. + Name string + // Description can be used by observers to describe the metric to users. + Description string + // Keys is the set of labels that collectively describe rows of the metric. + Keys []label.Key +} + +// HistogramInt64 represents the construction information for an int64 histogram metric. +type HistogramInt64 struct { + // Name is the unique name of this metric. + Name string + // Description can be used by observers to describe the metric to users. + Description string + // Keys is the set of labels that collectively describe rows of the metric. + Keys []label.Key + // Buckets holds the inclusive upper bound of each bucket in the histogram. + Buckets []int64 +} + +// HistogramFloat64 represents the construction information for an float64 histogram metric. +type HistogramFloat64 struct { + // Name is the unique name of this metric. + Name string + // Description can be used by observers to describe the metric to users. + Description string + // Keys is the set of labels that collectively describe rows of the metric. + Keys []label.Key + // Buckets holds the inclusive upper bound of each bucket in the histogram. + Buckets []float64 +} + +// Count creates a new metric based on the Scalar information that counts +// the number of times the supplied int64 measure is set. +// Metrics of this type will use Int64Data. +func (info Scalar) Count(e *Config, key label.Key) { + data := &Int64Data{Info: &info, key: nil} + e.subscribe(key, data.count) +} + +// SumInt64 creates a new metric based on the Scalar information that sums all +// the values recorded on the int64 measure. +// Metrics of this type will use Int64Data. +func (info Scalar) SumInt64(e *Config, key *keys.Int64) { + data := &Int64Data{Info: &info, key: key} + e.subscribe(key, data.sum) +} + +// LatestInt64 creates a new metric based on the Scalar information that tracks +// the most recent value recorded on the int64 measure. +// Metrics of this type will use Int64Data. +func (info Scalar) LatestInt64(e *Config, key *keys.Int64) { + data := &Int64Data{Info: &info, IsGauge: true, key: key} + e.subscribe(key, data.latest) +} + +// SumFloat64 creates a new metric based on the Scalar information that sums all +// the values recorded on the float64 measure. +// Metrics of this type will use Float64Data. +func (info Scalar) SumFloat64(e *Config, key *keys.Float64) { + data := &Float64Data{Info: &info, key: key} + e.subscribe(key, data.sum) +} + +// LatestFloat64 creates a new metric based on the Scalar information that tracks +// the most recent value recorded on the float64 measure. +// Metrics of this type will use Float64Data. +func (info Scalar) LatestFloat64(e *Config, key *keys.Float64) { + data := &Float64Data{Info: &info, IsGauge: true, key: key} + e.subscribe(key, data.latest) +} + +// Record creates a new metric based on the HistogramInt64 information that +// tracks the bucketized counts of values recorded on the int64 measure. +// Metrics of this type will use HistogramInt64Data. +func (info HistogramInt64) Record(e *Config, key *keys.Int64) { + data := &HistogramInt64Data{Info: &info, key: key} + e.subscribe(key, data.record) +} + +// Record creates a new metric based on the HistogramFloat64 information that +// tracks the bucketized counts of values recorded on the float64 measure. +// Metrics of this type will use HistogramFloat64Data. +func (info HistogramFloat64) Record(e *Config, key *keys.Float64) { + data := &HistogramFloat64Data{Info: &info, key: key} + e.subscribe(key, data.record) +} diff --git a/contribs/gnopls/internal/event/export/ocagent/README.md b/contribs/gnopls/internal/event/export/ocagent/README.md new file mode 100644 index 00000000000..bb347eeec08 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/README.md @@ -0,0 +1,139 @@ +# Exporting Metrics and Traces with OpenCensus, Zipkin, and Prometheus + +This tutorial provides a minimum example to verify that metrics and traces +can be exported to OpenCensus from Go tools. + +## Setting up oragent + +1. Ensure you have [docker](https://www.docker.com/get-started) and [docker-compose](https://docs.docker.com/compose/install/). +2. Clone [oragent](https://github.com/orijtech/oragent). +3. In the oragent directory, start the services: +```bash +docker-compose up +``` +If everything goes well, you should see output resembling the following: +``` +Starting oragent_zipkin_1 ... done +Starting oragent_oragent_1 ... done +Starting oragent_prometheus_1 ... done +... +``` +* You can check the status of the OpenCensus agent using zPages at http://localhost:55679/debug/tracez. +* You can now access the Prometheus UI at http://localhost:9445. +* You can now access the Zipkin UI at http://localhost:9444. +4. To shut down oragent, hit Ctrl+C in the terminal. +5. You can also start oragent in detached mode by running `docker-compose up -d`. To stop oragent while detached, run `docker-compose down`. + +## Exporting Metrics and Traces +1. Clone the [tools](https://golang.org/x/tools) subrepository. +1. Inside `internal`, create a file named `main.go` with the following contents: +```go +package main + +import ( + "context" + "fmt" + "math/rand" + "net/http" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/metric" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/ocagent" +) + +type testExporter struct { + metrics metric.Exporter + ocagent *ocagent.Exporter +} + +func (e *testExporter) ProcessEvent(ctx context.Context, ev event.Event) (context.Context, event.Event) { + ctx, ev = export.Tag(ctx, ev) + ctx, ev = export.ContextSpan(ctx, ev) + ctx, ev = e.metrics.ProcessEvent(ctx, ev) + ctx, ev = e.ocagent.ProcessEvent(ctx, ev) + return ctx, ev +} + +func main() { + exporter := &testExporter{} + + exporter.ocagent = ocagent.Connect(&ocagent.Config{ + Start: time.Now(), + Address: "http://127.0.0.1:55678", + Service: "go-tools-test", + Rate: 5 * time.Second, + Client: &http.Client{}, + }) + event.SetExporter(exporter) + + ctx := context.TODO() + mLatency := event.NewFloat64Key("latency", "the latency in milliseconds") + distribution := metric.HistogramFloat64Data{ + Info: &metric.HistogramFloat64{ + Name: "latencyDistribution", + Description: "the various latencies", + Buckets: []float64{0, 10, 50, 100, 200, 400, 800, 1000, 1400, 2000, 5000, 10000, 15000}, + }, + } + + distribution.Info.Record(&exporter.metrics, mLatency) + + for { + sleep := randomSleep() + _, end := event.StartSpan(ctx, "main.randomSleep()") + time.Sleep(time.Duration(sleep) * time.Millisecond) + end() + event.Record(ctx, mLatency.Of(float64(sleep))) + + fmt.Println("Latency: ", float64(sleep)) + } +} + +func randomSleep() int64 { + var max int64 + switch modulus := time.Now().Unix() % 5; modulus { + case 0: + max = 17001 + case 1: + max = 8007 + case 2: + max = 917 + case 3: + max = 87 + case 4: + max = 1173 + } + return rand.Int63n(max) +} + +``` +3. Run the new file from within the tools repository: +```bash +go run internal/main.go +``` +4. After about 5 seconds, OpenCensus should start receiving your new metrics, which you can see at http://localhost:8844/metrics. This page will look similar to the following: +``` +# HELP promdemo_latencyDistribution the various latencies +# TYPE promdemo_latencyDistribution histogram +promdemo_latencyDistribution_bucket{vendor="otc",le="0"} 0 +promdemo_latencyDistribution_bucket{vendor="otc",le="10"} 2 +promdemo_latencyDistribution_bucket{vendor="otc",le="50"} 9 +promdemo_latencyDistribution_bucket{vendor="otc",le="100"} 22 +promdemo_latencyDistribution_bucket{vendor="otc",le="200"} 35 +promdemo_latencyDistribution_bucket{vendor="otc",le="400"} 49 +promdemo_latencyDistribution_bucket{vendor="otc",le="800"} 63 +promdemo_latencyDistribution_bucket{vendor="otc",le="1000"} 78 +promdemo_latencyDistribution_bucket{vendor="otc",le="1400"} 93 +promdemo_latencyDistribution_bucket{vendor="otc",le="2000"} 108 +promdemo_latencyDistribution_bucket{vendor="otc",le="5000"} 123 +promdemo_latencyDistribution_bucket{vendor="otc",le="10000"} 138 +promdemo_latencyDistribution_bucket{vendor="otc",le="15000"} 153 +promdemo_latencyDistribution_bucket{vendor="otc",le="+Inf"} 15 +promdemo_latencyDistribution_sum{vendor="otc"} 1641 +promdemo_latencyDistribution_count{vendor="otc"} 15 +``` +5. After a few more seconds, Prometheus should start displaying your new metrics. You can view the distribution at http://localhost:9445/graph?g0.range_input=5m&g0.stacked=1&g0.expr=rate(oragent_latencyDistribution_bucket%5B5m%5D)&g0.tab=0. + +6. Zipkin should also start displaying traces. You can view them at http://localhost:9444/zipkin/?limit=10&lookback=300000&serviceName=go-tools-test. \ No newline at end of file diff --git a/contribs/gnopls/internal/event/export/ocagent/metrics.go b/contribs/gnopls/internal/event/export/ocagent/metrics.go new file mode 100644 index 00000000000..ff10e6d0de1 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/metrics.go @@ -0,0 +1,213 @@ +// 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. + +package ocagent + +import ( + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/metric" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/ocagent/wire" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// dataToMetricDescriptor return a *wire.MetricDescriptor based on data. +func dataToMetricDescriptor(data metric.Data) *wire.MetricDescriptor { + if data == nil { + return nil + } + descriptor := &wire.MetricDescriptor{ + Name: data.Handle(), + Description: getDescription(data), + // TODO: Unit? + Type: dataToMetricDescriptorType(data), + LabelKeys: getLabelKeys(data), + } + + return descriptor +} + +// getDescription returns the description of data. +func getDescription(data metric.Data) string { + switch d := data.(type) { + case *metric.Int64Data: + return d.Info.Description + + case *metric.Float64Data: + return d.Info.Description + + case *metric.HistogramInt64Data: + return d.Info.Description + + case *metric.HistogramFloat64Data: + return d.Info.Description + } + + return "" +} + +// getLabelKeys returns a slice of *wire.LabelKeys based on the keys +// in data. +func getLabelKeys(data metric.Data) []*wire.LabelKey { + switch d := data.(type) { + case *metric.Int64Data: + return infoKeysToLabelKeys(d.Info.Keys) + + case *metric.Float64Data: + return infoKeysToLabelKeys(d.Info.Keys) + + case *metric.HistogramInt64Data: + return infoKeysToLabelKeys(d.Info.Keys) + + case *metric.HistogramFloat64Data: + return infoKeysToLabelKeys(d.Info.Keys) + } + + return nil +} + +// dataToMetricDescriptorType returns a wire.MetricDescriptor_Type based on the +// underlying type of data. +func dataToMetricDescriptorType(data metric.Data) wire.MetricDescriptor_Type { + switch d := data.(type) { + case *metric.Int64Data: + if d.IsGauge { + return wire.MetricDescriptor_GAUGE_INT64 + } + return wire.MetricDescriptor_CUMULATIVE_INT64 + + case *metric.Float64Data: + if d.IsGauge { + return wire.MetricDescriptor_GAUGE_DOUBLE + } + return wire.MetricDescriptor_CUMULATIVE_DOUBLE + + case *metric.HistogramInt64Data: + return wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION + + case *metric.HistogramFloat64Data: + return wire.MetricDescriptor_CUMULATIVE_DISTRIBUTION + } + + return wire.MetricDescriptor_UNSPECIFIED +} + +// dataToTimeseries returns a slice of *wire.TimeSeries based on the +// points in data. +func dataToTimeseries(data metric.Data, start time.Time) []*wire.TimeSeries { + if data == nil { + return nil + } + + numRows := numRows(data) + startTimestamp := convertTimestamp(start) + timeseries := make([]*wire.TimeSeries, 0, numRows) + + for i := 0; i < numRows; i++ { + timeseries = append(timeseries, &wire.TimeSeries{ + StartTimestamp: &startTimestamp, + // TODO: labels? + Points: dataToPoints(data, i), + }) + } + + return timeseries +} + +// numRows returns the number of rows in data. +func numRows(data metric.Data) int { + switch d := data.(type) { + case *metric.Int64Data: + return len(d.Rows) + case *metric.Float64Data: + return len(d.Rows) + case *metric.HistogramInt64Data: + return len(d.Rows) + case *metric.HistogramFloat64Data: + return len(d.Rows) + } + + return 0 +} + +// dataToPoints returns an array of *wire.Points based on the point(s) +// in data at index i. +func dataToPoints(data metric.Data, i int) []*wire.Point { + switch d := data.(type) { + case *metric.Int64Data: + timestamp := convertTimestamp(d.EndTime) + return []*wire.Point{ + { + Value: wire.PointInt64Value{ + Int64Value: d.Rows[i], + }, + Timestamp: ×tamp, + }, + } + case *metric.Float64Data: + timestamp := convertTimestamp(d.EndTime) + return []*wire.Point{ + { + Value: wire.PointDoubleValue{ + DoubleValue: d.Rows[i], + }, + Timestamp: ×tamp, + }, + } + case *metric.HistogramInt64Data: + row := d.Rows[i] + bucketBounds := make([]float64, len(d.Info.Buckets)) + for i, val := range d.Info.Buckets { + bucketBounds[i] = float64(val) + } + return distributionToPoints(row.Values, row.Count, float64(row.Sum), bucketBounds, d.EndTime) + case *metric.HistogramFloat64Data: + row := d.Rows[i] + return distributionToPoints(row.Values, row.Count, row.Sum, d.Info.Buckets, d.EndTime) + } + + return nil +} + +// distributionToPoints returns an array of *wire.Points containing a +// wire.PointDistributionValue representing a distribution with the +// supplied counts, count, and sum. +func distributionToPoints(counts []int64, count int64, sum float64, bucketBounds []float64, end time.Time) []*wire.Point { + buckets := make([]*wire.Bucket, len(counts)) + for i := 0; i < len(counts); i++ { + buckets[i] = &wire.Bucket{ + Count: counts[i], + } + } + timestamp := convertTimestamp(end) + return []*wire.Point{ + { + Value: wire.PointDistributionValue{ + DistributionValue: &wire.DistributionValue{ + Count: count, + Sum: sum, + // TODO: SumOfSquaredDeviation? + Buckets: buckets, + BucketOptions: &wire.BucketOptionsExplicit{ + Bounds: bucketBounds, + }, + }, + }, + Timestamp: ×tamp, + }, + } +} + +// infoKeysToLabelKeys returns an array of *wire.LabelKeys containing the +// string values of the elements of labelKeys. +func infoKeysToLabelKeys(infoKeys []label.Key) []*wire.LabelKey { + labelKeys := make([]*wire.LabelKey, 0, len(infoKeys)) + for _, key := range infoKeys { + labelKeys = append(labelKeys, &wire.LabelKey{ + Key: key.Name(), + }) + } + + return labelKeys +} diff --git a/contribs/gnopls/internal/event/export/ocagent/metrics_test.go b/contribs/gnopls/internal/event/export/ocagent/metrics_test.go new file mode 100644 index 00000000000..ce8749f5d81 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/metrics_test.go @@ -0,0 +1,144 @@ +// 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 ocagent_test + +import ( + "context" + "errors" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" +) + +func TestEncodeMetric(t *testing.T) { + exporter := registerExporter() + const prefix = testNodeStr + ` + "metrics":[` + const suffix = `]}` + tests := []struct { + name string + run func(ctx context.Context) + want string + }{ + { + name: "HistogramFloat64, HistogramInt64", + run: func(ctx context.Context) { + ctx = event.Label(ctx, keyMethod.Of("godoc.ServeHTTP")) + event.Metric(ctx, latencyMs.Of(96.58)) + ctx = event.Label(ctx, keys.Err.Of(errors.New("panic: fatal signal"))) + event.Metric(ctx, bytesIn.Of(97e2)) + }, + want: prefix + ` + { + "metric_descriptor": { + "name": "latency_ms", + "description": "The latency of calls in milliseconds", + "type": 6, + "label_keys": [ + { + "key": "method" + }, + { + "key": "route" + } + ] + }, + "timeseries": [ + { + "start_timestamp": "1970-01-01T00:00:00Z", + "points": [ + { + "timestamp": "1970-01-01T00:00:40Z", + "distributionValue": { + "count": 1, + "sum": 96.58, + "bucket_options": { + "explicit": { + "bounds": [ + 0, + 5, + 10, + 25, + 50 + ] + } + }, + "buckets": [ + {}, + {}, + {}, + {}, + {} + ] + } + } + ] + } + ] + }, + { + "metric_descriptor": { + "name": "latency_ms", + "description": "The latency of calls in milliseconds", + "type": 6, + "label_keys": [ + { + "key": "method" + }, + { + "key": "route" + } + ] + }, + "timeseries": [ + { + "start_timestamp": "1970-01-01T00:00:00Z", + "points": [ + { + "timestamp": "1970-01-01T00:00:40Z", + "distributionValue": { + "count": 1, + "sum": 9700, + "bucket_options": { + "explicit": { + "bounds": [ + 0, + 10, + 50, + 100, + 500, + 1000, + 2000 + ] + } + }, + "buckets": [ + {}, + {}, + {}, + {}, + {}, + {}, + {} + ] + } + } + ] + } + ] + }` + suffix, + }, + } + + ctx := context.TODO() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.run(ctx) + got := exporter.Output("/v1/metrics") + checkJSON(t, got, []byte(tt.want)) + }) + } +} diff --git a/contribs/gnopls/internal/event/export/ocagent/ocagent.go b/contribs/gnopls/internal/event/export/ocagent/ocagent.go new file mode 100644 index 00000000000..b0f9fb80600 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/ocagent.go @@ -0,0 +1,358 @@ +// 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. + +// Package ocagent adds the ability to export all telemetry to an ocagent. +// This keeps the compile time dependencies to zero and allows the agent to +// have the exporters needed for telemetry aggregation and viewing systems. +package ocagent + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "sync" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/metric" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/ocagent/wire" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +type Config struct { + Start time.Time + Host string + Process uint32 + Client *http.Client + Service string + Address string + Rate time.Duration +} + +var ( + connectMu sync.Mutex + exporters = make(map[Config]*Exporter) +) + +// Discover finds the local agent to export to, it will return nil if there +// is not one running. +// TODO: Actually implement a discovery protocol rather than a hard coded address +func Discover() *Config { + return &Config{ + Address: "http://localhost:55678", + } +} + +type Exporter struct { + mu sync.Mutex + config Config + spans []*export.Span + metrics []metric.Data +} + +// Connect creates a process specific exporter with the specified +// serviceName and the address of the ocagent to which it will upload +// its telemetry. +func Connect(config *Config) *Exporter { + if config == nil || config.Address == "off" { + return nil + } + resolved := *config + if resolved.Host == "" { + hostname, _ := os.Hostname() + resolved.Host = hostname + } + if resolved.Process == 0 { + resolved.Process = uint32(os.Getpid()) + } + if resolved.Client == nil { + resolved.Client = http.DefaultClient + } + if resolved.Service == "" { + resolved.Service = filepath.Base(os.Args[0]) + } + if resolved.Rate == 0 { + resolved.Rate = 2 * time.Second + } + + connectMu.Lock() + defer connectMu.Unlock() + if exporter, found := exporters[resolved]; found { + return exporter + } + exporter := &Exporter{config: resolved} + exporters[resolved] = exporter + if exporter.config.Start.IsZero() { + exporter.config.Start = time.Now() + } + go func() { + for range time.Tick(exporter.config.Rate) { + exporter.Flush() + } + }() + return exporter +} + +func (e *Exporter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { + switch { + case event.IsEnd(ev): + e.mu.Lock() + defer e.mu.Unlock() + span := export.GetSpan(ctx) + if span != nil { + e.spans = append(e.spans, span) + } + case event.IsMetric(ev): + e.mu.Lock() + defer e.mu.Unlock() + data := metric.Entries.Get(lm).([]metric.Data) + e.metrics = append(e.metrics, data...) + } + return ctx +} + +func (e *Exporter) Flush() { + e.mu.Lock() + defer e.mu.Unlock() + spans := make([]*wire.Span, len(e.spans)) + for i, s := range e.spans { + spans[i] = convertSpan(s) + } + e.spans = nil + metrics := make([]*wire.Metric, len(e.metrics)) + for i, m := range e.metrics { + metrics[i] = convertMetric(m, e.config.Start) + } + e.metrics = nil + + if len(spans) > 0 { + e.send("/v1/trace", &wire.ExportTraceServiceRequest{ + Node: e.config.buildNode(), + Spans: spans, + //TODO: Resource? + }) + } + if len(metrics) > 0 { + e.send("/v1/metrics", &wire.ExportMetricsServiceRequest{ + Node: e.config.buildNode(), + Metrics: metrics, + //TODO: Resource? + }) + } +} + +func (cfg *Config) buildNode() *wire.Node { + return &wire.Node{ + Identifier: &wire.ProcessIdentifier{ + HostName: cfg.Host, + Pid: cfg.Process, + StartTimestamp: convertTimestamp(cfg.Start), + }, + LibraryInfo: &wire.LibraryInfo{ + Language: wire.LanguageGo, + ExporterVersion: "0.0.1", + CoreLibraryVersion: "x/tools", + }, + ServiceInfo: &wire.ServiceInfo{ + Name: cfg.Service, + }, + } +} + +func (e *Exporter) send(endpoint string, message interface{}) { + blob, err := json.Marshal(message) + if err != nil { + errorInExport("ocagent failed to marshal message for %v: %v", endpoint, err) + return + } + uri := e.config.Address + endpoint + req, err := http.NewRequest("POST", uri, bytes.NewReader(blob)) + if err != nil { + errorInExport("ocagent failed to build request for %v: %v", uri, err) + return + } + req.Header.Set("Content-Type", "application/json") + res, err := e.config.Client.Do(req) + if err != nil { + errorInExport("ocagent failed to send message: %v \n", err) + return + } + if res.Body != nil { + res.Body.Close() + } +} + +func errorInExport(message string, args ...interface{}) { + // This function is useful when debugging the exporter, but in general we + // want to just drop any export +} + +func convertTimestamp(t time.Time) wire.Timestamp { + return t.Format(time.RFC3339Nano) +} + +func toTruncatableString(s string) *wire.TruncatableString { + if s == "" { + return nil + } + return &wire.TruncatableString{Value: s} +} + +func convertSpan(span *export.Span) *wire.Span { + result := &wire.Span{ + TraceID: span.ID.TraceID[:], + SpanID: span.ID.SpanID[:], + TraceState: nil, //TODO? + ParentSpanID: span.ParentID[:], + Name: toTruncatableString(span.Name), + Kind: wire.UnspecifiedSpanKind, + StartTime: convertTimestamp(span.Start().At()), + EndTime: convertTimestamp(span.Finish().At()), + Attributes: convertAttributes(span.Start(), 1), + TimeEvents: convertEvents(span.Events()), + SameProcessAsParentSpan: true, + //TODO: StackTrace? + //TODO: Links? + //TODO: Status? + //TODO: Resource? + } + return result +} + +func convertMetric(data metric.Data, start time.Time) *wire.Metric { + descriptor := dataToMetricDescriptor(data) + timeseries := dataToTimeseries(data, start) + + if descriptor == nil && timeseries == nil { + return nil + } + + // TODO: handle Histogram metrics + return &wire.Metric{ + MetricDescriptor: descriptor, + Timeseries: timeseries, + // TODO: attach Resource? + } +} + +func skipToValidLabel(list label.List, index int) (int, label.Label) { + // skip to the first valid label + for ; list.Valid(index); index++ { + l := list.Label(index) + if !l.Valid() || l.Key() == keys.Label { + continue + } + return index, l + } + return -1, label.Label{} +} + +func convertAttributes(list label.List, index int) *wire.Attributes { + index, l := skipToValidLabel(list, index) + if !l.Valid() { + return nil + } + attributes := make(map[string]wire.Attribute) + for { + if l.Valid() { + attributes[l.Key().Name()] = convertAttribute(l) + } + index++ + if !list.Valid(index) { + return &wire.Attributes{AttributeMap: attributes} + } + l = list.Label(index) + } +} + +func convertAttribute(l label.Label) wire.Attribute { + switch key := l.Key().(type) { + case *keys.Int: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.Int8: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.Int16: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.Int32: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.Int64: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.UInt: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.UInt8: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.UInt16: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.UInt32: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.UInt64: + return wire.IntAttribute{IntValue: int64(key.From(l))} + case *keys.Float32: + return wire.DoubleAttribute{DoubleValue: float64(key.From(l))} + case *keys.Float64: + return wire.DoubleAttribute{DoubleValue: key.From(l)} + case *keys.Boolean: + return wire.BoolAttribute{BoolValue: key.From(l)} + case *keys.String: + return wire.StringAttribute{StringValue: toTruncatableString(key.From(l))} + case *keys.Error: + return wire.StringAttribute{StringValue: toTruncatableString(key.From(l).Error())} + case *keys.Value: + return wire.StringAttribute{StringValue: toTruncatableString(fmt.Sprint(key.From(l)))} + default: + return wire.StringAttribute{StringValue: toTruncatableString(fmt.Sprintf("%T", key))} + } +} + +func convertEvents(events []core.Event) *wire.TimeEvents { + //TODO: MessageEvents? + result := make([]wire.TimeEvent, len(events)) + for i, event := range events { + result[i] = convertEvent(event) + } + return &wire.TimeEvents{TimeEvent: result} +} + +func convertEvent(ev core.Event) wire.TimeEvent { + return wire.TimeEvent{ + Time: convertTimestamp(ev.At()), + Annotation: convertAnnotation(ev), + } +} + +func getAnnotationDescription(ev core.Event) (string, int) { + l := ev.Label(0) + if l.Key() != keys.Msg { + return "", 0 + } + if msg := keys.Msg.From(l); msg != "" { + return msg, 1 + } + l = ev.Label(1) + if l.Key() != keys.Err { + return "", 1 + } + if err := keys.Err.From(l); err != nil { + return err.Error(), 2 + } + return "", 2 +} + +func convertAnnotation(ev core.Event) *wire.Annotation { + description, index := getAnnotationDescription(ev) + if _, l := skipToValidLabel(ev, index); !l.Valid() && description == "" { + return nil + } + return &wire.Annotation{ + Description: toTruncatableString(description), + Attributes: convertAttributes(ev, index), + } +} diff --git a/contribs/gnopls/internal/event/export/ocagent/ocagent_test.go b/contribs/gnopls/internal/event/export/ocagent/ocagent_test.go new file mode 100644 index 00000000000..347e4ce5a58 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/ocagent_test.go @@ -0,0 +1,210 @@ +// 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. + +package ocagent_test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/metric" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/ocagent" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +const testNodeStr = `{ + "node":{ + "identifier":{ + "host_name":"tester", + "pid":1, + "start_timestamp":"1970-01-01T00:00:00Z" + }, + "library_info":{ + "language":4, + "exporter_version":"0.0.1", + "core_library_version":"x/tools" + }, + "service_info":{ + "name":"ocagent-tests" + } + },` + +var ( + keyDB = keys.NewString("db", "the database name") + keyMethod = keys.NewString("method", "a metric grouping key") + keyRoute = keys.NewString("route", "another metric grouping key") + + key1DB = keys.NewString("1_db", "A test string key") + + key2aAge = keys.NewFloat64("2a_age", "A test float64 key") + key2bTTL = keys.NewFloat32("2b_ttl", "A test float32 key") + key2cExpiryMS = keys.NewFloat64("2c_expiry_ms", "A test float64 key") + + key3aRetry = keys.NewBoolean("3a_retry", "A test boolean key") + key3bStale = keys.NewBoolean("3b_stale", "Another test boolean key") + + key4aMax = keys.NewInt("4a_max", "A test int key") + key4bOpcode = keys.NewInt8("4b_opcode", "A test int8 key") + key4cBase = keys.NewInt16("4c_base", "A test int16 key") + key4eChecksum = keys.NewInt32("4e_checksum", "A test int32 key") + key4fMode = keys.NewInt64("4f_mode", "A test int64 key") + + key5aMin = keys.NewUInt("5a_min", "A test uint key") + key5bMix = keys.NewUInt8("5b_mix", "A test uint8 key") + key5cPort = keys.NewUInt16("5c_port", "A test uint16 key") + key5dMinHops = keys.NewUInt32("5d_min_hops", "A test uint32 key") + key5eMaxHops = keys.NewUInt64("5e_max_hops", "A test uint64 key") + + recursiveCalls = keys.NewInt64("recursive_calls", "Number of recursive calls") + bytesIn = keys.NewInt64("bytes_in", "Number of bytes in") //, unit.Bytes) + latencyMs = keys.NewFloat64("latency", "The latency in milliseconds") //, unit.Milliseconds) + + metricLatency = metric.HistogramFloat64{ + Name: "latency_ms", + Description: "The latency of calls in milliseconds", + Keys: []label.Key{keyMethod, keyRoute}, + Buckets: []float64{0, 5, 10, 25, 50}, + } + + metricBytesIn = metric.HistogramInt64{ + Name: "latency_ms", + Description: "The latency of calls in milliseconds", + Keys: []label.Key{keyMethod, keyRoute}, + Buckets: []int64{0, 10, 50, 100, 500, 1000, 2000}, + } + + metricRecursiveCalls = metric.Scalar{ + Name: "latency_ms", + Description: "The latency of calls in milliseconds", + Keys: []label.Key{keyMethod, keyRoute}, + } +) + +type testExporter struct { + ocagent *ocagent.Exporter + sent fakeSender +} + +func registerExporter() *testExporter { + exporter := &testExporter{} + cfg := ocagent.Config{ + Host: "tester", + Process: 1, + Service: "ocagent-tests", + Client: &http.Client{Transport: &exporter.sent}, + } + cfg.Start, _ = time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00Z") + exporter.ocagent = ocagent.Connect(&cfg) + + metrics := metric.Config{} + metricLatency.Record(&metrics, latencyMs) + metricBytesIn.Record(&metrics, bytesIn) + metricRecursiveCalls.SumInt64(&metrics, recursiveCalls) + + e := exporter.ocagent.ProcessEvent + e = metrics.Exporter(e) + e = spanFixer(e) + e = export.Spans(e) + e = export.Labels(e) + e = timeFixer(e) + event.SetExporter(e) + return exporter +} + +func timeFixer(output event.Exporter) event.Exporter { + start, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:30Z") + at, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:40Z") + end, _ := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:50Z") + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + switch { + case event.IsStart(ev): + ev = core.CloneEvent(ev, start) + case event.IsEnd(ev): + ev = core.CloneEvent(ev, end) + default: + ev = core.CloneEvent(ev, at) + } + return output(ctx, ev, lm) + } +} + +func spanFixer(output event.Exporter) event.Exporter { + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + if event.IsStart(ev) { + span := export.GetSpan(ctx) + span.ID = export.SpanContext{} + } + return output(ctx, ev, lm) + } +} + +func (e *testExporter) Output(route string) []byte { + e.ocagent.Flush() + return e.sent.get(route) +} + +func checkJSON(t *testing.T, got, want []byte) { + // compare the compact form, to allow for formatting differences + g := &bytes.Buffer{} + if err := json.Compact(g, got); err != nil { + t.Fatal(err) + } + w := &bytes.Buffer{} + if err := json.Compact(w, want); err != nil { + t.Fatal(err) + } + if g.String() != w.String() { + t.Fatalf("Got:\n%s\nWant:\n%s", g, w) + } +} + +type fakeSender struct { + mu sync.Mutex + data map[string][]byte +} + +func (s *fakeSender) get(route string) []byte { + s.mu.Lock() + defer s.mu.Unlock() + data, found := s.data[route] + if found { + delete(s.data, route) + } + return data +} + +func (s *fakeSender) RoundTrip(req *http.Request) (*http.Response, error) { + s.mu.Lock() + defer s.mu.Unlock() + if s.data == nil { + s.data = make(map[string][]byte) + } + data, err := io.ReadAll(req.Body) + if err != nil { + return nil, err + } + path := req.URL.EscapedPath() + if _, found := s.data[path]; found { + return nil, fmt.Errorf("duplicate delivery to %v", path) + } + s.data[path] = data + return &http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + }, nil +} diff --git a/contribs/gnopls/internal/event/export/ocagent/trace_test.go b/contribs/gnopls/internal/event/export/ocagent/trace_test.go new file mode 100644 index 00000000000..b4289411fc2 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/trace_test.go @@ -0,0 +1,158 @@ +// 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. + +package ocagent_test + +import ( + "context" + "errors" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" +) + +func TestTrace(t *testing.T) { + exporter := registerExporter() + const prefix = testNodeStr + ` + "spans":[{ + "trace_id":"AAAAAAAAAAAAAAAAAAAAAA==", + "span_id":"AAAAAAAAAAA=", + "parent_span_id":"AAAAAAAAAAA=", + "name":{"value":"event span"}, + "start_time":"1970-01-01T00:00:30Z", + "end_time":"1970-01-01T00:00:50Z", + "time_events":{ +` + const suffix = ` + }, + "same_process_as_parent_span":true + }] +}` + + tests := []struct { + name string + run func(ctx context.Context) + want string + }{ + { + name: "no labels", + run: func(ctx context.Context) { + event.Label(ctx) + }, + want: prefix + ` + "timeEvent":[{"time":"1970-01-01T00:00:40Z"}] + ` + suffix, + }, + { + name: "description no error", + run: func(ctx context.Context) { + event.Log(ctx, "cache miss", keyDB.Of("godb")) + }, + want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{ +"description": { "value": "cache miss" }, +"attributes": { + "attributeMap": { + "db": { "stringValue": { "value": "godb" } } + } +} +}}]` + suffix, + }, + + { + name: "description and error", + run: func(ctx context.Context) { + event.Error(ctx, "cache miss", + errors.New("no network connectivity"), + keyDB.Of("godb"), + ) + }, + want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{ +"description": { "value": "cache miss" }, +"attributes": { + "attributeMap": { + "db": { "stringValue": { "value": "godb" } }, + "error": { "stringValue": { "value": "no network connectivity" } } + } +} +}}]` + suffix, + }, + { + name: "no description, but error", + run: func(ctx context.Context) { + event.Error(ctx, "", + errors.New("no network connectivity"), + keyDB.Of("godb"), + ) + }, + want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{ +"description": { "value": "no network connectivity" }, +"attributes": { + "attributeMap": { + "db": { "stringValue": { "value": "godb" } } + } +} +}}]` + suffix, + }, + { + name: "enumerate all attribute types", + run: func(ctx context.Context) { + event.Log(ctx, "cache miss", + key1DB.Of("godb"), + + key2aAge.Of(0.456), // Constant converted into "float64" + key2bTTL.Of(float32(5000)), + key2cExpiryMS.Of(float64(1e3)), + + key3aRetry.Of(false), + key3bStale.Of(true), + + key4aMax.Of(0x7fff), // Constant converted into "int" + key4bOpcode.Of(int8(0x7e)), + key4cBase.Of(int16(1<<9)), + key4eChecksum.Of(int32(0x11f7e294)), + key4fMode.Of(int64(0644)), + + key5aMin.Of(uint(1)), + key5bMix.Of(uint8(44)), + key5cPort.Of(uint16(55678)), + key5dMinHops.Of(uint32(1<<9)), + key5eMaxHops.Of(uint64(0xffffff)), + ) + }, + want: prefix + `"timeEvent":[{"time":"1970-01-01T00:00:40Z","annotation":{ +"description": { "value": "cache miss" }, +"attributes": { + "attributeMap": { + "1_db": { "stringValue": { "value": "godb" } }, + "2a_age": { "doubleValue": 0.456 }, + "2b_ttl": { "doubleValue": 5000 }, + "2c_expiry_ms": { "doubleValue": 1000 }, + "3a_retry": {}, + "3b_stale": { "boolValue": true }, + "4a_max": { "intValue": 32767 }, + "4b_opcode": { "intValue": 126 }, + "4c_base": { "intValue": 512 }, + "4e_checksum": { "intValue": 301458068 }, + "4f_mode": { "intValue": 420 }, + "5a_min": { "intValue": 1 }, + "5b_mix": { "intValue": 44 }, + "5c_port": { "intValue": 55678 }, + "5d_min_hops": { "intValue": 512 }, + "5e_max_hops": { "intValue": 16777215 } + } +} +}}]` + suffix, + }, + } + ctx := context.TODO() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, done := event.Start(ctx, "event span") + tt.run(ctx) + done() + got := exporter.Output("/v1/trace") + checkJSON(t, got, []byte(tt.want)) + }) + } +} diff --git a/contribs/gnopls/internal/event/export/ocagent/wire/common.go b/contribs/gnopls/internal/event/export/ocagent/wire/common.go new file mode 100644 index 00000000000..f22b535654c --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/wire/common.go @@ -0,0 +1,101 @@ +// 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. + +package wire + +// This file holds common ocagent types + +type Node struct { + Identifier *ProcessIdentifier `json:"identifier,omitempty"` + LibraryInfo *LibraryInfo `json:"library_info,omitempty"` + ServiceInfo *ServiceInfo `json:"service_info,omitempty"` + Attributes map[string]string `json:"attributes,omitempty"` +} + +type Resource struct { + Type string `json:"type,omitempty"` + Labels map[string]string `json:"labels,omitempty"` +} + +type TruncatableString struct { + Value string `json:"value,omitempty"` + TruncatedByteCount int32 `json:"truncated_byte_count,omitempty"` +} + +type Attributes struct { + AttributeMap map[string]Attribute `json:"attributeMap,omitempty"` + DroppedAttributesCount int32 `json:"dropped_attributes_count,omitempty"` +} + +type StringAttribute struct { + StringValue *TruncatableString `json:"stringValue,omitempty"` +} + +type IntAttribute struct { + IntValue int64 `json:"intValue,omitempty"` +} + +type BoolAttribute struct { + BoolValue bool `json:"boolValue,omitempty"` +} + +type DoubleAttribute struct { + DoubleValue float64 `json:"doubleValue,omitempty"` +} + +type Attribute interface { + labelAttribute() +} + +func (StringAttribute) labelAttribute() {} +func (IntAttribute) labelAttribute() {} +func (BoolAttribute) labelAttribute() {} +func (DoubleAttribute) labelAttribute() {} + +type StackTrace struct { + StackFrames *StackFrames `json:"stack_frames,omitempty"` + StackTraceHashID uint64 `json:"stack_trace_hash_id,omitempty"` +} + +type StackFrames struct { + Frame []*StackFrame `json:"frame,omitempty"` + DroppedFramesCount int32 `json:"dropped_frames_count,omitempty"` +} + +type StackFrame struct { + FunctionName *TruncatableString `json:"function_name,omitempty"` + OriginalFunctionName *TruncatableString `json:"original_function_name,omitempty"` + FileName *TruncatableString `json:"file_name,omitempty"` + LineNumber int64 `json:"line_number,omitempty"` + ColumnNumber int64 `json:"column_number,omitempty"` + LoadModule *Module `json:"load_module,omitempty"` + SourceVersion *TruncatableString `json:"source_version,omitempty"` +} + +type Module struct { + Module *TruncatableString `json:"module,omitempty"` + BuildID *TruncatableString `json:"build_id,omitempty"` +} + +type ProcessIdentifier struct { + HostName string `json:"host_name,omitempty"` + Pid uint32 `json:"pid,omitempty"` + StartTimestamp Timestamp `json:"start_timestamp,omitempty"` +} + +type LibraryInfo struct { + Language Language `json:"language,omitempty"` + ExporterVersion string `json:"exporter_version,omitempty"` + CoreLibraryVersion string `json:"core_library_version,omitempty"` +} + +type Language int32 + +const ( + LanguageGo Language = 4 +) + +type ServiceInfo struct { + Name string `json:"name,omitempty"` +} diff --git a/contribs/gnopls/internal/event/export/ocagent/wire/core.go b/contribs/gnopls/internal/event/export/ocagent/wire/core.go new file mode 100644 index 00000000000..95c05d66906 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/wire/core.go @@ -0,0 +1,17 @@ +// 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. + +package wire + +// This file contains type that match core proto types + +type Timestamp = string + +type Int64Value struct { + Value int64 `json:"value,omitempty"` +} + +type DoubleValue struct { + Value float64 `json:"value,omitempty"` +} diff --git a/contribs/gnopls/internal/event/export/ocagent/wire/metrics.go b/contribs/gnopls/internal/event/export/ocagent/wire/metrics.go new file mode 100644 index 00000000000..6cb58943c00 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/wire/metrics.go @@ -0,0 +1,204 @@ +// 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. + +package wire + +import ( + "encoding/json" + "fmt" +) + +type ExportMetricsServiceRequest struct { + Node *Node `json:"node,omitempty"` + Metrics []*Metric `json:"metrics,omitempty"` + Resource *Resource `json:"resource,omitempty"` +} + +type Metric struct { + MetricDescriptor *MetricDescriptor `json:"metric_descriptor,omitempty"` + Timeseries []*TimeSeries `json:"timeseries,omitempty"` + Resource *Resource `json:"resource,omitempty"` +} + +type MetricDescriptor struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Unit string `json:"unit,omitempty"` + Type MetricDescriptor_Type `json:"type,omitempty"` + LabelKeys []*LabelKey `json:"label_keys,omitempty"` +} + +type MetricDescriptor_Type int32 + +const ( + MetricDescriptor_UNSPECIFIED MetricDescriptor_Type = 0 + MetricDescriptor_GAUGE_INT64 MetricDescriptor_Type = 1 + MetricDescriptor_GAUGE_DOUBLE MetricDescriptor_Type = 2 + MetricDescriptor_GAUGE_DISTRIBUTION MetricDescriptor_Type = 3 + MetricDescriptor_CUMULATIVE_INT64 MetricDescriptor_Type = 4 + MetricDescriptor_CUMULATIVE_DOUBLE MetricDescriptor_Type = 5 + MetricDescriptor_CUMULATIVE_DISTRIBUTION MetricDescriptor_Type = 6 + MetricDescriptor_SUMMARY MetricDescriptor_Type = 7 +) + +type LabelKey struct { + Key string `json:"key,omitempty"` + Description string `json:"description,omitempty"` +} + +type TimeSeries struct { + StartTimestamp *Timestamp `json:"start_timestamp,omitempty"` + LabelValues []*LabelValue `json:"label_values,omitempty"` + Points []*Point `json:"points,omitempty"` +} + +type LabelValue struct { + Value string `json:"value,omitempty"` + HasValue bool `json:"has_value,omitempty"` +} + +type Point struct { + Timestamp *Timestamp `json:"timestamp,omitempty"` + Value PointValue `json:"value,omitempty"` +} + +type PointInt64Value struct { + Int64Value int64 `json:"int64Value,omitempty"` +} + +// MarshalJSON creates JSON formatted the same way as jsonpb so that the +// OpenCensus service can correctly determine the underlying value type. +// This custom MarshalJSON exists because, +// by default *Point is JSON marshalled as: +// +// {"value": {"int64Value": 1}} +// +// but it should be marshalled as: +// +// {"int64Value": 1} +func (p *Point) MarshalJSON() ([]byte, error) { + if p == nil { + return []byte("null"), nil + } + + switch d := p.Value.(type) { + case PointInt64Value: + return json.Marshal(&struct { + Timestamp *Timestamp `json:"timestamp,omitempty"` + Value int64 `json:"int64Value,omitempty"` + }{ + Timestamp: p.Timestamp, + Value: d.Int64Value, + }) + case PointDoubleValue: + return json.Marshal(&struct { + Timestamp *Timestamp `json:"timestamp,omitempty"` + Value float64 `json:"doubleValue,omitempty"` + }{ + Timestamp: p.Timestamp, + Value: d.DoubleValue, + }) + case PointDistributionValue: + return json.Marshal(&struct { + Timestamp *Timestamp `json:"timestamp,omitempty"` + Value *DistributionValue `json:"distributionValue,omitempty"` + }{ + Timestamp: p.Timestamp, + Value: d.DistributionValue, + }) + default: + return nil, fmt.Errorf("unknown point type %T", p.Value) + } +} + +type PointDoubleValue struct { + DoubleValue float64 `json:"doubleValue,omitempty"` +} + +type PointDistributionValue struct { + DistributionValue *DistributionValue `json:"distributionValue,omitempty"` +} + +type PointSummaryValue struct { + SummaryValue *SummaryValue `json:"summaryValue,omitempty"` +} + +type PointValue interface { + labelPointValue() +} + +func (PointInt64Value) labelPointValue() {} +func (PointDoubleValue) labelPointValue() {} +func (PointDistributionValue) labelPointValue() {} +func (PointSummaryValue) labelPointValue() {} + +type DistributionValue struct { + Count int64 `json:"count,omitempty"` + Sum float64 `json:"sum,omitempty"` + SumOfSquaredDeviation float64 `json:"sum_of_squared_deviation,omitempty"` + BucketOptions BucketOptions `json:"bucket_options,omitempty"` + Buckets []*Bucket `json:"buckets,omitempty"` +} + +type BucketOptionsExplicit struct { + Bounds []float64 `json:"bounds,omitempty"` +} + +type BucketOptions interface { + labelBucketOptions() +} + +func (*BucketOptionsExplicit) labelBucketOptions() {} + +var _ BucketOptions = (*BucketOptionsExplicit)(nil) +var _ json.Marshaler = (*BucketOptionsExplicit)(nil) + +// Declared for the purpose of custom JSON marshaling without cycles. +type bucketOptionsExplicitAlias BucketOptionsExplicit + +// MarshalJSON creates JSON formatted the same way as jsonpb so that the +// OpenCensus service can correctly determine the underlying value type. +// This custom MarshalJSON exists because, +// by default BucketOptionsExplicit is JSON marshalled as: +// +// {"bounds":[1,2,3]} +// +// but it should be marshalled as: +// +// {"explicit":{"bounds":[1,2,3]}} +func (be *BucketOptionsExplicit) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Explicit *bucketOptionsExplicitAlias `json:"explicit,omitempty"` + }{ + Explicit: (*bucketOptionsExplicitAlias)(be), + }) +} + +type Bucket struct { + Count int64 `json:"count,omitempty"` + Exemplar *Exemplar `json:"exemplar,omitempty"` +} + +type Exemplar struct { + Value float64 `json:"value,omitempty"` + Timestamp *Timestamp `json:"timestamp,omitempty"` + Attachments map[string]string `json:"attachments,omitempty"` +} + +type SummaryValue struct { + Count *Int64Value `json:"count,omitempty"` + Sum *DoubleValue `json:"sum,omitempty"` + Snapshot *Snapshot `json:"snapshot,omitempty"` +} + +type Snapshot struct { + Count *Int64Value `json:"count,omitempty"` + Sum *DoubleValue `json:"sum,omitempty"` + PercentileValues []*SnapshotValueAtPercentile `json:"percentile_values,omitempty"` +} + +type SnapshotValueAtPercentile struct { + Percentile float64 `json:"percentile,omitempty"` + Value float64 `json:"value,omitempty"` +} diff --git a/contribs/gnopls/internal/event/export/ocagent/wire/metrics_test.go b/contribs/gnopls/internal/event/export/ocagent/wire/metrics_test.go new file mode 100644 index 00000000000..34247ad6332 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/wire/metrics_test.go @@ -0,0 +1,80 @@ +// 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 wire + +import ( + "reflect" + "testing" +) + +func TestMarshalJSON(t *testing.T) { + tests := []struct { + name string + pt *Point + want string + }{ + { + "PointInt64", + &Point{ + Value: PointInt64Value{ + Int64Value: 5, + }, + }, + `{"int64Value":5}`, + }, + { + "PointDouble", + &Point{ + Value: PointDoubleValue{ + DoubleValue: 3.14, + }, + }, + `{"doubleValue":3.14}`, + }, + { + "PointDistribution", + &Point{ + Value: PointDistributionValue{ + DistributionValue: &DistributionValue{ + Count: 3, + Sum: 10, + Buckets: []*Bucket{ + { + Count: 1, + }, + { + Count: 2, + }, + }, + BucketOptions: &BucketOptionsExplicit{ + Bounds: []float64{ + 0, 5, + }, + }, + }, + }, + }, + `{"distributionValue":{"count":3,"sum":10,"bucket_options":{"explicit":{"bounds":[0,5]}},"buckets":[{"count":1},{"count":2}]}}`, + }, + { + "nil point", + nil, + `null`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf, err := tt.pt.MarshalJSON() + if err != nil { + t.Fatalf("Got:\n%v\nWant:\n%v", err, nil) + } + got := string(buf) + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("Got:\n%s\nWant:\n%s", got, tt.want) + } + }) + } +} diff --git a/contribs/gnopls/internal/event/export/ocagent/wire/trace.go b/contribs/gnopls/internal/event/export/ocagent/wire/trace.go new file mode 100644 index 00000000000..88856673a18 --- /dev/null +++ b/contribs/gnopls/internal/event/export/ocagent/wire/trace.go @@ -0,0 +1,112 @@ +// 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. + +package wire + +type ExportTraceServiceRequest struct { + Node *Node `json:"node,omitempty"` + Spans []*Span `json:"spans,omitempty"` + Resource *Resource `json:"resource,omitempty"` +} + +type Span struct { + TraceID []byte `json:"trace_id,omitempty"` + SpanID []byte `json:"span_id,omitempty"` + TraceState *TraceState `json:"tracestate,omitempty"` + ParentSpanID []byte `json:"parent_span_id,omitempty"` + Name *TruncatableString `json:"name,omitempty"` + Kind SpanKind `json:"kind,omitempty"` + StartTime Timestamp `json:"start_time,omitempty"` + EndTime Timestamp `json:"end_time,omitempty"` + Attributes *Attributes `json:"attributes,omitempty"` + StackTrace *StackTrace `json:"stack_trace,omitempty"` + TimeEvents *TimeEvents `json:"time_events,omitempty"` + Links *Links `json:"links,omitempty"` + Status *Status `json:"status,omitempty"` + Resource *Resource `json:"resource,omitempty"` + SameProcessAsParentSpan bool `json:"same_process_as_parent_span,omitempty"` + ChildSpanCount bool `json:"child_span_count,omitempty"` +} + +type TraceState struct { + Entries []*TraceStateEntry `json:"entries,omitempty"` +} + +type TraceStateEntry struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` +} + +type SpanKind int32 + +const ( + UnspecifiedSpanKind SpanKind = 0 + ServerSpanKind SpanKind = 1 + ClientSpanKind SpanKind = 2 +) + +type TimeEvents struct { + TimeEvent []TimeEvent `json:"timeEvent,omitempty"` + DroppedAnnotationsCount int32 `json:"dropped_annotations_count,omitempty"` + DroppedMessageEventsCount int32 `json:"dropped_message_events_count,omitempty"` +} + +type TimeEvent struct { + Time Timestamp `json:"time,omitempty"` + MessageEvent *MessageEvent `json:"messageEvent,omitempty"` + Annotation *Annotation `json:"annotation,omitempty"` +} + +type Annotation struct { + Description *TruncatableString `json:"description,omitempty"` + Attributes *Attributes `json:"attributes,omitempty"` +} + +type MessageEvent struct { + Type MessageEventType `json:"type,omitempty"` + ID uint64 `json:"id,omitempty"` + UncompressedSize uint64 `json:"uncompressed_size,omitempty"` + CompressedSize uint64 `json:"compressed_size,omitempty"` +} + +type MessageEventType int32 + +const ( + UnspecifiedMessageEvent MessageEventType = iota + SentMessageEvent + ReceivedMessageEvent +) + +type TimeEventValue interface { + labelTimeEventValue() +} + +func (Annotation) labelTimeEventValue() {} +func (MessageEvent) labelTimeEventValue() {} + +type Links struct { + Link []*Link `json:"link,omitempty"` + DroppedLinksCount int32 `json:"dropped_links_count,omitempty"` +} + +type Link struct { + TraceID []byte `json:"trace_id,omitempty"` + SpanID []byte `json:"span_id,omitempty"` + Type LinkType `json:"type,omitempty"` + Attributes *Attributes `json:"attributes,omitempty"` + TraceState *TraceState `json:"tracestate,omitempty"` +} + +type LinkType int32 + +const ( + UnspecifiedLinkType LinkType = 0 + ChildLinkType LinkType = 1 + ParentLinkType LinkType = 2 +) + +type Status struct { + Code int32 `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} diff --git a/contribs/gnopls/internal/event/export/printer.go b/contribs/gnopls/internal/event/export/printer.go new file mode 100644 index 00000000000..6a52907970d --- /dev/null +++ b/contribs/gnopls/internal/event/export/printer.go @@ -0,0 +1,43 @@ +// 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 export + +import ( + "io" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +type Printer struct { + buffer [128]byte +} + +func (p *Printer) WriteEvent(w io.Writer, ev core.Event, lm label.Map) { + buf := p.buffer[:0] + if !ev.At().IsZero() { + w.Write(ev.At().AppendFormat(buf, "2006/01/02 15:04:05 ")) + } + msg := keys.Msg.Get(lm) + io.WriteString(w, msg) + if err := keys.Err.Get(lm); err != nil { + if msg != "" { + io.WriteString(w, ": ") + } + io.WriteString(w, err.Error()) + } + for index := 0; ev.Valid(index); index++ { + l := ev.Label(index) + if !l.Valid() || l.Key() == keys.Msg || l.Key() == keys.Err { + continue + } + io.WriteString(w, "\n\t") + io.WriteString(w, l.Key().Name()) + io.WriteString(w, "=") + l.Key().Format(w, buf, l) + } + io.WriteString(w, "\n") +} diff --git a/contribs/gnopls/internal/event/export/prometheus/prometheus.go b/contribs/gnopls/internal/event/export/prometheus/prometheus.go new file mode 100644 index 00000000000..d661b209f26 --- /dev/null +++ b/contribs/gnopls/internal/event/export/prometheus/prometheus.go @@ -0,0 +1,129 @@ +// 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. + +package prometheus + +import ( + "bytes" + "context" + "fmt" + "net/http" + "sort" + "sync" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/metric" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +func New() *Exporter { + return &Exporter{} +} + +type Exporter struct { + mu sync.Mutex + metrics []metric.Data +} + +func (e *Exporter) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context { + if !event.IsMetric(ev) { + return ctx + } + e.mu.Lock() + defer e.mu.Unlock() + metrics := metric.Entries.Get(lm).([]metric.Data) + for _, data := range metrics { + name := data.Handle() + // We keep the metrics in name sorted order so the page is stable and easy + // to read. We do this with an insertion sort rather than sorting the list + // each time + index := sort.Search(len(e.metrics), func(i int) bool { + return e.metrics[i].Handle() >= name + }) + if index >= len(e.metrics) || e.metrics[index].Handle() != name { + // we have a new metric, so we need to make a space for it + old := e.metrics + e.metrics = make([]metric.Data, len(old)+1) + copy(e.metrics, old[:index]) + copy(e.metrics[index+1:], old[index:]) + } + e.metrics[index] = data + } + return ctx +} + +func (e *Exporter) header(w http.ResponseWriter, name, description string, isGauge, isHistogram bool) { + kind := "counter" + if isGauge { + kind = "gauge" + } + if isHistogram { + kind = "histogram" + } + fmt.Fprintf(w, "# HELP %s %s\n", name, description) + fmt.Fprintf(w, "# TYPE %s %s\n", name, kind) +} + +func (e *Exporter) row(w http.ResponseWriter, name string, group []label.Label, extra string, value interface{}) { + fmt.Fprint(w, name) + buf := &bytes.Buffer{} + fmt.Fprint(buf, group) + if extra != "" { + if buf.Len() > 0 { + fmt.Fprint(buf, ",") + } + fmt.Fprint(buf, extra) + } + if buf.Len() > 0 { + fmt.Fprint(w, "{") + buf.WriteTo(w) + fmt.Fprint(w, "}") + } + fmt.Fprintf(w, " %v\n", value) +} + +func (e *Exporter) Serve(w http.ResponseWriter, r *http.Request) { + e.mu.Lock() + defer e.mu.Unlock() + for _, data := range e.metrics { + switch data := data.(type) { + case *metric.Int64Data: + e.header(w, data.Info.Name, data.Info.Description, data.IsGauge, false) + for i, group := range data.Groups() { + e.row(w, data.Info.Name, group, "", data.Rows[i]) + } + + case *metric.Float64Data: + e.header(w, data.Info.Name, data.Info.Description, data.IsGauge, false) + for i, group := range data.Groups() { + e.row(w, data.Info.Name, group, "", data.Rows[i]) + } + + case *metric.HistogramInt64Data: + e.header(w, data.Info.Name, data.Info.Description, false, true) + for i, group := range data.Groups() { + row := data.Rows[i] + for j, b := range data.Info.Buckets { + e.row(w, data.Info.Name+"_bucket", group, fmt.Sprintf(`le="%v"`, b), row.Values[j]) + } + e.row(w, data.Info.Name+"_bucket", group, `le="+Inf"`, row.Count) + e.row(w, data.Info.Name+"_count", group, "", row.Count) + e.row(w, data.Info.Name+"_sum", group, "", row.Sum) + } + + case *metric.HistogramFloat64Data: + e.header(w, data.Info.Name, data.Info.Description, false, true) + for i, group := range data.Groups() { + row := data.Rows[i] + for j, b := range data.Info.Buckets { + e.row(w, data.Info.Name+"_bucket", group, fmt.Sprintf(`le="%v"`, b), row.Values[j]) + } + e.row(w, data.Info.Name+"_bucket", group, `le="+Inf"`, row.Count) + e.row(w, data.Info.Name+"_count", group, "", row.Count) + e.row(w, data.Info.Name+"_sum", group, "", row.Sum) + } + } + } +} diff --git a/contribs/gnopls/internal/event/export/trace.go b/contribs/gnopls/internal/event/export/trace.go new file mode 100644 index 00000000000..fa5ffe2c3d0 --- /dev/null +++ b/contribs/gnopls/internal/event/export/trace.go @@ -0,0 +1,117 @@ +// 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. + +package export + +import ( + "context" + "fmt" + "sync" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +type SpanContext struct { + TraceID TraceID + SpanID SpanID +} + +type Span struct { + Name string + ID SpanContext + ParentID SpanID + mu sync.Mutex + start core.Event + finish core.Event + events []core.Event +} + +type contextKeyType int + +const ( + spanContextKey = contextKeyType(iota) + labelContextKey +) + +func GetSpan(ctx context.Context) *Span { + v := ctx.Value(spanContextKey) + if v == nil { + return nil + } + return v.(*Span) +} + +// Spans creates an exporter that maintains hierarchical span structure in the +// context. +// It creates new spans on start events, adds events to the current span on +// log or label, and closes the span on end events. +// The span structure can then be used by other exporters. +func Spans(output event.Exporter) event.Exporter { + return func(ctx context.Context, ev core.Event, lm label.Map) context.Context { + switch { + case event.IsLog(ev), event.IsLabel(ev): + if span := GetSpan(ctx); span != nil { + span.mu.Lock() + span.events = append(span.events, ev) + span.mu.Unlock() + } + case event.IsStart(ev): + span := &Span{ + Name: keys.Start.Get(lm), + start: ev, + } + if parent := GetSpan(ctx); parent != nil { + span.ID.TraceID = parent.ID.TraceID + span.ParentID = parent.ID.SpanID + } else { + span.ID.TraceID = newTraceID() + } + span.ID.SpanID = newSpanID() + ctx = context.WithValue(ctx, spanContextKey, span) + case event.IsEnd(ev): + if span := GetSpan(ctx); span != nil { + span.mu.Lock() + span.finish = ev + span.mu.Unlock() + } + case event.IsDetach(ev): + ctx = context.WithValue(ctx, spanContextKey, nil) + } + return output(ctx, ev, lm) + } +} + +func (s *SpanContext) Format(f fmt.State, r rune) { + fmt.Fprintf(f, "%v:%v", s.TraceID, s.SpanID) +} + +func (s *Span) Start() core.Event { + // start never changes after construction, so we don't need to hold the mutex + return s.start +} + +func (s *Span) Finish() core.Event { + s.mu.Lock() + defer s.mu.Unlock() + return s.finish +} + +func (s *Span) Events() []core.Event { + s.mu.Lock() + defer s.mu.Unlock() + return s.events +} + +func (s *Span) Format(f fmt.State, r rune) { + s.mu.Lock() + defer s.mu.Unlock() + fmt.Fprintf(f, "%v %v", s.Name, s.ID) + if s.ParentID.IsValid() { + fmt.Fprintf(f, "[%v]", s.ParentID) + } + fmt.Fprintf(f, " %v->%v", s.start, s.finish) +} diff --git a/contribs/gnopls/internal/event/keys/keys.go b/contribs/gnopls/internal/event/keys/keys.go new file mode 100644 index 00000000000..a21ec4c06a1 --- /dev/null +++ b/contribs/gnopls/internal/event/keys/keys.go @@ -0,0 +1,564 @@ +// 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. + +package keys + +import ( + "fmt" + "io" + "math" + "strconv" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Value represents a key for untyped values. +type Value struct { + name string + description string +} + +// New creates a new Key for untyped values. +func New(name, description string) *Value { + return &Value{name: name, description: description} +} + +func (k *Value) Name() string { return k.name } +func (k *Value) Description() string { return k.description } + +func (k *Value) Format(w io.Writer, buf []byte, l label.Label) { + fmt.Fprint(w, k.From(l)) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Value) Get(lm label.Map) interface{} { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return nil +} + +// From can be used to get a value from a Label. +func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() } + +// Of creates a new Label with this key and the supplied value. +func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) } + +// Tag represents a key for tagging labels that have no value. +// These are used when the existence of the label is the entire information it +// carries, such as marking events to be of a specific kind, or from a specific +// package. +type Tag struct { + name string + description string +} + +// NewTag creates a new Key for tagging labels. +func NewTag(name, description string) *Tag { + return &Tag{name: name, description: description} +} + +func (k *Tag) Name() string { return k.name } +func (k *Tag) Description() string { return k.description } + +func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {} + +// New creates a new Label with this key. +func (k *Tag) New() label.Label { return label.OfValue(k, nil) } + +// Int represents a key +type Int struct { + name string + description string +} + +// NewInt creates a new Key for int values. +func NewInt(name, description string) *Int { + return &Int{name: name, description: description} +} + +func (k *Int) Name() string { return k.name } +func (k *Int) Description() string { return k.description } + +func (k *Int) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int) Get(lm label.Map) int { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int) From(t label.Label) int { return int(t.Unpack64()) } + +// Int8 represents a key +type Int8 struct { + name string + description string +} + +// NewInt8 creates a new Key for int8 values. +func NewInt8(name, description string) *Int8 { + return &Int8{name: name, description: description} +} + +func (k *Int8) Name() string { return k.name } +func (k *Int8) Description() string { return k.description } + +func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int8) Get(lm label.Map) int8 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) } + +// Int16 represents a key +type Int16 struct { + name string + description string +} + +// NewInt16 creates a new Key for int16 values. +func NewInt16(name, description string) *Int16 { + return &Int16{name: name, description: description} +} + +func (k *Int16) Name() string { return k.name } +func (k *Int16) Description() string { return k.description } + +func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int16) Get(lm label.Map) int16 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) } + +// Int32 represents a key +type Int32 struct { + name string + description string +} + +// NewInt32 creates a new Key for int32 values. +func NewInt32(name, description string) *Int32 { + return &Int32{name: name, description: description} +} + +func (k *Int32) Name() string { return k.name } +func (k *Int32) Description() string { return k.description } + +func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int32) Get(lm label.Map) int32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) } + +// Int64 represents a key +type Int64 struct { + name string + description string +} + +// NewInt64 creates a new Key for int64 values. +func NewInt64(name, description string) *Int64 { + return &Int64{name: name, description: description} +} + +func (k *Int64) Name() string { return k.name } +func (k *Int64) Description() string { return k.description } + +func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, k.From(l), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int64) Get(lm label.Map) int64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) } + +// UInt represents a key +type UInt struct { + name string + description string +} + +// NewUInt creates a new Key for uint values. +func NewUInt(name, description string) *UInt { + return &UInt{name: name, description: description} +} + +func (k *UInt) Name() string { return k.name } +func (k *UInt) Description() string { return k.description } + +func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt) Get(lm label.Map) uint { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) } + +// UInt8 represents a key +type UInt8 struct { + name string + description string +} + +// NewUInt8 creates a new Key for uint8 values. +func NewUInt8(name, description string) *UInt8 { + return &UInt8{name: name, description: description} +} + +func (k *UInt8) Name() string { return k.name } +func (k *UInt8) Description() string { return k.description } + +func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt8) Get(lm label.Map) uint8 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) } + +// UInt16 represents a key +type UInt16 struct { + name string + description string +} + +// NewUInt16 creates a new Key for uint16 values. +func NewUInt16(name, description string) *UInt16 { + return &UInt16{name: name, description: description} +} + +func (k *UInt16) Name() string { return k.name } +func (k *UInt16) Description() string { return k.description } + +func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt16) Get(lm label.Map) uint16 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) } + +// UInt32 represents a key +type UInt32 struct { + name string + description string +} + +// NewUInt32 creates a new Key for uint32 values. +func NewUInt32(name, description string) *UInt32 { + return &UInt32{name: name, description: description} +} + +func (k *UInt32) Name() string { return k.name } +func (k *UInt32) Description() string { return k.description } + +func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt32) Get(lm label.Map) uint32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) } + +// UInt64 represents a key +type UInt64 struct { + name string + description string +} + +// NewUInt64 creates a new Key for uint64 values. +func NewUInt64(name, description string) *UInt64 { + return &UInt64{name: name, description: description} +} + +func (k *UInt64) Name() string { return k.name } +func (k *UInt64) Description() string { return k.description } + +func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, k.From(l), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt64) Get(lm label.Map) uint64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() } + +// Float32 represents a key +type Float32 struct { + name string + description string +} + +// NewFloat32 creates a new Key for float32 values. +func NewFloat32(name, description string) *Float32 { + return &Float32{name: name, description: description} +} + +func (k *Float32) Name() string { return k.name } +func (k *Float32) Description() string { return k.description } + +func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Float32) Of(v float32) label.Label { + return label.Of64(k, uint64(math.Float32bits(v))) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Float32) Get(lm label.Map) float32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Float32) From(t label.Label) float32 { + return math.Float32frombits(uint32(t.Unpack64())) +} + +// Float64 represents a key +type Float64 struct { + name string + description string +} + +// NewFloat64 creates a new Key for int64 values. +func NewFloat64(name, description string) *Float64 { + return &Float64{name: name, description: description} +} + +func (k *Float64) Name() string { return k.name } +func (k *Float64) Description() string { return k.description } + +func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Float64) Of(v float64) label.Label { + return label.Of64(k, math.Float64bits(v)) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Float64) Get(lm label.Map) float64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Float64) From(t label.Label) float64 { + return math.Float64frombits(t.Unpack64()) +} + +// String represents a key +type String struct { + name string + description string +} + +// NewString creates a new Key for int64 values. +func NewString(name, description string) *String { + return &String{name: name, description: description} +} + +func (k *String) Name() string { return k.name } +func (k *String) Description() string { return k.description } + +func (k *String) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendQuote(buf, k.From(l))) +} + +// Of creates a new Label with this key and the supplied value. +func (k *String) Of(v string) label.Label { return label.OfString(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *String) Get(lm label.Map) string { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return "" +} + +// From can be used to get a value from a Label. +func (k *String) From(t label.Label) string { return t.UnpackString() } + +// Boolean represents a key +type Boolean struct { + name string + description string +} + +// NewBoolean creates a new Key for bool values. +func NewBoolean(name, description string) *Boolean { + return &Boolean{name: name, description: description} +} + +func (k *Boolean) Name() string { return k.name } +func (k *Boolean) Description() string { return k.description } + +func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendBool(buf, k.From(l))) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Boolean) Of(v bool) label.Label { + if v { + return label.Of64(k, 1) + } + return label.Of64(k, 0) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Boolean) Get(lm label.Map) bool { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return false +} + +// From can be used to get a value from a Label. +func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 } + +// Error represents a key +type Error struct { + name string + description string +} + +// NewError creates a new Key for int64 values. +func NewError(name, description string) *Error { + return &Error{name: name, description: description} +} + +func (k *Error) Name() string { return k.name } +func (k *Error) Description() string { return k.description } + +func (k *Error) Format(w io.Writer, buf []byte, l label.Label) { + io.WriteString(w, k.From(l).Error()) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Error) Get(lm label.Map) error { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return nil +} + +// From can be used to get a value from a Label. +func (k *Error) From(t label.Label) error { + err, _ := t.UnpackValue().(error) + return err +} diff --git a/contribs/gnopls/internal/event/keys/standard.go b/contribs/gnopls/internal/event/keys/standard.go new file mode 100644 index 00000000000..7e958665921 --- /dev/null +++ b/contribs/gnopls/internal/event/keys/standard.go @@ -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 keys + +var ( + // Msg is a key used to add message strings to label lists. + Msg = NewString("message", "a readable message") + // Label is a key used to indicate an event adds labels to the context. + Label = NewTag("label", "a label context marker") + // Start is used for things like traces that have a name. + Start = NewString("start", "span start") + // Metric is a key used to indicate an event records metrics. + End = NewTag("end", "a span end marker") + // Metric is a key used to indicate an event records metrics. + Detach = NewTag("detach", "a span detach marker") + // Err is a key used to add error values to label lists. + Err = NewError("error", "an error that occurred") + // Metric is a key used to indicate an event records metrics. + Metric = NewTag("metric", "a metric event marker") +) diff --git a/contribs/gnopls/internal/event/keys/util.go b/contribs/gnopls/internal/event/keys/util.go new file mode 100644 index 00000000000..c0e8e731c90 --- /dev/null +++ b/contribs/gnopls/internal/event/keys/util.go @@ -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 keys + +import ( + "sort" + "strings" +) + +// Join returns a canonical join of the keys in S: +// a sorted comma-separated string list. +func Join[S ~[]T, T ~string](s S) string { + strs := make([]string, 0, len(s)) + for _, v := range s { + strs = append(strs, string(v)) + } + sort.Strings(strs) + return strings.Join(strs, ",") +} diff --git a/contribs/gnopls/internal/event/keys/util_test.go b/contribs/gnopls/internal/event/keys/util_test.go new file mode 100644 index 00000000000..c3e285e3ba5 --- /dev/null +++ b/contribs/gnopls/internal/event/keys/util_test.go @@ -0,0 +1,29 @@ +// 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 keys + +import "testing" + +func TestJoin(t *testing.T) { + type T string + type S []T + + tests := []struct { + data S + want string + }{ + {S{"a", "b", "c"}, "a,b,c"}, + {S{"b", "a", "c"}, "a,b,c"}, + {S{"c", "a", "b"}, "a,b,c"}, + {nil, ""}, + {S{}, ""}, + } + + for _, test := range tests { + if got := Join(test.data); got != test.want { + t.Errorf("Join(%v) = %q, want %q", test.data, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/event/label/label.go b/contribs/gnopls/internal/event/label/label.go new file mode 100644 index 00000000000..0f526e1f9ab --- /dev/null +++ b/contribs/gnopls/internal/event/label/label.go @@ -0,0 +1,215 @@ +// 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. + +package label + +import ( + "fmt" + "io" + "reflect" + "unsafe" +) + +// Key is used as the identity of a Label. +// Keys are intended to be compared by pointer only, the name should be unique +// for communicating with external systems, but it is not required or enforced. +type Key interface { + // Name returns the key name. + Name() string + // Description returns a string that can be used to describe the value. + Description() string + + // Format is used in formatting to append the value of the label to the + // supplied buffer. + // The formatter may use the supplied buf as a scratch area to avoid + // allocations. + Format(w io.Writer, buf []byte, l Label) +} + +// Label holds a key and value pair. +// It is normally used when passing around lists of labels. +type Label struct { + key Key + packed uint64 + untyped interface{} +} + +// Map is the interface to a collection of Labels indexed by key. +type Map interface { + // Find returns the label that matches the supplied key. + Find(key Key) Label +} + +// List is the interface to something that provides an iterable +// list of labels. +// Iteration should start from 0 and continue until Valid returns false. +type List interface { + // Valid returns true if the index is within range for the list. + // It does not imply the label at that index will itself be valid. + Valid(index int) bool + // Label returns the label at the given index. + Label(index int) Label +} + +// list implements LabelList for a list of Labels. +type list struct { + labels []Label +} + +// filter wraps a LabelList filtering out specific labels. +type filter struct { + keys []Key + underlying List +} + +// listMap implements LabelMap for a simple list of labels. +type listMap struct { + labels []Label +} + +// mapChain implements LabelMap for a list of underlying LabelMap. +type mapChain struct { + maps []Map +} + +// OfValue creates a new label from the key and value. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} } + +// UnpackValue assumes the label was built using LabelOfValue and returns the value +// that was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) UnpackValue() interface{} { return t.untyped } + +// Of64 creates a new label from a key and a uint64. This is often +// used for non uint64 values that can be packed into a uint64. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} } + +// Unpack64 assumes the label was built using LabelOf64 and returns the value that +// was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) Unpack64() uint64 { return t.packed } + +type stringptr unsafe.Pointer + +// OfString creates a new label from a key and a string. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func OfString(k Key, v string) Label { + hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) + return Label{ + key: k, + packed: uint64(hdr.Len), + untyped: stringptr(hdr.Data), + } +} + +// UnpackString assumes the label was built using LabelOfString and returns the +// value that was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) UnpackString() string { + var v string + hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) + hdr.Data = uintptr(t.untyped.(stringptr)) + hdr.Len = int(t.packed) + return v +} + +// Valid returns true if the Label is a valid one (it has a key). +func (t Label) Valid() bool { return t.key != nil } + +// Key returns the key of this Label. +func (t Label) Key() Key { return t.key } + +// Format is used for debug printing of labels. +func (t Label) Format(f fmt.State, r rune) { + if !t.Valid() { + io.WriteString(f, `nil`) + return + } + io.WriteString(f, t.Key().Name()) + io.WriteString(f, "=") + var buf [128]byte + t.Key().Format(f, buf[:0], t) +} + +func (l *list) Valid(index int) bool { + return index >= 0 && index < len(l.labels) +} + +func (l *list) Label(index int) Label { + return l.labels[index] +} + +func (f *filter) Valid(index int) bool { + return f.underlying.Valid(index) +} + +func (f *filter) Label(index int) Label { + l := f.underlying.Label(index) + for _, f := range f.keys { + if l.Key() == f { + return Label{} + } + } + return l +} + +func (lm listMap) Find(key Key) Label { + for _, l := range lm.labels { + if l.Key() == key { + return l + } + } + return Label{} +} + +func (c mapChain) Find(key Key) Label { + for _, src := range c.maps { + l := src.Find(key) + if l.Valid() { + return l + } + } + return Label{} +} + +var emptyList = &list{} + +func NewList(labels ...Label) List { + if len(labels) == 0 { + return emptyList + } + return &list{labels: labels} +} + +func Filter(l List, keys ...Key) List { + if len(keys) == 0 { + return l + } + return &filter{keys: keys, underlying: l} +} + +func NewMap(labels ...Label) Map { + return listMap{labels: labels} +} + +func MergeMaps(srcs ...Map) Map { + var nonNil []Map + for _, src := range srcs { + if src != nil { + nonNil = append(nonNil, src) + } + } + if len(nonNil) == 1 { + return nonNil[0] + } + return mapChain{maps: nonNil} +} diff --git a/contribs/gnopls/internal/event/label/label_test.go b/contribs/gnopls/internal/event/label/label_test.go new file mode 100644 index 00000000000..ca272afc853 --- /dev/null +++ b/contribs/gnopls/internal/event/label/label_test.go @@ -0,0 +1,285 @@ +// 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 label_test + +import ( + "bytes" + "fmt" + "runtime" + "testing" + "unsafe" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +var ( + AKey = keys.NewString("A", "") + BKey = keys.NewString("B", "") + CKey = keys.NewString("C", "") + A = AKey.Of("a") + B = BKey.Of("b") + C = CKey.Of("c") + all = []label.Label{A, B, C} +) + +func TestList(t *testing.T) { + for _, test := range []struct { + name string + labels []label.Label + expect string + }{{ + name: "empty", + }, { + name: "single", + labels: []label.Label{A}, + expect: `A="a"`, + }, { + name: "invalid", + labels: []label.Label{{}}, + expect: ``, + }, { + name: "two", + labels: []label.Label{A, B}, + expect: `A="a", B="b"`, + }, { + name: "three", + labels: []label.Label{A, B, C}, + expect: `A="a", B="b", C="c"`, + }, { + name: "missing A", + labels: []label.Label{{}, B, C}, + expect: `B="b", C="c"`, + }, { + name: "missing B", + labels: []label.Label{A, {}, C}, + expect: `A="a", C="c"`, + }, { + name: "missing C", + labels: []label.Label{A, B, {}}, + expect: `A="a", B="b"`, + }, { + name: "missing AB", + labels: []label.Label{{}, {}, C}, + expect: `C="c"`, + }, { + name: "missing AC", + labels: []label.Label{{}, B, {}}, + expect: `B="b"`, + }, { + name: "missing BC", + labels: []label.Label{A, {}, {}}, + expect: `A="a"`, + }} { + t.Run(test.name, func(t *testing.T) { + got := printList(label.NewList(test.labels...)) + if got != test.expect { + t.Errorf("got %q want %q", got, test.expect) + } + }) + } +} + +func TestFilter(t *testing.T) { + for _, test := range []struct { + name string + labels []label.Label + filters []label.Key + expect string + }{{ + name: "no filters", + labels: all, + expect: `A="a", B="b", C="c"`, + }, { + name: "no labels", + filters: []label.Key{AKey}, + expect: ``, + }, { + name: "filter A", + labels: all, + filters: []label.Key{AKey}, + expect: `B="b", C="c"`, + }, { + name: "filter B", + labels: all, + filters: []label.Key{BKey}, + expect: `A="a", C="c"`, + }, { + name: "filter C", + labels: all, + filters: []label.Key{CKey}, + expect: `A="a", B="b"`, + }, { + name: "filter AC", + labels: all, + filters: []label.Key{AKey, CKey}, + expect: `B="b"`, + }} { + t.Run(test.name, func(t *testing.T) { + labels := label.NewList(test.labels...) + got := printList(label.Filter(labels, test.filters...)) + if got != test.expect { + t.Errorf("got %q want %q", got, test.expect) + } + }) + } +} + +func TestMap(t *testing.T) { + for _, test := range []struct { + name string + labels []label.Label + keys []label.Key + expect string + }{{ + name: "no labels", + keys: []label.Key{AKey}, + expect: `nil`, + }, { + name: "match A", + labels: all, + keys: []label.Key{AKey}, + expect: `A="a"`, + }, { + name: "match B", + labels: all, + keys: []label.Key{BKey}, + expect: `B="b"`, + }, { + name: "match C", + labels: all, + keys: []label.Key{CKey}, + expect: `C="c"`, + }, { + name: "match ABC", + labels: all, + keys: []label.Key{AKey, BKey, CKey}, + expect: `A="a", B="b", C="c"`, + }, { + name: "missing A", + labels: []label.Label{{}, B, C}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `nil, B="b", C="c"`, + }, { + name: "missing B", + labels: []label.Label{A, {}, C}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `A="a", nil, C="c"`, + }, { + name: "missing C", + labels: []label.Label{A, B, {}}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `A="a", B="b", nil`, + }} { + t.Run(test.name, func(t *testing.T) { + lm := label.NewMap(test.labels...) + got := printMap(lm, test.keys) + if got != test.expect { + t.Errorf("got %q want %q", got, test.expect) + } + }) + } +} + +func TestMapMerge(t *testing.T) { + for _, test := range []struct { + name string + maps []label.Map + keys []label.Key + expect string + }{{ + name: "no maps", + keys: []label.Key{AKey}, + expect: `nil`, + }, { + name: "one map", + maps: []label.Map{label.NewMap(all...)}, + keys: []label.Key{AKey}, + expect: `A="a"`, + }, { + name: "invalid map", + maps: []label.Map{label.NewMap()}, + keys: []label.Key{AKey}, + expect: `nil`, + }, { + name: "two maps", + maps: []label.Map{label.NewMap(B, C), label.NewMap(A)}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `A="a", B="b", C="c"`, + }, { + name: "invalid start map", + maps: []label.Map{label.NewMap(), label.NewMap(B, C)}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `nil, B="b", C="c"`, + }, { + name: "invalid mid map", + maps: []label.Map{label.NewMap(A), label.NewMap(), label.NewMap(C)}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `A="a", nil, C="c"`, + }, { + name: "invalid end map", + maps: []label.Map{label.NewMap(A, B), label.NewMap()}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `A="a", B="b", nil`, + }, { + name: "three maps one nil", + maps: []label.Map{label.NewMap(A), label.NewMap(B), nil}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `A="a", B="b", nil`, + }, { + name: "two maps one nil", + maps: []label.Map{label.NewMap(A, B), nil}, + keys: []label.Key{AKey, BKey, CKey}, + expect: `A="a", B="b", nil`, + }} { + t.Run(test.name, func(t *testing.T) { + tagMap := label.MergeMaps(test.maps...) + got := printMap(tagMap, test.keys) + if got != test.expect { + t.Errorf("got %q want %q", got, test.expect) + } + }) + } +} + +func printList(list label.List) string { + buf := &bytes.Buffer{} + for index := 0; list.Valid(index); index++ { + l := list.Label(index) + if !l.Valid() { + continue + } + if buf.Len() > 0 { + buf.WriteString(", ") + } + fmt.Fprint(buf, l) + } + return buf.String() +} + +func printMap(lm label.Map, keys []label.Key) string { + buf := &bytes.Buffer{} + for _, key := range keys { + if buf.Len() > 0 { + buf.WriteString(", ") + } + fmt.Fprint(buf, lm.Find(key)) + } + return buf.String() +} + +func TestAttemptedStringCorruption(t *testing.T) { + defer func() { + r := recover() + if _, ok := r.(*runtime.TypeAssertionError); !ok { + t.Fatalf("wanted to recover TypeAssertionError, got %T", r) + } + }() + + var x uint64 = 12390 + p := unsafe.Pointer(&x) + l := label.OfValue(AKey, p) + _ = l.UnpackString() +} diff --git a/contribs/gnopls/internal/facts/facts.go b/contribs/gnopls/internal/facts/facts.go new file mode 100644 index 00000000000..e1c18d373c3 --- /dev/null +++ b/contribs/gnopls/internal/facts/facts.go @@ -0,0 +1,389 @@ +// Copyright 2018 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 facts defines a serializable set of analysis.Fact. +// +// It provides a partial implementation of the Fact-related parts of the +// analysis.Pass interface for use in analysis drivers such as "go vet" +// and other build systems. +// +// The serial format is unspecified and may change, so the same version +// of this package must be used for reading and writing serialized facts. +// +// The handling of facts in the analysis system parallels the handling +// of type information in the compiler: during compilation of package P, +// the compiler emits an export data file that describes the type of +// every object (named thing) defined in package P, plus every object +// indirectly reachable from one of those objects. Thus the downstream +// compiler of package Q need only load one export data file per direct +// import of Q, and it will learn everything about the API of package P +// and everything it needs to know about the API of P's dependencies. +// +// Similarly, analysis of package P emits a fact set containing facts +// about all objects exported from P, plus additional facts about only +// those objects of P's dependencies that are reachable from the API of +// package P; the downstream analysis of Q need only load one fact set +// per direct import of Q. +// +// The notion of "exportedness" that matters here is that of the +// compiler. According to the language spec, a method pkg.T.f is +// unexported simply because its name starts with lowercase. But the +// compiler must nonetheless export f so that downstream compilations can +// accurately ascertain whether pkg.T implements an interface pkg.I +// defined as interface{f()}. Exported thus means "described in export +// data". +package facts + +import ( + "bytes" + "encoding/gob" + "fmt" + "go/types" + "io" + "log" + "reflect" + "sort" + "sync" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/types/objectpath" +) + +const debug = false + +// A Set is a set of analysis.Facts. +// +// Decode creates a Set of facts by reading from the imports of a given +// package, and Encode writes out the set. Between these operation, +// the Import and Export methods will query and update the set. +// +// All of Set's methods except String are safe to call concurrently. +type Set struct { + pkg *types.Package + mu sync.Mutex + m map[key]analysis.Fact +} + +type key struct { + pkg *types.Package + obj types.Object // (object facts only) + t reflect.Type +} + +// ImportObjectFact implements analysis.Pass.ImportObjectFact. +func (s *Set) ImportObjectFact(obj types.Object, ptr analysis.Fact) bool { + if obj == nil { + panic("nil object") + } + key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(ptr)} + s.mu.Lock() + defer s.mu.Unlock() + if v, ok := s.m[key]; ok { + reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) + return true + } + return false +} + +// ExportObjectFact implements analysis.Pass.ExportObjectFact. +func (s *Set) ExportObjectFact(obj types.Object, fact analysis.Fact) { + if obj.Pkg() != s.pkg { + log.Panicf("in package %s: ExportObjectFact(%s, %T): can't set fact on object belonging another package", + s.pkg, obj, fact) + } + key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(fact)} + s.mu.Lock() + s.m[key] = fact // clobber any existing entry + s.mu.Unlock() +} + +func (s *Set) AllObjectFacts(filter map[reflect.Type]bool) []analysis.ObjectFact { + var facts []analysis.ObjectFact + s.mu.Lock() + for k, v := range s.m { + if k.obj != nil && filter[k.t] { + facts = append(facts, analysis.ObjectFact{Object: k.obj, Fact: v}) + } + } + s.mu.Unlock() + return facts +} + +// ImportPackageFact implements analysis.Pass.ImportPackageFact. +func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool { + if pkg == nil { + panic("nil package") + } + key := key{pkg: pkg, t: reflect.TypeOf(ptr)} + s.mu.Lock() + defer s.mu.Unlock() + if v, ok := s.m[key]; ok { + reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem()) + return true + } + return false +} + +// ExportPackageFact implements analysis.Pass.ExportPackageFact. +func (s *Set) ExportPackageFact(fact analysis.Fact) { + key := key{pkg: s.pkg, t: reflect.TypeOf(fact)} + s.mu.Lock() + s.m[key] = fact // clobber any existing entry + s.mu.Unlock() +} + +func (s *Set) AllPackageFacts(filter map[reflect.Type]bool) []analysis.PackageFact { + var facts []analysis.PackageFact + s.mu.Lock() + for k, v := range s.m { + if k.obj == nil && filter[k.t] { + facts = append(facts, analysis.PackageFact{Package: k.pkg, Fact: v}) + } + } + s.mu.Unlock() + return facts +} + +// gobFact is the Gob declaration of a serialized fact. +type gobFact struct { + PkgPath string // path of package + Object objectpath.Path // optional path of object relative to package itself + Fact analysis.Fact // type and value of user-defined Fact +} + +// A Decoder decodes the facts from the direct imports of the package +// provided to NewEncoder. A single decoder may be used to decode +// multiple fact sets (e.g. each for a different set of fact types) +// for the same package. Each call to Decode returns an independent +// fact set. +type Decoder struct { + pkg *types.Package + getPackage GetPackageFunc +} + +// NewDecoder returns a fact decoder for the specified package. +// +// It uses a brute-force recursive approach to enumerate all objects +// defined by dependencies of pkg, so that it can learn the set of +// package paths that may be mentioned in the fact encoding. This does +// not scale well; use [NewDecoderFunc] where possible. +func NewDecoder(pkg *types.Package) *Decoder { + // Compute the import map for this package. + // See the package doc comment. + m := importMap(pkg.Imports()) + getPackageFunc := func(path string) *types.Package { return m[path] } + return NewDecoderFunc(pkg, getPackageFunc) +} + +// NewDecoderFunc returns a fact decoder for the specified package. +// +// It calls the getPackage function for the package path string of +// each dependency (perhaps indirect) that it encounters in the +// encoding. If the function returns nil, the fact is discarded. +// +// This function is preferred over [NewDecoder] when the client is +// capable of efficient look-up of packages by package path. +func NewDecoderFunc(pkg *types.Package, getPackage GetPackageFunc) *Decoder { + return &Decoder{ + pkg: pkg, + getPackage: getPackage, + } +} + +// A GetPackageFunc function returns the package denoted by a package path. +type GetPackageFunc = func(pkgPath string) *types.Package + +// Decode decodes all the facts relevant to the analysis of package +// pkgPath. The read function reads serialized fact data from an external +// source for one of pkg's direct imports, identified by package path. +// The empty file is a valid encoding of an empty fact set. +// +// It is the caller's responsibility to call gob.Register on all +// necessary fact types. +// +// Concurrent calls to Decode are safe, so long as the +// [GetPackageFunc] (if any) is also concurrency-safe. +func (d *Decoder) Decode(read func(pkgPath string) ([]byte, error)) (*Set, error) { + // Read facts from imported packages. + // Facts may describe indirectly imported packages, or their objects. + m := make(map[key]analysis.Fact) // one big bucket + for _, imp := range d.pkg.Imports() { + logf := func(format string, args ...interface{}) { + if debug { + prefix := fmt.Sprintf("in %s, importing %s: ", + d.pkg.Path(), imp.Path()) + log.Print(prefix, fmt.Sprintf(format, args...)) + } + } + + // Read the gob-encoded facts. + data, err := read(imp.Path()) + if err != nil { + return nil, fmt.Errorf("in %s, can't import facts for package %q: %v", + d.pkg.Path(), imp.Path(), err) + } + if len(data) == 0 { + continue // no facts + } + var gobFacts []gobFact + if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&gobFacts); err != nil { + return nil, fmt.Errorf("decoding facts for %q: %v", imp.Path(), err) + } + logf("decoded %d facts: %v", len(gobFacts), gobFacts) + + // Parse each one into a key and a Fact. + for _, f := range gobFacts { + factPkg := d.getPackage(f.PkgPath) // possibly an indirect dependency + if factPkg == nil { + // Fact relates to a dependency that was + // unused in this translation unit. Skip. + logf("no package %q; discarding %v", f.PkgPath, f.Fact) + continue + } + key := key{pkg: factPkg, t: reflect.TypeOf(f.Fact)} + if f.Object != "" { + // object fact + obj, err := objectpath.Object(factPkg, f.Object) + if err != nil { + // (most likely due to unexported object) + // TODO(adonovan): audit for other possibilities. + logf("no object for path: %v; discarding %s", err, f.Fact) + continue + } + key.obj = obj + logf("read %T fact %s for %v", f.Fact, f.Fact, key.obj) + } else { + // package fact + logf("read %T fact %s for %v", f.Fact, f.Fact, factPkg) + } + m[key] = f.Fact + } + } + + return &Set{pkg: d.pkg, m: m}, nil +} + +// Encode encodes a set of facts to a memory buffer. +// +// It may fail if one of the Facts could not be gob-encoded, but this is +// a sign of a bug in an Analyzer. +func (s *Set) Encode() []byte { + encoder := new(objectpath.Encoder) + + // TODO(adonovan): opt: use a more efficient encoding + // that avoids repeating PkgPath for each fact. + + // Gather all facts, including those from imported packages. + var gobFacts []gobFact + + s.mu.Lock() + for k, fact := range s.m { + if debug { + log.Printf("%v => %s\n", k, fact) + } + + // Don't export facts that we imported from another + // package, unless they represent fields or methods, + // or package-level types. + // (Facts about packages, and other package-level + // objects, are only obtained from direct imports so + // they needn't be reexported.) + // + // This is analogous to the pruning done by "deep" + // export data for types, but not as precise because + // we aren't careful about which structs or methods + // we rexport: it should be only those referenced + // from the API of s.pkg. + // TODO(adonovan): opt: be more precise. e.g. + // intersect with the set of objects computed by + // importMap(s.pkg.Imports()). + // TODO(adonovan): opt: implement "shallow" facts. + if k.pkg != s.pkg { + if k.obj == nil { + continue // imported package fact + } + if _, isType := k.obj.(*types.TypeName); !isType && + k.obj.Parent() == k.obj.Pkg().Scope() { + continue // imported fact about package-level non-type object + } + } + + var object objectpath.Path + if k.obj != nil { + path, err := encoder.For(k.obj) + if err != nil { + if debug { + log.Printf("discarding fact %s about %s\n", fact, k.obj) + } + continue // object not accessible from package API; discard fact + } + object = path + } + gobFacts = append(gobFacts, gobFact{ + PkgPath: k.pkg.Path(), + Object: object, + Fact: fact, + }) + } + s.mu.Unlock() + + // Sort facts by (package, object, type) for determinism. + sort.Slice(gobFacts, func(i, j int) bool { + x, y := gobFacts[i], gobFacts[j] + if x.PkgPath != y.PkgPath { + return x.PkgPath < y.PkgPath + } + if x.Object != y.Object { + return x.Object < y.Object + } + tx := reflect.TypeOf(x.Fact) + ty := reflect.TypeOf(y.Fact) + if tx != ty { + return tx.String() < ty.String() + } + return false // equal + }) + + var buf bytes.Buffer + if len(gobFacts) > 0 { + if err := gob.NewEncoder(&buf).Encode(gobFacts); err != nil { + // Fact encoding should never fail. Identify the culprit. + for _, gf := range gobFacts { + if err := gob.NewEncoder(io.Discard).Encode(gf); err != nil { + fact := gf.Fact + pkgpath := reflect.TypeOf(fact).Elem().PkgPath() + log.Panicf("internal error: gob encoding of analysis fact %s failed: %v; please report a bug against fact %T in package %q", + fact, err, fact, pkgpath) + } + } + } + } + + if debug { + log.Printf("package %q: encode %d facts, %d bytes\n", + s.pkg.Path(), len(gobFacts), buf.Len()) + } + + return buf.Bytes() +} + +// String is provided only for debugging, and must not be called +// concurrent with any Import/Export method. +func (s *Set) String() string { + var buf bytes.Buffer + buf.WriteString("{") + for k, f := range s.m { + if buf.Len() > 1 { + buf.WriteString(", ") + } + if k.obj != nil { + buf.WriteString(k.obj.String()) + } else { + buf.WriteString(k.pkg.Path()) + } + fmt.Fprintf(&buf, ": %v", f) + } + buf.WriteString("}") + return buf.String() +} diff --git a/contribs/gnopls/internal/facts/facts_test.go b/contribs/gnopls/internal/facts/facts_test.go new file mode 100644 index 00000000000..158a6631b27 --- /dev/null +++ b/contribs/gnopls/internal/facts/facts_test.go @@ -0,0 +1,560 @@ +// Copyright 2018 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 facts_test + +import ( + "encoding/gob" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "os" + "reflect" + "strings" + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/go/packages" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/facts" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +type myFact struct { + S string +} + +func (f *myFact) String() string { return fmt.Sprintf("myFact(%s)", f.S) } +func (f *myFact) AFact() {} + +func init() { + gob.Register(new(myFact)) +} + +func TestEncodeDecode(t *testing.T) { + tests := []struct { + name string + typeparams bool // requires typeparams to be enabled + files map[string]string + plookups []pkgLookups // see testEncodeDecode for details + }{ + { + name: "loading-order", + // c -> b -> a, a2 + // c does not directly depend on a, but it indirectly uses a.T. + // + // Package a2 is never loaded directly so it is incomplete. + // + // We use only types in this example because we rely on + // types.Eval to resolve the lookup expressions, and it only + // works for types. This is a definite gap in the typechecker API. + files: map[string]string{ + "a/a.go": `package a; type A int; type T int`, + "a2/a.go": `package a2; type A2 int; type Unneeded int`, + "b/b.go": `package b; import ("a"; "a2"); type B chan a2.A2; type F func() a.T`, + "c/c.go": `package c; import "b"; type C []b.B`, + }, + // In the following table, we analyze packages (a, b, c) in order, + // look up various objects accessible within each package, + // and see if they have a fact. The "analysis" exports a fact + // for every object at package level. + // + // Note: Loop iterations are not independent test cases; + // order matters, as we populate factmap. + plookups: []pkgLookups{ + {"a", []lookup{ + {"A", "myFact(a.A)"}, + }}, + {"b", []lookup{ + {"a.A", "myFact(a.A)"}, + {"a.T", "myFact(a.T)"}, + {"B", "myFact(b.B)"}, + {"F", "myFact(b.F)"}, + {"F(nil)()", "myFact(a.T)"}, // (result type of b.F) + }}, + {"c", []lookup{ + {"b.B", "myFact(b.B)"}, + {"b.F", "myFact(b.F)"}, + {"b.F(nil)()", "myFact(a.T)"}, + {"C", "myFact(c.C)"}, + {"C{}[0]", "myFact(b.B)"}, + {"<-(C{}[0])", "no fact"}, // object but no fact (we never "analyze" a2) + }}, + }, + }, + { + name: "underlying", + // c->b->a + // c does not import a directly or use any of its types, but it does use + // the types within a indirectly. c.q has the type a.a so package a should + // be included by importMap. + files: map[string]string{ + "a/a.go": `package a; type a int; type T *a`, + "b/b.go": `package b; import "a"; type B a.T`, + "c/c.go": `package c; import "b"; type C b.B; var q = *C(nil)`, + }, + plookups: []pkgLookups{ + {"a", []lookup{ + {"a", "myFact(a.a)"}, + {"T", "myFact(a.T)"}, + }}, + {"b", []lookup{ + {"B", "myFact(b.B)"}, + {"B(nil)", "myFact(b.B)"}, + {"*(B(nil))", "myFact(a.a)"}, + }}, + {"c", []lookup{ + {"C", "myFact(c.C)"}, + {"C(nil)", "myFact(c.C)"}, + {"*C(nil)", "myFact(a.a)"}, + {"q", "myFact(a.a)"}, + }}, + }, + }, + { + name: "methods", + // c->b->a + // c does not import a directly or use any of its types, but it does use + // the types within a indirectly via a method. + files: map[string]string{ + "a/a.go": `package a; type T int`, + "b/b.go": `package b; import "a"; type B struct{}; func (_ B) M() a.T { return 0 }`, + "c/c.go": `package c; import "b"; var C b.B`, + }, + plookups: []pkgLookups{ + {"a", []lookup{ + {"T", "myFact(a.T)"}, + }}, + {"b", []lookup{ + {"B{}", "myFact(b.B)"}, + {"B{}.M()", "myFact(a.T)"}, + }}, + {"c", []lookup{ + {"C", "myFact(b.B)"}, + {"C.M()", "myFact(a.T)"}, + }}, + }, + }, + { + name: "globals", + files: map[string]string{ + "a/a.go": `package a; + type T1 int + type T2 int + type T3 int + type T4 int + type T5 int + type K int; type V string + `, + "b/b.go": `package b + import "a" + var ( + G1 []a.T1 + G2 [7]a.T2 + G3 chan a.T3 + G4 *a.T4 + G5 struct{ F a.T5 } + G6 map[a.K]a.V + ) + `, + "c/c.go": `package c; import "b"; + var ( + v1 = b.G1 + v2 = b.G2 + v3 = b.G3 + v4 = b.G4 + v5 = b.G5 + v6 = b.G6 + ) + `, + }, + plookups: []pkgLookups{ + {"a", []lookup{}}, + {"b", []lookup{}}, + {"c", []lookup{ + {"v1[0]", "myFact(a.T1)"}, + {"v2[0]", "myFact(a.T2)"}, + {"<-v3", "myFact(a.T3)"}, + {"*v4", "myFact(a.T4)"}, + {"v5.F", "myFact(a.T5)"}, + {"v6[0]", "myFact(a.V)"}, + }}, + }, + }, + { + name: "typeparams", + typeparams: true, + files: map[string]string{ + "a/a.go": `package a + type T1 int + type T2 int + type T3 interface{Foo()} + type T4 int + type T5 int + type T6 interface{Foo()} + `, + "b/b.go": `package b + import "a" + type N1[T a.T1|int8] func() T + type N2[T any] struct{ F T } + type N3[T a.T3] func() T + type N4[T a.T4|int8] func() T + type N5[T interface{Bar() a.T5} ] func() T + + type t5 struct{}; func (t5) Bar() a.T5 { return 0 } + + var G1 N1[a.T1] + var G2 func() N2[a.T2] + var G3 N3[a.T3] + var G4 N4[a.T4] + var G5 N5[t5] + + func F6[T a.T6]() T { var x T; return x } + `, + "c/c.go": `package c; import "b"; + var ( + v1 = b.G1 + v2 = b.G2 + v3 = b.G3 + v4 = b.G4 + v5 = b.G5 + v6 = b.F6[t6] + ) + + type t6 struct{}; func (t6) Foo() {} + `, + }, + plookups: []pkgLookups{ + {"a", []lookup{}}, + {"b", []lookup{}}, + {"c", []lookup{ + {"v1", "myFact(b.N1)"}, + {"v1()", "myFact(a.T1)"}, + {"v2()", "myFact(b.N2)"}, + {"v2().F", "myFact(a.T2)"}, + {"v3", "myFact(b.N3)"}, + {"v4", "myFact(b.N4)"}, + {"v4()", "myFact(a.T4)"}, + {"v5", "myFact(b.N5)"}, + {"v5()", "myFact(b.t5)"}, + {"v6()", "myFact(c.t6)"}, + }}, + }, + }, + } + + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + t.Parallel() + testEncodeDecode(t, test.files, test.plookups) + }) + } +} + +type lookup struct { + objexpr string + want string +} + +type pkgLookups struct { + path string + lookups []lookup +} + +// testEncodeDecode tests fact encoding and decoding and simulates how package facts +// are passed during analysis. It operates on a group of Go file contents. Then +// for each <package, []lookup> in tests it does the following: +// 1. loads and type checks the package, +// 2. calls (*facts.Decoder).Decode to load the facts exported by its imports, +// 3. exports a myFact Fact for all of package level objects, +// 4. For each lookup for the current package: +// 4.a) lookup the types.Object for a Go source expression in the current package +// (or confirms one is not expected want=="no object"), +// 4.b) finds a Fact for the object (or confirms one is not expected want=="no fact"), +// 4.c) compares the content of the Fact to want. +// 5. encodes the Facts of the package. +// +// Note: tests are not independent test cases; order matters (as does a package being +// skipped). It changes what Facts can be imported. +// +// Failures are reported on t. +func testEncodeDecode(t *testing.T, files map[string]string, tests []pkgLookups) { + dir, cleanup, err := analysistest.WriteFiles(files) + if err != nil { + t.Fatal(err) + } + defer cleanup() + + // factmap represents the passing of encoded facts from one + // package to another. In practice one would use the file system. + factmap := make(map[string][]byte) + read := func(pkgPath string) ([]byte, error) { return factmap[pkgPath], nil } + + // Analyze packages in order, look up various objects accessible within + // each package, and see if they have a fact. The "analysis" exports a + // fact for every object at package level. + // + // Note: Loop iterations are not independent test cases; + // order matters, as we populate factmap. + for _, test := range tests { + // load package + pkg, err := load(t, dir, test.path) + if err != nil { + t.Fatal(err) + } + + // decode + facts, err := facts.NewDecoder(pkg).Decode(read) + if err != nil { + t.Fatalf("Decode failed: %v", err) + } + t.Logf("decode %s facts = %v", pkg.Path(), facts) // show all facts + + // export + // (one fact for each package-level object) + for _, name := range pkg.Scope().Names() { + obj := pkg.Scope().Lookup(name) + fact := &myFact{obj.Pkg().Name() + "." + obj.Name()} + facts.ExportObjectFact(obj, fact) + } + t.Logf("exported %s facts = %v", pkg.Path(), facts) // show all facts + + // import + // (after export, because an analyzer may import its own facts) + for _, lookup := range test.lookups { + fact := new(myFact) + var got string + if obj := find(pkg, lookup.objexpr); obj == nil { + got = "no object" + } else if facts.ImportObjectFact(obj, fact) { + got = fact.String() + } else { + got = "no fact" + } + if got != lookup.want { + t.Errorf("in %s, ImportObjectFact(%s, %T) = %s, want %s", + pkg.Path(), lookup.objexpr, fact, got, lookup.want) + } + } + + // encode + factmap[pkg.Path()] = facts.Encode() + } +} + +func find(p *types.Package, expr string) types.Object { + // types.Eval only allows us to compute a TypeName object for an expression. + // TODO(adonovan): support other expressions that denote an object: + // - an identifier (or qualified ident) for a func, const, or var + // - new(T).f for a field or method + // I've added CheckExpr in https://go-review.googlesource.com/c/go/+/144677. + // If that becomes available, use it. + + // Choose an arbitrary position within the (single-file) package + // so that we are within the scope of its import declarations. + somepos := p.Scope().Lookup(p.Scope().Names()[0]).Pos() + tv, err := types.Eval(token.NewFileSet(), p, somepos, expr) + if err != nil { + return nil + } + if n, ok := aliases.Unalias(tv.Type).(*types.Named); ok { + return n.Obj() + } + return nil +} + +func load(t *testing.T, dir string, path string) (*types.Package, error) { + cfg := &packages.Config{ + Mode: packages.LoadSyntax, + Dir: dir, + Env: append(os.Environ(), "GOPATH="+dir, "GO111MODULE=off", "GOPROXY=off"), + } + testenv.NeedsGoPackagesEnv(t, cfg.Env) + pkgs, err := packages.Load(cfg, path) + if err != nil { + return nil, err + } + if packages.PrintErrors(pkgs) > 0 { + return nil, fmt.Errorf("packages had errors") + } + if len(pkgs) == 0 { + return nil, fmt.Errorf("no package matched %s", path) + } + return pkgs[0].Types, nil +} + +type otherFact struct { + S string +} + +func (f *otherFact) String() string { return fmt.Sprintf("otherFact(%s)", f.S) } +func (f *otherFact) AFact() {} + +func TestFactFilter(t *testing.T) { + files := map[string]string{ + "a/a.go": `package a; type A int`, + } + dir, cleanup, err := analysistest.WriteFiles(files) + if err != nil { + t.Fatal(err) + } + defer cleanup() + + pkg, err := load(t, dir, "a") + if err != nil { + t.Fatal(err) + } + + obj := pkg.Scope().Lookup("A") + s, err := facts.NewDecoder(pkg).Decode(func(pkgPath string) ([]byte, error) { return nil, nil }) + if err != nil { + t.Fatal(err) + } + s.ExportObjectFact(obj, &myFact{"good object fact"}) + s.ExportPackageFact(&myFact{"good package fact"}) + s.ExportObjectFact(obj, &otherFact{"bad object fact"}) + s.ExportPackageFact(&otherFact{"bad package fact"}) + + filter := map[reflect.Type]bool{ + reflect.TypeOf(&myFact{}): true, + } + + pkgFacts := s.AllPackageFacts(filter) + wantPkgFacts := `[{package a ("a") myFact(good package fact)}]` + if got := fmt.Sprintf("%v", pkgFacts); got != wantPkgFacts { + t.Errorf("AllPackageFacts: got %v, want %v", got, wantPkgFacts) + } + + objFacts := s.AllObjectFacts(filter) + wantObjFacts := "[{type a.A int myFact(good object fact)}]" + if got := fmt.Sprintf("%v", objFacts); got != wantObjFacts { + t.Errorf("AllObjectFacts: got %v, want %v", got, wantObjFacts) + } +} + +// TestMalformed checks that facts can be encoded and decoded *despite* +// types.Config.Check returning an error. Importing facts is expected to +// happen when Analyzers have RunDespiteErrors set to true. So this +// needs to robust, e.g. no infinite loops. +func TestMalformed(t *testing.T) { + var findPkg func(*types.Package, string) *types.Package + findPkg = func(p *types.Package, name string) *types.Package { + if p.Name() == name { + return p + } + for _, o := range p.Imports() { + if f := findPkg(o, name); f != nil { + return f + } + } + return nil + } + + type pkgTest struct { + content string + err string // if non-empty, expected substring of err.Error() from conf.Check(). + wants map[string]string // package path to expected name + } + tests := []struct { + name string + pkgs []pkgTest + }{ + { + name: "initialization-cycle", + pkgs: []pkgTest{ + // Notation: myFact(a.[N]) means: package a has members {N}. + { + content: `package a; type N[T any] struct { F *N[N[T]] }`, + err: "instantiation cycle:", + wants: map[string]string{"a": "myFact(a.[N])", "b": "no package", "c": "no package"}, + }, + { + content: `package b; import "a"; type B a.N[int]`, + wants: map[string]string{"a": "myFact(a.[N])", "b": "myFact(b.[B])", "c": "no package"}, + }, + { + content: `package c; import "b"; var C b.B`, + wants: map[string]string{"a": "no fact", "b": "myFact(b.[B])", "c": "myFact(c.[C])"}, + // package fact myFact(a.[N]) not reexported + }, + }, + }, + } + + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // setup for test wide variables. + packages := make(map[string]*types.Package) + conf := types.Config{ + Importer: closure(packages), + Error: func(err error) {}, // do not stop on first type checking error + } + fset := token.NewFileSet() + factmap := make(map[string][]byte) + read := func(pkgPath string) ([]byte, error) { return factmap[pkgPath], nil } + + // Processes the pkgs in order. For package, export a package fact, + // and use this fact to verify which package facts are reachable via Decode. + // We allow for packages to have type checking errors. + for i, pkgTest := range test.pkgs { + // parse + f, err := parser.ParseFile(fset, fmt.Sprintf("%d.go", i), pkgTest.content, 0) + if err != nil { + t.Fatal(err) + } + + // typecheck + pkg, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) + var got string + if err != nil { + got = err.Error() + } + if !strings.Contains(got, pkgTest.err) { + t.Fatalf("%s: type checking error %q did not match pattern %q", pkg.Path(), err.Error(), pkgTest.err) + } + packages[pkg.Path()] = pkg + + // decode facts + facts, err := facts.NewDecoder(pkg).Decode(read) + if err != nil { + t.Fatalf("Decode failed: %v", err) + } + + // export facts + fact := &myFact{fmt.Sprintf("%s.%s", pkg.Name(), pkg.Scope().Names())} + facts.ExportPackageFact(fact) + + // import facts + for other, want := range pkgTest.wants { + fact := new(myFact) + var got string + if found := findPkg(pkg, other); found == nil { + got = "no package" + } else if facts.ImportPackageFact(found, fact) { + got = fact.String() + } else { + got = "no fact" + } + if got != want { + t.Errorf("in %s, ImportPackageFact(%s, %T) = %s, want %s", + pkg.Path(), other, fact, got, want) + } + } + + // encode facts + factmap[pkg.Path()] = facts.Encode() + } + }) + } +} + +type closure map[string]*types.Package + +func (c closure) Import(path string) (*types.Package, error) { return c[path], nil } diff --git a/contribs/gnopls/internal/facts/imports.go b/contribs/gnopls/internal/facts/imports.go new file mode 100644 index 00000000000..ea576ed9cec --- /dev/null +++ b/contribs/gnopls/internal/facts/imports.go @@ -0,0 +1,136 @@ +// Copyright 2018 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 facts + +import ( + "go/types" + + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" +) + +// importMap computes the import map for a package by traversing the +// entire exported API each of its imports. +// +// This is a workaround for the fact that we cannot access the map used +// internally by the types.Importer returned by go/importer. The entries +// in this map are the packages and objects that may be relevant to the +// current analysis unit. +// +// Packages in the map that are only indirectly imported may be +// incomplete (!pkg.Complete()). +// +// This function scales very poorly with packages' transitive object +// references, which can be more than a million for each package near +// the top of a large project. (This was a significant contributor to +// #60621.) +// TODO(adonovan): opt: compute this information more efficiently +// by obtaining it from the internals of the gcexportdata decoder. +func importMap(imports []*types.Package) map[string]*types.Package { + objects := make(map[types.Object]bool) + typs := make(map[types.Type]bool) // Named and TypeParam + packages := make(map[string]*types.Package) + + var addObj func(obj types.Object) + var addType func(T types.Type) + + addObj = func(obj types.Object) { + if !objects[obj] { + objects[obj] = true + addType(obj.Type()) + if pkg := obj.Pkg(); pkg != nil { + packages[pkg.Path()] = pkg + } + } + } + + addType = func(T types.Type) { + switch T := T.(type) { + case *aliases.Alias: + addType(aliases.Unalias(T)) + case *types.Basic: + // nop + case *types.Named: + // Remove infinite expansions of *types.Named by always looking at the origin. + // Some named types with type parameters [that will not type check] have + // infinite expansions: + // type N[T any] struct { F *N[N[T]] } + // importMap() is called on such types when Analyzer.RunDespiteErrors is true. + T = T.Origin() + if !typs[T] { + typs[T] = true + addObj(T.Obj()) + addType(T.Underlying()) + for i := 0; i < T.NumMethods(); i++ { + addObj(T.Method(i)) + } + if tparams := T.TypeParams(); tparams != nil { + for i := 0; i < tparams.Len(); i++ { + addType(tparams.At(i)) + } + } + if targs := T.TypeArgs(); targs != nil { + for i := 0; i < targs.Len(); i++ { + addType(targs.At(i)) + } + } + } + case *types.Pointer: + addType(T.Elem()) + case *types.Slice: + addType(T.Elem()) + case *types.Array: + addType(T.Elem()) + case *types.Chan: + addType(T.Elem()) + case *types.Map: + addType(T.Key()) + addType(T.Elem()) + case *types.Signature: + addType(T.Params()) + addType(T.Results()) + if tparams := T.TypeParams(); tparams != nil { + for i := 0; i < tparams.Len(); i++ { + addType(tparams.At(i)) + } + } + case *types.Struct: + for i := 0; i < T.NumFields(); i++ { + addObj(T.Field(i)) + } + case *types.Tuple: + for i := 0; i < T.Len(); i++ { + addObj(T.At(i)) + } + case *types.Interface: + for i := 0; i < T.NumMethods(); i++ { + addObj(T.Method(i)) + } + for i := 0; i < T.NumEmbeddeds(); i++ { + addType(T.EmbeddedType(i)) // walk Embedded for implicits + } + case *types.Union: + for i := 0; i < T.Len(); i++ { + addType(T.Term(i).Type()) + } + case *types.TypeParam: + if !typs[T] { + typs[T] = true + addObj(T.Obj()) + addType(T.Constraint()) + } + } + } + + for _, imp := range imports { + packages[imp.Path()] = imp + + scope := imp.Scope() + for _, name := range scope.Names() { + addObj(scope.Lookup(name)) + } + } + + return packages +} diff --git a/contribs/gnopls/internal/fakenet/conn.go b/contribs/gnopls/internal/fakenet/conn.go new file mode 100644 index 00000000000..c9cdaf27a7b --- /dev/null +++ b/contribs/gnopls/internal/fakenet/conn.go @@ -0,0 +1,129 @@ +// Copyright 2018 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 fakenet + +import ( + "io" + "net" + "sync" + "time" +) + +// NewConn returns a net.Conn built on top of the supplied reader and writer. +// It decouples the read and write on the conn from the underlying stream +// to enable Close to abort ones that are in progress. +// It's primary use is to fake a network connection from stdin and stdout. +func NewConn(name string, in io.ReadCloser, out io.WriteCloser) net.Conn { + c := &fakeConn{ + name: name, + reader: newFeeder(in.Read), + writer: newFeeder(out.Write), + in: in, + out: out, + } + go c.reader.run() + go c.writer.run() + return c +} + +type fakeConn struct { + name string + reader *connFeeder + writer *connFeeder + in io.ReadCloser + out io.WriteCloser +} + +type fakeAddr string + +// connFeeder serializes calls to the source function (io.Reader.Read or +// io.Writer.Write) by delegating them to a channel. This also allows calls to +// be intercepted when the connection is closed, and cancelled early if the +// connection is closed while the calls are still outstanding. +type connFeeder struct { + source func([]byte) (int, error) + input chan []byte + result chan feedResult + mu sync.Mutex + closed bool + done chan struct{} +} + +type feedResult struct { + n int + err error +} + +func (c *fakeConn) Close() error { + c.reader.close() + c.writer.close() + c.in.Close() + c.out.Close() + return nil +} + +func (c *fakeConn) Read(b []byte) (n int, err error) { return c.reader.do(b) } +func (c *fakeConn) Write(b []byte) (n int, err error) { return c.writer.do(b) } +func (c *fakeConn) LocalAddr() net.Addr { return fakeAddr(c.name) } +func (c *fakeConn) RemoteAddr() net.Addr { return fakeAddr(c.name) } +func (c *fakeConn) SetDeadline(t time.Time) error { return nil } +func (c *fakeConn) SetReadDeadline(t time.Time) error { return nil } +func (c *fakeConn) SetWriteDeadline(t time.Time) error { return nil } +func (a fakeAddr) Network() string { return "fake" } +func (a fakeAddr) String() string { return string(a) } + +func newFeeder(source func([]byte) (int, error)) *connFeeder { + return &connFeeder{ + source: source, + input: make(chan []byte), + result: make(chan feedResult), + done: make(chan struct{}), + } +} + +func (f *connFeeder) close() { + f.mu.Lock() + if !f.closed { + f.closed = true + close(f.done) + } + f.mu.Unlock() +} + +func (f *connFeeder) do(b []byte) (n int, err error) { + // send the request to the worker + select { + case f.input <- b: + case <-f.done: + return 0, io.EOF + } + // get the result from the worker + select { + case r := <-f.result: + return r.n, r.err + case <-f.done: + return 0, io.EOF + } +} + +func (f *connFeeder) run() { + var b []byte + for { + // wait for an input request + select { + case b = <-f.input: + case <-f.done: + return + } + // invoke the underlying method + n, err := f.source(b) + // send the result back to the requester + select { + case f.result <- feedResult{n: n, err: err}: + case <-f.done: + return + } + } +} diff --git a/contribs/gnopls/internal/file/file.go b/contribs/gnopls/internal/file/file.go index 5f8be06cf60..1e75603b2b0 100644 --- a/contribs/gnopls/internal/file/file.go +++ b/contribs/gnopls/internal/file/file.go @@ -9,7 +9,7 @@ import ( "context" "fmt" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // An Identity identifies the name and contents of a file. diff --git a/contribs/gnopls/internal/file/kind.go b/contribs/gnopls/internal/file/kind.go index 087a57f32d0..e25ded3486c 100644 --- a/contribs/gnopls/internal/file/kind.go +++ b/contribs/gnopls/internal/file/kind.go @@ -7,7 +7,7 @@ package file import ( "fmt" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // Kind describes the kind of the file in question. @@ -18,8 +18,8 @@ const ( // UnknownKind is a file type we don't know about. UnknownKind = Kind(iota) - // Go is a Go source file. - Go + // Gno is a Gno source file. + Gno // Mod is a go.mod file. Mod // Sum is a go.sum file. @@ -32,16 +32,16 @@ const ( func (k Kind) String() string { switch k { - case Go: - return "go" + case Gno: + return "gno" case Mod: - return "go.mod" + return "gno.mod" case Sum: - return "go.sum" + return "gno.sum" case Tmpl: return "tmpl" case Work: - return "go.work" + return "gno.work" default: return fmt.Sprintf("internal error: unknown file kind %d", k) } @@ -52,11 +52,11 @@ func (k Kind) String() string { // or UnknownKind if the language is not one recognized by gopls. func KindForLang(langID protocol.LanguageKind) Kind { switch langID { - case "go": - return Go - case "go.mod": + case "gno": + return Gno + case "gno.mod": return Mod - case "go.sum": + case "gno.sum": return Sum case "tmpl", "gotmpl": return Tmpl diff --git a/contribs/gnopls/internal/file/modification.go b/contribs/gnopls/internal/file/modification.go index a53bb17898a..3fa2476e021 100644 --- a/contribs/gnopls/internal/file/modification.go +++ b/contribs/gnopls/internal/file/modification.go @@ -4,7 +4,7 @@ package file -import "golang.org/x/tools/gopls/internal/protocol" +import "github.com/gnolang/gno/contribs/gnopls/internal/protocol" // Modification represents a modification to a file. type Modification struct { diff --git a/contribs/gnopls/internal/filecache/filecache.go b/contribs/gnopls/internal/filecache/filecache.go index 243e9547128..c62a1a53956 100644 --- a/contribs/gnopls/internal/filecache/filecache.go +++ b/contribs/gnopls/internal/filecache/filecache.go @@ -38,8 +38,8 @@ import ( "sync/atomic" "time" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/lru" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/lru" ) // Start causes the filecache to initialize and start garbage gollection. diff --git a/contribs/gnopls/internal/filecache/filecache_test.go b/contribs/gnopls/internal/filecache/filecache_test.go index 3419db4b513..8e1f3c1e739 100644 --- a/contribs/gnopls/internal/filecache/filecache_test.go +++ b/contribs/gnopls/internal/filecache/filecache_test.go @@ -22,8 +22,8 @@ import ( "testing" "golang.org/x/sync/errgroup" - "golang.org/x/tools/gopls/internal/filecache" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/filecache" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestBasics(t *testing.T) { diff --git a/contribs/gnopls/internal/fuzzy/input_test.go b/contribs/gnopls/internal/fuzzy/input_test.go index 4f3239372aa..b84692e114c 100644 --- a/contribs/gnopls/internal/fuzzy/input_test.go +++ b/contribs/gnopls/internal/fuzzy/input_test.go @@ -9,7 +9,7 @@ import ( "sort" "testing" - "golang.org/x/tools/gopls/internal/fuzzy" + "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" ) var rolesTests = []struct { diff --git a/contribs/gnopls/internal/fuzzy/matcher_test.go b/contribs/gnopls/internal/fuzzy/matcher_test.go index 056da25d675..9924713ef24 100644 --- a/contribs/gnopls/internal/fuzzy/matcher_test.go +++ b/contribs/gnopls/internal/fuzzy/matcher_test.go @@ -13,7 +13,7 @@ import ( "math" "testing" - "golang.org/x/tools/gopls/internal/fuzzy" + "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" ) type comparator struct { diff --git a/contribs/gnopls/internal/fuzzy/self_test.go b/contribs/gnopls/internal/fuzzy/self_test.go index 1c64f1953df..f5fbb64c05c 100644 --- a/contribs/gnopls/internal/fuzzy/self_test.go +++ b/contribs/gnopls/internal/fuzzy/self_test.go @@ -7,7 +7,7 @@ package fuzzy_test import ( "testing" - . "golang.org/x/tools/gopls/internal/fuzzy" + . "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" ) func BenchmarkSelf_Matcher(b *testing.B) { diff --git a/contribs/gnopls/internal/fuzzy/symbol_test.go b/contribs/gnopls/internal/fuzzy/symbol_test.go index 99e2152cef3..2ade97988c0 100644 --- a/contribs/gnopls/internal/fuzzy/symbol_test.go +++ b/contribs/gnopls/internal/fuzzy/symbol_test.go @@ -11,7 +11,7 @@ import ( "testing" "golang.org/x/tools/go/packages" - . "golang.org/x/tools/gopls/internal/fuzzy" + . "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" ) func TestSymbolMatchIndex(t *testing.T) { diff --git a/contribs/gnopls/internal/gcimporter/bexport_test.go b/contribs/gnopls/internal/gcimporter/bexport_test.go new file mode 100644 index 00000000000..30e88555ddd --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/bexport_test.go @@ -0,0 +1,378 @@ +// Copyright 2016 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 gcimporter_test + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "path/filepath" + "reflect" + "runtime" + "sort" + "strings" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" +) + +var isRace = false + +func fileLine(fset *token.FileSet, obj types.Object) string { + posn := fset.Position(obj.Pos()) + filename := filepath.Clean(strings.ReplaceAll(posn.Filename, "$GOROOT", runtime.GOROOT())) + return fmt.Sprintf("%s:%d", filename, posn.Line) +} + +func equalType(x, y types.Type) error { + x = aliases.Unalias(x) + y = aliases.Unalias(y) + if reflect.TypeOf(x) != reflect.TypeOf(y) { + return fmt.Errorf("unequal kinds: %T vs %T", x, y) + } + switch x := x.(type) { + case *types.Interface: + y := y.(*types.Interface) + // TODO(gri): enable separate emission of Embedded interfaces + // and ExplicitMethods then use this logic. + // if x.NumEmbeddeds() != y.NumEmbeddeds() { + // return fmt.Errorf("unequal number of embedded interfaces: %d vs %d", + // x.NumEmbeddeds(), y.NumEmbeddeds()) + // } + // for i := 0; i < x.NumEmbeddeds(); i++ { + // xi := x.Embedded(i) + // yi := y.Embedded(i) + // if xi.String() != yi.String() { + // return fmt.Errorf("mismatched %th embedded interface: %s vs %s", + // i, xi, yi) + // } + // } + // if x.NumExplicitMethods() != y.NumExplicitMethods() { + // return fmt.Errorf("unequal methods: %d vs %d", + // x.NumExplicitMethods(), y.NumExplicitMethods()) + // } + // for i := 0; i < x.NumExplicitMethods(); i++ { + // xm := x.ExplicitMethod(i) + // ym := y.ExplicitMethod(i) + // if xm.Name() != ym.Name() { + // return fmt.Errorf("mismatched %th method: %s vs %s", i, xm, ym) + // } + // if err := equalType(xm.Type(), ym.Type()); err != nil { + // return fmt.Errorf("mismatched %s method: %s", xm.Name(), err) + // } + // } + if x.NumMethods() != y.NumMethods() { + return fmt.Errorf("unequal methods: %d vs %d", + x.NumMethods(), y.NumMethods()) + } + for i := 0; i < x.NumMethods(); i++ { + xm := x.Method(i) + ym := y.Method(i) + if xm.Name() != ym.Name() { + return fmt.Errorf("mismatched %dth method: %s vs %s", i, xm, ym) + } + if err := equalType(xm.Type(), ym.Type()); err != nil { + return fmt.Errorf("mismatched %s method: %s", xm.Name(), err) + } + } + // Constraints are handled explicitly in the *TypeParam case below, so we + // don't yet need to consider embeddeds here. + // TODO(rfindley): consider the type set here. + case *types.Array: + y := y.(*types.Array) + if x.Len() != y.Len() { + return fmt.Errorf("unequal array lengths: %d vs %d", x.Len(), y.Len()) + } + if err := equalType(x.Elem(), y.Elem()); err != nil { + return fmt.Errorf("array elements: %s", err) + } + case *types.Basic: + y := y.(*types.Basic) + if x.Kind() != y.Kind() { + return fmt.Errorf("unequal basic types: %s vs %s", x, y) + } + case *types.Chan: + y := y.(*types.Chan) + if x.Dir() != y.Dir() { + return fmt.Errorf("unequal channel directions: %d vs %d", x.Dir(), y.Dir()) + } + if err := equalType(x.Elem(), y.Elem()); err != nil { + return fmt.Errorf("channel elements: %s", err) + } + case *types.Map: + y := y.(*types.Map) + if err := equalType(x.Key(), y.Key()); err != nil { + return fmt.Errorf("map keys: %s", err) + } + if err := equalType(x.Elem(), y.Elem()); err != nil { + return fmt.Errorf("map values: %s", err) + } + case *types.Named: + y := y.(*types.Named) + return cmpNamed(x, y) + case *types.Pointer: + y := y.(*types.Pointer) + if err := equalType(x.Elem(), y.Elem()); err != nil { + return fmt.Errorf("pointer elements: %s", err) + } + case *types.Signature: + y := y.(*types.Signature) + if err := equalType(x.Params(), y.Params()); err != nil { + return fmt.Errorf("parameters: %s", err) + } + if err := equalType(x.Results(), y.Results()); err != nil { + return fmt.Errorf("results: %s", err) + } + if x.Variadic() != y.Variadic() { + return fmt.Errorf("unequal variadicity: %t vs %t", + x.Variadic(), y.Variadic()) + } + if (x.Recv() != nil) != (y.Recv() != nil) { + return fmt.Errorf("unequal receivers: %s vs %s", x.Recv(), y.Recv()) + } + if x.Recv() != nil { + // TODO(adonovan): fix: this assertion fires for interface methods. + // The type of the receiver of an interface method is a named type + // if the Package was loaded from export data, or an unnamed (interface) + // type if the Package was produced by type-checking ASTs. + // if err := equalType(x.Recv().Type(), y.Recv().Type()); err != nil { + // return fmt.Errorf("receiver: %s", err) + // } + } + if err := equalTypeParams(x.TypeParams(), y.TypeParams()); err != nil { + return fmt.Errorf("type params: %s", err) + } + if err := equalTypeParams(x.RecvTypeParams(), y.RecvTypeParams()); err != nil { + return fmt.Errorf("recv type params: %s", err) + } + case *types.Slice: + y := y.(*types.Slice) + if err := equalType(x.Elem(), y.Elem()); err != nil { + return fmt.Errorf("slice elements: %s", err) + } + case *types.Struct: + y := y.(*types.Struct) + if x.NumFields() != y.NumFields() { + return fmt.Errorf("unequal struct fields: %d vs %d", + x.NumFields(), y.NumFields()) + } + for i := 0; i < x.NumFields(); i++ { + xf := x.Field(i) + yf := y.Field(i) + if xf.Name() != yf.Name() { + return fmt.Errorf("mismatched fields: %s vs %s", xf, yf) + } + if err := equalType(xf.Type(), yf.Type()); err != nil { + return fmt.Errorf("struct field %s: %s", xf.Name(), err) + } + if x.Tag(i) != y.Tag(i) { + return fmt.Errorf("struct field %s has unequal tags: %q vs %q", + xf.Name(), x.Tag(i), y.Tag(i)) + } + } + case *types.Tuple: + y := y.(*types.Tuple) + if x.Len() != y.Len() { + return fmt.Errorf("unequal tuple lengths: %d vs %d", x.Len(), y.Len()) + } + for i := 0; i < x.Len(); i++ { + if err := equalType(x.At(i).Type(), y.At(i).Type()); err != nil { + return fmt.Errorf("tuple element %d: %s", i, err) + } + } + case *types.TypeParam: + y := y.(*types.TypeParam) + if x.String() != y.String() { + return fmt.Errorf("unequal named types: %s vs %s", x, y) + } + // For now, just compare constraints by type string to short-circuit + // cycles. We have to make interfaces explicit as export data currently + // doesn't support marking interfaces as implicit. + // TODO(rfindley): remove makeExplicit once export data contains an + // implicit bit. + xc := makeExplicit(x.Constraint()).String() + yc := makeExplicit(y.Constraint()).String() + if xc != yc { + return fmt.Errorf("unequal constraints: %s vs %s", xc, yc) + } + + default: + panic(fmt.Sprintf("unexpected %T type", x)) + } + return nil +} + +// cmpNamed compares two named types x and y, returning an error for any +// discrepancies. It does not compare their underlying types. +func cmpNamed(x, y *types.Named) error { + xOrig := x.Origin() + yOrig := y.Origin() + if xOrig.String() != yOrig.String() { + return fmt.Errorf("unequal named types: %s vs %s", x, y) + } + if err := equalTypeParams(x.TypeParams(), y.TypeParams()); err != nil { + return fmt.Errorf("type parameters: %s", err) + } + if err := equalTypeArgs(x.TypeArgs(), y.TypeArgs()); err != nil { + return fmt.Errorf("type arguments: %s", err) + } + if x.NumMethods() != y.NumMethods() { + return fmt.Errorf("unequal methods: %d vs %d", + x.NumMethods(), y.NumMethods()) + } + // Unfortunately method sorting is not canonical, so sort before comparing. + var xms, yms []*types.Func + for i := 0; i < x.NumMethods(); i++ { + xms = append(xms, x.Method(i)) + yms = append(yms, y.Method(i)) + } + for _, ms := range [][]*types.Func{xms, yms} { + sort.Slice(ms, func(i, j int) bool { + return ms[i].Name() < ms[j].Name() + }) + } + for i, xm := range xms { + ym := yms[i] + if xm.Name() != ym.Name() { + return fmt.Errorf("mismatched %dth method: %s vs %s", i, xm, ym) + } + // Calling equalType here leads to infinite recursion, so just compare + // strings. + if xm.String() != ym.String() { + return fmt.Errorf("unequal methods: %s vs %s", x, y) + } + } + return nil +} + +// makeExplicit returns an explicit version of typ, if typ is an implicit +// interface. Otherwise it returns typ unmodified. +func makeExplicit(typ types.Type) types.Type { + if iface, _ := typ.(*types.Interface); iface != nil && iface.IsImplicit() { + var methods []*types.Func + for i := 0; i < iface.NumExplicitMethods(); i++ { + methods = append(methods, iface.Method(i)) + } + var embeddeds []types.Type + for i := 0; i < iface.NumEmbeddeds(); i++ { + embeddeds = append(embeddeds, iface.EmbeddedType(i)) + } + return types.NewInterfaceType(methods, embeddeds) + } + return typ +} + +func equalTypeArgs(x, y *types.TypeList) error { + if x.Len() != y.Len() { + return fmt.Errorf("unequal lengths: %d vs %d", x.Len(), y.Len()) + } + for i := 0; i < x.Len(); i++ { + if err := equalType(x.At(i), y.At(i)); err != nil { + return fmt.Errorf("type %d: %s", i, err) + } + } + return nil +} + +func equalTypeParams(x, y *types.TypeParamList) error { + if x.Len() != y.Len() { + return fmt.Errorf("unequal lengths: %d vs %d", x.Len(), y.Len()) + } + for i := 0; i < x.Len(); i++ { + if err := equalType(x.At(i), y.At(i)); err != nil { + return fmt.Errorf("type parameter %d: %s", i, err) + } + } + return nil +} + +// TestVeryLongFile tests the position of an import object declared in +// a very long input file. Line numbers greater than maxlines are +// reported as line 1, not garbage or token.NoPos. +func TestVeryLongFile(t *testing.T) { + // parse and typecheck + longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int" + fset1 := token.NewFileSet() + f, err := parser.ParseFile(fset1, "foo.go", longFile, 0) + if err != nil { + t.Fatal(err) + } + var conf types.Config + pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + // export + var out bytes.Buffer + if err := gcimporter.IExportData(&out, fset1, pkg); err != nil { + t.Fatal(err) + } + exportdata := out.Bytes() + + // import + imports := make(map[string]*types.Package) + fset2 := token.NewFileSet() + _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) + if err != nil { + t.Fatalf("BImportData(%s): %v", pkg.Path(), err) + } + + // compare + posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos()) + posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos()) + if want := "foo.go:1:1"; posn2.String() != want { + t.Errorf("X position = %s, want %s (orig was %s)", + posn2, want, posn1) + } +} + +const src = ` +package p + +type ( + T0 = int32 + T1 = struct{} + T2 = struct{ T1 } + Invalid = foo // foo is undeclared +) +` + +func checkPkg(t *testing.T, pkg *types.Package, label string) { + T1 := types.NewStruct(nil, nil) + T2 := types.NewStruct([]*types.Var{types.NewField(0, pkg, "T1", T1, true)}, nil) + + for _, test := range []struct { + name string + typ types.Type + }{ + {"T0", types.Typ[types.Int32]}, + {"T1", T1}, + {"T2", T2}, + {"Invalid", types.Typ[types.Invalid]}, + } { + obj := pkg.Scope().Lookup(test.name) + if obj == nil { + t.Errorf("%s: %s not found", label, test.name) + continue + } + tname, _ := obj.(*types.TypeName) + if tname == nil { + t.Errorf("%s: %v not a type name", label, obj) + continue + } + if !tname.IsAlias() { + t.Errorf("%s: %v: not marked as alias", label, tname) + continue + } + if got := tname.Type(); !types.Identical(got, test.typ) { + t.Errorf("%s: %v: got %v; want %v", label, tname, got, test.typ) + } + } +} diff --git a/contribs/gnopls/internal/gcimporter/bimport.go b/contribs/gnopls/internal/gcimporter/bimport.go new file mode 100644 index 00000000000..d98b0db2a9a --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/bimport.go @@ -0,0 +1,150 @@ +// Copyright 2015 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 the remaining vestiges of +// $GOROOT/src/go/internal/gcimporter/bimport.go. + +package gcimporter + +import ( + "fmt" + "go/token" + "go/types" + "sync" +) + +func errorf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} + +const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go + +// Synthesize a token.Pos +type fakeFileSet struct { + fset *token.FileSet + files map[string]*fileInfo +} + +type fileInfo struct { + file *token.File + lastline int +} + +const maxlines = 64 * 1024 + +func (s *fakeFileSet) pos(file string, line, column int) token.Pos { + // TODO(mdempsky): Make use of column. + + // Since we don't know the set of needed file positions, we reserve maxlines + // positions per file. We delay calling token.File.SetLines until all + // positions have been calculated (by way of fakeFileSet.setLines), so that + // we can avoid setting unnecessary lines. See also golang/go#46586. + f := s.files[file] + if f == nil { + f = &fileInfo{file: s.fset.AddFile(file, -1, maxlines)} + s.files[file] = f + } + if line > maxlines { + line = 1 + } + if line > f.lastline { + f.lastline = line + } + + // Return a fake position assuming that f.file consists only of newlines. + return token.Pos(f.file.Base() + line - 1) +} + +func (s *fakeFileSet) setLines() { + fakeLinesOnce.Do(func() { + fakeLines = make([]int, maxlines) + for i := range fakeLines { + fakeLines[i] = i + } + }) + for _, f := range s.files { + f.file.SetLines(fakeLines[:f.lastline]) + } +} + +var ( + fakeLines []int + fakeLinesOnce sync.Once +) + +func chanDir(d int) types.ChanDir { + // tag values must match the constants in cmd/compile/internal/gc/go.go + switch d { + case 1 /* Crecv */ : + return types.RecvOnly + case 2 /* Csend */ : + return types.SendOnly + case 3 /* Cboth */ : + return types.SendRecv + default: + errorf("unexpected channel dir %d", d) + return 0 + } +} + +var predeclOnce sync.Once +var predecl []types.Type // initialized lazily + +func predeclared() []types.Type { + predeclOnce.Do(func() { + // initialize lazily to be sure that all + // elements have been initialized before + predecl = []types.Type{ // basic types + types.Typ[types.Bool], + types.Typ[types.Int], + types.Typ[types.Int8], + types.Typ[types.Int16], + types.Typ[types.Int32], + types.Typ[types.Int64], + types.Typ[types.Uint], + types.Typ[types.Uint8], + types.Typ[types.Uint16], + types.Typ[types.Uint32], + types.Typ[types.Uint64], + types.Typ[types.Uintptr], + types.Typ[types.Float32], + types.Typ[types.Float64], + types.Typ[types.Complex64], + types.Typ[types.Complex128], + types.Typ[types.String], + + // basic type aliases + types.Universe.Lookup("byte").Type(), + types.Universe.Lookup("rune").Type(), + + // error + types.Universe.Lookup("error").Type(), + + // untyped types + types.Typ[types.UntypedBool], + types.Typ[types.UntypedInt], + types.Typ[types.UntypedRune], + types.Typ[types.UntypedFloat], + types.Typ[types.UntypedComplex], + types.Typ[types.UntypedString], + types.Typ[types.UntypedNil], + + // package unsafe + types.Typ[types.UnsafePointer], + + // invalid type + types.Typ[types.Invalid], // only appears in packages with errors + + // used internally by gc; never used by this package or in .a files + anyType{}, + } + predecl = append(predecl, additionalPredeclared()...) + }) + return predecl +} + +type anyType struct{} + +func (t anyType) Underlying() types.Type { return t } +func (t anyType) String() string { return "any" } diff --git a/contribs/gnopls/internal/gcimporter/exportdata.go b/contribs/gnopls/internal/gcimporter/exportdata.go new file mode 100644 index 00000000000..f6437feb1cf --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/exportdata.go @@ -0,0 +1,99 @@ +// Copyright 2011 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 is a copy of $GOROOT/src/go/internal/gcimporter/exportdata.go. + +// This file implements FindExportData. + +package gcimporter + +import ( + "bufio" + "fmt" + "io" + "strconv" + "strings" +) + +func readGopackHeader(r *bufio.Reader) (name string, size int64, err error) { + // See $GOROOT/include/ar.h. + hdr := make([]byte, 16+12+6+6+8+10+2) + _, err = io.ReadFull(r, hdr) + if err != nil { + return + } + // leave for debugging + if false { + fmt.Printf("header: %s", hdr) + } + s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10])) + length, err := strconv.Atoi(s) + size = int64(length) + if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' { + err = fmt.Errorf("invalid archive header") + return + } + name = strings.TrimSpace(string(hdr[:16])) + return +} + +// FindExportData positions the reader r at the beginning of the +// export data section of an underlying GC-created object/archive +// file by reading from it. The reader must be positioned at the +// start of the file before calling this function. The hdr result +// is the string before the export data, either "$$" or "$$B". +// The size result is the length of the export data in bytes, or -1 if not known. +func FindExportData(r *bufio.Reader) (hdr string, size int64, err error) { + // Read first line to make sure this is an object file. + line, err := r.ReadSlice('\n') + if err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + + if string(line) == "!<arch>\n" { + // Archive file. Scan to __.PKGDEF. + var name string + if name, size, err = readGopackHeader(r); err != nil { + return + } + + // First entry should be __.PKGDEF. + if name != "__.PKGDEF" { + err = fmt.Errorf("go archive is missing __.PKGDEF") + return + } + + // Read first line of __.PKGDEF data, so that line + // is once again the first line of the input. + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + size -= int64(len(line)) + } + + // Now at __.PKGDEF in archive or still at beginning of file. + // Either way, line should begin with "go object ". + if !strings.HasPrefix(string(line), "go object ") { + err = fmt.Errorf("not a Go object file") + return + } + + // Skip over object header to export data. + // Begins after first line starting with $$. + for line[0] != '$' { + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + size -= int64(len(line)) + } + hdr = string(line) + if size < 0 { + size = -1 + } + + return +} diff --git a/contribs/gnopls/internal/gcimporter/gcimporter.go b/contribs/gnopls/internal/gcimporter/gcimporter.go new file mode 100644 index 00000000000..a1c4d2bf24f --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/gcimporter.go @@ -0,0 +1,266 @@ +// Copyright 2011 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 is a reduced copy of $GOROOT/src/go/internal/gcimporter/gcimporter.go. + +// Package gcimporter provides various functions for reading +// gc-generated object files that can be used to implement the +// Importer interface defined by the Go 1.5 standard library package. +// +// The encoding is deterministic: if the encoder is applied twice to +// the same types.Package data structure, both encodings are equal. +// This property may be important to avoid spurious changes in +// applications such as build systems. +// +// However, the encoder is not necessarily idempotent. Importing an +// exported package may yield a types.Package that, while it +// represents the same set of Go types as the original, may differ in +// the details of its internal representation. Because of these +// differences, re-encoding the imported package may yield a +// different, but equally valid, encoding of the package. +package gcimporter // import "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" + +import ( + "bufio" + "bytes" + "fmt" + "go/build" + "go/token" + "go/types" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" +) + +const ( + // Enable debug during development: it adds some additional checks, and + // prevents errors from being recovered. + debug = false + + // If trace is set, debugging output is printed to std out. + trace = false +) + +var exportMap sync.Map // package dir → func() (string, bool) + +// lookupGorootExport returns the location of the export data +// (normally found in the build cache, but located in GOROOT/pkg +// in prior Go releases) for the package located in pkgDir. +// +// (We use the package's directory instead of its import path +// mainly to simplify handling of the packages in src/vendor +// and cmd/vendor.) +func lookupGorootExport(pkgDir string) (string, bool) { + f, ok := exportMap.Load(pkgDir) + if !ok { + var ( + listOnce sync.Once + exportPath string + ) + f, _ = exportMap.LoadOrStore(pkgDir, func() (string, bool) { + listOnce.Do(func() { + cmd := exec.Command("go", "list", "-export", "-f", "{{.Export}}", pkgDir) + cmd.Dir = build.Default.GOROOT + var output []byte + output, err := cmd.Output() + if err != nil { + return + } + + exports := strings.Split(string(bytes.TrimSpace(output)), "\n") + if len(exports) != 1 { + return + } + + exportPath = exports[0] + }) + + return exportPath, exportPath != "" + }) + } + + return f.(func() (string, bool))() +} + +var pkgExts = [...]string{".a", ".o"} + +// FindPkg returns the filename and unique package id for an import +// path based on package information provided by build.Import (using +// the build.Default build.Context). A relative srcDir is interpreted +// relative to the current working directory. +// If no file was found, an empty filename is returned. +func FindPkg(path, srcDir string) (filename, id string) { + if path == "" { + return + } + + var noext string + switch { + default: + // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" + // Don't require the source files to be present. + if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 + srcDir = abs + } + bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + if bp.PkgObj == "" { + var ok bool + if bp.Goroot && bp.Dir != "" { + filename, ok = lookupGorootExport(bp.Dir) + } + if !ok { + id = path // make sure we have an id to print in error message + return + } + } else { + noext = strings.TrimSuffix(bp.PkgObj, ".a") + id = bp.ImportPath + } + + case build.IsLocalImport(path): + // "./x" -> "/this/directory/x.ext", "/this/directory/x" + noext = filepath.Join(srcDir, path) + id = noext + + case filepath.IsAbs(path): + // for completeness only - go/build.Import + // does not support absolute imports + // "/x" -> "/x.ext", "/x" + noext = path + id = path + } + + if false { // for debugging + if path != id { + fmt.Printf("%s -> %s\n", path, id) + } + } + + if filename != "" { + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + // try extensions + for _, ext := range pkgExts { + filename = noext + ext + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + filename = "" // not found + return +} + +// Import imports a gc-generated package given its import path and srcDir, adds +// the corresponding package object to the packages map, and returns the object. +// The packages map must contain all packages already imported. +func Import(packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { + var rc io.ReadCloser + var filename, id string + if lookup != nil { + // With custom lookup specified, assume that caller has + // converted path to a canonical import path for use in the map. + if path == "unsafe" { + return types.Unsafe, nil + } + id = path + + // No need to re-import if the package was imported completely before. + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + f, err := lookup(path) + if err != nil { + return nil, err + } + rc = f + } else { + filename, id = FindPkg(path, srcDir) + if filename == "" { + if path == "unsafe" { + return types.Unsafe, nil + } + return nil, fmt.Errorf("can't find import: %q", id) + } + + // no need to re-import if the package was imported completely before + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + + // open file + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + // add file name to error + err = fmt.Errorf("%s: %v", filename, err) + } + }() + rc = f + } + defer rc.Close() + + var hdr string + var size int64 + buf := bufio.NewReader(rc) + if hdr, size, err = FindExportData(buf); err != nil { + return + } + + switch hdr { + case "$$B\n": + var data []byte + data, err = io.ReadAll(buf) + if err != nil { + break + } + + // TODO(gri): allow clients of go/importer to provide a FileSet. + // Or, define a new standard go/types/gcexportdata package. + fset := token.NewFileSet() + + // Select appropriate importer. + if len(data) > 0 { + switch data[0] { + case 'v', 'c', 'd': // binary, till go1.10 + return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0]) + + case 'i': // indexed, till go1.19 + _, pkg, err := IImportData(fset, packages, data[1:], id) + return pkg, err + + case 'u': // unified, from go1.20 + _, pkg, err := UImportData(fset, packages, data[1:size], id) + return pkg, err + + default: + l := len(data) + if l > 10 { + l = 10 + } + return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), id) + } + } + + default: + err = fmt.Errorf("unknown export data header: %q", hdr) + } + + return +} + +type byPath []*types.Package + +func (a byPath) Len() int { return len(a) } +func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } diff --git a/contribs/gnopls/internal/gcimporter/gcimporter_test.go b/contribs/gnopls/internal/gcimporter/gcimporter_test.go new file mode 100644 index 00000000000..053aa0b9e1b --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/gcimporter_test.go @@ -0,0 +1,1050 @@ +// Copyright 2011 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 is a copy of $GOROOT/src/go/internal/gcimporter/gcimporter_test.go, +// adjusted to make it build with code from (std lib) internal/testenv copied. + +package gcimporter + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + goimporter "go/importer" + goparser "go/parser" + "go/token" + "go/types" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "sort" + "strings" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/goroot" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +func TestMain(m *testing.M) { + testenv.ExitIfSmallMachine() + os.Exit(m.Run()) +} + +// ---------------------------------------------------------------------------- + +func needsCompiler(t *testing.T, compiler string) { + if runtime.Compiler == compiler { + return + } + switch compiler { + case "gc": + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } +} + +// compile runs the compiler on filename, with dirname as the working directory, +// and writes the output file to outdirname. +// compile gives the resulting package a packagepath of p. +func compile(t *testing.T, dirname, filename, outdirname string, packagefiles map[string]string) string { + return compilePkg(t, dirname, filename, outdirname, packagefiles, "p") +} + +func compilePkg(t *testing.T, dirname, filename, outdirname string, packagefiles map[string]string, pkg string) string { + testenv.NeedsGoBuild(t) + + // filename must end with ".go" + basename := strings.TrimSuffix(filepath.Base(filename), ".go") + ok := filename != basename + if !ok { + t.Fatalf("filename doesn't end in .go: %s", filename) + } + objname := basename + ".o" + outname := filepath.Join(outdirname, objname) + + importcfgfile := os.DevNull + if len(packagefiles) > 0 { + importcfgfile = filepath.Join(outdirname, basename) + ".importcfg" + importcfg := new(bytes.Buffer) + fmt.Fprintf(importcfg, "# import config") + for k, v := range packagefiles { + fmt.Fprintf(importcfg, "\npackagefile %s=%s\n", k, v) + } + if err := os.WriteFile(importcfgfile, importcfg.Bytes(), 0655); err != nil { + t.Fatal(err) + } + } + + importreldir := strings.ReplaceAll(outdirname, string(os.PathSeparator), "/") + cmd := exec.Command("go", "tool", "compile", "-p", pkg, "-D", importreldir, "-importcfg", importcfgfile, "-o", outname, filename) + cmd.Dir = dirname + if out, err := cmd.CombinedOutput(); err != nil { + t.Logf("%s", out) + t.Fatalf("go tool compile %s failed: %s", filename, err) + } + return outname +} + +func testPath(t *testing.T, path, srcDir string) *types.Package { + t0 := time.Now() + pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil) + if err != nil { + t.Errorf("testPath(%s): %s", path, err) + return nil + } + t.Logf("testPath(%s): %v", path, time.Since(t0)) + return pkg +} + +func mktmpdir(t *testing.T) string { + tmpdir, err := os.MkdirTemp("", "gcimporter_test") + if err != nil { + t.Fatal("mktmpdir:", err) + } + if err := os.Mkdir(filepath.Join(tmpdir, "testdata"), 0700); err != nil { + os.RemoveAll(tmpdir) + t.Fatal("mktmpdir:", err) + } + return tmpdir +} + +const testfile = "exports.go" + +func TestImportTestdata(t *testing.T) { + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + packageFiles := map[string]string{} + for _, pkg := range []string{"go/ast", "go/token"} { + export, _ := FindPkg(pkg, "testdata") + if export == "" { + t.Fatalf("no export data found for %s", pkg) + } + packageFiles[pkg] = export + } + + compile(t, "testdata", testfile, filepath.Join(tmpdir, "testdata"), packageFiles) + + // filename should end with ".go" + filename := testfile[:len(testfile)-3] + if pkg := testPath(t, "./testdata/"+filename, tmpdir); pkg != nil { + // The package's Imports list must include all packages + // explicitly imported by testfile, plus all packages + // referenced indirectly via exported objects in testfile. + // With the textual export format (when run against Go1.6), + // the list may also include additional packages that are + // not strictly required for import processing alone (they + // are exported to err "on the safe side"). + // For now, we just test the presence of a few packages + // that we know are there for sure. + got := fmt.Sprint(pkg.Imports()) + wants := []string{"go/ast", "go/token"} + if unifiedIR { + wants = []string{"go/ast"} + } + for _, want := range wants { + if !strings.Contains(got, want) { + t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want) + } + } + } +} + +func TestImportTypeparamTests(t *testing.T) { + if testing.Short() { + t.Skipf("in short mode, skipping test that requires export data for all of std") + } + + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + // Check go files in test/typeparam, except those that fail for a known + // reason. + rootDir := filepath.Join(runtime.GOROOT(), "test", "typeparam") + list, err := os.ReadDir(rootDir) + if err != nil { + t.Fatal(err) + } + + var skip map[string]string + if !unifiedIR { + // The Go 1.18 frontend still fails several cases. + skip = map[string]string{ + "equal.go": "inconsistent embedded sorting", // TODO(rfindley): investigate this. + "nested.go": "fails to compile", // TODO(rfindley): investigate this. + "issue47631.go": "can not handle local type declarations", + "issue55101.go": "fails to compile", + } + } + + for _, entry := range list { + if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { + // For now, only consider standalone go files. + continue + } + + t.Run(entry.Name(), func(t *testing.T) { + if reason, ok := skip[entry.Name()]; ok { + t.Skip(reason) + } + + filename := filepath.Join(rootDir, entry.Name()) + src, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) { + // We're bypassing the logic of run.go here, so be conservative about + // the files we consider in an attempt to make this test more robust to + // changes in test/typeparams. + t.Skipf("not detected as a run test") + } + + // Compile and import, and compare the resulting package with the package + // that was type-checked directly. + pkgFiles, err := goroot.PkgfileMap() + if err != nil { + t.Fatal(err) + } + compile(t, rootDir, entry.Name(), filepath.Join(tmpdir, "testdata"), pkgFiles) + pkgName := strings.TrimSuffix(entry.Name(), ".go") + imported := importPkg(t, "./testdata/"+pkgName, tmpdir) + checked := checkFile(t, filename, src) + + seen := make(map[string]bool) + for _, name := range imported.Scope().Names() { + if !token.IsExported(name) { + continue // ignore synthetic names like .inittask and .dict.* + } + seen[name] = true + + importedObj := imported.Scope().Lookup(name) + got := types.ObjectString(importedObj, types.RelativeTo(imported)) + + checkedObj := checked.Scope().Lookup(name) + if checkedObj == nil { + t.Fatalf("imported object %q was not type-checked", name) + } + want := types.ObjectString(checkedObj, types.RelativeTo(checked)) + + if got != want { + t.Errorf("imported %q as %q, want %q", name, got, want) + } + } + + for _, name := range checked.Scope().Names() { + if !token.IsExported(name) || seen[name] { + continue + } + t.Errorf("did not import object %q", name) + } + }) + } +} + +func checkFile(t *testing.T, filename string, src []byte) *types.Package { + fset := token.NewFileSet() + f, err := goparser.ParseFile(fset, filename, src, 0) + if err != nil { + t.Fatal(err) + } + config := types.Config{ + Importer: goimporter.Default(), + } + pkg, err := config.Check("", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + return pkg +} + +func TestVersionHandling(t *testing.T) { + if debug { + t.Skip("TestVersionHandling panics in debug mode") + } + + // This package only handles gc export data. + needsCompiler(t, "gc") + + const dir = "./testdata/versions" + list, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + corruptdir := filepath.Join(tmpdir, "testdata", "versions") + if err := os.Mkdir(corruptdir, 0700); err != nil { + t.Fatal(err) + } + + for _, f := range list { + name := f.Name() + if !strings.HasSuffix(name, ".a") { + continue // not a package file + } + if strings.Contains(name, "corrupted") { + continue // don't process a leftover corrupted file + } + pkgpath := "./" + name[:len(name)-2] + + if testing.Verbose() { + t.Logf("importing %s", name) + } + + // test that export data can be imported + _, err := Import(make(map[string]*types.Package), pkgpath, dir, nil) + if err != nil { + t.Errorf("import %q failed: %v", pkgpath, err) + continue + } + + // create file with corrupted export data + // 1) read file + data, err := os.ReadFile(filepath.Join(dir, name)) + if err != nil { + t.Fatal(err) + } + // 2) find export data + i := bytes.Index(data, []byte("\n$$B\n")) + 5 + j := bytes.Index(data[i:], []byte("\n$$\n")) + i + if i < 0 || j < 0 || i > j { + t.Fatalf("export data section not found (i = %d, j = %d)", i, j) + } + // 3) corrupt the data (increment every 7th byte) + for k := j - 13; k >= i; k -= 7 { + data[k]++ + } + // 4) write the file + pkgpath += "_corrupted" + filename := filepath.Join(corruptdir, pkgpath) + ".a" + os.WriteFile(filename, data, 0666) + + // test that importing the corrupted file results in an error + _, err = Import(make(map[string]*types.Package), pkgpath, corruptdir, nil) + if err == nil { + t.Errorf("import corrupted %q succeeded", pkgpath) + } else if msg := err.Error(); !strings.Contains(msg, "internal error") { + t.Errorf("import %q error incorrect (%s)", pkgpath, msg) + } + } +} + +func TestImportStdLib(t *testing.T) { + if testing.Short() { + t.Skip("the imports can be expensive, and this test is especially slow when the build cache is empty") + } + // This package only handles gc export data. + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + // Get list of packages in stdlib. Filter out test-only packages with {{if .GoFiles}} check. + var stderr bytes.Buffer + cmd := exec.Command("go", "list", "-f", "{{if .GoFiles}}{{.ImportPath}}{{end}}", "std") + cmd.Stderr = &stderr + out, err := cmd.Output() + if err != nil { + t.Fatalf("failed to run go list to determine stdlib packages: %v\nstderr:\n%v", err, stderr.String()) + } + pkgs := strings.Fields(string(out)) + + var nimports int + for _, pkg := range pkgs { + t.Run(pkg, func(t *testing.T) { + if testPath(t, pkg, filepath.Join(testenv.GOROOT(t), "src", path.Dir(pkg))) != nil { + nimports++ + } + }) + } + const minPkgs = 225 // 'GOOS=plan9 go1.18 list std | wc -l' reports 228; most other platforms have more. + if len(pkgs) < minPkgs { + t.Fatalf("too few packages (%d) were imported", nimports) + } + + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + want string +}{ + // non-interfaces + {"crypto.Hash", "type Hash uint"}, + {"go/ast.ObjKind", "type ObjKind int"}, + {"go/types.Qualifier", "type Qualifier func(*Package) string"}, + {"go/types.Comparable", "func Comparable(T Type) bool"}, + {"math.Pi", "const Pi untyped float"}, + {"math.Sin", "func Sin(x float64) float64"}, + {"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"}, + + // interfaces + {"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key any) any}"}, + {"crypto.Decrypter", "type Decrypter interface{Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error); Public() PublicKey}"}, + {"encoding.BinaryMarshaler", "type BinaryMarshaler interface{MarshalBinary() (data []byte, err error)}"}, + {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, + {"go/ast.Node", "type Node interface{End() go/token.Pos; Pos() go/token.Pos}"}, + {"go/types.Type", "type Type interface{String() string; Underlying() Type}"}, +} + +func TestImportedTypes(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + for _, test := range importedObjectTests { + obj := importObject(t, test.name) + if obj == nil { + continue // error reported elsewhere + } + got := types.ObjectString(obj, types.RelativeTo(obj.Pkg())) + + // TODO(rsc): Delete this block once go.dev/cl/368254 lands. + if got != test.want && test.want == strings.ReplaceAll(got, "interface{}", "any") { + got = test.want + } + + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if named, _ := aliases.Unalias(obj.Type()).(*types.Named); named != nil { + verifyInterfaceMethodRecvs(t, named, 0) + } + } +} + +func TestImportedConsts(t *testing.T) { + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + tests := []struct { + name string + want constant.Kind + }{ + {"math.Pi", constant.Float}, + {"math.MaxFloat64", constant.Float}, + {"math.MaxInt64", constant.Int}, + } + + for _, test := range tests { + obj := importObject(t, test.name) + if got := obj.(*types.Const).Val().Kind(); got != test.want { + t.Errorf("%s: imported as constant.Kind(%v), want constant.Kind(%v)", test.name, got, test.want) + } + } +} + +// importObject imports the object specified by a name of the form +// <import path>.<object name>, e.g. go/types.Type. +// +// If any errors occur they are reported via t and the resulting object will +// be nil. +func importObject(t *testing.T, name string) types.Object { + s := strings.Split(name, ".") + if len(s) != 2 { + t.Fatal("inconsistent test data") + } + importPath := s[0] + objName := s[1] + + pkg, err := Import(make(map[string]*types.Package), importPath, ".", nil) + if err != nil { + t.Error(err) + return nil + } + + obj := pkg.Scope().Lookup(objName) + if obj == nil { + t.Errorf("%s: object not found", name) + return nil + } + return obj +} + +// verifyInterfaceMethodRecvs verifies that method receiver types +// are named if the methods belong to a named interface type. +func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) { + // avoid endless recursion in case of an embedding bug that lead to a cycle + if level > 10 { + t.Errorf("%s: embeds itself", named) + return + } + + iface, _ := named.Underlying().(*types.Interface) + if iface == nil { + return // not an interface + } + + // check explicitly declared methods + for i := 0; i < iface.NumExplicitMethods(); i++ { + m := iface.ExplicitMethod(i) + recv := m.Type().(*types.Signature).Recv() + if recv == nil { + t.Errorf("%s: missing receiver type", m) + continue + } + if recv.Type() != named { + t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named) + } + } + + // check embedded interfaces (if they are named, too) + for i := 0; i < iface.NumEmbeddeds(); i++ { + // embedding of interfaces cannot have cycles; recursion will terminate + if etype, _ := aliases.Unalias(iface.EmbeddedType(i)).(*types.Named); etype != nil { + verifyInterfaceMethodRecvs(t, etype, level+1) + } + } +} + +func TestIssue5815(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + pkg := importPkg(t, "strings", ".") + + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if obj.Pkg() == nil { + t.Errorf("no pkg for %s", obj) + } + if tname, _ := obj.(*types.TypeName); tname != nil { + named := aliases.Unalias(tname.Type()).(*types.Named) + for i := 0; i < named.NumMethods(); i++ { + m := named.Method(i) + if m.Pkg() == nil { + t.Errorf("no pkg for %s", m) + } + } + } + } +} + +// Smoke test to ensure that imported methods get the correct package. +func TestCorrectMethodPackage(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + imports := make(map[string]*types.Package) + _, err := Import(imports, "net/http", ".", nil) + if err != nil { + t.Fatal(err) + } + + mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() + mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex + sel := mset.Lookup(nil, "Lock") + lock := sel.Obj().(*types.Func) + if got, want := lock.Pkg().Path(), "sync"; got != want { + t.Errorf("got package path %q; want %q", got, want) + } +} + +func TestIssue13566(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + testoutdir := filepath.Join(tmpdir, "testdata") + + // b.go needs to be compiled from the output directory so that the compiler can + // find the compiled package a. We pass the full path to compile() so that we + // don't have to copy the file to that directory. + bpath, err := filepath.Abs(filepath.Join("testdata", "b.go")) + if err != nil { + t.Fatal(err) + } + + jsonExport, _ := FindPkg("encoding/json", "testdata") + if jsonExport == "" { + t.Fatalf("no export data found for encoding/json") + } + + compilePkg(t, "testdata", "a.go", testoutdir, map[string]string{"encoding/json": jsonExport}, apkg(testoutdir)) + compile(t, testoutdir, bpath, testoutdir, map[string]string{apkg(testoutdir): filepath.Join(testoutdir, "a.o")}) + + // import must succeed (test for issue at hand) + pkg := importPkg(t, "./testdata/b", tmpdir) + + // make sure all indirectly imported packages have names + for _, imp := range pkg.Imports() { + if imp.Name() == "" { + t.Errorf("no name for %s package", imp.Path()) + } + } +} + +func TestIssue13898(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + // import go/internal/gcimporter which imports go/types partially + imports := make(map[string]*types.Package) + _, err := Import(imports, "go/internal/gcimporter", ".", nil) + if err != nil { + t.Fatal(err) + } + + // look for go/types package + var goTypesPkg *types.Package + for path, pkg := range imports { + if path == "go/types" { + goTypesPkg = pkg + break + } + } + if goTypesPkg == nil { + t.Fatal("go/types not found") + } + + // look for go/types.Object type + obj := lookupObj(t, goTypesPkg.Scope(), "Object") + typ, ok := aliases.Unalias(obj.Type()).(*types.Named) + if !ok { + t.Fatalf("go/types.Object type is %v; wanted named type", typ) + } + + // lookup go/types.Object.Pkg method + m, index, indirect := types.LookupFieldOrMethod(typ, false, nil, "Pkg") + if m == nil { + t.Fatalf("go/types.Object.Pkg not found (index = %v, indirect = %v)", index, indirect) + } + + // the method must belong to go/types + if m.Pkg().Path() != "go/types" { + t.Fatalf("found %v; want go/types", m.Pkg()) + } +} + +func TestIssue15517(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + compile(t, "testdata", "p.go", filepath.Join(tmpdir, "testdata"), nil) + + // Multiple imports of p must succeed without redeclaration errors. + // We use an import path that's not cleaned up so that the eventual + // file path for the package is different from the package path; this + // will expose the error if it is present. + // + // (Issue: Both the textual and the binary importer used the file path + // of the package to be imported as key into the shared packages map. + // However, the binary importer then used the package path to identify + // the imported package to mark it as complete; effectively marking the + // wrong package as complete. By using an "unclean" package path, the + // file and package path are different, exposing the problem if present. + // The same issue occurs with vendoring.) + imports := make(map[string]*types.Package) + for i := 0; i < 3; i++ { + if _, err := Import(imports, "./././testdata/p", tmpdir, nil); err != nil { + t.Fatal(err) + } + } +} + +func TestIssue15920(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue15920") +} + +func TestIssue20046(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + // "./issue20046".V.M must exist + pkg := compileAndImportPkg(t, "issue20046") + obj := lookupObj(t, pkg.Scope(), "V") + if m, index, indirect := types.LookupFieldOrMethod(obj.Type(), false, nil, "M"); m == nil { + t.Fatalf("V.M not found (index = %v, indirect = %v)", index, indirect) + } +} + +func TestIssue25301(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue25301") +} + +func TestIssue51836(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + testoutdir := filepath.Join(tmpdir, "testdata") + + dir := filepath.Join("testdata", "issue51836") + // Following the pattern of TestIssue13898, aa.go needs to be compiled from + // the output directory. We pass the full path to compile() so that we don't + // have to copy the file to that directory. + bpath, err := filepath.Abs(filepath.Join(dir, "aa.go")) + if err != nil { + t.Fatal(err) + } + compilePkg(t, dir, "a.go", testoutdir, nil, apkg(testoutdir)) + compile(t, testoutdir, bpath, testoutdir, map[string]string{apkg(testoutdir): filepath.Join(testoutdir, "a.o")}) + + // import must succeed (test for issue at hand) + _ = importPkg(t, "./testdata/aa", tmpdir) +} + +func TestIssue61561(t *testing.T) { + const src = `package p + +type I[P any] interface { + m(P) + n() P +} + +type J = I[int] + +type StillBad[P any] *interface{b(P)} + +type K = StillBad[string] +` + fset := token.NewFileSet() + f, err := goparser.ParseFile(fset, "p.go", src, 0) + if f == nil { + // Some test cases may have parse errors, but we must always have a + // file. + t.Fatalf("ParseFile returned nil file. Err: %v", err) + } + + config := &types.Config{} + pkg1, err := config.Check("p", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + // Export it. (Shallowness isn't important here.) + data, err := IExportShallow(fset, pkg1, nil) + if err != nil { + t.Fatalf("export: %v", err) // any failure to export is a bug + } + + // Re-import it. + imports := make(map[string]*types.Package) + pkg2, err := IImportShallow(fset, GetPackagesFromMap(imports), data, "p", nil) + if err != nil { + t.Fatalf("import: %v", err) // any failure of IExport+IImport is a bug. + } + + insts := []types.Type{ + pkg2.Scope().Lookup("J").Type(), + // This test is still racy, because the incomplete interface is contained + // within a nested type expression. + // + // Uncomment this once golang/go#61561 is fixed. + // pkg2.Scope().Lookup("K").Type().Underlying().(*types.Pointer).Elem(), + } + + // Use the interface instances concurrently. + for _, inst := range insts { + var wg sync.WaitGroup + for i := 0; i < 2; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _ = types.NewMethodSet(inst) + }() + } + wg.Wait() + } +} + +func TestIssue57015(t *testing.T) { + // This package only handles gc export data. + needsCompiler(t, "gc") + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue57015") +} + +// This is a regression test for a failure to export a package +// containing type errors. +// +// Though the issues and tests are specific, they may be representatives of a +// class of exporter bugs on ill-typed code that we have yet to flush out. +// +// TODO(adonovan): systematize our search for similar problems using +// fuzz testing. +func TestExportInvalid(t *testing.T) { + + tests := []struct { + name string + src string + objName string + }{ + // The lack of a receiver causes Recv.Type=Invalid. + // (The type checker then treats Foo as a package-level + // function, inserting it into the package scope.) + // The exporter needs to apply the same treatment. + {"issue 57729", `package p; func () Foo() {}`, "Foo"}, + + // It must be possible to export a constant with unknown kind, even if its + // type is known. + {"issue 60605", `package p; const EPSILON float64 = 1e-`, "EPSILON"}, + + // We must not crash when exporting a struct with unknown package. + {"issue 60891", `package p; type I[P any] int; const C I[struct{}] = 42`, "C"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Parse the ill-typed input. + fset := token.NewFileSet() + + f, err := goparser.ParseFile(fset, "p.go", test.src, 0) + if f == nil { + // Some test cases may have parse errors, but we must always have a + // file. + t.Fatalf("ParseFile returned nil file. Err: %v", err) + } + + // Type check it, expecting errors. + config := &types.Config{ + Error: func(err error) { t.Log(err) }, // don't abort at first error + } + pkg1, _ := config.Check("p", fset, []*ast.File{f}, nil) + + // Export it. + // (Shallowness isn't important here.) + data, err := IExportShallow(fset, pkg1, nil) + if err != nil { + t.Fatalf("export: %v", err) // any failure to export is a bug + } + + // Re-import it. + imports := make(map[string]*types.Package) + pkg2, err := IImportShallow(fset, GetPackagesFromMap(imports), data, "p", nil) + if err != nil { + t.Fatalf("import: %v", err) // any failure of IExport+IImport is a bug. + } + + // Check that the expected object is present in both packages. + // We can't assert the type hasn't changed: it may have, in some cases. + hasObj1 := pkg1.Scope().Lookup(test.objName) != nil + hasObj2 := pkg2.Scope().Lookup(test.objName) != nil + if hasObj1 != hasObj2 { + t.Errorf("export+import changed Lookup(%q)!=nil: was %t, became %t", test.objName, hasObj1, hasObj2) + } + }) + } +} + +func TestIssue58296(t *testing.T) { + // Compiles packages c, b, and a where c imports b and b imports a, + // then imports c with stub *types.Packages for b and a, and checks that + // both a and b are in the Imports() of c. + // + // This is how go/packages can read the exportdata when NeedDeps is off. + + // This package only handles gc export data. + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + testoutdir := filepath.Join(tmpdir, "testdata") + + apkg := filepath.Join(testoutdir, "a") + bpkg := filepath.Join(testoutdir, "b") + cpkg := filepath.Join(testoutdir, "c") + + srcdir := filepath.Join("testdata", "issue58296") + compilePkg(t, filepath.Join(srcdir, "a"), "a.go", testoutdir, nil, apkg) + compilePkg(t, filepath.Join(srcdir, "b"), "b.go", testoutdir, map[string]string{apkg: filepath.Join(testoutdir, "a.o")}, bpkg) + compilePkg(t, filepath.Join(srcdir, "c"), "c.go", testoutdir, map[string]string{bpkg: filepath.Join(testoutdir, "b.o")}, cpkg) + + // The export data reader for c cannot rely on Package.Imports + // being populated for a or b. (For the imports {a,b} it is unset.) + imports := map[string]*types.Package{ + apkg: types.NewPackage(apkg, "a"), + bpkg: types.NewPackage(bpkg, "b"), + } + + // make sure a and b are both imported by c. + pkg, err := Import(imports, "./c", testoutdir, nil) + if err != nil { + t.Fatal(err) + } + + var names []string + for _, imp := range pkg.Imports() { + names = append(names, imp.Name()) + } + sort.Strings(names) + + if got, want := strings.Join(names, ","), "a,b"; got != want { + t.Errorf("got imports %v for package c. wanted %v", names, want) + } +} + +func TestIssueAliases(t *testing.T) { + // This package only handles gc export data. + testenv.NeedsGo1Point(t, 24) + needsCompiler(t, "gc") + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + testenv.NeedsGoExperiment(t, "aliastypeparams") + + t.Setenv("GODEBUG", fmt.Sprintf("gotypesalias=%d", 1)) + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + testoutdir := filepath.Join(tmpdir, "testdata") + + apkg := filepath.Join(testoutdir, "a") + bpkg := filepath.Join(testoutdir, "b") + cpkg := filepath.Join(testoutdir, "c") + + // compile a, b and c into gc export data. + srcdir := filepath.Join("testdata", "aliases") + compilePkg(t, filepath.Join(srcdir, "a"), "a.go", testoutdir, nil, apkg) + compilePkg(t, filepath.Join(srcdir, "b"), "b.go", testoutdir, map[string]string{apkg: filepath.Join(testoutdir, "a.o")}, bpkg) + compilePkg(t, filepath.Join(srcdir, "c"), "c.go", testoutdir, + map[string]string{apkg: filepath.Join(testoutdir, "a.o"), bpkg: filepath.Join(testoutdir, "b.o")}, + cpkg, + ) + + // import c from gc export data using a and b. + pkg, err := Import(map[string]*types.Package{ + apkg: types.NewPackage(apkg, "a"), + bpkg: types.NewPackage(bpkg, "b"), + }, "./c", testoutdir, nil) + if err != nil { + t.Fatal(err) + } + + // Check c's objects and types. + var objs []string + for _, imp := range pkg.Scope().Names() { + obj := pkg.Scope().Lookup(imp) + s := fmt.Sprintf("%s : %s", obj.Name(), obj.Type()) + s = strings.ReplaceAll(s, testoutdir, "testdata") + objs = append(objs, s) + } + sort.Strings(objs) + + want := strings.Join([]string{ + "S : struct{F int}", + "T : struct{F int}", + "U : testdata/a.A[string]", + "V : testdata/a.A[int]", + "W : testdata/b.B[string]", + "X : testdata/b.B[int]", + "Y : testdata/c.c[string]", + "Z : testdata/c.c[int]", + "c : testdata/c.c", + }, ",") + if got := strings.Join(objs, ","); got != want { + t.Errorf("got imports %v for package c. wanted %v", objs, want) + } +} + +// apkg returns the package "a" prefixed by (as a package) testoutdir +func apkg(testoutdir string) string { + apkg := testoutdir + "/a" + if os.PathSeparator != '/' { + apkg = strings.ReplaceAll(apkg, string(os.PathSeparator), "/") + } + return apkg +} + +func importPkg(t *testing.T, path, srcDir string) *types.Package { + pkg, err := Import(make(map[string]*types.Package), path, srcDir, nil) + if err != nil { + t.Fatal(err) + } + return pkg +} + +func compileAndImportPkg(t *testing.T, name string) *types.Package { + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + compile(t, "testdata", name+".go", filepath.Join(tmpdir, "testdata"), nil) + return importPkg(t, "./testdata/"+name, tmpdir) +} + +func lookupObj(t *testing.T, scope *types.Scope, name string) types.Object { + if obj := scope.Lookup(name); obj != nil { + return obj + } + t.Fatalf("%s not found", name) + return nil +} diff --git a/contribs/gnopls/internal/gcimporter/iexport.go b/contribs/gnopls/internal/gcimporter/iexport.go new file mode 100644 index 00000000000..db7946b52d3 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/iexport.go @@ -0,0 +1,1569 @@ +// 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. + +// Indexed package export. +// +// The indexed export data format is an evolution of the previous +// binary export data format. Its chief contribution is introducing an +// index table, which allows efficient random access of individual +// declarations and inline function bodies. In turn, this allows +// avoiding unnecessary work for compilation units that import large +// packages. +// +// +// The top-level data format is structured as: +// +// Header struct { +// Tag byte // 'i' +// Version uvarint +// StringSize uvarint +// DataSize uvarint +// } +// +// Strings [StringSize]byte +// Data [DataSize]byte +// +// MainIndex []struct{ +// PkgPath stringOff +// PkgName stringOff +// PkgHeight uvarint +// +// Decls []struct{ +// Name stringOff +// Offset declOff +// } +// } +// +// Fingerprint [8]byte +// +// uvarint means a uint64 written out using uvarint encoding. +// +// []T means a uvarint followed by that many T objects. In other +// words: +// +// Len uvarint +// Elems [Len]T +// +// stringOff means a uvarint that indicates an offset within the +// Strings section. At that offset is another uvarint, followed by +// that many bytes, which form the string value. +// +// declOff means a uvarint that indicates an offset within the Data +// section where the associated declaration can be found. +// +// +// There are five kinds of declarations, distinguished by their first +// byte: +// +// type Var struct { +// Tag byte // 'V' +// Pos Pos +// Type typeOff +// } +// +// type Func struct { +// Tag byte // 'F' or 'G' +// Pos Pos +// TypeParams []typeOff // only present if Tag == 'G' +// Signature Signature +// } +// +// type Const struct { +// Tag byte // 'C' +// Pos Pos +// Value Value +// } +// +// type Type struct { +// Tag byte // 'T' or 'U' +// Pos Pos +// TypeParams []typeOff // only present if Tag == 'U' +// Underlying typeOff +// +// Methods []struct{ // omitted if Underlying is an interface type +// Pos Pos +// Name stringOff +// Recv Param +// Signature Signature +// } +// } +// +// type Alias struct { +// Tag byte // 'A' or 'B' +// Pos Pos +// TypeParams []typeOff // only present if Tag == 'B' +// Type typeOff +// } +// +// // "Automatic" declaration of each typeparam +// type TypeParam struct { +// Tag byte // 'P' +// Pos Pos +// Implicit bool +// Constraint typeOff +// } +// +// typeOff means a uvarint that either indicates a predeclared type, +// or an offset into the Data section. If the uvarint is less than +// predeclReserved, then it indicates the index into the predeclared +// types list (see predeclared in bexport.go for order). Otherwise, +// subtracting predeclReserved yields the offset of a type descriptor. +// +// Value means a type, kind, and type-specific value. See +// (*exportWriter).value for details. +// +// +// There are twelve kinds of type descriptors, distinguished by an itag: +// +// type DefinedType struct { +// Tag itag // definedType +// Name stringOff +// PkgPath stringOff +// } +// +// type PointerType struct { +// Tag itag // pointerType +// Elem typeOff +// } +// +// type SliceType struct { +// Tag itag // sliceType +// Elem typeOff +// } +// +// type ArrayType struct { +// Tag itag // arrayType +// Len uint64 +// Elem typeOff +// } +// +// type ChanType struct { +// Tag itag // chanType +// Dir uint64 // 1 RecvOnly; 2 SendOnly; 3 SendRecv +// Elem typeOff +// } +// +// type MapType struct { +// Tag itag // mapType +// Key typeOff +// Elem typeOff +// } +// +// type FuncType struct { +// Tag itag // signatureType +// PkgPath stringOff +// Signature Signature +// } +// +// type StructType struct { +// Tag itag // structType +// PkgPath stringOff +// Fields []struct { +// Pos Pos +// Name stringOff +// Type typeOff +// Embedded bool +// Note stringOff +// } +// } +// +// type InterfaceType struct { +// Tag itag // interfaceType +// PkgPath stringOff +// Embeddeds []struct { +// Pos Pos +// Type typeOff +// } +// Methods []struct { +// Pos Pos +// Name stringOff +// Signature Signature +// } +// } +// +// // Reference to a type param declaration +// type TypeParamType struct { +// Tag itag // typeParamType +// Name stringOff +// PkgPath stringOff +// } +// +// // Instantiation of a generic type (like List[T2] or List[int]) +// type InstanceType struct { +// Tag itag // instanceType +// Pos pos +// TypeArgs []typeOff +// BaseType typeOff +// } +// +// type UnionType struct { +// Tag itag // interfaceType +// Terms []struct { +// tilde bool +// Type typeOff +// } +// } +// +// +// +// type Signature struct { +// Params []Param +// Results []Param +// Variadic bool // omitted if Results is empty +// } +// +// type Param struct { +// Pos Pos +// Name stringOff +// Type typOff +// } +// +// +// Pos encodes a file:line:column triple, incorporating a simple delta +// encoding scheme within a data object. See exportWriter.pos for +// details. + +package gcimporter + +import ( + "bytes" + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "math/big" + "reflect" + "sort" + "strconv" + "strings" + + "golang.org/x/tools/go/types/objectpath" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" +) + +// IExportShallow encodes "shallow" export data for the specified package. +// +// No promises are made about the encoding other than that it can be decoded by +// the same version of IIExportShallow. If you plan to save export data in the +// file system, be sure to include a cryptographic digest of the executable in +// the key to avoid version skew. +// +// If the provided reportf func is non-nil, it will be used for reporting bugs +// encountered during export. +// TODO(rfindley): remove reportf when we are confident enough in the new +// objectpath encoding. +func IExportShallow(fset *token.FileSet, pkg *types.Package, reportf ReportFunc) ([]byte, error) { + // In principle this operation can only fail if out.Write fails, + // but that's impossible for bytes.Buffer---and as a matter of + // fact iexportCommon doesn't even check for I/O errors. + // TODO(adonovan): handle I/O errors properly. + // TODO(adonovan): use byte slices throughout, avoiding copying. + const bundle, shallow = false, true + var out bytes.Buffer + err := iexportCommon(&out, fset, bundle, shallow, iexportVersion, []*types.Package{pkg}) + return out.Bytes(), err +} + +// IImportShallow decodes "shallow" types.Package data encoded by +// IExportShallow in the same executable. This function cannot import data from +// cmd/compile or gcexportdata.Write. +// +// The importer calls getPackages to obtain package symbols for all +// packages mentioned in the export data, including the one being +// decoded. +// +// If the provided reportf func is non-nil, it will be used for reporting bugs +// encountered during import. +// TODO(rfindley): remove reportf when we are confident enough in the new +// objectpath encoding. +func IImportShallow(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, path string, reportf ReportFunc) (*types.Package, error) { + const bundle = false + const shallow = true + pkgs, err := iimportCommon(fset, getPackages, data, bundle, path, shallow, reportf) + if err != nil { + return nil, err + } + return pkgs[0], nil +} + +// ReportFunc is the type of a function used to report formatted bugs. +type ReportFunc = func(string, ...interface{}) + +// Current bundled export format version. Increase with each format change. +// 0: initial implementation +const bundleVersion = 0 + +// IExportData writes indexed export data for pkg to out. +// +// If no file set is provided, position info will be missing. +// The package path of the top-level package will not be recorded, +// so that calls to IImportData can override with a provided package path. +func IExportData(out io.Writer, fset *token.FileSet, pkg *types.Package) error { + const bundle, shallow = false, false + return iexportCommon(out, fset, bundle, shallow, iexportVersion, []*types.Package{pkg}) +} + +// IExportBundle writes an indexed export bundle for pkgs to out. +func IExportBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error { + const bundle, shallow = true, false + return iexportCommon(out, fset, bundle, shallow, iexportVersion, pkgs) +} + +func iexportCommon(out io.Writer, fset *token.FileSet, bundle, shallow bool, version int, pkgs []*types.Package) (err error) { + if !debug { + defer func() { + if e := recover(); e != nil { + if ierr, ok := e.(internalError); ok { + err = ierr + return + } + // Not an internal error; panic again. + panic(e) + } + }() + } + + p := iexporter{ + fset: fset, + version: version, + shallow: shallow, + allPkgs: map[*types.Package]bool{}, + stringIndex: map[string]uint64{}, + declIndex: map[types.Object]uint64{}, + tparamNames: map[types.Object]string{}, + typIndex: map[types.Type]uint64{}, + } + if !bundle { + p.localpkg = pkgs[0] + } + + for i, pt := range predeclared() { + p.typIndex[pt] = uint64(i) + } + if len(p.typIndex) > predeclReserved { + panic(internalErrorf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved)) + } + + // Initialize work queue with exported declarations. + for _, pkg := range pkgs { + scope := pkg.Scope() + for _, name := range scope.Names() { + if token.IsExported(name) { + p.pushDecl(scope.Lookup(name)) + } + } + + if bundle { + // Ensure pkg and its imports are included in the index. + p.allPkgs[pkg] = true + for _, imp := range pkg.Imports() { + p.allPkgs[imp] = true + } + } + } + + // Loop until no more work. + for !p.declTodo.empty() { + p.doDecl(p.declTodo.popHead()) + } + + // Produce index of offset of each file record in files. + var files intWriter + var fileOffset []uint64 // fileOffset[i] is offset in files of file encoded as i + if p.shallow { + fileOffset = make([]uint64, len(p.fileInfos)) + for i, info := range p.fileInfos { + fileOffset[i] = uint64(files.Len()) + p.encodeFile(&files, info.file, info.needed) + } + } + + // Append indices to data0 section. + dataLen := uint64(p.data0.Len()) + w := p.newWriter() + w.writeIndex(p.declIndex) + + if bundle { + w.uint64(uint64(len(pkgs))) + for _, pkg := range pkgs { + w.pkg(pkg) + imps := pkg.Imports() + w.uint64(uint64(len(imps))) + for _, imp := range imps { + w.pkg(imp) + } + } + } + w.flush() + + // Assemble header. + var hdr intWriter + if bundle { + hdr.uint64(bundleVersion) + } + hdr.uint64(uint64(p.version)) + hdr.uint64(uint64(p.strings.Len())) + if p.shallow { + hdr.uint64(uint64(files.Len())) + hdr.uint64(uint64(len(fileOffset))) + for _, offset := range fileOffset { + hdr.uint64(offset) + } + } + hdr.uint64(dataLen) + + // Flush output. + io.Copy(out, &hdr) + io.Copy(out, &p.strings) + if p.shallow { + io.Copy(out, &files) + } + io.Copy(out, &p.data0) + + return nil +} + +// encodeFile writes to w a representation of the file sufficient to +// faithfully restore position information about all needed offsets. +// Mutates the needed array. +func (p *iexporter) encodeFile(w *intWriter, file *token.File, needed []uint64) { + _ = needed[0] // precondition: needed is non-empty + + w.uint64(p.stringOff(file.Name())) + + size := uint64(file.Size()) + w.uint64(size) + + // Sort the set of needed offsets. Duplicates are harmless. + sort.Slice(needed, func(i, j int) bool { return needed[i] < needed[j] }) + + lines := tokeninternal.GetLines(file) // byte offset of each line start + w.uint64(uint64(len(lines))) + + // Rather than record the entire array of line start offsets, + // we save only a sparse list of (index, offset) pairs for + // the start of each line that contains a needed position. + var sparse [][2]int // (index, offset) pairs +outer: + for i, lineStart := range lines { + lineEnd := size + if i < len(lines)-1 { + lineEnd = uint64(lines[i+1]) + } + // Does this line contains a needed offset? + if needed[0] < lineEnd { + sparse = append(sparse, [2]int{i, lineStart}) + for needed[0] < lineEnd { + needed = needed[1:] + if len(needed) == 0 { + break outer + } + } + } + } + + // Delta-encode the columns. + w.uint64(uint64(len(sparse))) + var prev [2]int + for _, pair := range sparse { + w.uint64(uint64(pair[0] - prev[0])) + w.uint64(uint64(pair[1] - prev[1])) + prev = pair + } +} + +// writeIndex writes out an object index. mainIndex indicates whether +// we're writing out the main index, which is also read by +// non-compiler tools and includes a complete package description +// (i.e., name and height). +func (w *exportWriter) writeIndex(index map[types.Object]uint64) { + type pkgObj struct { + obj types.Object + name string // qualified name; differs from obj.Name for type params + } + // Build a map from packages to objects from that package. + pkgObjs := map[*types.Package][]pkgObj{} + + // For the main index, make sure to include every package that + // we reference, even if we're not exporting (or reexporting) + // any symbols from it. + if w.p.localpkg != nil { + pkgObjs[w.p.localpkg] = nil + } + for pkg := range w.p.allPkgs { + pkgObjs[pkg] = nil + } + + for obj := range index { + name := w.p.exportName(obj) + pkgObjs[obj.Pkg()] = append(pkgObjs[obj.Pkg()], pkgObj{obj, name}) + } + + var pkgs []*types.Package + for pkg, objs := range pkgObjs { + pkgs = append(pkgs, pkg) + + sort.Slice(objs, func(i, j int) bool { + return objs[i].name < objs[j].name + }) + } + + sort.Slice(pkgs, func(i, j int) bool { + return w.exportPath(pkgs[i]) < w.exportPath(pkgs[j]) + }) + + w.uint64(uint64(len(pkgs))) + for _, pkg := range pkgs { + w.string(w.exportPath(pkg)) + w.string(pkg.Name()) + w.uint64(uint64(0)) // package height is not needed for go/types + + objs := pkgObjs[pkg] + w.uint64(uint64(len(objs))) + for _, obj := range objs { + w.string(obj.name) + w.uint64(index[obj.obj]) + } + } +} + +// exportName returns the 'exported' name of an object. It differs from +// obj.Name() only for type parameters (see tparamExportName for details). +func (p *iexporter) exportName(obj types.Object) (res string) { + if name := p.tparamNames[obj]; name != "" { + return name + } + return obj.Name() +} + +type iexporter struct { + fset *token.FileSet + out *bytes.Buffer + version int + + shallow bool // don't put types from other packages in the index + objEncoder *objectpath.Encoder // encodes objects from other packages in shallow mode; lazily allocated + localpkg *types.Package // (nil in bundle mode) + + // allPkgs tracks all packages that have been referenced by + // the export data, so we can ensure to include them in the + // main index. + allPkgs map[*types.Package]bool + + declTodo objQueue + + strings intWriter + stringIndex map[string]uint64 + + // In shallow mode, object positions are encoded as (file, offset). + // Each file is recorded as a line-number table. + // Only the lines of needed positions are saved faithfully. + fileInfo map[*token.File]uint64 // value is index in fileInfos + fileInfos []*filePositions + + data0 intWriter + declIndex map[types.Object]uint64 + tparamNames map[types.Object]string // typeparam->exported name + typIndex map[types.Type]uint64 + + indent int // for tracing support +} + +type filePositions struct { + file *token.File + needed []uint64 // unordered list of needed file offsets +} + +func (p *iexporter) trace(format string, args ...interface{}) { + if !trace { + // Call sites should also be guarded, but having this check here allows + // easily enabling/disabling debug trace statements. + return + } + fmt.Printf(strings.Repeat("..", p.indent)+format+"\n", args...) +} + +// objectpathEncoder returns the lazily allocated objectpath.Encoder to use +// when encoding objects in other packages during shallow export. +// +// Using a shared Encoder amortizes some of cost of objectpath search. +func (p *iexporter) objectpathEncoder() *objectpath.Encoder { + if p.objEncoder == nil { + p.objEncoder = new(objectpath.Encoder) + } + return p.objEncoder +} + +// stringOff returns the offset of s within the string section. +// If not already present, it's added to the end. +func (p *iexporter) stringOff(s string) uint64 { + off, ok := p.stringIndex[s] + if !ok { + off = uint64(p.strings.Len()) + p.stringIndex[s] = off + + p.strings.uint64(uint64(len(s))) + p.strings.WriteString(s) + } + return off +} + +// fileIndexAndOffset returns the index of the token.File and the byte offset of pos within it. +func (p *iexporter) fileIndexAndOffset(file *token.File, pos token.Pos) (uint64, uint64) { + index, ok := p.fileInfo[file] + if !ok { + index = uint64(len(p.fileInfo)) + p.fileInfos = append(p.fileInfos, &filePositions{file: file}) + if p.fileInfo == nil { + p.fileInfo = make(map[*token.File]uint64) + } + p.fileInfo[file] = index + } + // Record each needed offset. + info := p.fileInfos[index] + offset := uint64(file.Offset(pos)) + info.needed = append(info.needed, offset) + + return index, offset +} + +// pushDecl adds n to the declaration work queue, if not already present. +func (p *iexporter) pushDecl(obj types.Object) { + // Package unsafe is known to the compiler and predeclared. + // Caller should not ask us to do export it. + if obj.Pkg() == types.Unsafe { + panic("cannot export package unsafe") + } + + // Shallow export data: don't index decls from other packages. + if p.shallow && obj.Pkg() != p.localpkg { + return + } + + if _, ok := p.declIndex[obj]; ok { + return + } + + p.declIndex[obj] = ^uint64(0) // mark obj present in work queue + p.declTodo.pushTail(obj) +} + +// exportWriter handles writing out individual data section chunks. +type exportWriter struct { + p *iexporter + + data intWriter + prevFile string + prevLine int64 + prevColumn int64 +} + +func (w *exportWriter) exportPath(pkg *types.Package) string { + if pkg == w.p.localpkg { + return "" + } + return pkg.Path() +} + +func (p *iexporter) doDecl(obj types.Object) { + if trace { + p.trace("exporting decl %v (%T)", obj, obj) + p.indent++ + defer func() { + p.indent-- + p.trace("=> %s", obj) + }() + } + w := p.newWriter() + + switch obj := obj.(type) { + case *types.Var: + w.tag(varTag) + w.pos(obj.Pos()) + w.typ(obj.Type(), obj.Pkg()) + + case *types.Func: + sig, _ := obj.Type().(*types.Signature) + if sig.Recv() != nil { + // We shouldn't see methods in the package scope, + // but the type checker may repair "func () F() {}" + // to "func (Invalid) F()" and then treat it like "func F()", + // so allow that. See golang/go#57729. + if sig.Recv().Type() != types.Typ[types.Invalid] { + panic(internalErrorf("unexpected method: %v", sig)) + } + } + + // Function. + if sig.TypeParams().Len() == 0 { + w.tag(funcTag) + } else { + w.tag(genericFuncTag) + } + w.pos(obj.Pos()) + // The tparam list of the function type is the declaration of the type + // params. So, write out the type params right now. Then those type params + // will be referenced via their type offset (via typOff) in all other + // places in the signature and function where they are used. + // + // While importing the type parameters, tparamList computes and records + // their export name, so that it can be later used when writing the index. + if tparams := sig.TypeParams(); tparams.Len() > 0 { + w.tparamList(obj.Name(), tparams, obj.Pkg()) + } + w.signature(sig) + + case *types.Const: + w.tag(constTag) + w.pos(obj.Pos()) + w.value(obj.Type(), obj.Val()) + + case *types.TypeName: + t := obj.Type() + + if tparam, ok := aliases.Unalias(t).(*types.TypeParam); ok { + w.tag(typeParamTag) + w.pos(obj.Pos()) + constraint := tparam.Constraint() + if p.version >= iexportVersionGo1_18 { + implicit := false + if iface, _ := aliases.Unalias(constraint).(*types.Interface); iface != nil { + implicit = iface.IsImplicit() + } + w.bool(implicit) + } + w.typ(constraint, obj.Pkg()) + break + } + + if obj.IsAlias() { + alias, materialized := t.(*aliases.Alias) // may fail when aliases are not enabled + + var tparams *types.TypeParamList + if materialized { + tparams = aliases.TypeParams(alias) + } + if tparams.Len() == 0 { + w.tag(aliasTag) + } else { + w.tag(genericAliasTag) + } + w.pos(obj.Pos()) + if tparams.Len() > 0 { + w.tparamList(obj.Name(), tparams, obj.Pkg()) + } + if materialized { + // Preserve materialized aliases, + // even of non-exported types. + t = aliases.Rhs(alias) + } + w.typ(t, obj.Pkg()) + break + } + + // Defined type. + named, ok := t.(*types.Named) + if !ok { + panic(internalErrorf("%s is not a defined type", t)) + } + + if named.TypeParams().Len() == 0 { + w.tag(typeTag) + } else { + w.tag(genericTypeTag) + } + w.pos(obj.Pos()) + + if named.TypeParams().Len() > 0 { + // While importing the type parameters, tparamList computes and records + // their export name, so that it can be later used when writing the index. + w.tparamList(obj.Name(), named.TypeParams(), obj.Pkg()) + } + + underlying := named.Underlying() + w.typ(underlying, obj.Pkg()) + + if types.IsInterface(t) { + break + } + + n := named.NumMethods() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + m := named.Method(i) + w.pos(m.Pos()) + w.string(m.Name()) + sig, _ := m.Type().(*types.Signature) + + // Receiver type parameters are type arguments of the receiver type, so + // their name must be qualified before exporting recv. + if rparams := sig.RecvTypeParams(); rparams.Len() > 0 { + prefix := obj.Name() + "." + m.Name() + for i := 0; i < rparams.Len(); i++ { + rparam := rparams.At(i) + name := tparamExportName(prefix, rparam) + w.p.tparamNames[rparam.Obj()] = name + } + } + w.param(sig.Recv()) + w.signature(sig) + } + + default: + panic(internalErrorf("unexpected object: %v", obj)) + } + + p.declIndex[obj] = w.flush() +} + +func (w *exportWriter) tag(tag byte) { + w.data.WriteByte(tag) +} + +func (w *exportWriter) pos(pos token.Pos) { + if w.p.shallow { + w.posV2(pos) + } else if w.p.version >= iexportVersionPosCol { + w.posV1(pos) + } else { + w.posV0(pos) + } +} + +// posV2 encoding (used only in shallow mode) records positions as +// (file, offset), where file is the index in the token.File table +// (which records the file name and newline offsets) and offset is a +// byte offset. It effectively ignores //line directives. +func (w *exportWriter) posV2(pos token.Pos) { + if pos == token.NoPos { + w.uint64(0) + return + } + file := w.p.fset.File(pos) // fset must be non-nil + index, offset := w.p.fileIndexAndOffset(file, pos) + w.uint64(1 + index) + w.uint64(offset) +} + +func (w *exportWriter) posV1(pos token.Pos) { + if w.p.fset == nil { + w.int64(0) + return + } + + p := w.p.fset.Position(pos) + file := p.Filename + line := int64(p.Line) + column := int64(p.Column) + + deltaColumn := (column - w.prevColumn) << 1 + deltaLine := (line - w.prevLine) << 1 + + if file != w.prevFile { + deltaLine |= 1 + } + if deltaLine != 0 { + deltaColumn |= 1 + } + + w.int64(deltaColumn) + if deltaColumn&1 != 0 { + w.int64(deltaLine) + if deltaLine&1 != 0 { + w.string(file) + } + } + + w.prevFile = file + w.prevLine = line + w.prevColumn = column +} + +func (w *exportWriter) posV0(pos token.Pos) { + if w.p.fset == nil { + w.int64(0) + return + } + + p := w.p.fset.Position(pos) + file := p.Filename + line := int64(p.Line) + + // When file is the same as the last position (common case), + // we can save a few bytes by delta encoding just the line + // number. + // + // Note: Because data objects may be read out of order (or not + // at all), we can only apply delta encoding within a single + // object. This is handled implicitly by tracking prevFile and + // prevLine as fields of exportWriter. + + if file == w.prevFile { + delta := line - w.prevLine + w.int64(delta) + if delta == deltaNewFile { + w.int64(-1) + } + } else { + w.int64(deltaNewFile) + w.int64(line) // line >= 0 + w.string(file) + w.prevFile = file + } + w.prevLine = line +} + +func (w *exportWriter) pkg(pkg *types.Package) { + // Ensure any referenced packages are declared in the main index. + w.p.allPkgs[pkg] = true + + w.string(w.exportPath(pkg)) +} + +func (w *exportWriter) qualifiedType(obj *types.TypeName) { + name := w.p.exportName(obj) + + // Ensure any referenced declarations are written out too. + w.p.pushDecl(obj) + w.string(name) + w.pkg(obj.Pkg()) +} + +// TODO(rfindley): what does 'pkg' even mean here? It would be better to pass +// it in explicitly into signatures and structs that may use it for +// constructing fields. +func (w *exportWriter) typ(t types.Type, pkg *types.Package) { + w.data.uint64(w.p.typOff(t, pkg)) +} + +func (p *iexporter) newWriter() *exportWriter { + return &exportWriter{p: p} +} + +func (w *exportWriter) flush() uint64 { + off := uint64(w.p.data0.Len()) + io.Copy(&w.p.data0, &w.data) + return off +} + +func (p *iexporter) typOff(t types.Type, pkg *types.Package) uint64 { + off, ok := p.typIndex[t] + if !ok { + w := p.newWriter() + w.doTyp(t, pkg) + off = predeclReserved + w.flush() + p.typIndex[t] = off + } + return off +} + +func (w *exportWriter) startType(k itag) { + w.data.uint64(uint64(k)) +} + +func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { + if trace { + w.p.trace("exporting type %s (%T)", t, t) + w.p.indent++ + defer func() { + w.p.indent-- + w.p.trace("=> %s", t) + }() + } + switch t := t.(type) { + case *aliases.Alias: + if targs := aliases.TypeArgs(t); targs.Len() > 0 { + w.startType(instanceType) + w.pos(t.Obj().Pos()) + w.typeList(targs, pkg) + w.typ(aliases.Origin(t), pkg) + return + } + w.startType(aliasType) + w.qualifiedType(t.Obj()) + + case *types.Named: + if targs := t.TypeArgs(); targs.Len() > 0 { + w.startType(instanceType) + // TODO(rfindley): investigate if this position is correct, and if it + // matters. + w.pos(t.Obj().Pos()) + w.typeList(targs, pkg) + w.typ(t.Origin(), pkg) + return + } + w.startType(definedType) + w.qualifiedType(t.Obj()) + + case *types.TypeParam: + w.startType(typeParamType) + w.qualifiedType(t.Obj()) + + case *types.Pointer: + w.startType(pointerType) + w.typ(t.Elem(), pkg) + + case *types.Slice: + w.startType(sliceType) + w.typ(t.Elem(), pkg) + + case *types.Array: + w.startType(arrayType) + w.uint64(uint64(t.Len())) + w.typ(t.Elem(), pkg) + + case *types.Chan: + w.startType(chanType) + // 1 RecvOnly; 2 SendOnly; 3 SendRecv + var dir uint64 + switch t.Dir() { + case types.RecvOnly: + dir = 1 + case types.SendOnly: + dir = 2 + case types.SendRecv: + dir = 3 + } + w.uint64(dir) + w.typ(t.Elem(), pkg) + + case *types.Map: + w.startType(mapType) + w.typ(t.Key(), pkg) + w.typ(t.Elem(), pkg) + + case *types.Signature: + w.startType(signatureType) + w.pkg(pkg) + w.signature(t) + + case *types.Struct: + w.startType(structType) + n := t.NumFields() + // Even for struct{} we must emit some qualifying package, because that's + // what the compiler does, and thus that's what the importer expects. + fieldPkg := pkg + if n > 0 { + fieldPkg = t.Field(0).Pkg() + } + if fieldPkg == nil { + // TODO(rfindley): improve this very hacky logic. + // + // The importer expects a package to be set for all struct types, even + // those with no fields. A better encoding might be to set NumFields + // before pkg. setPkg panics with a nil package, which may be possible + // to reach with invalid packages (and perhaps valid packages, too?), so + // (arbitrarily) set the localpkg if available. + // + // Alternatively, we may be able to simply guarantee that pkg != nil, by + // reconsidering the encoding of constant values. + if w.p.shallow { + fieldPkg = w.p.localpkg + } else { + panic(internalErrorf("no package to set for empty struct")) + } + } + w.pkg(fieldPkg) + w.uint64(uint64(n)) + + for i := 0; i < n; i++ { + f := t.Field(i) + if w.p.shallow { + w.objectPath(f) + } + w.pos(f.Pos()) + w.string(f.Name()) // unexported fields implicitly qualified by prior setPkg + w.typ(f.Type(), fieldPkg) + w.bool(f.Anonymous()) + w.string(t.Tag(i)) // note (or tag) + } + + case *types.Interface: + w.startType(interfaceType) + w.pkg(pkg) + + n := t.NumEmbeddeds() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + ft := t.EmbeddedType(i) + tPkg := pkg + if named, _ := aliases.Unalias(ft).(*types.Named); named != nil { + w.pos(named.Obj().Pos()) + } else { + w.pos(token.NoPos) + } + w.typ(ft, tPkg) + } + + // See comment for struct fields. In shallow mode we change the encoding + // for interface methods that are promoted from other packages. + + n = t.NumExplicitMethods() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + m := t.ExplicitMethod(i) + if w.p.shallow { + w.objectPath(m) + } + w.pos(m.Pos()) + w.string(m.Name()) + sig, _ := m.Type().(*types.Signature) + w.signature(sig) + } + + case *types.Union: + w.startType(unionType) + nt := t.Len() + w.uint64(uint64(nt)) + for i := 0; i < nt; i++ { + term := t.Term(i) + w.bool(term.Tilde()) + w.typ(term.Type(), pkg) + } + + default: + panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t))) + } +} + +// objectPath writes the package and objectPath to use to look up obj in a +// different package, when encoding in "shallow" mode. +// +// When doing a shallow import, the importer creates only the local package, +// and requests package symbols for dependencies from the client. +// However, certain types defined in the local package may hold objects defined +// (perhaps deeply) within another package. +// +// For example, consider the following: +// +// package a +// func F() chan * map[string] struct { X int } +// +// package b +// import "a" +// var B = a.F() +// +// In this example, the type of b.B holds fields defined in package a. +// In order to have the correct canonical objects for the field defined in the +// type of B, they are encoded as objectPaths and later looked up in the +// importer. The same problem applies to interface methods. +func (w *exportWriter) objectPath(obj types.Object) { + if obj.Pkg() == nil || obj.Pkg() == w.p.localpkg { + // obj.Pkg() may be nil for the builtin error.Error. + // In this case, or if obj is declared in the local package, no need to + // encode. + w.string("") + return + } + objectPath, err := w.p.objectpathEncoder().For(obj) + if err != nil { + // Fall back to the empty string, which will cause the importer to create a + // new object, which matches earlier behavior. Creating a new object is + // sufficient for many purposes (such as type checking), but causes certain + // references algorithms to fail (golang/go#60819). However, we didn't + // notice this problem during months of gopls@v0.12.0 testing. + // + // TODO(golang/go#61674): this workaround is insufficient, as in the case + // where the field forwarded from an instantiated type that may not appear + // in the export data of the original package: + // + // // package a + // type A[P any] struct{ F P } + // + // // package b + // type B a.A[int] + // + // We need to update references algorithms not to depend on this + // de-duplication, at which point we may want to simply remove the + // workaround here. + w.string("") + return + } + w.string(string(objectPath)) + w.pkg(obj.Pkg()) +} + +func (w *exportWriter) signature(sig *types.Signature) { + w.paramList(sig.Params()) + w.paramList(sig.Results()) + if sig.Params().Len() > 0 { + w.bool(sig.Variadic()) + } +} + +func (w *exportWriter) typeList(ts *types.TypeList, pkg *types.Package) { + w.uint64(uint64(ts.Len())) + for i := 0; i < ts.Len(); i++ { + w.typ(ts.At(i), pkg) + } +} + +func (w *exportWriter) tparamList(prefix string, list *types.TypeParamList, pkg *types.Package) { + ll := uint64(list.Len()) + w.uint64(ll) + for i := 0; i < list.Len(); i++ { + tparam := list.At(i) + // Set the type parameter exportName before exporting its type. + exportName := tparamExportName(prefix, tparam) + w.p.tparamNames[tparam.Obj()] = exportName + w.typ(list.At(i), pkg) + } +} + +const blankMarker = "$" + +// tparamExportName returns the 'exported' name of a type parameter, which +// differs from its actual object name: it is prefixed with a qualifier, and +// blank type parameter names are disambiguated by their index in the type +// parameter list. +func tparamExportName(prefix string, tparam *types.TypeParam) string { + assert(prefix != "") + name := tparam.Obj().Name() + if name == "_" { + name = blankMarker + strconv.Itoa(tparam.Index()) + } + return prefix + "." + name +} + +// tparamName returns the real name of a type parameter, after stripping its +// qualifying prefix and reverting blank-name encoding. See tparamExportName +// for details. +func tparamName(exportName string) string { + // Remove the "path" from the type param name that makes it unique. + ix := strings.LastIndex(exportName, ".") + if ix < 0 { + errorf("malformed type parameter export name %s: missing prefix", exportName) + } + name := exportName[ix+1:] + if strings.HasPrefix(name, blankMarker) { + return "_" + } + return name +} + +func (w *exportWriter) paramList(tup *types.Tuple) { + n := tup.Len() + w.uint64(uint64(n)) + for i := 0; i < n; i++ { + w.param(tup.At(i)) + } +} + +func (w *exportWriter) param(obj types.Object) { + w.pos(obj.Pos()) + w.localIdent(obj) + w.typ(obj.Type(), obj.Pkg()) +} + +func (w *exportWriter) value(typ types.Type, v constant.Value) { + w.typ(typ, nil) + if w.p.version >= iexportVersionGo1_18 { + w.int64(int64(v.Kind())) + } + + if v.Kind() == constant.Unknown { + // golang/go#60605: treat unknown constant values as if they have invalid type + // + // This loses some fidelity over the package type-checked from source, but that + // is acceptable. + // + // TODO(rfindley): we should switch on the recorded constant kind rather + // than the constant type + return + } + + switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType { + case types.IsBoolean: + w.bool(constant.BoolVal(v)) + case types.IsInteger: + var i big.Int + if i64, exact := constant.Int64Val(v); exact { + i.SetInt64(i64) + } else if ui64, exact := constant.Uint64Val(v); exact { + i.SetUint64(ui64) + } else { + i.SetString(v.ExactString(), 10) + } + w.mpint(&i, typ) + case types.IsFloat: + f := constantToFloat(v) + w.mpfloat(f, typ) + case types.IsComplex: + w.mpfloat(constantToFloat(constant.Real(v)), typ) + w.mpfloat(constantToFloat(constant.Imag(v)), typ) + case types.IsString: + w.string(constant.StringVal(v)) + default: + if b.Kind() == types.Invalid { + // package contains type errors + break + } + panic(internalErrorf("unexpected type %v (%v)", typ, typ.Underlying())) + } +} + +// constantToFloat converts a constant.Value with kind constant.Float to a +// big.Float. +func constantToFloat(x constant.Value) *big.Float { + x = constant.ToFloat(x) + // Use the same floating-point precision (512) as cmd/compile + // (see Mpprec in cmd/compile/internal/gc/mpfloat.go). + const mpprec = 512 + var f big.Float + f.SetPrec(mpprec) + if v, exact := constant.Float64Val(x); exact { + // float64 + f.SetFloat64(v) + } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { + // TODO(gri): add big.Rat accessor to constant.Value. + n := valueToRat(num) + d := valueToRat(denom) + f.SetRat(n.Quo(n, d)) + } else { + // Value too large to represent as a fraction => inaccessible. + // TODO(gri): add big.Float accessor to constant.Value. + _, ok := f.SetString(x.ExactString()) + assert(ok) + } + return &f +} + +func valueToRat(x constant.Value) *big.Rat { + // Convert little-endian to big-endian. + // I can't believe this is necessary. + bytes := constant.Bytes(x) + for i := 0; i < len(bytes)/2; i++ { + bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] + } + return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) +} + +// mpint exports a multi-precision integer. +// +// For unsigned types, small values are written out as a single +// byte. Larger values are written out as a length-prefixed big-endian +// byte string, where the length prefix is encoded as its complement. +// For example, bytes 0, 1, and 2 directly represent the integer +// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-, +// 2-, and 3-byte big-endian string follow. +// +// Encoding for signed types use the same general approach as for +// unsigned types, except small values use zig-zag encoding and the +// bottom bit of length prefix byte for large values is reserved as a +// sign bit. +// +// The exact boundary between small and large encodings varies +// according to the maximum number of bytes needed to encode a value +// of type typ. As a special case, 8-bit types are always encoded as a +// single byte. +// +// TODO(mdempsky): Is this level of complexity really worthwhile? +func (w *exportWriter) mpint(x *big.Int, typ types.Type) { + basic, ok := typ.Underlying().(*types.Basic) + if !ok { + panic(internalErrorf("unexpected type %v (%T)", typ.Underlying(), typ.Underlying())) + } + + signed, maxBytes := intSize(basic) + + negative := x.Sign() < 0 + if !signed && negative { + panic(internalErrorf("negative unsigned integer; type %v, value %v", typ, x)) + } + + b := x.Bytes() + if len(b) > 0 && b[0] == 0 { + panic(internalErrorf("leading zeros")) + } + if uint(len(b)) > maxBytes { + panic(internalErrorf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x)) + } + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + // Check if x can use small value encoding. + if len(b) <= 1 { + var ux uint + if len(b) == 1 { + ux = uint(b[0]) + } + if signed { + ux <<= 1 + if negative { + ux-- + } + } + if ux < maxSmall { + w.data.WriteByte(byte(ux)) + return + } + } + + n := 256 - uint(len(b)) + if signed { + n = 256 - 2*uint(len(b)) + if negative { + n |= 1 + } + } + if n < maxSmall || n >= 256 { + panic(internalErrorf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n)) + } + + w.data.WriteByte(byte(n)) + w.data.Write(b) +} + +// mpfloat exports a multi-precision floating point number. +// +// The number's value is decomposed into mantissa × 2**exponent, where +// mantissa is an integer. The value is written out as mantissa (as a +// multi-precision integer) and then the exponent, except exponent is +// omitted if mantissa is zero. +func (w *exportWriter) mpfloat(f *big.Float, typ types.Type) { + if f.IsInf() { + panic("infinite constant") + } + + // Break into f = mant × 2**exp, with 0.5 <= mant < 1. + var mant big.Float + exp := int64(f.MantExp(&mant)) + + // Scale so that mant is an integer. + prec := mant.MinPrec() + mant.SetMantExp(&mant, int(prec)) + exp -= int64(prec) + + manti, acc := mant.Int(nil) + if acc != big.Exact { + panic(internalErrorf("mantissa scaling failed for %f (%s)", f, acc)) + } + w.mpint(manti, typ) + if manti.Sign() != 0 { + w.int64(exp) + } +} + +func (w *exportWriter) bool(b bool) bool { + var x uint64 + if b { + x = 1 + } + w.uint64(x) + return b +} + +func (w *exportWriter) int64(x int64) { w.data.int64(x) } +func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) } +func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) } + +func (w *exportWriter) localIdent(obj types.Object) { + // Anonymous parameters. + if obj == nil { + w.string("") + return + } + + name := obj.Name() + if name == "_" { + w.string("_") + return + } + + w.string(name) +} + +type intWriter struct { + bytes.Buffer +} + +func (w *intWriter) int64(x int64) { + var buf [binary.MaxVarintLen64]byte + n := binary.PutVarint(buf[:], x) + w.Write(buf[:n]) +} + +func (w *intWriter) uint64(x uint64) { + var buf [binary.MaxVarintLen64]byte + n := binary.PutUvarint(buf[:], x) + w.Write(buf[:n]) +} + +func assert(cond bool) { + if !cond { + panic("internal error: assertion failed") + } +} + +// The below is copied from go/src/cmd/compile/internal/gc/syntax.go. + +// objQueue is a FIFO queue of types.Object. The zero value of objQueue is +// a ready-to-use empty queue. +type objQueue struct { + ring []types.Object + head, tail int +} + +// empty returns true if q contains no Nodes. +func (q *objQueue) empty() bool { + return q.head == q.tail +} + +// pushTail appends n to the tail of the queue. +func (q *objQueue) pushTail(obj types.Object) { + if len(q.ring) == 0 { + q.ring = make([]types.Object, 16) + } else if q.head+len(q.ring) == q.tail { + // Grow the ring. + nring := make([]types.Object, len(q.ring)*2) + // Copy the old elements. + part := q.ring[q.head%len(q.ring):] + if q.tail-q.head <= len(part) { + part = part[:q.tail-q.head] + copy(nring, part) + } else { + pos := copy(nring, part) + copy(nring[pos:], q.ring[:q.tail%len(q.ring)]) + } + q.ring, q.head, q.tail = nring, 0, q.tail-q.head + } + + q.ring[q.tail%len(q.ring)] = obj + q.tail++ +} + +// popHead pops a node from the head of the queue. It panics if q is empty. +func (q *objQueue) popHead() types.Object { + if q.empty() { + panic("dequeue empty") + } + obj := q.ring[q.head%len(q.ring)] + q.head++ + return obj +} + +// internalError represents an error generated inside this package. +type internalError string + +func (e internalError) Error() string { return "gcimporter: " + string(e) } + +// TODO(adonovan): make this call panic, so that it's symmetric with errorf. +// Otherwise it's easy to forget to do anything with the error. +// +// TODO(adonovan): also, consider switching the names "errorf" and +// "internalErrorf" as the former is used for bugs, whose cause is +// internal inconsistency, whereas the latter is used for ordinary +// situations like bad input, whose cause is external. +func internalErrorf(format string, args ...interface{}) error { + return internalError(fmt.Sprintf(format, args...)) +} diff --git a/contribs/gnopls/internal/gcimporter/iexport_common_test.go b/contribs/gnopls/internal/gcimporter/iexport_common_test.go new file mode 100644 index 00000000000..abc6aa64b92 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/iexport_common_test.go @@ -0,0 +1,16 @@ +// 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 gcimporter + +// Temporarily expose version-related functionality so that we can test at +// specific export data versions. + +var IExportCommon = iexportCommon + +const ( + IExportVersion = iexportVersion + IExportVersionGenerics = iexportVersionGenerics + IExportVersionGo1_18 = iexportVersionGo1_18 +) diff --git a/contribs/gnopls/internal/gcimporter/iexport_go118_test.go b/contribs/gnopls/internal/gcimporter/iexport_go118_test.go new file mode 100644 index 00000000000..b834ca813ac --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/iexport_go118_test.go @@ -0,0 +1,257 @@ +// 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 gcimporter_test + +// This file defines test of generics features introduce in go1.18. + +import ( + "bytes" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +// TODO(rfindley): migrate this to testdata, as has been done in the standard library. +func TestGenericExport(t *testing.T) { + const src = ` +package generic + +type Any any + +type T[A, B any] struct { Left A; Right B } + +func (T[P, Q]) m() {} + +var X T[int, string] = T[int, string]{1, "hi"} + +func ToInt[P interface{ ~int }](p P) int { return int(p) } + +var IntID = ToInt[int] + +type G[C comparable] int + +func ImplicitFunc[T ~int]() {} + +type ImplicitType[T ~int] int + +// Exercise constant import/export +const C1 = 42 +const C2 int = 42 +const C3 float64 = 42 + +type Constraint[T any] interface { + m(T) +} + +// TODO(rfindley): revert to multiple blanks once the restriction on multiple +// blanks is removed from the type checker. +// type Blanks[_ any, _ Constraint[int]] int +// func (Blanks[_, _]) m() {} +type Blanks[_ any] int +func (Blanks[_]) m() {} +` + testExportSrc(t, []byte(src)) +} + +func testExportSrc(t *testing.T, src []byte) { + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + testenv.NeedsGoBuild(t) + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "g.go", src, 0) + if err != nil { + t.Fatal(err) + } + conf := types.Config{ + Importer: importer.Default(), + } + pkg, err := conf.Check("", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + // export + version := gcimporter.IExportVersion + data, err := iexport(fset, version, pkg) + if err != nil { + t.Fatal(err) + } + + testPkgData(t, fset, version, pkg, data) +} + +func TestImportTypeparamTests(t *testing.T) { + testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache + + // Check go files in test/typeparam. + rootDir := filepath.Join(runtime.GOROOT(), "test", "typeparam") + list, err := os.ReadDir(rootDir) + if err != nil { + t.Fatal(err) + } + + if isUnifiedBuilder() { + t.Skip("unified export data format is currently unsupported") + } + + for _, entry := range list { + if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { + // For now, only consider standalone go files. + continue + } + + t.Run(entry.Name(), func(t *testing.T) { + filename := filepath.Join(rootDir, entry.Name()) + src, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + + if !bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// compile")) { + // We're bypassing the logic of run.go here, so be conservative about + // the files we consider in an attempt to make this test more robust to + // changes in test/typeparams. + t.Skipf("not detected as a run test") + } + + testExportSrc(t, src) + }) + } +} + +func TestRecursiveExport_Issue51219(t *testing.T) { + const srca = ` +package a + +type Interaction[DataT InteractionDataConstraint] struct { +} + +type InteractionDataConstraint interface { + []byte | + UserCommandInteractionData +} + +type UserCommandInteractionData struct { + resolvedInteractionWithOptions +} + +type resolvedInteractionWithOptions struct { + Resolved Resolved +} + +type Resolved struct { + Users ResolvedData[User] +} + +type ResolvedData[T ResolvedDataConstraint] map[uint64]T + +type ResolvedDataConstraint interface { + User | Message +} + +type User struct{} + +type Message struct { + Interaction *Interaction[[]byte] +} +` + + const srcb = ` +package b + +import ( + "a" +) + +// InteractionRequest is an incoming request Interaction +type InteractionRequest[T a.InteractionDataConstraint] struct { + a.Interaction[T] +} +` + + const srcp = ` +package p + +import ( + "b" +) + +// ResponseWriterMock mocks corde's ResponseWriter interface +type ResponseWriterMock struct { + x b.InteractionRequest[[]byte] +} +` + + importer := &testImporter{ + src: map[string][]byte{ + "a": []byte(srca), + "b": []byte(srcb), + "p": []byte(srcp), + }, + pkgs: make(map[string]*types.Package), + } + _, err := importer.Import("p") + if err != nil { + t.Fatal(err) + } +} + +// testImporter is a helper to test chains of imports using export data. +type testImporter struct { + src map[string][]byte // original source + pkgs map[string]*types.Package // memoized imported packages +} + +func (t *testImporter) Import(path string) (*types.Package, error) { + if pkg, ok := t.pkgs[path]; ok { + return pkg, nil + } + src, ok := t.src[path] + if !ok { + return nil, fmt.Errorf("unknown path %v", path) + } + + // Type-check, but don't return this package directly. + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path+".go", src, 0) + if err != nil { + return nil, err + } + conf := types.Config{ + Importer: t, + } + pkg, err := conf.Check(path, fset, []*ast.File{f}, nil) + if err != nil { + return nil, err + } + + // Export and import to get the package imported from export data. + exportdata, err := iexport(fset, gcimporter.IExportVersion, pkg) + if err != nil { + return nil, err + } + imports := make(map[string]*types.Package) + fset2 := token.NewFileSet() + _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) + if err != nil { + return nil, err + } + t.pkgs[path] = pkg2 + return pkg2, nil +} diff --git a/contribs/gnopls/internal/gcimporter/iexport_test.go b/contribs/gnopls/internal/gcimporter/iexport_test.go new file mode 100644 index 00000000000..f93e826914c --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/iexport_test.go @@ -0,0 +1,601 @@ +// 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. + +// This is a copy of bexport_test.go for iexport.go. + +//go:build go1.11 +// +build go1.11 + +package gcimporter_test + +import ( + "bufio" + "bytes" + "fmt" + "go/ast" + "go/build" + "go/constant" + "go/importer" + "go/parser" + "go/token" + "go/types" + "io" + "math/big" + "os" + "path/filepath" + "reflect" + "runtime" + "sort" + "strings" + "testing" + + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/buildutil" + "golang.org/x/tools/go/gcexportdata" + "golang.org/x/tools/go/loader" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams/genericfeatures" +) + +func readExportFile(filename string) ([]byte, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + + buf := bufio.NewReader(f) + if _, _, err := gcimporter.FindExportData(buf); err != nil { + return nil, err + } + + if ch, err := buf.ReadByte(); err != nil { + return nil, err + } else if ch != 'i' { + return nil, fmt.Errorf("unexpected byte: %v", ch) + } + + return io.ReadAll(buf) +} + +func iexport(fset *token.FileSet, version int, pkg *types.Package) ([]byte, error) { + var buf bytes.Buffer + const bundle, shallow = false, false + if err := gcimporter.IExportCommon(&buf, fset, bundle, shallow, version, []*types.Package{pkg}); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// isUnifiedBuilder reports whether we are executing on a go builder that uses +// unified export data. +func isUnifiedBuilder() bool { + return os.Getenv("GO_BUILDER_NAME") == "linux-amd64-unified" +} + +const minStdlibPackages = 248 + +func TestIExportData_stdlib(t *testing.T) { + if runtime.Compiler == "gccgo" { + t.Skip("gccgo standard library is inaccessible") + } + testenv.NeedsGoBuild(t) + if isRace { + t.Skipf("stdlib tests take too long in race mode and flake on builders") + } + if testing.Short() { + t.Skip("skipping RAM hungry test in -short mode") + } + + // Load, parse and type-check the program. + ctxt := build.Default // copy + ctxt.GOPATH = "" // disable GOPATH + conf := loader.Config{ + Build: &ctxt, + AllowErrors: true, + TypeChecker: types.Config{ + Sizes: types.SizesFor(ctxt.Compiler, ctxt.GOARCH), + Error: func(err error) { t.Log(err) }, + }, + } + for _, path := range buildutil.AllPackages(conf.Build) { + conf.Import(path) + } + + // Create a package containing type and value errors to ensure + // they are properly encoded/decoded. + f, err := conf.ParseFile("haserrors/haserrors.go", `package haserrors +const UnknownValue = "" + 0 +type UnknownType undefined +`) + if err != nil { + t.Fatal(err) + } + conf.CreateFromFiles("haserrors", f) + + prog, err := conf.Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + + var sorted []*types.Package + isUnified := isUnifiedBuilder() + for pkg, info := range prog.AllPackages { + // Temporarily skip packages that use generics on the unified builder, to + // fix TryBots. + // + // TODO(#48595): fix this test with GOEXPERIMENT=unified. + inspect := inspector.New(info.Files) + features := genericfeatures.ForPackage(inspect, &info.Info) + if isUnified && features != 0 { + t.Logf("skipping package %q which uses generics", pkg.Path()) + continue + } + if info.Files != nil { // non-empty directory + sorted = append(sorted, pkg) + } + } + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Path() < sorted[j].Path() + }) + + version := gcimporter.IExportVersion + numPkgs := len(sorted) + if want := minStdlibPackages; numPkgs < want { + t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) + } + + // TODO(adonovan): opt: parallelize this slow loop. + for _, pkg := range sorted { + if exportdata, err := iexport(conf.Fset, version, pkg); err != nil { + t.Error(err) + } else { + testPkgData(t, conf.Fset, version, pkg, exportdata) + } + + if pkg.Name() == "main" || pkg.Name() == "haserrors" { + // skip; no export data + } else if bp, err := ctxt.Import(pkg.Path(), "", build.FindOnly); err != nil { + t.Log("warning:", err) + } else if exportdata, err := readExportFile(bp.PkgObj); err != nil { + t.Log("warning:", err) + } else { + testPkgData(t, conf.Fset, version, pkg, exportdata) + } + } + + var bundle bytes.Buffer + if err := gcimporter.IExportBundle(&bundle, conf.Fset, sorted); err != nil { + t.Fatal(err) + } + fset2 := token.NewFileSet() + imports := make(map[string]*types.Package) + pkgs2, err := gcimporter.IImportBundle(fset2, imports, bundle.Bytes()) + if err != nil { + t.Fatal(err) + } + + for i, pkg := range sorted { + testPkg(t, conf.Fset, version, pkg, fset2, pkgs2[i]) + } +} + +func testPkgData(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, exportdata []byte) { + imports := make(map[string]*types.Package) + fset2 := token.NewFileSet() + _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) + if err != nil { + t.Errorf("IImportData(%s): %v", pkg.Path(), err) + } + + testPkg(t, fset, version, pkg, fset2, pkg2) +} + +func testPkg(t *testing.T, fset *token.FileSet, version int, pkg *types.Package, fset2 *token.FileSet, pkg2 *types.Package) { + if _, err := iexport(fset2, version, pkg2); err != nil { + t.Errorf("reexport %q: %v", pkg.Path(), err) + } + + // Compare the packages' corresponding members. + for _, name := range pkg.Scope().Names() { + if !token.IsExported(name) { + continue + } + obj1 := pkg.Scope().Lookup(name) + obj2 := pkg2.Scope().Lookup(name) + if obj2 == nil { + t.Errorf("%s.%s not found, want %s", pkg.Path(), name, obj1) + continue + } + + fl1 := fileLine(fset, obj1) + fl2 := fileLine(fset2, obj2) + if fl1 != fl2 { + t.Errorf("%s.%s: got posn %s, want %s", + pkg.Path(), name, fl2, fl1) + } + + if err := cmpObj(obj1, obj2); err != nil { + t.Errorf("%s.%s: %s\ngot: %s\nwant: %s", + pkg.Path(), name, err, obj2, obj1) + } + } +} + +// TestIExportData_long tests the position of an import object declared in +// a very long input file. Line numbers greater than maxlines are +// reported as line 1, not garbage or token.NoPos. +func TestIExportData_long(t *testing.T) { + // parse and typecheck + longFile := "package foo" + strings.Repeat("\n", 123456) + "var X int" + fset1 := token.NewFileSet() + f, err := parser.ParseFile(fset1, "foo.go", longFile, 0) + if err != nil { + t.Fatal(err) + } + var conf types.Config + pkg, err := conf.Check("foo", fset1, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + // export + exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg) + if err != nil { + t.Fatal(err) + } + + // import + imports := make(map[string]*types.Package) + fset2 := token.NewFileSet() + _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg.Path()) + if err != nil { + t.Fatalf("IImportData(%s): %v", pkg.Path(), err) + } + + // compare + posn1 := fset1.Position(pkg.Scope().Lookup("X").Pos()) + posn2 := fset2.Position(pkg2.Scope().Lookup("X").Pos()) + if want := "foo.go:1:1"; posn2.String() != want { + t.Errorf("X position = %s, want %s (orig was %s)", + posn2, want, posn1) + } +} + +func TestIExportData_typealiases(t *testing.T) { + // parse and typecheck + fset1 := token.NewFileSet() + f, err := parser.ParseFile(fset1, "p.go", src, 0) + if err != nil { + t.Fatal(err) + } + var conf types.Config + pkg1, err := conf.Check("p", fset1, []*ast.File{f}, nil) + if err == nil { + // foo in undeclared in src; we should see an error + t.Fatal("invalid source type-checked without error") + } + if pkg1 == nil { + // despite incorrect src we should see a (partially) type-checked package + t.Fatal("nil package returned") + } + checkPkg(t, pkg1, "export") + + // export + // use a nil fileset here to confirm that it doesn't panic + exportdata, err := iexport(nil, gcimporter.IExportVersion, pkg1) + if err != nil { + t.Fatal(err) + } + + // import + imports := make(map[string]*types.Package) + fset2 := token.NewFileSet() + _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg1.Path()) + if err != nil { + t.Fatalf("IImportData(%s): %v", pkg1.Path(), err) + } + checkPkg(t, pkg2, "import") +} + +// cmpObj reports how x and y differ. They are assumed to belong to different +// universes so cannot be compared directly. It is an adapted version of +// equalObj in bexport_test.go. +func cmpObj(x, y types.Object) error { + if reflect.TypeOf(x) != reflect.TypeOf(y) { + return fmt.Errorf("%T vs %T", x, y) + } + xt := x.Type() + yt := y.Type() + switch x := x.(type) { + case *types.Var, *types.Func: + // ok + case *types.Const: + xval := x.Val() + yval := y.(*types.Const).Val() + equal := constant.Compare(xval, token.EQL, yval) + if !equal { + // try approx. comparison + xkind := xval.Kind() + ykind := yval.Kind() + if xkind == constant.Complex || ykind == constant.Complex { + equal = same(constant.Real(xval), constant.Real(yval)) && + same(constant.Imag(xval), constant.Imag(yval)) + } else if xkind == constant.Float || ykind == constant.Float { + equal = same(xval, yval) + } else if xkind == constant.Unknown && ykind == constant.Unknown { + equal = true + } + } + if !equal { + return fmt.Errorf("unequal constants %s vs %s", xval, yval) + } + case *types.TypeName: + if xalias, yalias := x.IsAlias(), y.(*types.TypeName).IsAlias(); xalias != yalias { + return fmt.Errorf("mismatching IsAlias(): %s vs %s", x, y) + } + + // equalType does not recurse into the underlying types of named types, so + // we must pass the underlying type explicitly here. However, in doing this + // we may skip checking the features of the named types themselves, in + // situations where the type name is not referenced by the underlying or + // any other top-level declarations. Therefore, we must explicitly compare + // named types here, before passing their underlying types into equalType. + xn, _ := aliases.Unalias(xt).(*types.Named) + yn, _ := aliases.Unalias(yt).(*types.Named) + if (xn == nil) != (yn == nil) { + return fmt.Errorf("mismatching types: %T vs %T", xt, yt) + } + if xn != nil { + if err := cmpNamed(xn, yn); err != nil { + return err + } + } + xt = xt.Underlying() + yt = yt.Underlying() + default: + return fmt.Errorf("unexpected %T", x) + } + return equalType(xt, yt) +} + +// Use the same floating-point precision (512) as cmd/compile +// (see Mpprec in cmd/compile/internal/gc/mpfloat.go). +const mpprec = 512 + +// same compares non-complex numeric values and reports if they are approximately equal. +func same(x, y constant.Value) bool { + xf := constantToFloat(x) + yf := constantToFloat(y) + d := new(big.Float).Sub(xf, yf) + d.Abs(d) + eps := big.NewFloat(1.0 / (1 << (mpprec - 1))) // allow for 1 bit of error + return d.Cmp(eps) < 0 +} + +// copy of the function with the same name in iexport.go. +func constantToFloat(x constant.Value) *big.Float { + var f big.Float + f.SetPrec(mpprec) + if v, exact := constant.Float64Val(x); exact { + // float64 + f.SetFloat64(v) + } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { + // TODO(gri): add big.Rat accessor to constant.Value. + n := valueToRat(num) + d := valueToRat(denom) + f.SetRat(n.Quo(n, d)) + } else { + // Value too large to represent as a fraction => inaccessible. + // TODO(gri): add big.Float accessor to constant.Value. + _, ok := f.SetString(x.ExactString()) + if !ok { + panic("should not reach here") + } + } + return &f +} + +// copy of the function with the same name in iexport.go. +func valueToRat(x constant.Value) *big.Rat { + // Convert little-endian to big-endian. + // I can't believe this is necessary. + bytes := constant.Bytes(x) + for i := 0; i < len(bytes)/2; i++ { + bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] + } + return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) +} + +// This is a regression test for a bug in iexport of types.Struct: +// unexported fields were losing their implicit package qualifier. +func TestUnexportedStructFields(t *testing.T) { + fset := token.NewFileSet() + export := make(map[string][]byte) + + // process parses and type-checks a single-file + // package and saves its export data. + process := func(path, content string) { + syntax, err := parser.ParseFile(fset, path+"/x.go", content, 0) + if err != nil { + t.Fatal(err) + } + packages := make(map[string]*types.Package) // keys are package paths + cfg := &types.Config{ + Importer: importerFunc(func(path string) (*types.Package, error) { + data, ok := export[path] + if !ok { + return nil, fmt.Errorf("missing export data for %s", path) + } + return gcexportdata.Read(bytes.NewReader(data), fset, packages, path) + }), + } + pkg := types.NewPackage(path, syntax.Name.Name) + check := types.NewChecker(cfg, fset, pkg, nil) + if err := check.Files([]*ast.File{syntax}); err != nil { + t.Fatal(err) + } + var out bytes.Buffer + if err := gcexportdata.Write(&out, fset, pkg); err != nil { + t.Fatal(err) + } + export[path] = out.Bytes() + } + + // Historically this led to a spurious error: + // "cannot convert a.M (variable of type a.MyTime) to type time.Time" + // because the private fields of Time and MyTime were not identical. + process("time", `package time; type Time struct { x, y int }`) + process("a", `package a; import "time"; type MyTime time.Time; var M MyTime`) + process("b", `package b; import ("a"; "time"); var _ = time.Time(a.M)`) +} + +type importerFunc func(path string) (*types.Package, error) + +func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } + +// TestIExportDataTypeParameterizedAliases tests IExportData +// on both declarations and uses of type parameterized aliases. +func TestIExportDataTypeParameterizedAliases(t *testing.T) { + testenv.NeedsGo1Point(t, 23) + + testenv.NeedsGoExperiment(t, "aliastypeparams") + t.Setenv("GODEBUG", "gotypesalias=1") + + // High level steps: + // * parse and typecheck + // * export the data for the importer (via IExportData), + // * import the data (via either x/tools or GOROOT's gcimporter), and + // * check the imported types. + + const src = `package a + +type A[T any] = *T +type B[R any, S *R] = []S +type C[U any] = B[U, A[U]] + +type Named int +type Chained = C[Named] // B[Named, A[Named]] = B[Named, *Named] = []*Named +` + + // parse and typecheck + fset1 := token.NewFileSet() + f, err := parser.ParseFile(fset1, "a", src, 0) + if err != nil { + t.Fatal(err) + } + var conf types.Config + pkg1, err := conf.Check("a", fset1, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + testcases := map[string]func(t *testing.T) *types.Package{ + // Read the result of IExportData through x/tools/internal/gcimporter.IImportData. + "tools": func(t *testing.T) *types.Package { + // export + exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg1) + if err != nil { + t.Fatal(err) + } + + // import + imports := make(map[string]*types.Package) + fset2 := token.NewFileSet() + _, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg1.Path()) + if err != nil { + t.Fatalf("IImportData(%s): %v", pkg1.Path(), err) + } + return pkg2 + }, + // Read the result of IExportData through $GOROOT/src/internal/gcimporter.IImportData. + // + // This test fakes creating an old go object file in indexed format. + // This means that it can be loaded by go/importer or go/types. + // This step is not supported, but it does give test coverage for stdlib. + "goroot": func(t *testing.T) *types.Package { + // Write indexed export data file contents. + // + // TODO(taking): Slightly unclear to what extent this step should be supported by go/importer. + var buf bytes.Buffer + buf.WriteString("go object \n$$B\n") // object file header + if err := gcexportdata.Write(&buf, fset1, pkg1); err != nil { + t.Fatal(err) + } + + // Write export data to temporary file + out := t.TempDir() + name := filepath.Join(out, "a.out") + if err := os.WriteFile(name+".a", buf.Bytes(), 0644); err != nil { + t.Fatal(err) + } + pkg2, err := importer.Default().Import(name) + if err != nil { + t.Fatal(err) + } + return pkg2 + }, + } + + for name, importer := range testcases { + t.Run(name, func(t *testing.T) { + pkg := importer(t) + + obj := pkg.Scope().Lookup("A") + if obj == nil { + t.Fatalf("failed to find %q in package %s", "A", pkg) + } + + // Check that A is type A[T any] = *T. + // TODO(taking): fix how go/types prints parameterized aliases to simplify tests. + alias, ok := obj.Type().(*aliases.Alias) + if !ok { + t.Fatalf("Obj %s is not an Alias", obj) + } + + targs := aliases.TypeArgs(alias) + if targs.Len() != 0 { + t.Errorf("%s has %d type arguments. expected 0", alias, targs.Len()) + } + + tparams := aliases.TypeParams(alias) + if tparams.Len() != 1 { + t.Fatalf("%s has %d type arguments. expected 1", alias, targs.Len()) + } + tparam := tparams.At(0) + if got, want := tparam.String(), "T"; got != want { + t.Errorf("(%q).TypeParams().At(0)=%q. want %q", alias, got, want) + } + + anyt := types.Universe.Lookup("any").Type() + if c := tparam.Constraint(); !types.Identical(anyt, c) { + t.Errorf("(%q).Constraint()=%q. expected %q", tparam, c, anyt) + } + + ptparam := types.NewPointer(tparam) + if rhs := aliases.Rhs(alias); !types.Identical(ptparam, rhs) { + t.Errorf("(%q).Rhs()=%q. expected %q", alias, rhs, ptparam) + } + + // TODO(taking): add tests for B and C once it is simpler to write tests. + + chained := pkg.Scope().Lookup("Chained") + if chained == nil { + t.Fatalf("failed to find %q in package %s", "Chained", pkg) + } + + named, _ := pkg.Scope().Lookup("Named").(*types.TypeName) + if named == nil { + t.Fatalf("failed to find %q in package %s", "Named", pkg) + } + + want := types.NewSlice(types.NewPointer(named.Type())) + if got := chained.Type(); !types.Identical(got, want) { + t.Errorf("(%q).Type()=%q which should be identical to %q", chained, got, want) + } + }) + } +} diff --git a/contribs/gnopls/internal/gcimporter/iimport.go b/contribs/gnopls/internal/gcimporter/iimport.go new file mode 100644 index 00000000000..1baa3895718 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/iimport.go @@ -0,0 +1,1100 @@ +// Copyright 2018 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. + +// Indexed package import. +// See iexport.go for the export data format. + +// This file is a copy of $GOROOT/src/go/internal/gcimporter/iimport.go. + +package gcimporter + +import ( + "bytes" + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "math/big" + "sort" + "strings" + + "golang.org/x/tools/go/types/objectpath" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" +) + +type intReader struct { + *bytes.Reader + path string +} + +func (r *intReader) int64() int64 { + i, err := binary.ReadVarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +func (r *intReader) uint64() uint64 { + i, err := binary.ReadUvarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +// Keep this in sync with constants in iexport.go. +const ( + iexportVersionGo1_11 = 0 + iexportVersionPosCol = 1 + iexportVersionGo1_18 = 2 + iexportVersionGenerics = 2 + + iexportVersionCurrent = 2 +) + +type ident struct { + pkg *types.Package + name string +} + +const predeclReserved = 32 + +type itag uint64 + +const ( + // Types + definedType itag = iota + pointerType + sliceType + arrayType + chanType + mapType + signatureType + structType + interfaceType + typeParamType + instanceType + unionType + aliasType +) + +// Object tags +const ( + varTag = 'V' + funcTag = 'F' + genericFuncTag = 'G' + constTag = 'C' + aliasTag = 'A' + genericAliasTag = 'B' + typeParamTag = 'P' + typeTag = 'T' + genericTypeTag = 'U' +) + +// IImportData imports a package from the serialized package data +// and returns 0 and a reference to the package. +// If the export data version is not recognized or the format is otherwise +// compromised, an error is returned. +func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) { + pkgs, err := iimportCommon(fset, GetPackagesFromMap(imports), data, false, path, false, nil) + if err != nil { + return 0, nil, err + } + return 0, pkgs[0], nil +} + +// IImportBundle imports a set of packages from the serialized package bundle. +func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data []byte) ([]*types.Package, error) { + return iimportCommon(fset, GetPackagesFromMap(imports), data, true, "", false, nil) +} + +// A GetPackagesFunc function obtains the non-nil symbols for a set of +// packages, creating and recursively importing them as needed. An +// implementation should store each package symbol is in the Pkg +// field of the items array. +// +// Any error causes importing to fail. This can be used to quickly read +// the import manifest of an export data file without fully decoding it. +type GetPackagesFunc = func(items []GetPackagesItem) error + +// A GetPackagesItem is a request from the importer for the package +// symbol of the specified name and path. +type GetPackagesItem struct { + Name, Path string + Pkg *types.Package // to be filled in by GetPackagesFunc call + + // private importer state + pathOffset uint64 + nameIndex map[string]uint64 +} + +// GetPackagesFromMap returns a GetPackagesFunc that retrieves +// packages from the given map of package path to package. +// +// The returned function may mutate m: each requested package that is not +// found is created with types.NewPackage and inserted into m. +func GetPackagesFromMap(m map[string]*types.Package) GetPackagesFunc { + return func(items []GetPackagesItem) error { + for i, item := range items { + pkg, ok := m[item.Path] + if !ok { + pkg = types.NewPackage(item.Path, item.Name) + m[item.Path] = pkg + } + items[i].Pkg = pkg + } + return nil + } +} + +func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, bundle bool, path string, shallow bool, reportf ReportFunc) (pkgs []*types.Package, err error) { + const currentVersion = iexportVersionCurrent + version := int64(-1) + if !debug { + defer func() { + if e := recover(); e != nil { + if bundle { + err = fmt.Errorf("%v", e) + } else if version > currentVersion { + err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) + } else { + err = fmt.Errorf("internal error while importing %q (%v); please report an issue", path, e) + } + } + }() + } + + r := &intReader{bytes.NewReader(data), path} + + if bundle { + if v := r.uint64(); v != bundleVersion { + errorf("unknown bundle format version %d", v) + } + } + + version = int64(r.uint64()) + switch version { + case iexportVersionGo1_18, iexportVersionPosCol, iexportVersionGo1_11: + default: + if version > iexportVersionGo1_18 { + errorf("unstable iexport format version %d, just rebuild compiler and std library", version) + } else { + errorf("unknown iexport format version %d", version) + } + } + + sLen := int64(r.uint64()) + var fLen int64 + var fileOffset []uint64 + if shallow { + // Shallow mode uses a different position encoding. + fLen = int64(r.uint64()) + fileOffset = make([]uint64, r.uint64()) + for i := range fileOffset { + fileOffset[i] = r.uint64() + } + } + dLen := int64(r.uint64()) + + whence, _ := r.Seek(0, io.SeekCurrent) + stringData := data[whence : whence+sLen] + fileData := data[whence+sLen : whence+sLen+fLen] + declData := data[whence+sLen+fLen : whence+sLen+fLen+dLen] + r.Seek(sLen+fLen+dLen, io.SeekCurrent) + + p := iimporter{ + version: int(version), + ipath: path, + aliases: aliases.Enabled(), + shallow: shallow, + reportf: reportf, + + stringData: stringData, + stringCache: make(map[uint64]string), + fileOffset: fileOffset, + fileData: fileData, + fileCache: make([]*token.File, len(fileOffset)), + pkgCache: make(map[uint64]*types.Package), + + declData: declData, + pkgIndex: make(map[*types.Package]map[string]uint64), + typCache: make(map[uint64]types.Type), + // Separate map for typeparams, keyed by their package and unique + // name. + tparamIndex: make(map[ident]types.Type), + + fake: fakeFileSet{ + fset: fset, + files: make(map[string]*fileInfo), + }, + } + defer p.fake.setLines() // set lines for files in fset + + for i, pt := range predeclared() { + p.typCache[uint64(i)] = pt + } + + // Gather the relevant packages from the manifest. + items := make([]GetPackagesItem, r.uint64()) + uniquePkgPaths := make(map[string]bool) + for i := range items { + pkgPathOff := r.uint64() + pkgPath := p.stringAt(pkgPathOff) + pkgName := p.stringAt(r.uint64()) + _ = r.uint64() // package height; unused by go/types + + if pkgPath == "" { + pkgPath = path + } + items[i].Name = pkgName + items[i].Path = pkgPath + items[i].pathOffset = pkgPathOff + + // Read index for package. + nameIndex := make(map[string]uint64) + nSyms := r.uint64() + // In shallow mode, only the current package (i=0) has an index. + assert(!(shallow && i > 0 && nSyms != 0)) + for ; nSyms > 0; nSyms-- { + name := p.stringAt(r.uint64()) + nameIndex[name] = r.uint64() + } + + items[i].nameIndex = nameIndex + + uniquePkgPaths[pkgPath] = true + } + // Debugging #63822; hypothesis: there are duplicate PkgPaths. + if len(uniquePkgPaths) != len(items) { + reportf("found duplicate PkgPaths while reading export data manifest: %v", items) + } + + // Request packages all at once from the client, + // enabling a parallel implementation. + if err := getPackages(items); err != nil { + return nil, err // don't wrap this error + } + + // Check the results and complete the index. + pkgList := make([]*types.Package, len(items)) + for i, item := range items { + pkg := item.Pkg + if pkg == nil { + errorf("internal error: getPackages returned nil package for %q", item.Path) + } else if pkg.Path() != item.Path { + errorf("internal error: getPackages returned wrong path %q, want %q", pkg.Path(), item.Path) + } else if pkg.Name() != item.Name { + errorf("internal error: getPackages returned wrong name %s for package %q, want %s", pkg.Name(), item.Path, item.Name) + } + p.pkgCache[item.pathOffset] = pkg + p.pkgIndex[pkg] = item.nameIndex + pkgList[i] = pkg + } + + if bundle { + pkgs = make([]*types.Package, r.uint64()) + for i := range pkgs { + pkg := p.pkgAt(r.uint64()) + imps := make([]*types.Package, r.uint64()) + for j := range imps { + imps[j] = p.pkgAt(r.uint64()) + } + pkg.SetImports(imps) + pkgs[i] = pkg + } + } else { + if len(pkgList) == 0 { + errorf("no packages found for %s", path) + panic("unreachable") + } + pkgs = pkgList[:1] + + // record all referenced packages as imports + list := append(([]*types.Package)(nil), pkgList[1:]...) + sort.Sort(byPath(list)) + pkgs[0].SetImports(list) + } + + for _, pkg := range pkgs { + if pkg.Complete() { + continue + } + + names := make([]string, 0, len(p.pkgIndex[pkg])) + for name := range p.pkgIndex[pkg] { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + p.doDecl(pkg, name) + } + + // package was imported completely and without errors + pkg.MarkComplete() + } + + // SetConstraint can't be called if the constraint type is not yet complete. + // When type params are created in the typeParamTag case of (*importReader).obj(), + // the associated constraint type may not be complete due to recursion. + // Therefore, we defer calling SetConstraint there, and call it here instead + // after all types are complete. + for _, d := range p.later { + d.t.SetConstraint(d.constraint) + } + + for _, typ := range p.interfaceList { + typ.Complete() + } + + // Workaround for golang/go#61561. See the doc for instanceList for details. + for _, typ := range p.instanceList { + if iface, _ := typ.Underlying().(*types.Interface); iface != nil { + iface.Complete() + } + } + + return pkgs, nil +} + +type setConstraintArgs struct { + t *types.TypeParam + constraint types.Type +} + +type iimporter struct { + version int + ipath string + + aliases bool + shallow bool + reportf ReportFunc // if non-nil, used to report bugs + + stringData []byte + stringCache map[uint64]string + fileOffset []uint64 // fileOffset[i] is offset in fileData for info about file encoded as i + fileData []byte + fileCache []*token.File // memoized decoding of file encoded as i + pkgCache map[uint64]*types.Package + + declData []byte + pkgIndex map[*types.Package]map[string]uint64 + typCache map[uint64]types.Type + tparamIndex map[ident]types.Type + + fake fakeFileSet + interfaceList []*types.Interface + + // Workaround for the go/types bug golang/go#61561: instances produced during + // instantiation may contain incomplete interfaces. Here we only complete the + // underlying type of the instance, which is the most common case but doesn't + // handle parameterized interface literals defined deeper in the type. + instanceList []types.Type // instances for later completion (see golang/go#61561) + + // Arguments for calls to SetConstraint that are deferred due to recursive types + later []setConstraintArgs + + indent int // for tracing support +} + +func (p *iimporter) trace(format string, args ...interface{}) { + if !trace { + // Call sites should also be guarded, but having this check here allows + // easily enabling/disabling debug trace statements. + return + } + fmt.Printf(strings.Repeat("..", p.indent)+format+"\n", args...) +} + +func (p *iimporter) doDecl(pkg *types.Package, name string) { + if debug { + p.trace("import decl %s", name) + p.indent++ + defer func() { + p.indent-- + p.trace("=> %s", name) + }() + } + // See if we've already imported this declaration. + if obj := pkg.Scope().Lookup(name); obj != nil { + return + } + + off, ok := p.pkgIndex[pkg][name] + if !ok { + // In deep mode, the index should be complete. In shallow + // mode, we should have already recursively loaded necessary + // dependencies so the above Lookup succeeds. + errorf("%v.%v not in index", pkg, name) + } + + r := &importReader{p: p, currPkg: pkg} + r.declReader.Reset(p.declData[off:]) + + r.obj(name) +} + +func (p *iimporter) stringAt(off uint64) string { + if s, ok := p.stringCache[off]; ok { + return s + } + + slen, n := binary.Uvarint(p.stringData[off:]) + if n <= 0 { + errorf("varint failed") + } + spos := off + uint64(n) + s := string(p.stringData[spos : spos+slen]) + p.stringCache[off] = s + return s +} + +func (p *iimporter) fileAt(index uint64) *token.File { + file := p.fileCache[index] + if file == nil { + off := p.fileOffset[index] + file = p.decodeFile(intReader{bytes.NewReader(p.fileData[off:]), p.ipath}) + p.fileCache[index] = file + } + return file +} + +func (p *iimporter) decodeFile(rd intReader) *token.File { + filename := p.stringAt(rd.uint64()) + size := int(rd.uint64()) + file := p.fake.fset.AddFile(filename, -1, size) + + // SetLines requires a nondecreasing sequence. + // Because it is common for clients to derive the interval + // [start, start+len(name)] from a start position, and we + // want to ensure that the end offset is on the same line, + // we fill in the gaps of the sparse encoding with values + // that strictly increase by the largest possible amount. + // This allows us to avoid having to record the actual end + // offset of each needed line. + + lines := make([]int, int(rd.uint64())) + var index, offset int + for i, n := 0, int(rd.uint64()); i < n; i++ { + index += int(rd.uint64()) + offset += int(rd.uint64()) + lines[index] = offset + + // Ensure monotonicity between points. + for j := index - 1; j > 0 && lines[j] == 0; j-- { + lines[j] = lines[j+1] - 1 + } + } + + // Ensure monotonicity after last point. + for j := len(lines) - 1; j > 0 && lines[j] == 0; j-- { + size-- + lines[j] = size + } + + if !file.SetLines(lines) { + errorf("SetLines failed: %d", lines) // can't happen + } + return file +} + +func (p *iimporter) pkgAt(off uint64) *types.Package { + if pkg, ok := p.pkgCache[off]; ok { + return pkg + } + path := p.stringAt(off) + errorf("missing package %q in %q", path, p.ipath) + return nil +} + +func (p *iimporter) typAt(off uint64, base *types.Named) types.Type { + if t, ok := p.typCache[off]; ok && canReuse(base, t) { + return t + } + + if off < predeclReserved { + errorf("predeclared type missing from cache: %v", off) + } + + r := &importReader{p: p} + r.declReader.Reset(p.declData[off-predeclReserved:]) + t := r.doType(base) + + if canReuse(base, t) { + p.typCache[off] = t + } + return t +} + +// canReuse reports whether the type rhs on the RHS of the declaration for def +// may be re-used. +// +// Specifically, if def is non-nil and rhs is an interface type with methods, it +// may not be re-used because we have a convention of setting the receiver type +// for interface methods to def. +func canReuse(def *types.Named, rhs types.Type) bool { + if def == nil { + return true + } + iface, _ := aliases.Unalias(rhs).(*types.Interface) + if iface == nil { + return true + } + // Don't use iface.Empty() here as iface may not be complete. + return iface.NumEmbeddeds() == 0 && iface.NumExplicitMethods() == 0 +} + +type importReader struct { + p *iimporter + declReader bytes.Reader + currPkg *types.Package + prevFile string + prevLine int64 + prevColumn int64 +} + +func (r *importReader) obj(name string) { + tag := r.byte() + pos := r.pos() + + switch tag { + case aliasTag, genericAliasTag: + var tparams []*types.TypeParam + if tag == genericAliasTag { + tparams = r.tparamList() + } + typ := r.typ() + obj := aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ, tparams) + r.declare(obj) + + case constTag: + typ, val := r.value() + + r.declare(types.NewConst(pos, r.currPkg, name, typ, val)) + + case funcTag, genericFuncTag: + var tparams []*types.TypeParam + if tag == genericFuncTag { + tparams = r.tparamList() + } + sig := r.signature(nil, nil, tparams) + r.declare(types.NewFunc(pos, r.currPkg, name, sig)) + + case typeTag, genericTypeTag: + // Types can be recursive. We need to setup a stub + // declaration before recursing. + obj := types.NewTypeName(pos, r.currPkg, name, nil) + named := types.NewNamed(obj, nil, nil) + // Declare obj before calling r.tparamList, so the new type name is recognized + // if used in the constraint of one of its own typeparams (see #48280). + r.declare(obj) + if tag == genericTypeTag { + tparams := r.tparamList() + named.SetTypeParams(tparams) + } + + underlying := r.p.typAt(r.uint64(), named).Underlying() + named.SetUnderlying(underlying) + + if !isInterface(underlying) { + for n := r.uint64(); n > 0; n-- { + mpos := r.pos() + mname := r.ident() + recv := r.param() + + // If the receiver has any targs, set those as the + // rparams of the method (since those are the + // typeparams being used in the method sig/body). + _, recvNamed := typesinternal.ReceiverNamed(recv) + targs := recvNamed.TypeArgs() + var rparams []*types.TypeParam + if targs.Len() > 0 { + rparams = make([]*types.TypeParam, targs.Len()) + for i := range rparams { + rparams[i] = aliases.Unalias(targs.At(i)).(*types.TypeParam) + } + } + msig := r.signature(recv, rparams, nil) + + named.AddMethod(types.NewFunc(mpos, r.currPkg, mname, msig)) + } + } + + case typeParamTag: + // We need to "declare" a typeparam in order to have a name that + // can be referenced recursively (if needed) in the type param's + // bound. + if r.p.version < iexportVersionGenerics { + errorf("unexpected type param type") + } + name0 := tparamName(name) + tn := types.NewTypeName(pos, r.currPkg, name0, nil) + t := types.NewTypeParam(tn, nil) + + // To handle recursive references to the typeparam within its + // bound, save the partial type in tparamIndex before reading the bounds. + id := ident{r.currPkg, name} + r.p.tparamIndex[id] = t + var implicit bool + if r.p.version >= iexportVersionGo1_18 { + implicit = r.bool() + } + constraint := r.typ() + if implicit { + iface, _ := aliases.Unalias(constraint).(*types.Interface) + if iface == nil { + errorf("non-interface constraint marked implicit") + } + iface.MarkImplicit() + } + // The constraint type may not be complete, if we + // are in the middle of a type recursion involving type + // constraints. So, we defer SetConstraint until we have + // completely set up all types in ImportData. + r.p.later = append(r.p.later, setConstraintArgs{t: t, constraint: constraint}) + + case varTag: + typ := r.typ() + + r.declare(types.NewVar(pos, r.currPkg, name, typ)) + + default: + errorf("unexpected tag: %v", tag) + } +} + +func (r *importReader) declare(obj types.Object) { + obj.Pkg().Scope().Insert(obj) +} + +func (r *importReader) value() (typ types.Type, val constant.Value) { + typ = r.typ() + if r.p.version >= iexportVersionGo1_18 { + // TODO: add support for using the kind. + _ = constant.Kind(r.int64()) + } + + switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType { + case types.IsBoolean: + val = constant.MakeBool(r.bool()) + + case types.IsString: + val = constant.MakeString(r.string()) + + case types.IsInteger: + var x big.Int + r.mpint(&x, b) + val = constant.Make(&x) + + case types.IsFloat: + val = r.mpfloat(b) + + case types.IsComplex: + re := r.mpfloat(b) + im := r.mpfloat(b) + val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + + default: + if b.Kind() == types.Invalid { + val = constant.MakeUnknown() + return + } + errorf("unexpected type %v", typ) // panics + panic("unreachable") + } + + return +} + +func intSize(b *types.Basic) (signed bool, maxBytes uint) { + if (b.Info() & types.IsUntyped) != 0 { + return true, 64 + } + + switch b.Kind() { + case types.Float32, types.Complex64: + return true, 3 + case types.Float64, types.Complex128: + return true, 7 + } + + signed = (b.Info() & types.IsUnsigned) == 0 + switch b.Kind() { + case types.Int8, types.Uint8: + maxBytes = 1 + case types.Int16, types.Uint16: + maxBytes = 2 + case types.Int32, types.Uint32: + maxBytes = 4 + default: + maxBytes = 8 + } + + return +} + +func (r *importReader) mpint(x *big.Int, typ *types.Basic) { + signed, maxBytes := intSize(typ) + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + n, _ := r.declReader.ReadByte() + if uint(n) < maxSmall { + v := int64(n) + if signed { + v >>= 1 + if n&1 != 0 { + v = ^v + } + } + x.SetInt64(v) + return + } + + v := -n + if signed { + v = -(n &^ 1) >> 1 + } + if v < 1 || uint(v) > maxBytes { + errorf("weird decoding: %v, %v => %v", n, signed, v) + } + b := make([]byte, v) + io.ReadFull(&r.declReader, b) + x.SetBytes(b) + if signed && n&1 != 0 { + x.Neg(x) + } +} + +func (r *importReader) mpfloat(typ *types.Basic) constant.Value { + var mant big.Int + r.mpint(&mant, typ) + var f big.Float + f.SetInt(&mant) + if f.Sign() != 0 { + f.SetMantExp(&f, int(r.int64())) + } + return constant.Make(&f) +} + +func (r *importReader) ident() string { + return r.string() +} + +func (r *importReader) qualifiedIdent() (*types.Package, string) { + name := r.string() + pkg := r.pkg() + return pkg, name +} + +func (r *importReader) pos() token.Pos { + if r.p.shallow { + // precise offsets are encoded only in shallow mode + return r.posv2() + } + if r.p.version >= iexportVersionPosCol { + r.posv1() + } else { + r.posv0() + } + + if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 { + return token.NoPos + } + return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn)) +} + +func (r *importReader) posv0() { + delta := r.int64() + if delta != deltaNewFile { + r.prevLine += delta + } else if l := r.int64(); l == -1 { + r.prevLine += deltaNewFile + } else { + r.prevFile = r.string() + r.prevLine = l + } +} + +func (r *importReader) posv1() { + delta := r.int64() + r.prevColumn += delta >> 1 + if delta&1 != 0 { + delta = r.int64() + r.prevLine += delta >> 1 + if delta&1 != 0 { + r.prevFile = r.string() + } + } +} + +func (r *importReader) posv2() token.Pos { + file := r.uint64() + if file == 0 { + return token.NoPos + } + tf := r.p.fileAt(file - 1) + return tf.Pos(int(r.uint64())) +} + +func (r *importReader) typ() types.Type { + return r.p.typAt(r.uint64(), nil) +} + +func isInterface(t types.Type) bool { + _, ok := aliases.Unalias(t).(*types.Interface) + return ok +} + +func (r *importReader) pkg() *types.Package { return r.p.pkgAt(r.uint64()) } +func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } + +func (r *importReader) doType(base *types.Named) (res types.Type) { + k := r.kind() + if debug { + r.p.trace("importing type %d (base: %v)", k, base) + r.p.indent++ + defer func() { + r.p.indent-- + r.p.trace("=> %s", res) + }() + } + switch k { + default: + errorf("unexpected kind tag in %q: %v", r.p.ipath, k) + return nil + + case aliasType, definedType: + pkg, name := r.qualifiedIdent() + r.p.doDecl(pkg, name) + return pkg.Scope().Lookup(name).(*types.TypeName).Type() + case pointerType: + return types.NewPointer(r.typ()) + case sliceType: + return types.NewSlice(r.typ()) + case arrayType: + n := r.uint64() + return types.NewArray(r.typ(), int64(n)) + case chanType: + dir := chanDir(int(r.uint64())) + return types.NewChan(dir, r.typ()) + case mapType: + return types.NewMap(r.typ(), r.typ()) + case signatureType: + r.currPkg = r.pkg() + return r.signature(nil, nil, nil) + + case structType: + r.currPkg = r.pkg() + + fields := make([]*types.Var, r.uint64()) + tags := make([]string, len(fields)) + for i := range fields { + var field *types.Var + if r.p.shallow { + field, _ = r.objectPathObject().(*types.Var) + } + + fpos := r.pos() + fname := r.ident() + ftyp := r.typ() + emb := r.bool() + tag := r.string() + + // Either this is not a shallow import, the field is local, or the + // encoded objectPath failed to produce an object (a bug). + // + // Even in this last, buggy case, fall back on creating a new field. As + // discussed in iexport.go, this is not correct, but mostly works and is + // preferable to failing (for now at least). + if field == nil { + field = types.NewField(fpos, r.currPkg, fname, ftyp, emb) + } + + fields[i] = field + tags[i] = tag + } + return types.NewStruct(fields, tags) + + case interfaceType: + r.currPkg = r.pkg() + + embeddeds := make([]types.Type, r.uint64()) + for i := range embeddeds { + _ = r.pos() + embeddeds[i] = r.typ() + } + + methods := make([]*types.Func, r.uint64()) + for i := range methods { + var method *types.Func + if r.p.shallow { + method, _ = r.objectPathObject().(*types.Func) + } + + mpos := r.pos() + mname := r.ident() + + // TODO(mdempsky): Matches bimport.go, but I + // don't agree with this. + var recv *types.Var + if base != nil { + recv = types.NewVar(token.NoPos, r.currPkg, "", base) + } + msig := r.signature(recv, nil, nil) + + if method == nil { + method = types.NewFunc(mpos, r.currPkg, mname, msig) + } + methods[i] = method + } + + typ := newInterface(methods, embeddeds) + r.p.interfaceList = append(r.p.interfaceList, typ) + return typ + + case typeParamType: + if r.p.version < iexportVersionGenerics { + errorf("unexpected type param type") + } + pkg, name := r.qualifiedIdent() + id := ident{pkg, name} + if t, ok := r.p.tparamIndex[id]; ok { + // We're already in the process of importing this typeparam. + return t + } + // Otherwise, import the definition of the typeparam now. + r.p.doDecl(pkg, name) + return r.p.tparamIndex[id] + + case instanceType: + if r.p.version < iexportVersionGenerics { + errorf("unexpected instantiation type") + } + // pos does not matter for instances: they are positioned on the original + // type. + _ = r.pos() + len := r.uint64() + targs := make([]types.Type, len) + for i := range targs { + targs[i] = r.typ() + } + baseType := r.typ() + // The imported instantiated type doesn't include any methods, so + // we must always use the methods of the base (orig) type. + // TODO provide a non-nil *Environment + t, _ := types.Instantiate(nil, baseType, targs, false) + + // Workaround for golang/go#61561. See the doc for instanceList for details. + r.p.instanceList = append(r.p.instanceList, t) + return t + + case unionType: + if r.p.version < iexportVersionGenerics { + errorf("unexpected instantiation type") + } + terms := make([]*types.Term, r.uint64()) + for i := range terms { + terms[i] = types.NewTerm(r.bool(), r.typ()) + } + return types.NewUnion(terms) + } +} + +func (r *importReader) kind() itag { + return itag(r.uint64()) +} + +// objectPathObject is the inverse of exportWriter.objectPath. +// +// In shallow mode, certain fields and methods may need to be looked up in an +// imported package. See the doc for exportWriter.objectPath for a full +// explanation. +func (r *importReader) objectPathObject() types.Object { + objPath := objectpath.Path(r.string()) + if objPath == "" { + return nil + } + pkg := r.pkg() + obj, err := objectpath.Object(pkg, objPath) + if err != nil { + if r.p.reportf != nil { + r.p.reportf("failed to find object for objectPath %q: %v", objPath, err) + } + } + return obj +} + +func (r *importReader) signature(recv *types.Var, rparams []*types.TypeParam, tparams []*types.TypeParam) *types.Signature { + params := r.paramList() + results := r.paramList() + variadic := params.Len() > 0 && r.bool() + return types.NewSignatureType(recv, rparams, tparams, params, results, variadic) +} + +func (r *importReader) tparamList() []*types.TypeParam { + n := r.uint64() + if n == 0 { + return nil + } + xs := make([]*types.TypeParam, n) + for i := range xs { + // Note: the standard library importer is tolerant of nil types here, + // though would panic in SetTypeParams. + xs[i] = aliases.Unalias(r.typ()).(*types.TypeParam) + } + return xs +} + +func (r *importReader) paramList() *types.Tuple { + xs := make([]*types.Var, r.uint64()) + for i := range xs { + xs[i] = r.param() + } + return types.NewTuple(xs...) +} + +func (r *importReader) param() *types.Var { + pos := r.pos() + name := r.ident() + typ := r.typ() + return types.NewParam(pos, r.currPkg, name, typ) +} + +func (r *importReader) bool() bool { + return r.uint64() != 0 +} + +func (r *importReader) int64() int64 { + n, err := binary.ReadVarint(&r.declReader) + if err != nil { + errorf("readVarint: %v", err) + } + return n +} + +func (r *importReader) uint64() uint64 { + n, err := binary.ReadUvarint(&r.declReader) + if err != nil { + errorf("readUvarint: %v", err) + } + return n +} + +func (r *importReader) byte() byte { + x, err := r.declReader.ReadByte() + if err != nil { + errorf("declReader.ReadByte: %v", err) + } + return x +} diff --git a/contribs/gnopls/internal/gcimporter/israce_test.go b/contribs/gnopls/internal/gcimporter/israce_test.go new file mode 100644 index 00000000000..885ba1c01c5 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/israce_test.go @@ -0,0 +1,12 @@ +// 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. + +//go:build race +// +build race + +package gcimporter_test + +func init() { + isRace = true +} diff --git a/contribs/gnopls/internal/gcimporter/main.go b/contribs/gnopls/internal/gcimporter/main.go new file mode 100644 index 00000000000..3725659090f --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/main.go @@ -0,0 +1,117 @@ +// 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. + +//go:build ignore + +// The gcimporter command reads the compiler's export data for the +// named packages and prints the decoded type information. +// +// It is provided for debugging export data problems. +package main + +import ( + "bytes" + "flag" + "fmt" + "go/token" + "go/types" + "log" + "os" + "sort" + + "golang.org/x/tools/go/gcexportdata" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/types/typeutil" + "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" +) + +func main() { + flag.Parse() + cfg := &packages.Config{ + Fset: token.NewFileSet(), + // Don't request NeedTypes: we want to be certain that + // we loaded the types ourselves, from export data. + Mode: packages.NeedName | packages.NeedExportFile, + } + pkgs, err := packages.Load(cfg, flag.Args()...) + if err != nil { + log.Fatal(err) + } + if packages.PrintErrors(pkgs) > 0 { + os.Exit(1) + } + + for _, pkg := range pkgs { + // Read types from compiler's unified export data file. + // This Package may included non-exported functions if they + // are called by inlinable exported functions. + var tpkg1 *types.Package + { + export, err := os.ReadFile(pkg.ExportFile) + if err != nil { + log.Fatalf("can't read %q export data: %v", pkg.PkgPath, err) + } + r, err := gcexportdata.NewReader(bytes.NewReader(export)) + if err != nil { + log.Fatalf("reading export data %s: %v", pkg.ExportFile, err) + } + tpkg1, err = gcexportdata.Read(r, cfg.Fset, make(map[string]*types.Package), pkg.PkgPath) + if err != nil { + log.Fatalf("decoding export data: %v", err) + } + } + fmt.Println("# Read from compiler's unified export data:") + printPackage(tpkg1) + + // Now reexport as indexed (deep) export data, and reimport. + // The Package will contain only exported symbols. + var tpkg2 *types.Package + { + var out bytes.Buffer + if err := gcimporter.IExportData(&out, cfg.Fset, tpkg1); err != nil { + log.Fatal(err) + } + var err error + _, tpkg2, err = gcimporter.IImportData(cfg.Fset, make(map[string]*types.Package), out.Bytes(), tpkg1.Path()) + if err != nil { + log.Fatal(err) + } + } + fmt.Println("# After round-tripping through indexed export data:") + printPackage(tpkg2) + } +} + +func printPackage(pkg *types.Package) { + fmt.Printf("package %s %q\n", pkg.Name(), pkg.Path()) + + if !pkg.Complete() { + fmt.Printf("\thas incomplete exported type info\n") + } + + // imports + var lines []string + for _, imp := range pkg.Imports() { + lines = append(lines, fmt.Sprintf("\timport %q", imp.Path())) + } + sort.Strings(lines) + for _, line := range lines { + fmt.Println(line) + } + + // types of package members + qual := types.RelativeTo(pkg) + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + fmt.Printf("\t%s\n", types.ObjectString(obj, qual)) + if _, ok := obj.(*types.TypeName); ok { + for _, meth := range typeutil.IntuitiveMethodSet(obj.Type(), nil) { + fmt.Printf("\t%s\n", types.SelectionString(meth, qual)) + } + } + } + + fmt.Println() +} diff --git a/contribs/gnopls/internal/gcimporter/newInterface10.go b/contribs/gnopls/internal/gcimporter/newInterface10.go new file mode 100644 index 00000000000..8b163e3d058 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/newInterface10.go @@ -0,0 +1,22 @@ +// Copyright 2018 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.11 +// +build !go1.11 + +package gcimporter + +import "go/types" + +func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface { + named := make([]*types.Named, len(embeddeds)) + for i, e := range embeddeds { + var ok bool + named[i], ok = e.(*types.Named) + if !ok { + panic("embedding of non-defined interfaces in interfaces is not supported before Go 1.11") + } + } + return types.NewInterface(methods, named) +} diff --git a/contribs/gnopls/internal/gcimporter/newInterface11.go b/contribs/gnopls/internal/gcimporter/newInterface11.go new file mode 100644 index 00000000000..49984f40fd8 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/newInterface11.go @@ -0,0 +1,14 @@ +// Copyright 2018 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.11 +// +build go1.11 + +package gcimporter + +import "go/types" + +func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface { + return types.NewInterfaceType(methods, embeddeds) +} diff --git a/contribs/gnopls/internal/gcimporter/shallow_test.go b/contribs/gnopls/internal/gcimporter/shallow_test.go new file mode 100644 index 00000000000..90d8c242bd1 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/shallow_test.go @@ -0,0 +1,231 @@ +// 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 gcimporter_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "os" + "strings" + "testing" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/packages" + "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +// TestShallowStd type-checks the standard library using shallow export data. +func TestShallowStd(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode; too slow (https://golang.org/issue/14113)") + } + testenv.NeedsTool(t, "go") + + // Load import graph of the standard library. + // (No parsing or type-checking.) + cfg := &packages.Config{ + Mode: packages.NeedImports | + packages.NeedName | + packages.NeedFiles | // see https://github.com/golang/go/issues/56632 + packages.NeedCompiledGoFiles, + Tests: false, + } + pkgs, err := packages.Load(cfg, "std") + if err != nil { + t.Fatalf("load: %v", err) + } + if len(pkgs) < 200 { + t.Fatalf("too few packages: %d", len(pkgs)) + } + + // Type check the packages in parallel postorder. + done := make(map[*packages.Package]chan struct{}) + packages.Visit(pkgs, nil, func(p *packages.Package) { + done[p] = make(chan struct{}) + }) + packages.Visit(pkgs, nil, + func(pkg *packages.Package) { + go func() { + // Wait for all deps to be done. + for _, imp := range pkg.Imports { + <-done[imp] + } + typecheck(t, pkg) + close(done[pkg]) + }() + }) + for _, root := range pkgs { + <-done[root] + } +} + +// typecheck reads, parses, and type-checks a package. +// It squirrels the export data in the ppkg.ExportFile field. +func typecheck(t *testing.T, ppkg *packages.Package) { + if ppkg.PkgPath == "unsafe" { + return // unsafe is special + } + + // Create a local FileSet just for this package. + fset := token.NewFileSet() + + // Parse files in parallel. + syntax := make([]*ast.File, len(ppkg.CompiledGoFiles)) + var group errgroup.Group + for i, filename := range ppkg.CompiledGoFiles { + i, filename := i, filename + group.Go(func() error { + f, err := parser.ParseFile(fset, filename, nil, parser.SkipObjectResolution) + if err != nil { + return err // e.g. missing file + } + syntax[i] = f + return nil + }) + } + if err := group.Wait(); err != nil { + t.Fatal(err) + } + // Inv: all files were successfully parsed. + + // Build map of dependencies by package path. + // (We don't compute this mapping for the entire + // packages graph because it is not globally consistent.) + depsByPkgPath := make(map[string]*packages.Package) + { + var visit func(*packages.Package) + visit = func(pkg *packages.Package) { + if depsByPkgPath[pkg.PkgPath] == nil { + depsByPkgPath[pkg.PkgPath] = pkg + for path := range pkg.Imports { + visit(pkg.Imports[path]) + } + } + } + visit(ppkg) + } + + // importer state + var ( + loadFromExportData func(*packages.Package) (*types.Package, error) + importMap = map[string]*types.Package{ // keys are PackagePaths + ppkg.PkgPath: types.NewPackage(ppkg.PkgPath, ppkg.Name), + } + ) + loadFromExportData = func(imp *packages.Package) (*types.Package, error) { + export := []byte(imp.ExportFile) + getPackages := func(items []gcimporter.GetPackagesItem) error { + for i, item := range items { + pkg, ok := importMap[item.Path] + if !ok { + dep, ok := depsByPkgPath[item.Path] + if !ok { + return fmt.Errorf("can't find dependency: %q", item.Path) + } + pkg = types.NewPackage(item.Path, dep.Name) + importMap[item.Path] = pkg + loadFromExportData(dep) // side effect: populate package scope + } + items[i].Pkg = pkg + } + return nil + } + return gcimporter.IImportShallow(fset, getPackages, export, imp.PkgPath, nil) + } + + // Type-check the syntax trees. + cfg := &types.Config{ + Error: func(e error) { + t.Error(e) + }, + Importer: importerFunc(func(importPath string) (*types.Package, error) { + if importPath == "unsafe" { + return types.Unsafe, nil // unsafe has no exportdata + } + imp, ok := ppkg.Imports[importPath] + if !ok { + return nil, fmt.Errorf("missing import %q", importPath) + } + return loadFromExportData(imp) + }), + } + + // (Use NewChecker+Files to ensure Package.Name is set explicitly.) + tpkg := types.NewPackage(ppkg.PkgPath, ppkg.Name) + _ = types.NewChecker(cfg, fset, tpkg, nil).Files(syntax) // ignore error + // Check sanity. + postTypeCheck(t, fset, tpkg) + + // Save the export data. + data, err := gcimporter.IExportShallow(fset, tpkg, nil) + if err != nil { + t.Fatalf("internal error marshalling export data: %v", err) + } + ppkg.ExportFile = string(data) +} + +// postTypeCheck is called after a package is type checked. +// We use it to assert additional correctness properties, +// for example, that the apparent location of "fmt.Println" +// corresponds to its source location: in other words, +// export+import preserves high-fidelity positions. +func postTypeCheck(t *testing.T, fset *token.FileSet, pkg *types.Package) { + // We hard-code a few interesting test-case objects. + var obj types.Object + switch pkg.Path() { + case "fmt": + // func fmt.Println + obj = pkg.Scope().Lookup("Println") + case "net/http": + // method (*http.Request).ParseForm + req := pkg.Scope().Lookup("Request") + obj, _, _ = types.LookupFieldOrMethod(req.Type(), true, pkg, "ParseForm") + default: + return + } + if obj == nil { + t.Errorf("object not found in package %s", pkg.Path()) + return + } + + // Now check the source fidelity of the object's position. + posn := fset.Position(obj.Pos()) + data, err := os.ReadFile(posn.Filename) + if err != nil { + t.Errorf("can't read source file declaring %v: %v", obj, err) + return + } + + // Check line and column denote a source interval containing the object's identifier. + line := strings.Split(string(data), "\n")[posn.Line-1] + + if id := line[posn.Column-1 : posn.Column-1+len(obj.Name())]; id != obj.Name() { + t.Errorf("%+v: expected declaration of %v at this line, column; got %q", posn, obj, line) + } + + // Check offset. + if id := string(data[posn.Offset : posn.Offset+len(obj.Name())]); id != obj.Name() { + t.Errorf("%+v: expected declaration of %v at this offset; got %q", posn, obj, id) + } + + // Check commutativity of Position() and start+len(name) operations: + // Position(startPos+len(name)) == Position(startPos) + len(name). + // This important property is a consequence of the way in which the + // decoder fills the gaps in the sparse line-start offset table. + endPosn := fset.Position(obj.Pos() + token.Pos(len(obj.Name()))) + wantEndPosn := token.Position{ + Filename: posn.Filename, + Offset: posn.Offset + len(obj.Name()), + Line: posn.Line, + Column: posn.Column + len(obj.Name()), + } + if endPosn != wantEndPosn { + t.Errorf("%+v: expected end Position of %v here; was at %+v", wantEndPosn, obj, endPosn) + } +} diff --git a/contribs/gnopls/internal/gcimporter/stdlib_test.go b/contribs/gnopls/internal/gcimporter/stdlib_test.go new file mode 100644 index 00000000000..b54cdb6b84e --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/stdlib_test.go @@ -0,0 +1,94 @@ +// 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 gcimporter_test + +import ( + "bytes" + "fmt" + "go/token" + "go/types" + "runtime" + "testing" + "unsafe" + + "golang.org/x/tools/go/gcexportdata" + "golang.org/x/tools/go/packages" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +// TestStdlib ensures that all packages in std and x/tools can be +// type-checked using export data. Takes around 3s. +func TestStdlib(t *testing.T) { + testenv.NeedsGoPackages(t) + + // gcexportdata.Read rapidly consumes FileSet address space, + // so disable the test on 32-bit machines. + // (We could use a fresh FileSet per type-check, but that + // would require us to re-parse the source using it.) + if unsafe.Sizeof(token.NoPos) < 8 { + t.Skip("skipping test on 32-bit machine") + } + + // Load, parse and type-check the standard library. + // If we have the full source code for x/tools, also load and type-check that. + cfg := &packages.Config{Mode: packages.LoadAllSyntax} + patterns := []string{"std"} + minPkgs := 225 // 'GOOS=plan9 go1.18 list std | wc -l' reports 228; most other platforms have more. + switch runtime.GOOS { + case "android", "ios": + // The go_.*_exec script for mobile builders only copies over the source tree + // for the package under test. + default: + patterns = append(patterns, "golang.org/x/tools/...") + minPkgs += 160 // At the time of writing, 'GOOS=plan9 go list ./... | wc -l' reports 188. + } + pkgs, err := packages.Load(cfg, patterns...) + if err != nil { + t.Fatalf("failed to load/parse/type-check: %v", err) + } + if packages.PrintErrors(pkgs) > 0 { + t.Fatal("there were errors during loading") + } + if len(pkgs) < minPkgs { + t.Errorf("too few packages (%d) were loaded", len(pkgs)) + } + + export := make(map[string][]byte) // keys are package IDs + + // Re-type check them all in post-order, using export data. + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + packages := make(map[string]*types.Package) // keys are package paths + cfg := &types.Config{ + Error: func(e error) { + t.Errorf("type error: %v", e) + }, + Importer: importerFunc(func(importPath string) (*types.Package, error) { + // Resolve import path to (vendored?) package path. + imported := pkg.Imports[importPath] + + if imported.PkgPath == "unsafe" { + return types.Unsafe, nil // unsafe has no exportdata + } + + data, ok := export[imported.ID] + if !ok { + return nil, fmt.Errorf("missing export data for %s", importPath) + } + return gcexportdata.Read(bytes.NewReader(data), pkg.Fset, packages, imported.PkgPath) + }), + } + + // Re-typecheck the syntax and save the export data in the map. + newPkg := types.NewPackage(pkg.PkgPath, pkg.Name) + check := types.NewChecker(cfg, pkg.Fset, newPkg, nil) + check.Files(pkg.Syntax) + + var out bytes.Buffer + if err := gcexportdata.Write(&out, pkg.Fset, newPkg); err != nil { + t.Fatalf("internal error writing export data: %v", err) + } + export[pkg.ID] = out.Bytes() + }) +} diff --git a/contribs/gnopls/internal/gcimporter/support_go118.go b/contribs/gnopls/internal/gcimporter/support_go118.go new file mode 100644 index 00000000000..0cd3b91b65a --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/support_go118.go @@ -0,0 +1,34 @@ +// 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 gcimporter + +import "go/types" + +const iexportVersion = iexportVersionGenerics + +// additionalPredeclared returns additional predeclared types in go.1.18. +func additionalPredeclared() []types.Type { + return []types.Type{ + // comparable + types.Universe.Lookup("comparable").Type(), + + // any + types.Universe.Lookup("any").Type(), + } +} + +// See cmd/compile/internal/types.SplitVargenSuffix. +func splitVargenSuffix(name string) (base, suffix string) { + i := len(name) + for i > 0 && name[i-1] >= '0' && name[i-1] <= '9' { + i-- + } + const dot = "·" + if i >= len(dot) && name[i-len(dot):i] == dot { + i -= len(dot) + return name[:i], name[i:] + } + return name, "" +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/a.go b/contribs/gnopls/internal/gcimporter/testdata/a.go new file mode 100644 index 00000000000..56e4292cda9 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/a.go @@ -0,0 +1,14 @@ +// Copyright 2016 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. + +// Input for TestIssue13566 + +package a + +import "encoding/json" + +type A struct { + a *A + json json.RawMessage +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/a/a.go b/contribs/gnopls/internal/gcimporter/testdata/a/a.go new file mode 100644 index 00000000000..56e4292cda9 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/a/a.go @@ -0,0 +1,14 @@ +// Copyright 2016 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. + +// Input for TestIssue13566 + +package a + +import "encoding/json" + +type A struct { + a *A + json json.RawMessage +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/aliases/a/a.go b/contribs/gnopls/internal/gcimporter/testdata/aliases/a/a.go new file mode 100644 index 00000000000..0558258e17a --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/aliases/a/a.go @@ -0,0 +1,14 @@ +// 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 a + +type A[T any] = *T + +type B = struct{ F int } + +func F() B { + type a[T any] = struct{ F T } + return a[int]{} +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/aliases/b/b.go b/contribs/gnopls/internal/gcimporter/testdata/aliases/b/b.go new file mode 100644 index 00000000000..9a2dbe2bafb --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/aliases/b/b.go @@ -0,0 +1,11 @@ +// 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 b + +import "./a" + +type B[S any] = struct { + F a.A[[]S] +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/aliases/c/c.go b/contribs/gnopls/internal/gcimporter/testdata/aliases/c/c.go new file mode 100644 index 00000000000..359cee61920 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/aliases/c/c.go @@ -0,0 +1,26 @@ +// 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 c + +import ( + "./a" + "./b" +) + +type c[V any] = struct { + G b.B[[3]V] +} + +var S struct{ F int } = a.B{} +var T struct{ F int } = a.F() + +var U a.A[string] = (*string)(nil) +var V a.A[int] = (*int)(nil) + +var W b.B[string] = struct{ F *[]string }{} +var X b.B[int] = struct{ F *[]int }{} + +var Y c[string] = struct{ G struct{ F *[][3]string } }{} +var Z c[int] = struct{ G struct{ F *[][3]int } }{} diff --git a/contribs/gnopls/internal/gcimporter/testdata/b.go b/contribs/gnopls/internal/gcimporter/testdata/b.go new file mode 100644 index 00000000000..41966782007 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/b.go @@ -0,0 +1,11 @@ +// Copyright 2016 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. + +// Input for TestIssue13566 + +package b + +import "./a" + +type A a.A diff --git a/contribs/gnopls/internal/gcimporter/testdata/exports.go b/contribs/gnopls/internal/gcimporter/testdata/exports.go new file mode 100644 index 00000000000..8ee28b0942b --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/exports.go @@ -0,0 +1,89 @@ +// Copyright 2011 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 is used to generate an object file which +// serves as test file for gcimporter_test.go. + +package exports + +import ( + "go/ast" +) + +// Issue 3682: Correctly read dotted identifiers from export data. +const init1 = 0 + +func init() {} + +const ( + C0 int = 0 + C1 = 3.14159265 + C2 = 2.718281828i + C3 = -123.456e-789 + C4 = +123.456E+789 + C5 = 1234i + C6 = "foo\n" + C7 = `bar\n` +) + +type ( + T1 int + T2 [10]int + T3 []int + T4 *int + T5 chan int + T6a chan<- int + T6b chan (<-chan int) + T6c chan<- (chan int) + T7 <-chan *ast.File + T8 struct{} + T9 struct { + a int + b, c float32 + d []string `go:"tag"` + } + T10 struct { + T8 + T9 + _ *T10 + } + T11 map[int]string + T12 interface{} + T13 interface { + m1() + m2(int) float32 + } + T14 interface { + T12 + T13 + m3(x ...struct{}) []T9 + } + T15 func() + T16 func(int) + T17 func(x int) + T18 func() float32 + T19 func() (x float32) + T20 func(...interface{}) + T21 struct{ next *T21 } + T22 struct{ link *T23 } + T23 struct{ link *T22 } + T24 *T24 + T25 *T26 + T26 *T27 + T27 *T25 + T28 func(T28) T28 +) + +var ( + V0 int + V1 = -991.0 +) + +func F1() {} +func F2(x int) {} +func F3() int { return 0 } +func F4() float32 { return 0 } +func F5(a, b, c int, u, v, w struct{ x, y T1 }, more ...interface{}) (p, q, r chan<- T10) + +func (p *T1) M1() diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue15920.go b/contribs/gnopls/internal/gcimporter/testdata/issue15920.go new file mode 100644 index 00000000000..c70f7d8267b --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue15920.go @@ -0,0 +1,11 @@ +// Copyright 2016 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 p + +// The underlying type of Error is the underlying type of error. +// Make sure we can import this again without problems. +type Error error + +func F() Error { return nil } diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue20046.go b/contribs/gnopls/internal/gcimporter/testdata/issue20046.go new file mode 100644 index 00000000000..c63ee821c95 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue20046.go @@ -0,0 +1,9 @@ +// Copyright 2017 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 p + +var V interface { + M() +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue25301.go b/contribs/gnopls/internal/gcimporter/testdata/issue25301.go new file mode 100644 index 00000000000..e3dc98b4e1f --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue25301.go @@ -0,0 +1,17 @@ +// Copyright 2018 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 issue25301 + +type ( + A = interface { + M() + } + T interface { + A + } + S struct{} +) + +func (S) M() { println("m") } diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue51836/a.go b/contribs/gnopls/internal/gcimporter/testdata/issue51836/a.go new file mode 100644 index 00000000000..e9223c9aa82 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue51836/a.go @@ -0,0 +1,8 @@ +// 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 + +type T[K any] struct { +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue51836/a/a.go b/contribs/gnopls/internal/gcimporter/testdata/issue51836/a/a.go new file mode 100644 index 00000000000..e9223c9aa82 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue51836/a/a.go @@ -0,0 +1,8 @@ +// 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 + +type T[K any] struct { +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue51836/aa.go b/contribs/gnopls/internal/gcimporter/testdata/issue51836/aa.go new file mode 100644 index 00000000000..d774be282e5 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue51836/aa.go @@ -0,0 +1,13 @@ +// 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 ( + "./a" +) + +type T[K any] struct { + t a.T[K] +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue57015.go b/contribs/gnopls/internal/gcimporter/testdata/issue57015.go new file mode 100644 index 00000000000..b6be81191f9 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue57015.go @@ -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 issue57015 + +type E error + +type X[T any] struct {} + +func F() X[interface { + E +}] { + panic(0) +} + diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue58296/a/a.go b/contribs/gnopls/internal/gcimporter/testdata/issue58296/a/a.go new file mode 100644 index 00000000000..236978a5c01 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue58296/a/a.go @@ -0,0 +1,9 @@ +// 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 a + +type A int + +func (A) f() {} diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue58296/b/b.go b/contribs/gnopls/internal/gcimporter/testdata/issue58296/b/b.go new file mode 100644 index 00000000000..8886ca57127 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue58296/b/b.go @@ -0,0 +1,11 @@ +// 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 b + +import "./a" + +type B struct { + a a.A +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/issue58296/c/c.go b/contribs/gnopls/internal/gcimporter/testdata/issue58296/c/c.go new file mode 100644 index 00000000000..bad8be81d37 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/issue58296/c/c.go @@ -0,0 +1,11 @@ +// 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 c + +import "./b" + +type C struct { + b b.B +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/p.go b/contribs/gnopls/internal/gcimporter/testdata/p.go new file mode 100644 index 00000000000..9e2e7057653 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/p.go @@ -0,0 +1,13 @@ +// Copyright 2016 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. + +// Input for TestIssue15517 + +package p + +const C = 0 + +var V int + +func F() {} diff --git a/contribs/gnopls/internal/gcimporter/testdata/versions/test.go b/contribs/gnopls/internal/gcimporter/testdata/versions/test.go new file mode 100644 index 00000000000..924f4447314 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/testdata/versions/test.go @@ -0,0 +1,27 @@ +// Copyright 2016 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 is a copy of $GOROOT/src/go/internal/gcimporter/testdata/versions.test.go. + +// To create a test case for a new export format version, +// build this package with the latest compiler and store +// the resulting .a file appropriately named in the versions +// directory. The VersionHandling test will pick it up. +// +// In the testdata/versions: +// +// go build -o test_go1.$X_$Y.a test.go +// +// with $X = Go version and $Y = export format version (e.g. 'i', 'u'). +// +// Make sure this source is extended such that it exercises +// whatever export format change has taken place. + +package test + +// Any release before and including Go 1.7 didn't encode +// the package for a blank struct field. +type BlankField struct { + _ int +} diff --git a/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.16_i.a b/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.16_i.a new file mode 100644 index 0000000000000000000000000000000000000000..35dc863e81cd71c58097b9fc92906fb095aed57e GIT binary patch literal 3464 zcmcImZ)_A*5TCs}=yk!g1Oz2U4y*{o?%lQ5yDMo3!O|imwFsyo#OvPfyY0f>?sadk z1=L^y#xMRs4EhCQNK7@T3E~Hkh!Tluh(^GOf+Pfi4;uers)>qPXLj%1U9YVW)JcCk zv%fcQ-n@Blt~<9)ww2x`Vk#wduj*`H-VyR7Qq-h8+k3`iF?Xb?skP|QBWhO2N^fL} z6H;Y+KsQ6OosGvrniZ9z@z6tyP0M6rx}Y0sNLNGiwq!e2tXw@1>s@1XYsSVFZMB*T zXF9uE*BM<MvG(=qI%go-<s{X4q9`^rEE9EM{q&CTx;(S<VOh0It6w(51L4h%Wf}Rf zZaU01Wh1O9dNya-_!f3p-ce;o4)^06-7@noBWV^0sQ?X%%M97v)S)v&t?3lRepn$u z=i1pegaCXXbgl&9J`uKc4fuzyB;LX-L`Z2?O0tTZE>*>QAFo2q(MXFo`u|pFt_WD^ zXxvbFx&i=P@_G0c^;N_cKfobGPXORJV#6d44(b_`0dk0s5esSnx@rMlLmZp}aI6St zOO8nr>ys2Ko0Uzq(a=rSDBD^g%S<OPmFuY~hNZ|x{)2;=rbwbGp2(y#t#LULO(a<| z(UMf+teHg=)|4!JHa9COQ&3o%B_qvEt(j;>O*59rv?LQTC9PQ*-N5zR<bq>q%w)Fg zFm;KW)S_!=WJ$W2v83zwOykYcEJ-Q2^K!PO78GVru=ff_S=l6GdUBRc;LFRHDVWLx zE)}J;3#OxEtSrm2zJhGTV^~y9YD!wi!bhTBOj?w#ytIh!cSR7%9lvIgs=|6fy@txk zPVYp?RxO@tbP4Q9eq{eq-{O^45wj2%5nYJgc&+b3{Tl+iwjGkL0Wcr+92`V)DQqTP z6p8?Vx4%+6vJRl3g7wIHfSbpVA^<Cr(@_6F-3C5CxEf821k1hEvEPca2=yV?_6ZO~ zgAhWDc+&-#jxQt&5No~Z0^I6S0JPAfoTU5-6!RGbt}Xz!Ye~H1(Y?IBS3UH+5t0_7 zm;wm3fHiStAwC~)lMnKp1Kf?|wrWyB@^!vV+BXT+^=viCPuKORe0V#$p);c7L*)#; zs8dm`tR^Y7r`;lZPVTeXiN6o+{_>l{d*hD`?`ysIVd1k^_nbTb%k9s;k~s4I@(WWE zgHN37JGF3U+n~~LxNWvAym{iD&Ryz*XZkN~cxnHnv!Cob)wb2x*nVjESYh9j#~&Xm z{66i@!B0<ovGtuD&e4ktjtpPfe){Y8#^WB&tx3+mcjxxaGY6v$&+GMXu<u%SKc@c_ zez)Pr8E+2@CokXe)c&FL_mn(clADU4TS-SpCB9MIf^+$eMDt&Zh!LtmL8*`TBYs8P zh6RrzUO>E9#LK7;AU;BzK%7CGL!3u^g}99P8F3Xs{Z)g9T8E(g%a?UzWMmsvC^U-w z3t1%GHVokZMo<1MvJreON4SbCh94&`HC||xFU2KaYVWaesc*_G#YF-}uSPjg1gcI@ z=)orm&!HRbas=+%gKRlpV(EVOj$)|}_)p%I;9{x>39+Q9DT0gZ$wc8S?XMh{v#tvj z*%iJE+S^#)n9EkjU4d+1jyoM66qkP2?jM)kFovbO)H8~u9Qcpm*c{NcBC|ixcLy>8 zO`<aSs9rSNM~6p@pN8mC1bE=dWKhj&$eD~F8RSz-27Xb2NCtjZxwS-9(tBewGr?dm zPTW5Uok69nIz4q(O)z-ftw^_qglq53yNlc&PnV7o2l@8+?)c{T-e^9Rh)eZaoG0yr z=PE;cL$#&Z0ikYk!Z#HJ!S8)tT+df$Wqj8b_D~8!U5@X&u8&*wHpVVZD47D(SARPJ z%pCv22}7kQgqyT40QeWc(+|J}|Iz~&6lgu_Pkl~HH0FC{o2m3oR$8%WR8E7x0d3g5 AkN^Mx literal 0 HcmV?d00001 diff --git a/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.17_i.a b/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.17_i.a new file mode 100644 index 0000000000000000000000000000000000000000..7a8ecb75c7ea06f530ae87dbae6b82d5435537d2 GIT binary patch literal 3638 zcmcInYitx%6uz^&w%f7T)<=0H>Vg%4(Aj5qx1<uN&?4B<02(32?d;6#bYOO7ncanz zgrLEM5RJjNF+^iR@P*;w4>6)5Myh`VO^~350D;5@M6`(}KB(u+o!d^A-Ovc0>^Jw^ z@1A?^x#!+9nVs4pTGGH=K9v&smUJ&z*cG4>a$0PtPM`5u%pQqE+8m31UNr+|dbJ`I z0<vhW*NlK@W#h4cYKDbyBJkiGK3&vwIiSgbX}!G*)io<u<W}ffsdHhsuC=NU1mk@@ zOM6OLBb-jIOe{|I^$e`MXByA*EiIkA#;v-kD|mlivGPGtHVt!~Xav^>O9j)^^Fhrh zD3&4WK~>VSIn#n(u%P4%vRDv<>tK#%8hM)$RFiNi9}e=Jx@fHF(iB~;?dJG(WD!TY zS4^=;fDjMix)&2}DNi=_uJ;aJOzedyc#=}hlwdj=9?1ypSISN_lB8)0{D0Y*$`i<N zIHAidRE`kR?4`I3S^=;N@H5~IA0bWG5wgR<0cdkIgd7EgY6;l}I8sMQ4^N0uPsj!b zg-3FVASi1E$;@U&LvGbILunN)wU||mLS7inr7G#BB<lG??`0yPL?oWbq%-YtF%(WD zm1LqVDaDnj5|Wfia?~;!m1IQ%QPN5>6pgfJ!WlWOD2Ys4G7*!~<6m8XkTfG>hE+48 z=@4><SS*;TVknkaP~^F`r&(9tgejmGVf>b9xQMDLNJV>I&NAhqq*ztb<uVkdH6ny3 zW|b<oZ10((AyqMPW#1|q1r2;<m8`P1DC+SToC70ODXqa-g~HS)E{YW*%))YTIT7sf zD<-MTEp<xkFDDiTss&rNc&gPVpoH0xU86FGDv$va0k=R&^g{c&hmgCVosA_zJPSw? zTaf91WiSZjJW|5e5%fR^6pdRb*+58(i=|{EEOr?QwWK&?6SPz5LSAA=0R(s=B}O`i zWELp<?T&{deoznrK!jRy<VNVAJ_E3Yk&~c<mblPvMnd7+f<B+JHSh^lZc7xNX8^;) zK*H-_MCgvd2b+!~S3%<HIGrP09f3sIG83L#?L_!lI^cYO+*eKtK%TToCm}c=lU>iY ziTg%jM^1aLCk_5T9UKO~d^AGp6kd*GV>yXY9JE8CM)tGZi1kxlFK?gy^aqp99{%Io zbg1L*A3Jv(ncMKvs~4Ua>f3&4{*<?yXYM_8>#_aq=R7Z*|M<7T9__`$rFT|sC8tkL zI`QQ*Uv+HIS1;H<^l9;}Enhr3Sp2glx_Gksob}3MsqfxA-ul_;`{op-raO0jedPFt z_a84DJTv3Np^KZ3{qRvG?%~|><n(*DZ7wzM47WV1HSSW*w>`g6`!#r=<(He^9pa9j zz2nI}gXs&nAs=9Z1H8U?oS62j0bbq7fNLo?fU|O-|0G}!052WB4dw%O0$z8p8`@6* zCjj3A&H;V`TmW1I3;})z)WAVc05kz61F*L35`-~FhKGkYVY&hU*B!qEG+JQw`bNv{ zABPT&wf+lebXmbSR%c+WHTp*DD!#G2TuY*FU`W}L7zsMTR6*SR(4eqL`apL%Vb=XX zyLO2*q=XENr7<Vh1BK$BW52LaMcSq)-H18}qj9G$-;B0x@ZwQn+reE{u#Qo>-1j1& zeYaB&@I>GEf?QUae&}+V2daN8jger?C>0_pqyGl$&A@Ru4Rhwf<l%e|o~O;=6cp^( zGOD04UNbLF$NS`C%h#ZSL$zpwLw;1?i;vWy0-t<@N5zjr02J1~7HiJ<{r)&*t3R1N zvsiMheqycPKkj<Qn})(#+M`#P>6Kb62qtWMY<FyPY;S0XyRIhXYrE|{<Z1U(bf|75 z{XVXtCgG_k9OtFqH{<zKcKf$=Zaap+HRM<x8rCwc(E{Hi6Gl|7vHatWG*|v`gTFmE upGFKPI_&Wg`1#;s_{b^mynUx6(tgmJ`UV$i%@2r{BI9@A$eNDgp7t;HV%AXr literal 0 HcmV?d00001 diff --git a/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.18.5_i.a b/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.18.5_i.a new file mode 100644 index 0000000000000000000000000000000000000000..6ed126f7e9280ed8ba79fea62bcf3695d236d40c GIT binary patch literal 4162 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.19_i.a b/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.19_i.a new file mode 100644 index 0000000000000000000000000000000000000000..ff8f5995bb8b676af52b79f36e7339f6aaca6a3c GIT binary patch literal 4026 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.20_u.a b/contribs/gnopls/internal/gcimporter/testdata/versions/test_go1.20_u.a new file mode 100644 index 0000000000000000000000000000000000000000..608dba8362402551fed063aa39a5c00417dd9849 GIT binary patch literal 4288 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/internal/gcimporter/unified_no.go b/contribs/gnopls/internal/gcimporter/unified_no.go new file mode 100644 index 00000000000..38b624cadab --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/unified_no.go @@ -0,0 +1,10 @@ +// 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 !goexperiment.unified +// +build !goexperiment.unified + +package gcimporter + +const unifiedIR = false diff --git a/contribs/gnopls/internal/gcimporter/unified_yes.go b/contribs/gnopls/internal/gcimporter/unified_yes.go new file mode 100644 index 00000000000..b5118d0b3a5 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/unified_yes.go @@ -0,0 +1,10 @@ +// 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 goexperiment.unified +// +build goexperiment.unified + +package gcimporter + +const unifiedIR = true diff --git a/contribs/gnopls/internal/gcimporter/ureader_yes.go b/contribs/gnopls/internal/gcimporter/ureader_yes.go new file mode 100644 index 00000000000..d0b96fa2d17 --- /dev/null +++ b/contribs/gnopls/internal/gcimporter/ureader_yes.go @@ -0,0 +1,740 @@ +// 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. + +// Derived from go/internal/gcimporter/ureader.go + +package gcimporter + +import ( + "fmt" + "go/token" + "go/types" + "sort" + "strings" + + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/pkgbits" +) + +// A pkgReader holds the shared state for reading a unified IR package +// description. +type pkgReader struct { + pkgbits.PkgDecoder + + fake fakeFileSet + + ctxt *types.Context + imports map[string]*types.Package // previously imported packages, indexed by path + aliases bool // create types.Alias nodes + + // lazily initialized arrays corresponding to the unified IR + // PosBase, Pkg, and Type sections, respectively. + posBases []string // position bases (i.e., file names) + pkgs []*types.Package + typs []types.Type + + // laterFns holds functions that need to be invoked at the end of + // import reading. + laterFns []func() + // laterFors is used in case of 'type A B' to ensure that B is processed before A. + laterFors map[types.Type]int + + // ifaces holds a list of constructed Interfaces, which need to have + // Complete called after importing is done. + ifaces []*types.Interface +} + +// later adds a function to be invoked at the end of import reading. +func (pr *pkgReader) later(fn func()) { + pr.laterFns = append(pr.laterFns, fn) +} + +// See cmd/compile/internal/noder.derivedInfo. +type derivedInfo struct { + idx pkgbits.Index +} + +// See cmd/compile/internal/noder.typeInfo. +type typeInfo struct { + idx pkgbits.Index + derived bool +} + +func UImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { + if !debug { + defer func() { + if x := recover(); x != nil { + err = fmt.Errorf("internal error in importing %q (%v); please report an issue", path, x) + } + }() + } + + s := string(data) + s = s[:strings.LastIndex(s, "\n$$\n")] + input := pkgbits.NewPkgDecoder(path, s) + pkg = readUnifiedPackage(fset, nil, imports, input) + return +} + +// laterFor adds a function to be invoked at the end of import reading, and records the type that function is finishing. +func (pr *pkgReader) laterFor(t types.Type, fn func()) { + if pr.laterFors == nil { + pr.laterFors = make(map[types.Type]int) + } + pr.laterFors[t] = len(pr.laterFns) + pr.laterFns = append(pr.laterFns, fn) +} + +// readUnifiedPackage reads a package description from the given +// unified IR export data decoder. +func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[string]*types.Package, input pkgbits.PkgDecoder) *types.Package { + pr := pkgReader{ + PkgDecoder: input, + + fake: fakeFileSet{ + fset: fset, + files: make(map[string]*fileInfo), + }, + + ctxt: ctxt, + imports: imports, + aliases: aliases.Enabled(), + + posBases: make([]string, input.NumElems(pkgbits.RelocPosBase)), + pkgs: make([]*types.Package, input.NumElems(pkgbits.RelocPkg)), + typs: make([]types.Type, input.NumElems(pkgbits.RelocType)), + } + defer pr.fake.setLines() + + r := pr.newReader(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic) + pkg := r.pkg() + if r.Version().Has(pkgbits.HasInit) { + r.Bool() + } + + for i, n := 0, r.Len(); i < n; i++ { + // As if r.obj(), but avoiding the Scope.Lookup call, + // to avoid eager loading of imports. + r.Sync(pkgbits.SyncObject) + if r.Version().Has(pkgbits.DerivedFuncInstance) { + assert(!r.Bool()) + } + r.p.objIdx(r.Reloc(pkgbits.RelocObj)) + assert(r.Len() == 0) + } + + r.Sync(pkgbits.SyncEOF) + + for _, fn := range pr.laterFns { + fn() + } + + for _, iface := range pr.ifaces { + iface.Complete() + } + + // Imports() of pkg are all of the transitive packages that were loaded. + var imps []*types.Package + for _, imp := range pr.pkgs { + if imp != nil && imp != pkg { + imps = append(imps, imp) + } + } + sort.Sort(byPath(imps)) + pkg.SetImports(imps) + + pkg.MarkComplete() + return pkg +} + +// A reader holds the state for reading a single unified IR element +// within a package. +type reader struct { + pkgbits.Decoder + + p *pkgReader + + dict *readerDict +} + +// A readerDict holds the state for type parameters that parameterize +// the current unified IR element. +type readerDict struct { + // bounds is a slice of typeInfos corresponding to the underlying + // bounds of the element's type parameters. + bounds []typeInfo + + // tparams is a slice of the constructed TypeParams for the element. + tparams []*types.TypeParam + + // derived is a slice of types derived from tparams, which may be + // instantiated while reading the current element. + derived []derivedInfo + derivedTypes []types.Type // lazily instantiated from derived +} + +func (pr *pkgReader) newReader(k pkgbits.RelocKind, idx pkgbits.Index, marker pkgbits.SyncMarker) *reader { + return &reader{ + Decoder: pr.NewDecoder(k, idx, marker), + p: pr, + } +} + +func (pr *pkgReader) tempReader(k pkgbits.RelocKind, idx pkgbits.Index, marker pkgbits.SyncMarker) *reader { + return &reader{ + Decoder: pr.TempDecoder(k, idx, marker), + p: pr, + } +} + +func (pr *pkgReader) retireReader(r *reader) { + pr.RetireDecoder(&r.Decoder) +} + +// @@@ Positions + +func (r *reader) pos() token.Pos { + r.Sync(pkgbits.SyncPos) + if !r.Bool() { + return token.NoPos + } + + // TODO(mdempsky): Delta encoding. + posBase := r.posBase() + line := r.Uint() + col := r.Uint() + return r.p.fake.pos(posBase, int(line), int(col)) +} + +func (r *reader) posBase() string { + return r.p.posBaseIdx(r.Reloc(pkgbits.RelocPosBase)) +} + +func (pr *pkgReader) posBaseIdx(idx pkgbits.Index) string { + if b := pr.posBases[idx]; b != "" { + return b + } + + var filename string + { + r := pr.tempReader(pkgbits.RelocPosBase, idx, pkgbits.SyncPosBase) + + // Within types2, position bases have a lot more details (e.g., + // keeping track of where //line directives appeared exactly). + // + // For go/types, we just track the file name. + + filename = r.String() + + if r.Bool() { // file base + // Was: "b = token.NewTrimmedFileBase(filename, true)" + } else { // line base + pos := r.pos() + line := r.Uint() + col := r.Uint() + + // Was: "b = token.NewLineBase(pos, filename, true, line, col)" + _, _, _ = pos, line, col + } + pr.retireReader(r) + } + b := filename + pr.posBases[idx] = b + return b +} + +// @@@ Packages + +func (r *reader) pkg() *types.Package { + r.Sync(pkgbits.SyncPkg) + return r.p.pkgIdx(r.Reloc(pkgbits.RelocPkg)) +} + +func (pr *pkgReader) pkgIdx(idx pkgbits.Index) *types.Package { + // TODO(mdempsky): Consider using some non-nil pointer to indicate + // the universe scope, so we don't need to keep re-reading it. + if pkg := pr.pkgs[idx]; pkg != nil { + return pkg + } + + pkg := pr.newReader(pkgbits.RelocPkg, idx, pkgbits.SyncPkgDef).doPkg() + pr.pkgs[idx] = pkg + return pkg +} + +func (r *reader) doPkg() *types.Package { + path := r.String() + switch path { + case "": + path = r.p.PkgPath() + case "builtin": + return nil // universe + case "unsafe": + return types.Unsafe + } + + if pkg := r.p.imports[path]; pkg != nil { + return pkg + } + + name := r.String() + + pkg := types.NewPackage(path, name) + r.p.imports[path] = pkg + + return pkg +} + +// @@@ Types + +func (r *reader) typ() types.Type { + return r.p.typIdx(r.typInfo(), r.dict) +} + +func (r *reader) typInfo() typeInfo { + r.Sync(pkgbits.SyncType) + if r.Bool() { + return typeInfo{idx: pkgbits.Index(r.Len()), derived: true} + } + return typeInfo{idx: r.Reloc(pkgbits.RelocType), derived: false} +} + +func (pr *pkgReader) typIdx(info typeInfo, dict *readerDict) types.Type { + idx := info.idx + var where *types.Type + if info.derived { + where = &dict.derivedTypes[idx] + idx = dict.derived[idx].idx + } else { + where = &pr.typs[idx] + } + + if typ := *where; typ != nil { + return typ + } + + var typ types.Type + { + r := pr.tempReader(pkgbits.RelocType, idx, pkgbits.SyncTypeIdx) + r.dict = dict + + typ = r.doTyp() + assert(typ != nil) + pr.retireReader(r) + } + // See comment in pkgReader.typIdx explaining how this happens. + if prev := *where; prev != nil { + return prev + } + + *where = typ + return typ +} + +func (r *reader) doTyp() (res types.Type) { + switch tag := pkgbits.CodeType(r.Code(pkgbits.SyncType)); tag { + default: + errorf("unhandled type tag: %v", tag) + panic("unreachable") + + case pkgbits.TypeBasic: + return types.Typ[r.Len()] + + case pkgbits.TypeNamed: + obj, targs := r.obj() + name := obj.(*types.TypeName) + if len(targs) != 0 { + t, _ := types.Instantiate(r.p.ctxt, name.Type(), targs, false) + return t + } + return name.Type() + + case pkgbits.TypeTypeParam: + return r.dict.tparams[r.Len()] + + case pkgbits.TypeArray: + len := int64(r.Uint64()) + return types.NewArray(r.typ(), len) + case pkgbits.TypeChan: + dir := types.ChanDir(r.Len()) + return types.NewChan(dir, r.typ()) + case pkgbits.TypeMap: + return types.NewMap(r.typ(), r.typ()) + case pkgbits.TypePointer: + return types.NewPointer(r.typ()) + case pkgbits.TypeSignature: + return r.signature(nil, nil, nil) + case pkgbits.TypeSlice: + return types.NewSlice(r.typ()) + case pkgbits.TypeStruct: + return r.structType() + case pkgbits.TypeInterface: + return r.interfaceType() + case pkgbits.TypeUnion: + return r.unionType() + } +} + +func (r *reader) structType() *types.Struct { + fields := make([]*types.Var, r.Len()) + var tags []string + for i := range fields { + pos := r.pos() + pkg, name := r.selector() + ftyp := r.typ() + tag := r.String() + embedded := r.Bool() + + fields[i] = types.NewField(pos, pkg, name, ftyp, embedded) + if tag != "" { + for len(tags) < i { + tags = append(tags, "") + } + tags = append(tags, tag) + } + } + return types.NewStruct(fields, tags) +} + +func (r *reader) unionType() *types.Union { + terms := make([]*types.Term, r.Len()) + for i := range terms { + terms[i] = types.NewTerm(r.Bool(), r.typ()) + } + return types.NewUnion(terms) +} + +func (r *reader) interfaceType() *types.Interface { + methods := make([]*types.Func, r.Len()) + embeddeds := make([]types.Type, r.Len()) + implicit := len(methods) == 0 && len(embeddeds) == 1 && r.Bool() + + for i := range methods { + pos := r.pos() + pkg, name := r.selector() + mtyp := r.signature(nil, nil, nil) + methods[i] = types.NewFunc(pos, pkg, name, mtyp) + } + + for i := range embeddeds { + embeddeds[i] = r.typ() + } + + iface := types.NewInterfaceType(methods, embeddeds) + if implicit { + iface.MarkImplicit() + } + + // We need to call iface.Complete(), but if there are any embedded + // defined types, then we may not have set their underlying + // interface type yet. So we need to defer calling Complete until + // after we've called SetUnderlying everywhere. + // + // TODO(mdempsky): After CL 424876 lands, it should be safe to call + // iface.Complete() immediately. + r.p.ifaces = append(r.p.ifaces, iface) + + return iface +} + +func (r *reader) signature(recv *types.Var, rtparams, tparams []*types.TypeParam) *types.Signature { + r.Sync(pkgbits.SyncSignature) + + params := r.params() + results := r.params() + variadic := r.Bool() + + return types.NewSignatureType(recv, rtparams, tparams, params, results, variadic) +} + +func (r *reader) params() *types.Tuple { + r.Sync(pkgbits.SyncParams) + + params := make([]*types.Var, r.Len()) + for i := range params { + params[i] = r.param() + } + + return types.NewTuple(params...) +} + +func (r *reader) param() *types.Var { + r.Sync(pkgbits.SyncParam) + + pos := r.pos() + pkg, name := r.localIdent() + typ := r.typ() + + return types.NewParam(pos, pkg, name, typ) +} + +// @@@ Objects + +func (r *reader) obj() (types.Object, []types.Type) { + r.Sync(pkgbits.SyncObject) + + if r.Version().Has(pkgbits.DerivedFuncInstance) { + assert(!r.Bool()) + } + + pkg, name := r.p.objIdx(r.Reloc(pkgbits.RelocObj)) + obj := pkgScope(pkg).Lookup(name) + + targs := make([]types.Type, r.Len()) + for i := range targs { + targs[i] = r.typ() + } + + return obj, targs +} + +func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { + + var objPkg *types.Package + var objName string + var tag pkgbits.CodeObj + { + rname := pr.tempReader(pkgbits.RelocName, idx, pkgbits.SyncObject1) + + objPkg, objName = rname.qualifiedIdent() + assert(objName != "") + + tag = pkgbits.CodeObj(rname.Code(pkgbits.SyncCodeObj)) + pr.retireReader(rname) + } + + if tag == pkgbits.ObjStub { + assert(objPkg == nil || objPkg == types.Unsafe) + return objPkg, objName + } + + // Ignore local types promoted to global scope (#55110). + if _, suffix := splitVargenSuffix(objName); suffix != "" { + return objPkg, objName + } + + if objPkg.Scope().Lookup(objName) == nil { + dict := pr.objDictIdx(idx) + + r := pr.newReader(pkgbits.RelocObj, idx, pkgbits.SyncObject1) + r.dict = dict + + declare := func(obj types.Object) { + objPkg.Scope().Insert(obj) + } + + switch tag { + default: + panic("weird") + + case pkgbits.ObjAlias: + pos := r.pos() + var tparams []*types.TypeParam + if r.Version().Has(pkgbits.AliasTypeParamNames) { + tparams = r.typeParamNames() + } + typ := r.typ() + declare(aliases.NewAlias(r.p.aliases, pos, objPkg, objName, typ, tparams)) + + case pkgbits.ObjConst: + pos := r.pos() + typ := r.typ() + val := r.Value() + declare(types.NewConst(pos, objPkg, objName, typ, val)) + + case pkgbits.ObjFunc: + pos := r.pos() + tparams := r.typeParamNames() + sig := r.signature(nil, nil, tparams) + declare(types.NewFunc(pos, objPkg, objName, sig)) + + case pkgbits.ObjType: + pos := r.pos() + + obj := types.NewTypeName(pos, objPkg, objName, nil) + named := types.NewNamed(obj, nil, nil) + declare(obj) + + named.SetTypeParams(r.typeParamNames()) + + setUnderlying := func(underlying types.Type) { + // If the underlying type is an interface, we need to + // duplicate its methods so we can replace the receiver + // parameter's type (#49906). + if iface, ok := aliases.Unalias(underlying).(*types.Interface); ok && iface.NumExplicitMethods() != 0 { + methods := make([]*types.Func, iface.NumExplicitMethods()) + for i := range methods { + fn := iface.ExplicitMethod(i) + sig := fn.Type().(*types.Signature) + + recv := types.NewVar(fn.Pos(), fn.Pkg(), "", named) + methods[i] = types.NewFunc(fn.Pos(), fn.Pkg(), fn.Name(), types.NewSignature(recv, sig.Params(), sig.Results(), sig.Variadic())) + } + + embeds := make([]types.Type, iface.NumEmbeddeds()) + for i := range embeds { + embeds[i] = iface.EmbeddedType(i) + } + + newIface := types.NewInterfaceType(methods, embeds) + r.p.ifaces = append(r.p.ifaces, newIface) + underlying = newIface + } + + named.SetUnderlying(underlying) + } + + // Since go.dev/cl/455279, we can assume rhs.Underlying() will + // always be non-nil. However, to temporarily support users of + // older snapshot releases, we continue to fallback to the old + // behavior for now. + // + // TODO(mdempsky): Remove fallback code and simplify after + // allowing time for snapshot users to upgrade. + rhs := r.typ() + if underlying := rhs.Underlying(); underlying != nil { + setUnderlying(underlying) + } else { + pk := r.p + pk.laterFor(named, func() { + // First be sure that the rhs is initialized, if it needs to be initialized. + delete(pk.laterFors, named) // prevent cycles + if i, ok := pk.laterFors[rhs]; ok { + f := pk.laterFns[i] + pk.laterFns[i] = func() {} // function is running now, so replace it with a no-op + f() // initialize RHS + } + setUnderlying(rhs.Underlying()) + }) + } + + for i, n := 0, r.Len(); i < n; i++ { + named.AddMethod(r.method()) + } + + case pkgbits.ObjVar: + pos := r.pos() + typ := r.typ() + declare(types.NewVar(pos, objPkg, objName, typ)) + } + } + + return objPkg, objName +} + +func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict { + + var dict readerDict + + { + r := pr.tempReader(pkgbits.RelocObjDict, idx, pkgbits.SyncObject1) + if implicits := r.Len(); implicits != 0 { + errorf("unexpected object with %v implicit type parameter(s)", implicits) + } + + dict.bounds = make([]typeInfo, r.Len()) + for i := range dict.bounds { + dict.bounds[i] = r.typInfo() + } + + dict.derived = make([]derivedInfo, r.Len()) + dict.derivedTypes = make([]types.Type, len(dict.derived)) + for i := range dict.derived { + dict.derived[i] = derivedInfo{idx: r.Reloc(pkgbits.RelocType)} + if r.Version().Has(pkgbits.DerivedInfoNeeded) { + assert(!r.Bool()) + } + } + + pr.retireReader(r) + } + // function references follow, but reader doesn't need those + + return &dict +} + +func (r *reader) typeParamNames() []*types.TypeParam { + r.Sync(pkgbits.SyncTypeParamNames) + + // Note: This code assumes it only processes objects without + // implement type parameters. This is currently fine, because + // reader is only used to read in exported declarations, which are + // always package scoped. + + if len(r.dict.bounds) == 0 { + return nil + } + + // Careful: Type parameter lists may have cycles. To allow for this, + // we construct the type parameter list in two passes: first we + // create all the TypeNames and TypeParams, then we construct and + // set the bound type. + + r.dict.tparams = make([]*types.TypeParam, len(r.dict.bounds)) + for i := range r.dict.bounds { + pos := r.pos() + pkg, name := r.localIdent() + + tname := types.NewTypeName(pos, pkg, name, nil) + r.dict.tparams[i] = types.NewTypeParam(tname, nil) + } + + typs := make([]types.Type, len(r.dict.bounds)) + for i, bound := range r.dict.bounds { + typs[i] = r.p.typIdx(bound, r.dict) + } + + // TODO(mdempsky): This is subtle, elaborate further. + // + // We have to save tparams outside of the closure, because + // typeParamNames() can be called multiple times with the same + // dictionary instance. + // + // Also, this needs to happen later to make sure SetUnderlying has + // been called. + // + // TODO(mdempsky): Is it safe to have a single "later" slice or do + // we need to have multiple passes? See comments on CL 386002 and + // go.dev/issue/52104. + tparams := r.dict.tparams + r.p.later(func() { + for i, typ := range typs { + tparams[i].SetConstraint(typ) + } + }) + + return r.dict.tparams +} + +func (r *reader) method() *types.Func { + r.Sync(pkgbits.SyncMethod) + pos := r.pos() + pkg, name := r.selector() + + rparams := r.typeParamNames() + sig := r.signature(r.param(), rparams, nil) + + _ = r.pos() // TODO(mdempsky): Remove; this is a hacker for linker.go. + return types.NewFunc(pos, pkg, name, sig) +} + +func (r *reader) qualifiedIdent() (*types.Package, string) { return r.ident(pkgbits.SyncSym) } +func (r *reader) localIdent() (*types.Package, string) { return r.ident(pkgbits.SyncLocalIdent) } +func (r *reader) selector() (*types.Package, string) { return r.ident(pkgbits.SyncSelector) } + +func (r *reader) ident(marker pkgbits.SyncMarker) (*types.Package, string) { + r.Sync(marker) + return r.pkg(), r.String() +} + +// pkgScope returns pkg.Scope(). +// If pkg is nil, it returns types.Universe instead. +// +// TODO(mdempsky): Remove after x/tools can depend on Go 1.19. +func pkgScope(pkg *types.Package) *types.Scope { + if pkg != nil { + return pkg.Scope() + } + return types.Universe +} diff --git a/contribs/gnopls/internal/gocommand/invoke.go b/contribs/gnopls/internal/gocommand/invoke.go new file mode 100644 index 00000000000..e4f1dab1498 --- /dev/null +++ b/contribs/gnopls/internal/gocommand/invoke.go @@ -0,0 +1,555 @@ +// 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 gocommand is a helper for calling the go command. +package gocommand + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "reflect" + "regexp" + "runtime" + "strconv" + "strings" + "sync" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// An Runner will run go command invocations and serialize +// them if it sees a concurrency error. +type Runner struct { + // once guards the runner initialization. + once sync.Once + + // inFlight tracks available workers. + inFlight chan struct{} + + // serialized guards the ability to run a go command serially, + // to avoid deadlocks when claiming workers. + serialized chan struct{} +} + +const maxInFlight = 10 + +func (runner *Runner) initialize() { + runner.once.Do(func() { + runner.inFlight = make(chan struct{}, maxInFlight) + runner.serialized = make(chan struct{}, 1) + }) +} + +// 1.13: go: updates to go.mod needed, but contents have changed +// 1.14: go: updating go.mod: existing contents have changed since last read +var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) + +// event keys for go command invocations +var ( + verb = keys.NewString("verb", "go command verb") + directory = keys.NewString("directory", "") +) + +func invLabels(inv Invocation) []label.Label { + return []label.Label{verb.Of(inv.Verb), directory.Of(inv.WorkingDir)} +} + +// Run is a convenience wrapper around RunRaw. +// It returns only stdout and a "friendly" error. +func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { + ctx, done := event.Start(ctx, "gocommand.Runner.Run", invLabels(inv)...) + defer done() + + stdout, _, friendly, _ := runner.RunRaw(ctx, inv) + return stdout, friendly +} + +// RunPiped runs the invocation serially, always waiting for any concurrent +// invocations to complete first. +func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error { + ctx, done := event.Start(ctx, "gocommand.Runner.RunPiped", invLabels(inv)...) + defer done() + + _, err := runner.runPiped(ctx, inv, stdout, stderr) + return err +} + +// RunRaw runs the invocation, serializing requests only if they fight over +// go.mod changes. +// Postcondition: both error results have same nilness. +func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { + ctx, done := event.Start(ctx, "gocommand.Runner.RunRaw", invLabels(inv)...) + defer done() + // Make sure the runner is always initialized. + runner.initialize() + + // First, try to run the go command concurrently. + stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv) + + // If we encounter a load concurrency error, we need to retry serially. + if friendlyErr != nil && modConcurrencyError.MatchString(friendlyErr.Error()) { + event.Error(ctx, "Load concurrency error, will retry serially", err) + + // Run serially by calling runPiped. + stdout.Reset() + stderr.Reset() + friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr) + } + + return stdout, stderr, friendlyErr, err +} + +// Postcondition: both error results have same nilness. +func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { + // Wait for 1 worker to become available. + select { + case <-ctx.Done(): + return nil, nil, ctx.Err(), ctx.Err() + case runner.inFlight <- struct{}{}: + defer func() { <-runner.inFlight }() + } + + stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} + friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr) + return stdout, stderr, friendlyErr, err +} + +// Postcondition: both error results have same nilness. +func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) { + // Make sure the runner is always initialized. + runner.initialize() + + // Acquire the serialization lock. This avoids deadlocks between two + // runPiped commands. + select { + case <-ctx.Done(): + return ctx.Err(), ctx.Err() + case runner.serialized <- struct{}{}: + defer func() { <-runner.serialized }() + } + + // Wait for all in-progress go commands to return before proceeding, + // to avoid load concurrency errors. + for i := 0; i < maxInFlight; i++ { + select { + case <-ctx.Done(): + return ctx.Err(), ctx.Err() + case runner.inFlight <- struct{}{}: + // Make sure we always "return" any workers we took. + defer func() { <-runner.inFlight }() + } + } + + return inv.runWithFriendlyError(ctx, stdout, stderr) +} + +// An Invocation represents a call to the go command. +type Invocation struct { + Verb string + Args []string + BuildFlags []string + + // If ModFlag is set, the go command is invoked with -mod=ModFlag. + // TODO(rfindley): remove, in favor of Args. + ModFlag string + + // If ModFile is set, the go command is invoked with -modfile=ModFile. + // TODO(rfindley): remove, in favor of Args. + ModFile string + + // Overlay is the name of the JSON overlay file that describes + // unsaved editor buffers; see [WriteOverlays]. + // If set, the go command is invoked with -overlay=Overlay. + // TODO(rfindley): remove, in favor of Args. + Overlay string + + // If CleanEnv is set, the invocation will run only with the environment + // in Env, not starting with os.Environ. + CleanEnv bool + Env []string + WorkingDir string + Logf func(format string, args ...interface{}) +} + +// Postcondition: both error results have same nilness. +func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) { + rawError = i.run(ctx, stdout, stderr) + if rawError != nil { + friendlyError = rawError + // Check for 'go' executable not being found. + if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { + friendlyError = fmt.Errorf("go command required, not found: %v", ee) + } + if ctx.Err() != nil { + friendlyError = ctx.Err() + } + friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) + } + return +} + +// logf logs if i.Logf is non-nil. +func (i *Invocation) logf(format string, args ...any) { + if i.Logf != nil { + i.Logf(format, args...) + } +} + +func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { + goArgs := []string{i.Verb} + + appendModFile := func() { + if i.ModFile != "" { + goArgs = append(goArgs, "-modfile="+i.ModFile) + } + } + appendModFlag := func() { + if i.ModFlag != "" { + goArgs = append(goArgs, "-mod="+i.ModFlag) + } + } + appendOverlayFlag := func() { + if i.Overlay != "" { + goArgs = append(goArgs, "-overlay="+i.Overlay) + } + } + + switch i.Verb { + case "env", "version": + goArgs = append(goArgs, i.Args...) + case "mod": + // mod needs the sub-verb before flags. + goArgs = append(goArgs, i.Args[0]) + appendModFile() + goArgs = append(goArgs, i.Args[1:]...) + case "get": + goArgs = append(goArgs, i.BuildFlags...) + appendModFile() + goArgs = append(goArgs, i.Args...) + + default: // notably list and build. + goArgs = append(goArgs, i.BuildFlags...) + appendModFile() + appendModFlag() + appendOverlayFlag() + goArgs = append(goArgs, i.Args...) + } + cmd := exec.Command("go", goArgs...) + cmd.Stdout = stdout + cmd.Stderr = stderr + + // cmd.WaitDelay was added only in go1.20 (see #50436). + if waitDelay := reflect.ValueOf(cmd).Elem().FieldByName("WaitDelay"); waitDelay.IsValid() { + // https://go.dev/issue/59541: don't wait forever copying stderr + // after the command has exited. + // After CL 484741 we copy stdout manually, so we we'll stop reading that as + // soon as ctx is done. However, we also don't want to wait around forever + // for stderr. Give a much-longer-than-reasonable delay and then assume that + // something has wedged in the kernel or runtime. + waitDelay.Set(reflect.ValueOf(30 * time.Second)) + } + + // The cwd gets resolved to the real path. On Darwin, where + // /tmp is a symlink, this breaks anything that expects the + // working directory to keep the original path, including the + // go command when dealing with modules. + // + // os.Getwd has a special feature where if the cwd and the PWD + // are the same node then it trusts the PWD, so by setting it + // in the env for the child process we fix up all the paths + // returned by the go command. + if !i.CleanEnv { + cmd.Env = os.Environ() + } + cmd.Env = append(cmd.Env, i.Env...) + if i.WorkingDir != "" { + cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) + cmd.Dir = i.WorkingDir + } + + debugStr := cmdDebugStr(cmd) + i.logf("starting %v", debugStr) + start := time.Now() + defer func() { + i.logf("%s for %v", time.Since(start), debugStr) + }() + + return runCmdContext(ctx, cmd) +} + +// DebugHangingGoCommands may be set by tests to enable additional +// instrumentation (including panics) for debugging hanging Go commands. +// +// See golang/go#54461 for details. +var DebugHangingGoCommands = false + +// runCmdContext is like exec.CommandContext except it sends os.Interrupt +// before os.Kill. +func runCmdContext(ctx context.Context, cmd *exec.Cmd) (err error) { + // If cmd.Stdout is not an *os.File, the exec package will create a pipe and + // copy it to the Writer in a goroutine until the process has finished and + // either the pipe reaches EOF or command's WaitDelay expires. + // + // However, the output from 'go list' can be quite large, and we don't want to + // keep reading (and allocating buffers) if we've already decided we don't + // care about the output. We don't want to wait for the process to finish, and + // we don't wait to wait for the WaitDelay to expire either. + // + // Instead, if cmd.Stdout requires a copying goroutine we explicitly replace + // it with a pipe (which is an *os.File), which we can close in order to stop + // copying output as soon as we realize we don't care about it. + var stdoutW *os.File + if cmd.Stdout != nil { + if _, ok := cmd.Stdout.(*os.File); !ok { + var stdoutR *os.File + stdoutR, stdoutW, err = os.Pipe() + if err != nil { + return err + } + prevStdout := cmd.Stdout + cmd.Stdout = stdoutW + + stdoutErr := make(chan error, 1) + go func() { + _, err := io.Copy(prevStdout, stdoutR) + if err != nil { + err = fmt.Errorf("copying stdout: %w", err) + } + stdoutErr <- err + }() + defer func() { + // We started a goroutine to copy a stdout pipe. + // Wait for it to finish, or terminate it if need be. + var err2 error + select { + case err2 = <-stdoutErr: + stdoutR.Close() + case <-ctx.Done(): + stdoutR.Close() + // Per https://pkg.go.dev/os#File.Close, the call to stdoutR.Close + // should cause the Read call in io.Copy to unblock and return + // immediately, but we still need to receive from stdoutErr to confirm + // that it has happened. + <-stdoutErr + err2 = ctx.Err() + } + if err == nil { + err = err2 + } + }() + + // Per https://pkg.go.dev/os/exec#Cmd, “If Stdout and Stderr are the + // same writer, and have a type that can be compared with ==, at most + // one goroutine at a time will call Write.” + // + // Since we're starting a goroutine that writes to cmd.Stdout, we must + // also update cmd.Stderr so that it still holds. + func() { + defer func() { recover() }() + if cmd.Stderr == prevStdout { + cmd.Stderr = cmd.Stdout + } + }() + } + } + + startTime := time.Now() + err = cmd.Start() + if stdoutW != nil { + // The child process has inherited the pipe file, + // so close the copy held in this process. + stdoutW.Close() + stdoutW = nil + } + if err != nil { + return err + } + + resChan := make(chan error, 1) + go func() { + resChan <- cmd.Wait() + }() + + // If we're interested in debugging hanging Go commands, stop waiting after a + // minute and panic with interesting information. + debug := DebugHangingGoCommands + if debug { + timer := time.NewTimer(1 * time.Minute) + defer timer.Stop() + select { + case err := <-resChan: + return err + case <-timer.C: + HandleHangingGoCommand(startTime, cmd) + case <-ctx.Done(): + } + } else { + select { + case err := <-resChan: + return err + case <-ctx.Done(): + } + } + + // Cancelled. Interrupt and see if it ends voluntarily. + if err := cmd.Process.Signal(os.Interrupt); err == nil { + // (We used to wait only 1s but this proved + // fragile on loaded builder machines.) + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + select { + case err := <-resChan: + return err + case <-timer.C: + } + } + + // Didn't shut down in response to interrupt. Kill it hard. + // TODO(rfindley): per advice from bcmills@, it may be better to send SIGQUIT + // on certain platforms, such as unix. + if err := cmd.Process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) && debug { + log.Printf("error killing the Go command: %v", err) + } + + return <-resChan +} + +func HandleHangingGoCommand(start time.Time, cmd *exec.Cmd) { + switch runtime.GOOS { + case "linux", "darwin", "freebsd", "netbsd": + fmt.Fprintln(os.Stderr, `DETECTED A HANGING GO COMMAND + +The gopls test runner has detected a hanging go command. In order to debug +this, the output of ps and lsof/fstat is printed below. + +See golang/go#54461 for more details.`) + + fmt.Fprintln(os.Stderr, "\nps axo ppid,pid,command:") + fmt.Fprintln(os.Stderr, "-------------------------") + psCmd := exec.Command("ps", "axo", "ppid,pid,command") + psCmd.Stdout = os.Stderr + psCmd.Stderr = os.Stderr + if err := psCmd.Run(); err != nil { + panic(fmt.Sprintf("running ps: %v", err)) + } + + listFiles := "lsof" + if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { + listFiles = "fstat" + } + + fmt.Fprintln(os.Stderr, "\n"+listFiles+":") + fmt.Fprintln(os.Stderr, "-----") + listFilesCmd := exec.Command(listFiles) + listFilesCmd.Stdout = os.Stderr + listFilesCmd.Stderr = os.Stderr + if err := listFilesCmd.Run(); err != nil { + panic(fmt.Sprintf("running %s: %v", listFiles, err)) + } + } + panic(fmt.Sprintf("detected hanging go command (golang/go#54461); waited %s\n\tcommand:%s\n\tpid:%d", time.Since(start), cmd, cmd.Process.Pid)) +} + +func cmdDebugStr(cmd *exec.Cmd) string { + env := make(map[string]string) + for _, kv := range cmd.Env { + split := strings.SplitN(kv, "=", 2) + if len(split) == 2 { + k, v := split[0], split[1] + env[k] = v + } + } + + var args []string + for _, arg := range cmd.Args { + quoted := strconv.Quote(arg) + if quoted[1:len(quoted)-1] != arg || strings.Contains(arg, " ") { + args = append(args, quoted) + } else { + args = append(args, arg) + } + } + return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], strings.Join(args, " ")) +} + +// WriteOverlays writes each value in the overlay (see the Overlay +// field of go/packages.Config) to a temporary file and returns the name +// of a JSON file describing the mapping that is suitable for the "go +// list -overlay" flag. +// +// On success, the caller must call the cleanup function exactly once +// when the files are no longer needed. +func WriteOverlays(overlay map[string][]byte) (filename string, cleanup func(), err error) { + // Do nothing if there are no overlays in the config. + if len(overlay) == 0 { + return "", func() {}, nil + } + + dir, err := os.MkdirTemp("", "gocommand-*") + if err != nil { + return "", nil, err + } + + // The caller must clean up this directory, + // unless this function returns an error. + // (The cleanup operand of each return + // statement below is ignored.) + defer func() { + cleanup = func() { + os.RemoveAll(dir) + } + if err != nil { + cleanup() + cleanup = nil + } + }() + + // Write each map entry to a temporary file. + overlays := make(map[string]string) + for k, v := range overlay { + // Use a unique basename for each file (001-foo.go), + // to avoid creating nested directories. + base := fmt.Sprintf("%d-%s", 1+len(overlays), filepath.Base(k)) + filename := filepath.Join(dir, base) + err := os.WriteFile(filename, v, 0666) + if err != nil { + return "", nil, err + } + overlays[k] = filename + } + + // Write the JSON overlay file that maps logical file names to temp files. + // + // OverlayJSON is the format overlay files are expected to be in. + // The Replace map maps from overlaid paths to replacement paths: + // the Go command will forward all reads trying to open + // each overlaid path to its replacement path, or consider the overlaid + // path not to exist if the replacement path is empty. + // + // From golang/go#39958. + type OverlayJSON struct { + Replace map[string]string `json:"replace,omitempty"` + } + b, err := json.Marshal(OverlayJSON{Replace: overlays}) + if err != nil { + return "", nil, err + } + filename = filepath.Join(dir, "overlay.json") + if err := os.WriteFile(filename, b, 0666); err != nil { + return "", nil, err + } + + return filename, nil, nil +} diff --git a/contribs/gnopls/internal/gocommand/invoke_test.go b/contribs/gnopls/internal/gocommand/invoke_test.go new file mode 100644 index 00000000000..36be0de0dc7 --- /dev/null +++ b/contribs/gnopls/internal/gocommand/invoke_test.go @@ -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 gocommand_test + +import ( + "context" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +func TestGoVersion(t *testing.T) { + testenv.NeedsTool(t, "go") + + inv := gocommand.Invocation{ + Verb: "version", + } + gocmdRunner := &gocommand.Runner{} + if _, err := gocmdRunner.Run(context.Background(), inv); err != nil { + t.Error(err) + } +} diff --git a/contribs/gnopls/internal/gocommand/vendor.go b/contribs/gnopls/internal/gocommand/vendor.go new file mode 100644 index 00000000000..e38d1fb4888 --- /dev/null +++ b/contribs/gnopls/internal/gocommand/vendor.go @@ -0,0 +1,163 @@ +// 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 gocommand + +import ( + "bytes" + "context" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "golang.org/x/mod/semver" +) + +// ModuleJSON holds information about a module. +type ModuleJSON struct { + Path string // module path + Version string // module version + Versions []string // available module versions (with -versions) + Replace *ModuleJSON // replaced by this module + Time *time.Time // time version was created + Update *ModuleJSON // available update, if any (with -u) + Main bool // is this the main module? + Indirect bool // is this module only an indirect dependency of main module? + Dir string // directory holding files for this module, if any + GoMod string // path to go.mod file used when loading this module, if any + GoVersion string // go version used in module +} + +var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) + +// VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands +// with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields, +// of which only Verb and Args are modified to run the appropriate Go command. +// Inspired by setDefaultBuildMod in modload/init.go +func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (bool, *ModuleJSON, error) { + mainMod, go114, err := getMainModuleAnd114(ctx, inv, r) + if err != nil { + return false, nil, err + } + + // We check the GOFLAGS to see if there is anything overridden or not. + inv.Verb = "env" + inv.Args = []string{"GOFLAGS"} + stdout, err := r.Run(ctx, inv) + if err != nil { + return false, nil, err + } + goflags := string(bytes.TrimSpace(stdout.Bytes())) + matches := modFlagRegexp.FindStringSubmatch(goflags) + var modFlag string + if len(matches) != 0 { + modFlag = matches[1] + } + // Don't override an explicit '-mod=' argument. + if modFlag == "vendor" { + return true, mainMod, nil + } else if modFlag != "" { + return false, nil, nil + } + if mainMod == nil || !go114 { + return false, nil, nil + } + // Check 1.14's automatic vendor mode. + if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() { + if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 { + // The Go version is at least 1.14, and a vendor directory exists. + // Set -mod=vendor by default. + return true, mainMod, nil + } + } + return false, nil, nil +} + +// getMainModuleAnd114 gets one of the main modules' information and whether the +// go command in use is 1.14+. This is the information needed to figure out +// if vendoring should be enabled. +func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) { + const format = `{{.Path}} +{{.Dir}} +{{.GoMod}} +{{.GoVersion}} +{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}} +` + inv.Verb = "list" + inv.Args = []string{"-m", "-f", format} + stdout, err := r.Run(ctx, inv) + if err != nil { + return nil, false, err + } + + lines := strings.Split(stdout.String(), "\n") + if len(lines) < 5 { + return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String()) + } + mod := &ModuleJSON{ + Path: lines[0], + Dir: lines[1], + GoMod: lines[2], + GoVersion: lines[3], + Main: true, + } + return mod, lines[4] == "go1.14", nil +} + +// WorkspaceVendorEnabled reports whether workspace vendoring is enabled. It takes a *Runner to execute Go commands +// with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields, +// of which only Verb and Args are modified to run the appropriate Go command. +// Inspired by setDefaultBuildMod in modload/init.go +func WorkspaceVendorEnabled(ctx context.Context, inv Invocation, r *Runner) (bool, []*ModuleJSON, error) { + inv.Verb = "env" + inv.Args = []string{"GOWORK"} + stdout, err := r.Run(ctx, inv) + if err != nil { + return false, nil, err + } + goWork := string(bytes.TrimSpace(stdout.Bytes())) + if fi, err := os.Stat(filepath.Join(filepath.Dir(goWork), "vendor")); err == nil && fi.IsDir() { + mainMods, err := getWorkspaceMainModules(ctx, inv, r) + if err != nil { + return false, nil, err + } + return true, mainMods, nil + } + return false, nil, nil +} + +// getWorkspaceMainModules gets the main modules' information. +// This is the information needed to figure out if vendoring should be enabled. +func getWorkspaceMainModules(ctx context.Context, inv Invocation, r *Runner) ([]*ModuleJSON, error) { + const format = `{{.Path}} +{{.Dir}} +{{.GoMod}} +{{.GoVersion}} +` + inv.Verb = "list" + inv.Args = []string{"-m", "-f", format} + stdout, err := r.Run(ctx, inv) + if err != nil { + return nil, err + } + + lines := strings.Split(strings.TrimSuffix(stdout.String(), "\n"), "\n") + if len(lines) < 4 { + return nil, fmt.Errorf("unexpected stdout: %q", stdout.String()) + } + mods := make([]*ModuleJSON, 0, len(lines)/4) + for i := 0; i < len(lines); i += 4 { + mods = append(mods, &ModuleJSON{ + Path: lines[i], + Dir: lines[i+1], + GoMod: lines[i+2], + GoVersion: lines[i+3], + Main: true, + }) + } + return mods, nil +} diff --git a/contribs/gnopls/internal/gocommand/version.go b/contribs/gnopls/internal/gocommand/version.go new file mode 100644 index 00000000000..446c5846a60 --- /dev/null +++ b/contribs/gnopls/internal/gocommand/version.go @@ -0,0 +1,71 @@ +// 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 gocommand + +import ( + "context" + "fmt" + "regexp" + "strings" +) + +// GoVersion reports the minor version number of the highest release +// tag built into the go command on the PATH. +// +// Note that this may be higher than the version of the go tool used +// to build this application, and thus the versions of the standard +// go/{scanner,parser,ast,types} packages that are linked into it. +// In that case, callers should either downgrade to the version of +// go used to build the application, or report an error that the +// application is too old to use the go command on the PATH. +func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) { + inv.Verb = "list" + inv.Args = []string{"-e", "-f", `{{context.ReleaseTags}}`, `--`, `unsafe`} + inv.BuildFlags = nil // This is not a build command. + inv.ModFlag = "" + inv.ModFile = "" + inv.Env = append(inv.Env[:len(inv.Env):len(inv.Env)], "GO111MODULE=off") + + stdoutBytes, err := r.Run(ctx, inv) + if err != nil { + return 0, err + } + stdout := stdoutBytes.String() + if len(stdout) < 3 { + return 0, fmt.Errorf("bad ReleaseTags output: %q", stdout) + } + // Split up "[go1.1 go1.15]" and return highest go1.X value. + tags := strings.Fields(stdout[1 : len(stdout)-2]) + for i := len(tags) - 1; i >= 0; i-- { + var version int + if _, err := fmt.Sscanf(tags[i], "go1.%d", &version); err != nil { + continue + } + return version, nil + } + return 0, fmt.Errorf("no parseable ReleaseTags in %v", tags) +} + +// GoVersionOutput returns the complete output of the go version command. +func GoVersionOutput(ctx context.Context, inv Invocation, r *Runner) (string, error) { + inv.Verb = "version" + goVersion, err := r.Run(ctx, inv) + if err != nil { + return "", err + } + return goVersion.String(), nil +} + +// ParseGoVersionOutput extracts the Go version string +// from the output of the "go version" command. +// Given an unrecognized form, it returns an empty string. +func ParseGoVersionOutput(data string) string { + re := regexp.MustCompile(`^go version (go\S+|devel \S+)`) + m := re.FindStringSubmatch(data) + if len(m) != 2 { + return "" // unrecognized version + } + return m[1] +} diff --git a/contribs/gnopls/internal/gocommand/version_test.go b/contribs/gnopls/internal/gocommand/version_test.go new file mode 100644 index 00000000000..27016e4c074 --- /dev/null +++ b/contribs/gnopls/internal/gocommand/version_test.go @@ -0,0 +1,31 @@ +// 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 gocommand + +import ( + "strconv" + "testing" +) + +func TestParseGoVersionOutput(t *testing.T) { + tests := []struct { + args string + want string + }{ + {"go version go1.12 linux/amd64", "go1.12"}, + {"go version go1.18.1 darwin/amd64", "go1.18.1"}, + {"go version go1.19.rc1 windows/arm64", "go1.19.rc1"}, + {"go version devel d5de62df152baf4de6e9fe81933319b86fd95ae4 linux/386", "devel d5de62df152baf4de6e9fe81933319b86fd95ae4"}, + {"go version devel go1.20-1f068f0dc7 Tue Oct 18 20:58:37 2022 +0000 darwin/amd64", "devel go1.20-1f068f0dc7"}, + {"v1.19.1 foo/bar", ""}, + } + for i, tt := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + if got := ParseGoVersionOutput(tt.args); got != tt.want { + t.Errorf("parseGoVersionOutput() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/contribs/gnopls/internal/golang/add_import.go b/contribs/gnopls/internal/golang/add_import.go index a43256a6a08..d98f41b45f0 100644 --- a/contribs/gnopls/internal/golang/add_import.go +++ b/contribs/gnopls/internal/golang/add_import.go @@ -7,11 +7,11 @@ package golang import ( "context" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" ) // AddImport adds a single import statement to the given file diff --git a/contribs/gnopls/internal/golang/assembly.go b/contribs/gnopls/internal/golang/assembly.go index 63d8e82d9fd..79d449c7245 100644 --- a/contribs/gnopls/internal/golang/assembly.go +++ b/contribs/gnopls/internal/golang/assembly.go @@ -21,8 +21,8 @@ import ( "strconv" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" ) // AssemblyHTML returns an HTML document containing an assembly listing of the selected function. diff --git a/contribs/gnopls/internal/golang/call_hierarchy.go b/contribs/gnopls/internal/golang/call_hierarchy.go index 04dc9deeb5d..126b542c46f 100644 --- a/contribs/gnopls/internal/golang/call_hierarchy.go +++ b/contribs/gnopls/internal/golang/call_hierarchy.go @@ -14,13 +14,13 @@ import ( "path/filepath" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // PrepareCallHierarchy returns an array of CallHierarchyItem for a file and the position within the file. diff --git a/contribs/gnopls/internal/golang/change_quote.go b/contribs/gnopls/internal/golang/change_quote.go index 6fa56d42615..eb6784ecd6b 100644 --- a/contribs/gnopls/internal/golang/change_quote.go +++ b/contribs/gnopls/internal/golang/change_quote.go @@ -11,13 +11,13 @@ import ( "strings" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" ) // convertStringLiteral reports whether we can convert between raw and interpreted diff --git a/contribs/gnopls/internal/golang/change_signature.go b/contribs/gnopls/internal/golang/change_signature.go index 72cbe4c2d90..efb54822e46 100644 --- a/contribs/gnopls/internal/golang/change_signature.go +++ b/contribs/gnopls/internal/golang/change_signature.go @@ -16,19 +16,19 @@ import ( "regexp" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" "golang.org/x/tools/imports" - internalastutil "golang.org/x/tools/internal/astutil" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/refactor/inline" - "golang.org/x/tools/internal/tokeninternal" - "golang.org/x/tools/internal/typesinternal" - "golang.org/x/tools/internal/versions" + internalastutil "github.com/gnolang/gno/contribs/gnopls/internal/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" ) // RemoveUnusedParameter computes a refactoring to remove the parameter diff --git a/contribs/gnopls/internal/golang/code_lens.go b/contribs/gnopls/internal/golang/code_lens.go index f0a5500b57f..242b0fc53b3 100644 --- a/contribs/gnopls/internal/golang/code_lens.go +++ b/contribs/gnopls/internal/golang/code_lens.go @@ -12,12 +12,12 @@ import ( "regexp" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" ) // CodeLensSources returns the supported sources of code lenses for Go files. diff --git a/contribs/gnopls/internal/golang/codeaction.go b/contribs/gnopls/internal/golang/codeaction.go index b245b96eec6..5068af386b9 100644 --- a/contribs/gnopls/internal/golang/codeaction.go +++ b/contribs/gnopls/internal/golang/codeaction.go @@ -14,19 +14,19 @@ import ( "strings" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/analysis/fillstruct" - "golang.org/x/tools/gopls/internal/analysis/fillswitch" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillstruct" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillswitch" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // CodeActions returns all enabled code actions (edits and other diff --git a/contribs/gnopls/internal/golang/comment.go b/contribs/gnopls/internal/golang/comment.go index 3a0d8153665..388743d9797 100644 --- a/contribs/gnopls/internal/golang/comment.go +++ b/contribs/gnopls/internal/golang/comment.go @@ -14,11 +14,11 @@ import ( "go/types" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) var errNoCommentReference = errors.New("no comment reference found") diff --git a/contribs/gnopls/internal/golang/completion/completion.go b/contribs/gnopls/internal/golang/completion/completion.go index 3fdc2a8c62a..3a328fbb718 100644 --- a/contribs/gnopls/internal/golang/completion/completion.go +++ b/contribs/gnopls/internal/golang/completion/completion.go @@ -29,24 +29,24 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/fuzzy" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/golang/completion/snippet" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - goplsastutil "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/gopls/internal/util/typesutil" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/stdlib" - "golang.org/x/tools/internal/typeparams" - "golang.org/x/tools/internal/typesinternal" - "golang.org/x/tools/internal/versions" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/golang/completion/snippet" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + goplsastutil "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/util/typesutil" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/stdlib" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" ) // A CompletionItem represents a possible completion suggested by the algorithm. diff --git a/contribs/gnopls/internal/golang/completion/definition.go b/contribs/gnopls/internal/golang/completion/definition.go index fc8b0ae5c69..23753dd5635 100644 --- a/contribs/gnopls/internal/golang/completion/definition.go +++ b/contribs/gnopls/internal/golang/completion/definition.go @@ -11,9 +11,9 @@ import ( "unicode" "unicode/utf8" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/golang/completion/snippet" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/golang/completion/snippet" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // some function definitions in test files can be completed diff --git a/contribs/gnopls/internal/golang/completion/format.go b/contribs/gnopls/internal/golang/completion/format.go index 2e35fd5de38..978d6f5a914 100644 --- a/contribs/gnopls/internal/golang/completion/format.go +++ b/contribs/gnopls/internal/golang/completion/format.go @@ -13,13 +13,13 @@ import ( "go/types" "strings" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/golang/completion/snippet" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/golang/completion/snippet" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" ) var ( diff --git a/contribs/gnopls/internal/golang/completion/fuzz.go b/contribs/gnopls/internal/golang/completion/fuzz.go index 313e7f7b391..6a54508ac32 100644 --- a/contribs/gnopls/internal/golang/completion/fuzz.go +++ b/contribs/gnopls/internal/golang/completion/fuzz.go @@ -10,7 +10,7 @@ import ( "go/types" "strings" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // golang/go#51089 diff --git a/contribs/gnopls/internal/golang/completion/keywords.go b/contribs/gnopls/internal/golang/completion/keywords.go index 3f2f5ac78cd..7f0f111e440 100644 --- a/contribs/gnopls/internal/golang/completion/keywords.go +++ b/contribs/gnopls/internal/golang/completion/keywords.go @@ -7,8 +7,8 @@ package completion import ( "go/ast" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" ) const ( diff --git a/contribs/gnopls/internal/golang/completion/literal.go b/contribs/gnopls/internal/golang/completion/literal.go index 62398f064c2..48897952436 100644 --- a/contribs/gnopls/internal/golang/completion/literal.go +++ b/contribs/gnopls/internal/golang/completion/literal.go @@ -11,12 +11,12 @@ import ( "strings" "unicode" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/golang/completion/snippet" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/golang/completion/snippet" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // literal generates composite literal, function literal, and make() diff --git a/contribs/gnopls/internal/golang/completion/package.go b/contribs/gnopls/internal/golang/completion/package.go index e71f5c9dd02..05982ef918f 100644 --- a/contribs/gnopls/internal/golang/completion/package.go +++ b/contribs/gnopls/internal/golang/completion/package.go @@ -18,13 +18,13 @@ import ( "strings" "unicode" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/fuzzy" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // packageClauseCompletions offers completions for a package declaration when diff --git a/contribs/gnopls/internal/golang/completion/package_test.go b/contribs/gnopls/internal/golang/completion/package_test.go index dc4058fa651..f4f8853feb1 100644 --- a/contribs/gnopls/internal/golang/completion/package_test.go +++ b/contribs/gnopls/internal/golang/completion/package_test.go @@ -7,7 +7,7 @@ package completion import ( "testing" - "golang.org/x/tools/gopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" ) func TestIsValidDirName(t *testing.T) { diff --git a/contribs/gnopls/internal/golang/completion/postfix_snippets.go b/contribs/gnopls/internal/golang/completion/postfix_snippets.go index 641fe8746eb..f82ae05d6be 100644 --- a/contribs/gnopls/internal/golang/completion/postfix_snippets.go +++ b/contribs/gnopls/internal/golang/completion/postfix_snippets.go @@ -16,15 +16,15 @@ import ( "sync" "text/template" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/golang/completion/snippet" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/golang/completion/snippet" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // Postfix snippets are artificial methods that allow the user to diff --git a/contribs/gnopls/internal/golang/completion/snippet.go b/contribs/gnopls/internal/golang/completion/snippet.go index 8df81f87672..55a668b6848 100644 --- a/contribs/gnopls/internal/golang/completion/snippet.go +++ b/contribs/gnopls/internal/golang/completion/snippet.go @@ -7,8 +7,8 @@ package completion import ( "go/ast" - "golang.org/x/tools/gopls/internal/golang/completion/snippet" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/golang/completion/snippet" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // structFieldSnippet calculates the snippet for struct literal field names. diff --git a/contribs/gnopls/internal/golang/completion/statements.go b/contribs/gnopls/internal/golang/completion/statements.go index ce80cfb08ce..6b0559253f2 100644 --- a/contribs/gnopls/internal/golang/completion/statements.go +++ b/contribs/gnopls/internal/golang/completion/statements.go @@ -11,10 +11,10 @@ import ( "go/types" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/golang/completion/snippet" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/golang/completion/snippet" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // addStatementCandidates adds full statement completion candidates diff --git a/contribs/gnopls/internal/golang/completion/util.go b/contribs/gnopls/internal/golang/completion/util.go index ad4ee5e09fc..c2cfdd3b73e 100644 --- a/contribs/gnopls/internal/golang/completion/util.go +++ b/contribs/gnopls/internal/golang/completion/util.go @@ -10,12 +10,12 @@ import ( "go/types" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" ) // exprAtPos returns the index of the expression containing pos. diff --git a/contribs/gnopls/internal/golang/definition.go b/contribs/gnopls/internal/golang/definition.go index f20fe85f541..f590e420b8e 100644 --- a/contribs/gnopls/internal/golang/definition.go +++ b/contribs/gnopls/internal/golang/definition.go @@ -15,14 +15,14 @@ import ( "regexp" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // Definition handles the textDocument/definition request for Go files. diff --git a/contribs/gnopls/internal/golang/diagnostics.go b/contribs/gnopls/internal/golang/diagnostics.go index 1c6da2e9d4e..76e250971a8 100644 --- a/contribs/gnopls/internal/golang/diagnostics.go +++ b/contribs/gnopls/internal/golang/diagnostics.go @@ -9,12 +9,12 @@ import ( "maps" "slices" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/progress" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/moremaps" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/progress" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/moremaps" ) // Analyze reports go/analysis-framework diagnostics in the specified package. diff --git a/contribs/gnopls/internal/golang/embeddirective.go b/contribs/gnopls/internal/golang/embeddirective.go index 3a35f907274..a40f66f4464 100644 --- a/contribs/gnopls/internal/golang/embeddirective.go +++ b/contribs/gnopls/internal/golang/embeddirective.go @@ -14,7 +14,7 @@ import ( "unicode" "unicode/utf8" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // ErrNoEmbed is returned by EmbedDefinition when no embed diff --git a/contribs/gnopls/internal/golang/extract.go b/contribs/gnopls/internal/golang/extract.go index 3610deeead3..bafb31cc425 100644 --- a/contribs/gnopls/internal/golang/extract.go +++ b/contribs/gnopls/internal/golang/extract.go @@ -18,9 +18,9 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/analysisinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" ) func extractVariable(fset *token.FileSet, start, end token.Pos, src []byte, file *ast.File, pkg *types.Package, info *types.Info) (*token.FileSet, *analysis.SuggestedFix, error) { diff --git a/contribs/gnopls/internal/golang/extracttofile.go b/contribs/gnopls/internal/golang/extracttofile.go index 0a1d74408d7..d952c4ee7bb 100644 --- a/contribs/gnopls/internal/golang/extracttofile.go +++ b/contribs/gnopls/internal/golang/extracttofile.go @@ -19,12 +19,12 @@ import ( "path/filepath" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // canExtractToNewFile reports whether the code in the given range can be extracted to a new file. diff --git a/contribs/gnopls/internal/golang/fix.go b/contribs/gnopls/internal/golang/fix.go index 3844fc0d65c..2bcc06c0b9a 100644 --- a/contribs/gnopls/internal/golang/fix.go +++ b/contribs/gnopls/internal/golang/fix.go @@ -12,17 +12,17 @@ import ( "go/types" "golang.org/x/tools/go/analysis" - "golang.org/x/tools/gopls/internal/analysis/embeddirective" - "golang.org/x/tools/gopls/internal/analysis/fillstruct" - "golang.org/x/tools/gopls/internal/analysis/stubmethods" - "golang.org/x/tools/gopls/internal/analysis/undeclaredname" - "golang.org/x/tools/gopls/internal/analysis/unusedparams" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/embeddirective" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillstruct" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/stubmethods" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/undeclaredname" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedparams" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" ) // A fixer is a function that suggests a fix for a diagnostic produced diff --git a/contribs/gnopls/internal/golang/folding_range.go b/contribs/gnopls/internal/golang/folding_range.go index 85faea5e31a..23e08648620 100644 --- a/contribs/gnopls/internal/golang/folding_range.go +++ b/contribs/gnopls/internal/golang/folding_range.go @@ -11,12 +11,12 @@ import ( "sort" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // FoldingRangeInfo holds range and kind info of folding for an ast.Node diff --git a/contribs/gnopls/internal/golang/format.go b/contribs/gnopls/internal/golang/format.go index 8f735f38cf4..fe89a6a4028 100644 --- a/contribs/gnopls/internal/golang/format.go +++ b/contribs/gnopls/internal/golang/format.go @@ -17,15 +17,15 @@ import ( "strings" "text/scanner" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/imports" - "golang.org/x/tools/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" gofumptFormat "mvdan.cc/gofumpt/format" ) diff --git a/contribs/gnopls/internal/golang/format_test.go b/contribs/gnopls/internal/golang/format_test.go index 4dbb4db71c0..d95cb0cd91b 100644 --- a/contribs/gnopls/internal/golang/format_test.go +++ b/contribs/gnopls/internal/golang/format_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/test/compare" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" ) func TestImportPrefix(t *testing.T) { diff --git a/contribs/gnopls/internal/golang/freesymbols.go b/contribs/gnopls/internal/golang/freesymbols.go index 0e2422d421b..8311baff5c8 100644 --- a/contribs/gnopls/internal/golang/freesymbols.go +++ b/contribs/gnopls/internal/golang/freesymbols.go @@ -18,12 +18,12 @@ import ( "strings" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/util/moremaps" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/util/moremaps" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // FreeSymbolsHTML returns an HTML document containing the report of diff --git a/contribs/gnopls/internal/golang/gc_annotations.go b/contribs/gnopls/internal/golang/gc_annotations.go index 03db9e74760..26b530a4c4e 100644 --- a/contribs/gnopls/internal/golang/gc_annotations.go +++ b/contribs/gnopls/internal/golang/gc_annotations.go @@ -13,12 +13,12 @@ import ( "path/filepath" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" ) // GCOptimizationDetails invokes the Go compiler on the specified diff --git a/contribs/gnopls/internal/golang/highlight.go b/contribs/gnopls/internal/golang/highlight.go index f53e73f3053..2cd2c04bf36 100644 --- a/contribs/gnopls/internal/golang/highlight.go +++ b/contribs/gnopls/internal/golang/highlight.go @@ -12,10 +12,10 @@ import ( "go/types" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.DocumentHighlight, error) { diff --git a/contribs/gnopls/internal/golang/hover.go b/contribs/gnopls/internal/golang/hover.go index 019d09ac027..39fce018011 100644 --- a/contribs/gnopls/internal/golang/hover.go +++ b/contribs/gnopls/internal/golang/hover.go @@ -27,22 +27,22 @@ import ( "golang.org/x/text/unicode/runenames" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - gastutil "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/gopls/internal/util/typesutil" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/stdlib" - "golang.org/x/tools/internal/tokeninternal" - "golang.org/x/tools/internal/typeparams" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + gastutil "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/util/typesutil" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/stdlib" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // hoverJSON contains the structured result of a hover query. It is diff --git a/contribs/gnopls/internal/golang/identifier.go b/contribs/gnopls/internal/golang/identifier.go index 30a83d3a05a..d5e42ac3e93 100644 --- a/contribs/gnopls/internal/golang/identifier.go +++ b/contribs/gnopls/internal/golang/identifier.go @@ -9,8 +9,8 @@ import ( "go/ast" "go/types" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // ErrNoIdentFound is error returned when no identifier is found at a particular position diff --git a/contribs/gnopls/internal/golang/identifier_test.go b/contribs/gnopls/internal/golang/identifier_test.go index b1e6d5a75a2..ebd331421d7 100644 --- a/contribs/gnopls/internal/golang/identifier_test.go +++ b/contribs/gnopls/internal/golang/identifier_test.go @@ -12,7 +12,7 @@ import ( "go/types" "testing" - "golang.org/x/tools/internal/versions" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" ) func TestSearchForEnclosing(t *testing.T) { diff --git a/contribs/gnopls/internal/golang/implementation.go b/contribs/gnopls/internal/golang/implementation.go index b3accff452f..ec9e384b683 100644 --- a/contribs/gnopls/internal/golang/implementation.go +++ b/contribs/gnopls/internal/golang/implementation.go @@ -17,14 +17,14 @@ import ( "sync" "golang.org/x/sync/errgroup" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/methodsets" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/methodsets" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // This file defines the new implementation of the 'implementation' diff --git a/contribs/gnopls/internal/golang/inlay_hint.go b/contribs/gnopls/internal/golang/inlay_hint.go index 6e2b7f40d33..848e7bf4210 100644 --- a/contribs/gnopls/internal/golang/inlay_hint.go +++ b/contribs/gnopls/internal/golang/inlay_hint.go @@ -13,14 +13,14 @@ import ( "go/types" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/typesutil" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/typeparams" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/typesutil" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) func InlayHint(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pRng protocol.Range) ([]protocol.InlayHint, error) { diff --git a/contribs/gnopls/internal/golang/inline.go b/contribs/gnopls/internal/golang/inline.go index 8e5e906c566..37d2ee797e8 100644 --- a/contribs/gnopls/internal/golang/inline.go +++ b/contribs/gnopls/internal/golang/inline.go @@ -16,13 +16,13 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/refactor/inline" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline" ) // enclosingStaticCall returns the innermost function call enclosing diff --git a/contribs/gnopls/internal/golang/inline_all.go b/contribs/gnopls/internal/golang/inline_all.go index addfe2bc250..49348c7f353 100644 --- a/contribs/gnopls/internal/golang/inline_all.go +++ b/contribs/gnopls/internal/golang/inline_all.go @@ -13,11 +13,11 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/refactor/inline" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline" ) // inlineAllCalls inlines all calls to the original function declaration diff --git a/contribs/gnopls/internal/golang/invertifcondition.go b/contribs/gnopls/internal/golang/invertifcondition.go index 16eaaa39bd2..c1e3c00e94a 100644 --- a/contribs/gnopls/internal/golang/invertifcondition.go +++ b/contribs/gnopls/internal/golang/invertifcondition.go @@ -13,7 +13,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // invertIfCondition is a singleFileFixFunc that inverts an if/else statement diff --git a/contribs/gnopls/internal/golang/known_packages.go b/contribs/gnopls/internal/golang/known_packages.go index 3b320d4f782..048672e41eb 100644 --- a/contribs/gnopls/internal/golang/known_packages.go +++ b/contribs/gnopls/internal/golang/known_packages.go @@ -13,11 +13,11 @@ import ( "sync" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" ) // KnownPackagePaths returns a new list of package paths of all known diff --git a/contribs/gnopls/internal/golang/lines.go b/contribs/gnopls/internal/golang/lines.go index 6a17e928b34..3029c51d2f5 100644 --- a/contribs/gnopls/internal/golang/lines.go +++ b/contribs/gnopls/internal/golang/lines.go @@ -19,7 +19,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // canSplitLines checks whether we can split lists of elements inside diff --git a/contribs/gnopls/internal/golang/linkname.go b/contribs/gnopls/internal/golang/linkname.go index c4ec3517b53..87981975af1 100644 --- a/contribs/gnopls/internal/golang/linkname.go +++ b/contribs/gnopls/internal/golang/linkname.go @@ -11,11 +11,11 @@ import ( "go/token" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // ErrNoLinkname is returned by LinknameDefinition when no linkname diff --git a/contribs/gnopls/internal/golang/pkgdoc.go b/contribs/gnopls/internal/golang/pkgdoc.go index ed8f1b388f0..33ae6b662bf 100644 --- a/contribs/gnopls/internal/golang/pkgdoc.go +++ b/contribs/gnopls/internal/golang/pkgdoc.go @@ -45,14 +45,14 @@ import ( "strings" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" - goplsastutil "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/stdlib" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + goplsastutil "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/stdlib" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" ) // DocFragment finds the package and (optionally) symbol identified by diff --git a/contribs/gnopls/internal/golang/references.go b/contribs/gnopls/internal/golang/references.go index 6679b45df6b..d8c12feab6d 100644 --- a/contribs/gnopls/internal/golang/references.go +++ b/contribs/gnopls/internal/golang/references.go @@ -25,14 +25,14 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/tools/go/types/objectpath" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/methodsets" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/methodsets" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // References returns a list of all references (sorted with diff --git a/contribs/gnopls/internal/golang/rename.go b/contribs/gnopls/internal/golang/rename.go index 12d9d283915..035a0d8f085 100644 --- a/contribs/gnopls/internal/golang/rename.go +++ b/contribs/gnopls/internal/golang/rename.go @@ -59,17 +59,17 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" "golang.org/x/tools/refactor/satisfy" ) diff --git a/contribs/gnopls/internal/golang/rename_check.go b/contribs/gnopls/internal/golang/rename_check.go index 574ea8dbea7..f4c3badf41f 100644 --- a/contribs/gnopls/internal/golang/rename_check.go +++ b/contribs/gnopls/internal/golang/rename_check.go @@ -43,11 +43,11 @@ import ( "unicode" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/typeparams" - "golang.org/x/tools/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" "golang.org/x/tools/refactor/satisfy" ) diff --git a/contribs/gnopls/internal/golang/semtok.go b/contribs/gnopls/internal/golang/semtok.go index 0da38bbaa2b..13ae895120e 100644 --- a/contribs/gnopls/internal/golang/semtok.go +++ b/contribs/gnopls/internal/golang/semtok.go @@ -20,16 +20,16 @@ import ( "strings" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/semtok" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/semtok" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // semDebug enables comprehensive logging of decisions diff --git a/contribs/gnopls/internal/golang/signature_help.go b/contribs/gnopls/internal/golang/signature_help.go index 26cb92c643b..be0566cd462 100644 --- a/contribs/gnopls/internal/golang/signature_help.go +++ b/contribs/gnopls/internal/golang/signature_help.go @@ -13,13 +13,13 @@ import ( "strings" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/typesutil" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/typesutil" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // SignatureHelp returns information about the signature of the innermost diff --git a/contribs/gnopls/internal/golang/snapshot.go b/contribs/gnopls/internal/golang/snapshot.go index c381c962d08..961f03f4d46 100644 --- a/contribs/gnopls/internal/golang/snapshot.go +++ b/contribs/gnopls/internal/golang/snapshot.go @@ -8,10 +8,10 @@ import ( "context" "fmt" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // NarrowestMetadataForFile returns metadata for the narrowest package diff --git a/contribs/gnopls/internal/golang/stub.go b/contribs/gnopls/internal/golang/stub.go index db405631c9e..5f5334689c3 100644 --- a/contribs/gnopls/internal/golang/stub.go +++ b/contribs/gnopls/internal/golang/stub.go @@ -18,14 +18,14 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/analysis/stubmethods" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/stubmethods" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" ) // stubMethodsFixer returns a suggested fix to declare the missing diff --git a/contribs/gnopls/internal/golang/symbols.go b/contribs/gnopls/internal/golang/symbols.go index 35959c2de7a..7db670d83ef 100644 --- a/contribs/gnopls/internal/golang/symbols.go +++ b/contribs/gnopls/internal/golang/symbols.go @@ -11,11 +11,11 @@ import ( "go/token" "go/types" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func DocumentSymbols(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.DocumentSymbol, error) { diff --git a/contribs/gnopls/internal/golang/type_definition.go b/contribs/gnopls/internal/golang/type_definition.go index a396793e48a..e0df7c243c0 100644 --- a/contribs/gnopls/internal/golang/type_definition.go +++ b/contribs/gnopls/internal/golang/type_definition.go @@ -9,10 +9,10 @@ import ( "fmt" "go/token" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // TypeDefinition handles the textDocument/typeDefinition request for Go files. diff --git a/contribs/gnopls/internal/golang/types_format.go b/contribs/gnopls/internal/golang/types_format.go index 41828244e11..2aa5e1f018d 100644 --- a/contribs/gnopls/internal/golang/types_format.go +++ b/contribs/gnopls/internal/golang/types_format.go @@ -15,13 +15,13 @@ import ( "go/types" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/tokeninternal" - "golang.org/x/tools/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" ) // FormatType returns the detail and kind for a types.Type. diff --git a/contribs/gnopls/internal/golang/util.go b/contribs/gnopls/internal/golang/util.go index 18f72421a64..35ba67d273a 100644 --- a/contribs/gnopls/internal/golang/util.go +++ b/contribs/gnopls/internal/golang/util.go @@ -13,14 +13,14 @@ import ( "regexp" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" ) // IsGenerated gets and reads the file denoted by uri and reports diff --git a/contribs/gnopls/internal/golang/workspace_symbol.go b/contribs/gnopls/internal/golang/workspace_symbol.go index c80174c78fd..ca7ec2fc7d1 100644 --- a/contribs/gnopls/internal/golang/workspace_symbol.go +++ b/contribs/gnopls/internal/golang/workspace_symbol.go @@ -13,12 +13,12 @@ import ( "strings" "unicode" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/fuzzy" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/fuzzy" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // maxSymbols defines the maximum number of symbol results that should ever be diff --git a/contribs/gnopls/internal/golang/workspace_symbol_test.go b/contribs/gnopls/internal/golang/workspace_symbol_test.go index 4982b767754..af314ef9799 100644 --- a/contribs/gnopls/internal/golang/workspace_symbol_test.go +++ b/contribs/gnopls/internal/golang/workspace_symbol_test.go @@ -7,7 +7,7 @@ package golang import ( "testing" - "golang.org/x/tools/gopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" ) func TestParseQuery(t *testing.T) { diff --git a/contribs/gnopls/internal/gopathwalk/walk.go b/contribs/gnopls/internal/gopathwalk/walk.go new file mode 100644 index 00000000000..8361515519f --- /dev/null +++ b/contribs/gnopls/internal/gopathwalk/walk.go @@ -0,0 +1,337 @@ +// Copyright 2018 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 gopathwalk is like filepath.Walk but specialized for finding Go +// packages, particularly in $GOPATH and $GOROOT. +package gopathwalk + +import ( + "bufio" + "bytes" + "io" + "io/fs" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "time" +) + +// Options controls the behavior of a Walk call. +type Options struct { + // If Logf is non-nil, debug logging is enabled through this function. + Logf func(format string, args ...interface{}) + + // Search module caches. Also disables legacy goimports ignore rules. + ModulesEnabled bool + + // Maximum number of concurrent calls to user-provided callbacks, + // or 0 for GOMAXPROCS. + Concurrency int +} + +// RootType indicates the type of a Root. +type RootType int + +const ( + RootUnknown RootType = iota + RootGOROOT + RootGOPATH + RootCurrentModule + RootModuleCache + RootOther +) + +// A Root is a starting point for a Walk. +type Root struct { + Path string + Type RootType +} + +// Walk concurrently walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. +// +// For each package found, add will be called with the absolute +// paths of the containing source directory and the package directory. +// +// Unlike filepath.WalkDir, Walk follows symbolic links +// (while guarding against cycles). +func Walk(roots []Root, add func(root Root, dir string), opts Options) { + WalkSkip(roots, add, func(Root, string) bool { return false }, opts) +} + +// WalkSkip concurrently walks Go source directories ($GOROOT, $GOPATH, etc) to +// find packages. +// +// For each package found, add will be called with the absolute +// paths of the containing source directory and the package directory. +// For each directory that will be scanned, skip will be called +// with the absolute paths of the containing source directory and the directory. +// If skip returns false on a directory it will be processed. +// +// Unlike filepath.WalkDir, WalkSkip follows symbolic links +// (while guarding against cycles). +func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root, dir string) bool, opts Options) { + for _, root := range roots { + walkDir(root, add, skip, opts) + } +} + +// walkDir creates a walker and starts fastwalk with this walker. +func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) { + if opts.Logf == nil { + opts.Logf = func(format string, args ...interface{}) {} + } + if _, err := os.Stat(root.Path); os.IsNotExist(err) { + opts.Logf("skipping nonexistent directory: %v", root.Path) + return + } + start := time.Now() + opts.Logf("scanning %s", root.Path) + + concurrency := opts.Concurrency + if concurrency == 0 { + // The walk be either CPU-bound or I/O-bound, depending on what the + // caller-supplied add function does and the details of the user's platform + // and machine. Rather than trying to fine-tune the concurrency level for a + // specific environment, we default to GOMAXPROCS: it is likely to be a good + // choice for a CPU-bound add function, and if it is instead I/O-bound, then + // dealing with I/O saturation is arguably the job of the kernel and/or + // runtime. (Oversaturating I/O seems unlikely to harm performance as badly + // as failing to saturate would.) + concurrency = runtime.GOMAXPROCS(0) + } + w := &walker{ + root: root, + add: add, + skip: skip, + opts: opts, + sem: make(chan struct{}, concurrency), + } + w.init() + + w.sem <- struct{}{} + path := root.Path + if path == "" { + path = "." + } + if fi, err := os.Lstat(path); err == nil { + w.walk(path, nil, fs.FileInfoToDirEntry(fi)) + } else { + w.opts.Logf("scanning directory %v: %v", root.Path, err) + } + <-w.sem + w.walking.Wait() + + opts.Logf("scanned %s in %v", root.Path, time.Since(start)) +} + +// walker is the callback for fastwalk.Walk. +type walker struct { + root Root // The source directory to scan. + add func(Root, string) // The callback that will be invoked for every possible Go package dir. + skip func(Root, string) bool // The callback that will be invoked for every dir. dir is skipped if it returns true. + opts Options // Options passed to Walk by the user. + + walking sync.WaitGroup + sem chan struct{} // Channel of semaphore tokens; send to acquire, receive to release. + ignoredDirs []string + + added sync.Map // map[string]bool +} + +// A symlinkList is a linked list of os.FileInfos for parent directories +// reached via symlinks. +type symlinkList struct { + info os.FileInfo + prev *symlinkList +} + +// init initializes the walker based on its Options +func (w *walker) init() { + var ignoredPaths []string + if w.root.Type == RootModuleCache { + ignoredPaths = []string{"cache"} + } + if !w.opts.ModulesEnabled && w.root.Type == RootGOPATH { + ignoredPaths = w.getIgnoredDirs(w.root.Path) + ignoredPaths = append(ignoredPaths, "v", "mod") + } + + for _, p := range ignoredPaths { + full := filepath.Join(w.root.Path, p) + w.ignoredDirs = append(w.ignoredDirs, full) + w.opts.Logf("Directory added to ignore list: %s", full) + } +} + +// getIgnoredDirs reads an optional config file at <path>/.goimportsignore +// of relative directories to ignore when scanning for go files. +// The provided path is one of the $GOPATH entries with "src" appended. +func (w *walker) getIgnoredDirs(path string) []string { + file := filepath.Join(path, ".goimportsignore") + slurp, err := os.ReadFile(file) + if err != nil { + w.opts.Logf("%v", err) + } else { + w.opts.Logf("Read %s", file) + } + if err != nil { + return nil + } + + var ignoredDirs []string + bs := bufio.NewScanner(bytes.NewReader(slurp)) + for bs.Scan() { + line := strings.TrimSpace(bs.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + ignoredDirs = append(ignoredDirs, line) + } + return ignoredDirs +} + +// shouldSkipDir reports whether the file should be skipped or not. +func (w *walker) shouldSkipDir(dir string) bool { + for _, ignoredDir := range w.ignoredDirs { + if dir == ignoredDir { + return true + } + } + if w.skip != nil { + // Check with the user specified callback. + return w.skip(w.root, dir) + } + return false +} + +// walk walks through the given path. +// +// Errors are logged if w.opts.Logf is non-nil, but otherwise ignored. +func (w *walker) walk(path string, pathSymlinks *symlinkList, d fs.DirEntry) { + if d.Type()&os.ModeSymlink != 0 { + // Walk the symlink's target rather than the symlink itself. + // + // (Note that os.Stat, unlike the lower-lever os.Readlink, + // follows arbitrarily many layers of symlinks, so it will eventually + // reach either a non-symlink or a nonexistent target.) + // + // TODO(bcmills): 'go list all' itself ignores symlinks within GOROOT/src + // and GOPATH/src. Do we really need to traverse them here? If so, why? + + fi, err := os.Stat(path) + if err != nil { + w.opts.Logf("%v", err) + return + } + + // Avoid walking symlink cycles: if we have already followed a symlink to + // this directory as a parent of itself, don't follow it again. + // + // This doesn't catch the first time through a cycle, but it also minimizes + // the number of extra stat calls we make if we *don't* encounter a cycle. + // Since we don't actually expect to encounter symlink cycles in practice, + // this seems like the right tradeoff. + for parent := pathSymlinks; parent != nil; parent = parent.prev { + if os.SameFile(fi, parent.info) { + return + } + } + + pathSymlinks = &symlinkList{ + info: fi, + prev: pathSymlinks, + } + d = fs.FileInfoToDirEntry(fi) + } + + if d.Type().IsRegular() { + if !strings.HasSuffix(path, ".go") { + return + } + + dir := filepath.Dir(path) + if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) { + // Doesn't make sense to have regular files + // directly in your $GOPATH/src or $GOROOT/src. + // + // TODO(bcmills): there are many levels of directory within + // RootModuleCache where this also wouldn't make sense, + // Can we generalize this to any directory without a corresponding + // import path? + return + } + + if _, dup := w.added.LoadOrStore(dir, true); !dup { + w.add(w.root, dir) + } + } + + if !d.IsDir() { + return + } + + base := filepath.Base(path) + if base == "" || base[0] == '.' || base[0] == '_' || + base == "testdata" || + (w.root.Type == RootGOROOT && w.opts.ModulesEnabled && base == "vendor") || + (!w.opts.ModulesEnabled && base == "node_modules") || + w.shouldSkipDir(path) { + return + } + + // Read the directory and walk its entries. + + f, err := os.Open(path) + if err != nil { + w.opts.Logf("%v", err) + return + } + defer f.Close() + + for { + // We impose an arbitrary limit on the number of ReadDir results per + // directory to limit the amount of memory consumed for stale or upcoming + // directory entries. The limit trades off CPU (number of syscalls to read + // the whole directory) against RAM (reachable directory entries other than + // the one currently being processed). + // + // Since we process the directories recursively, we will end up maintaining + // a slice of entries for each level of the directory tree. + // (Compare https://go.dev/issue/36197.) + ents, err := f.ReadDir(1024) + if err != nil { + if err != io.EOF { + w.opts.Logf("%v", err) + } + break + } + + for _, d := range ents { + nextPath := filepath.Join(path, d.Name()) + if d.IsDir() { + select { + case w.sem <- struct{}{}: + // Got a new semaphore token, so we can traverse the directory concurrently. + d := d + w.walking.Add(1) + go func() { + defer func() { + <-w.sem + w.walking.Done() + }() + w.walk(nextPath, pathSymlinks, d) + }() + continue + + default: + // No tokens available, so traverse serially. + } + } + + w.walk(nextPath, pathSymlinks, d) + } + } +} diff --git a/contribs/gnopls/internal/gopathwalk/walk_test.go b/contribs/gnopls/internal/gopathwalk/walk_test.go new file mode 100644 index 00000000000..8028f818588 --- /dev/null +++ b/contribs/gnopls/internal/gopathwalk/walk_test.go @@ -0,0 +1,252 @@ +// Copyright 2018 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 gopathwalk + +import ( + "os" + "path/filepath" + "reflect" + "runtime" + "sort" + "strings" + "sync" + "testing" +) + +func TestSymlinkTraversal(t *testing.T) { + t.Parallel() + + gopath := t.TempDir() + + if err := mapToDir(gopath, map[string]string{ + "a/b/c": "LINK:../../a/d", + "a/b/pkg/pkg.go": "package pkg", + "a/d/e": "LINK:../../a/b", + "a/d/pkg/pkg.go": "package pkg", + "a/f/loop": "LINK:../f", + "a/f/pkg/pkg.go": "package pkg", + "a/g/pkg/pkg.go": "LINK:../../f/pkg/pkg.go", + "a/self": "LINK:.", + }); err != nil { + switch runtime.GOOS { + case "windows", "plan9": + t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS) + } + t.Fatal(err) + } + + pkgc := make(chan []string, 1) + pkgc <- nil + add := func(root Root, dir string) { + rel, err := filepath.Rel(filepath.Join(root.Path, "src"), dir) + if err != nil { + t.Error(err) + } + pkgc <- append(<-pkgc, filepath.ToSlash(rel)) + } + + Walk([]Root{{Path: gopath, Type: RootGOPATH}}, add, Options{Logf: t.Logf}) + + pkgs := <-pkgc + sort.Strings(pkgs) + t.Logf("Found packages:\n\t%s", strings.Join(pkgs, "\n\t")) + + got := make(map[string]bool, len(pkgs)) + for _, pkg := range pkgs { + got[pkg] = true + } + tests := []struct { + path string + want bool + why string + }{ + { + path: "a/b/pkg", + want: true, + why: "found via regular directories", + }, + { + path: "a/b/c/pkg", + want: true, + why: "found via non-cyclic dir link", + }, + { + path: "a/b/c/e/pkg", + want: true, + why: "found via two non-cyclic dir links", + }, + { + path: "a/d/e/c/pkg", + want: true, + why: "found via two non-cyclic dir links", + }, + { + path: "a/f/loop/pkg", + want: true, + why: "found via a single parent-dir link", + }, + { + path: "a/f/loop/loop/pkg", + want: false, + why: "would follow loop symlink twice", + }, + { + path: "a/self/b/pkg", + want: true, + why: "follows self-link once", + }, + { + path: "a/self/self/b/pkg", + want: false, + why: "would follow self-link twice", + }, + } + for _, tc := range tests { + if got[tc.path] != tc.want { + if tc.want { + t.Errorf("MISSING: %s (%s)", tc.path, tc.why) + } else { + t.Errorf("UNEXPECTED: %s (%s)", tc.path, tc.why) + } + } + } +} + +// TestSkip tests that various goimports rules are followed in non-modules mode. +func TestSkip(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + if err := mapToDir(dir, map[string]string{ + "ignoreme/f.go": "package ignoreme", // ignored by .goimportsignore + "node_modules/f.go": "package nodemodules;", // ignored by hardcoded node_modules filter + "v/f.go": "package v;", // ignored by hardcoded vgo cache rule + "mod/f.go": "package mod;", // ignored by hardcoded vgo cache rule + "shouldfind/f.go": "package shouldfind;", // not ignored + + ".goimportsignore": "ignoreme\n", + }); err != nil { + t.Fatal(err) + } + + var found []string + var mu sync.Mutex + walkDir(Root{filepath.Join(dir, "src"), RootGOPATH}, + func(root Root, dir string) { + mu.Lock() + defer mu.Unlock() + found = append(found, dir[len(root.Path)+1:]) + }, func(root Root, dir string) bool { + return false + }, Options{ + ModulesEnabled: false, + Logf: t.Logf, + }) + if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) { + t.Errorf("expected to find only %v, got %v", want, found) + } +} + +// TestSkipFunction tests that scan successfully skips directories from user callback. +func TestSkipFunction(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + if err := mapToDir(dir, map[string]string{ + "ignoreme/f.go": "package ignoreme", // ignored by skip + "ignoreme/subignore/f.go": "package subignore", // also ignored by skip + "shouldfind/f.go": "package shouldfind;", // not ignored + }); err != nil { + t.Fatal(err) + } + + var found []string + var mu sync.Mutex + walkDir(Root{filepath.Join(dir, "src"), RootGOPATH}, + func(root Root, dir string) { + mu.Lock() + defer mu.Unlock() + found = append(found, dir[len(root.Path)+1:]) + }, func(root Root, dir string) bool { + return strings.HasSuffix(dir, "ignoreme") + }, + Options{ + ModulesEnabled: false, + Logf: t.Logf, + }) + if want := []string{"shouldfind"}; !reflect.DeepEqual(found, want) { + t.Errorf("expected to find only %v, got %v", want, found) + } +} + +// TestWalkSymlinkConcurrentDeletion is a regression test for the panic reported +// in https://go.dev/issue/58054#issuecomment-1791513726. +func TestWalkSymlinkConcurrentDeletion(t *testing.T) { + t.Parallel() + + src := t.TempDir() + + m := map[string]string{ + "dir/readme.txt": "dir is not a go package", + "dirlink": "LINK:dir", + } + if err := mapToDir(src, m); err != nil { + switch runtime.GOOS { + case "windows", "plan9": + t.Skipf("skipping symlink-requiring test on %s", runtime.GOOS) + } + t.Fatal(err) + } + + done := make(chan struct{}) + go func() { + if err := os.RemoveAll(src); err != nil { + t.Log(err) + } + close(done) + }() + defer func() { + <-done + }() + + add := func(root Root, dir string) { + t.Errorf("unexpected call to add(%q, %q)", root.Path, dir) + } + Walk([]Root{{Path: src, Type: RootGOPATH}}, add, Options{Logf: t.Logf}) +} + +func mapToDir(destDir string, files map[string]string) error { + var symlinkPaths []string + for path, contents := range files { + file := filepath.Join(destDir, "src", path) + if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { + return err + } + var err error + if strings.HasPrefix(contents, "LINK:") { + // To work around https://go.dev/issue/39183, wait to create symlinks + // until we have created all non-symlink paths. + symlinkPaths = append(symlinkPaths, path) + } else { + err = os.WriteFile(file, []byte(contents), 0644) + } + if err != nil { + return err + } + } + + for _, path := range symlinkPaths { + file := filepath.Join(destDir, "src", path) + target := filepath.FromSlash(strings.TrimPrefix(files[path], "LINK:")) + err := os.Symlink(target, file) + if err != nil { + return err + } + } + + return nil +} diff --git a/contribs/gnopls/internal/goroot/importcfg.go b/contribs/gnopls/internal/goroot/importcfg.go new file mode 100644 index 00000000000..f1cd28e2ec3 --- /dev/null +++ b/contribs/gnopls/internal/goroot/importcfg.go @@ -0,0 +1,71 @@ +// 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 goroot is a copy of package internal/goroot +// in the main GO repot. It provides a utility to produce +// an importcfg and import path to package file map mapping +// standard library packages to the locations of their export +// data files. +package goroot + +import ( + "bytes" + "fmt" + "os/exec" + "strings" + "sync" +) + +// Importcfg returns an importcfg file to be passed to the +// Go compiler that contains the cached paths for the .a files for the +// standard library. +func Importcfg() (string, error) { + var icfg bytes.Buffer + + m, err := PkgfileMap() + if err != nil { + return "", err + } + fmt.Fprintf(&icfg, "# import config") + for importPath, export := range m { + fmt.Fprintf(&icfg, "\npackagefile %s=%s", importPath, export) + } + s := icfg.String() + return s, nil +} + +var ( + stdlibPkgfileMap map[string]string + stdlibPkgfileErr error + once sync.Once +) + +// PkgfileMap returns a map of package paths to the location on disk +// of the .a file for the package. +// The caller must not modify the map. +func PkgfileMap() (map[string]string, error) { + once.Do(func() { + m := make(map[string]string) + output, err := exec.Command("go", "list", "-export", "-e", "-f", "{{.ImportPath}} {{.Export}}", "std", "cmd").Output() + if err != nil { + stdlibPkgfileErr = err + } + for _, line := range strings.Split(string(output), "\n") { + if line == "" { + continue + } + sp := strings.SplitN(line, " ", 2) + if len(sp) != 2 { + err = fmt.Errorf("determining pkgfile map: invalid line in go list output: %q", line) + return + } + importPath, export := sp[0], sp[1] + if export != "" { + m[importPath] = export + } + } + stdlibPkgfileMap = m + }) + return stdlibPkgfileMap, stdlibPkgfileErr +} diff --git a/contribs/gnopls/internal/imports/fix.go b/contribs/gnopls/internal/imports/fix.go new file mode 100644 index 00000000000..a8ff35119f5 --- /dev/null +++ b/contribs/gnopls/internal/imports/fix.go @@ -0,0 +1,1963 @@ +// Copyright 2013 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 imports + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "go/types" + "io/fs" + "io/ioutil" + "os" + "path" + "path/filepath" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "unicode" + "unicode/utf8" + + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/ast/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/gopathwalk" + "github.com/gnolang/gno/contribs/gnopls/internal/stdlib" +) + +// importToGroup is a list of functions which map from an import path to +// a group number. +var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){ + func(localPrefix, importPath string) (num int, ok bool) { + if localPrefix == "" { + return + } + for _, p := range strings.Split(localPrefix, ",") { + if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath { + return 3, true + } + } + return + }, + func(_, importPath string) (num int, ok bool) { + if strings.HasPrefix(importPath, "appengine") { + return 2, true + } + return + }, + func(_, importPath string) (num int, ok bool) { + firstComponent := strings.Split(importPath, "/")[0] + if strings.Contains(firstComponent, ".") { + return 1, true + } + return + }, +} + +func importGroup(localPrefix, importPath string) int { + for _, fn := range importToGroup { + if n, ok := fn(localPrefix, importPath); ok { + return n + } + } + return 0 +} + +type ImportFixType int + +const ( + AddImport ImportFixType = iota + DeleteImport + SetImportName +) + +type ImportFix struct { + // StmtInfo represents the import statement this fix will add, remove, or change. + StmtInfo ImportInfo + // IdentName is the identifier that this fix will add or remove. + IdentName string + // FixType is the type of fix this is (AddImport, DeleteImport, SetImportName). + FixType ImportFixType + Relevance float64 // see pkg +} + +// An ImportInfo represents a single import statement. +type ImportInfo struct { + ImportPath string // import path, e.g. "crypto/rand". + Name string // import name, e.g. "crand", or "" if none. +} + +// A packageInfo represents what's known about a package. +type packageInfo struct { + name string // real package name, if known. + exports map[string]bool // known exports. +} + +// parseOtherFiles parses all the Go files in srcDir except filename, including +// test files if filename looks like a test. +// +// It returns an error only if ctx is cancelled. Files with parse errors are +// ignored. +func parseOtherFiles(ctx context.Context, fset *token.FileSet, srcDir, filename string) ([]*ast.File, error) { + // This could use go/packages but it doesn't buy much, and it fails + // with https://golang.org/issue/26296 in LoadFiles mode in some cases. + considerTests := strings.HasSuffix(filename, "_test.go") + + fileBase := filepath.Base(filename) + packageFileInfos, err := os.ReadDir(srcDir) + if err != nil { + return nil, ctx.Err() + } + + var files []*ast.File + for _, fi := range packageFileInfos { + if ctx.Err() != nil { + return nil, ctx.Err() + } + if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") { + continue + } + if !considerTests && strings.HasSuffix(fi.Name(), "_test.go") { + continue + } + + f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, 0) + if err != nil { + continue + } + + files = append(files, f) + } + + return files, ctx.Err() +} + +// addGlobals puts the names of package vars into the provided map. +func addGlobals(f *ast.File, globals map[string]bool) { + for _, decl := range f.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + + for _, spec := range genDecl.Specs { + valueSpec, ok := spec.(*ast.ValueSpec) + if !ok { + continue + } + globals[valueSpec.Names[0].Name] = true + } + } +} + +// collectReferences builds a map of selector expressions, from +// left hand side (X) to a set of right hand sides (Sel). +func collectReferences(f *ast.File) references { + refs := references{} + + var visitor visitFn + visitor = func(node ast.Node) ast.Visitor { + if node == nil { + return visitor + } + switch v := node.(type) { + case *ast.SelectorExpr: + xident, ok := v.X.(*ast.Ident) + if !ok { + break + } + if xident.Obj != nil { + // If the parser can resolve it, it's not a package ref. + break + } + if !ast.IsExported(v.Sel.Name) { + // Whatever this is, it's not exported from a package. + break + } + pkgName := xident.Name + r := refs[pkgName] + if r == nil { + r = make(map[string]bool) + refs[pkgName] = r + } + r[v.Sel.Name] = true + } + return visitor + } + ast.Walk(visitor, f) + return refs +} + +// collectImports returns all the imports in f. +// Unnamed imports (., _) and "C" are ignored. +func collectImports(f *ast.File) []*ImportInfo { + var imports []*ImportInfo + for _, imp := range f.Imports { + var name string + if imp.Name != nil { + name = imp.Name.Name + } + if imp.Path.Value == `"C"` || name == "_" || name == "." { + continue + } + path := strings.Trim(imp.Path.Value, `"`) + imports = append(imports, &ImportInfo{ + Name: name, + ImportPath: path, + }) + } + return imports +} + +// findMissingImport searches pass's candidates for an import that provides +// pkg, containing all of syms. +func (p *pass) findMissingImport(pkg string, syms map[string]bool) *ImportInfo { + for _, candidate := range p.candidates { + pkgInfo, ok := p.knownPackages[candidate.ImportPath] + if !ok { + continue + } + if p.importIdentifier(candidate) != pkg { + continue + } + + allFound := true + for right := range syms { + if !pkgInfo.exports[right] { + allFound = false + break + } + } + + if allFound { + return candidate + } + } + return nil +} + +// references is set of references found in a Go file. The first map key is the +// left hand side of a selector expression, the second key is the right hand +// side, and the value should always be true. +type references map[string]map[string]bool + +// A pass contains all the inputs and state necessary to fix a file's imports. +// It can be modified in some ways during use; see comments below. +type pass struct { + // Inputs. These must be set before a call to load, and not modified after. + fset *token.FileSet // fset used to parse f and its siblings. + f *ast.File // the file being fixed. + srcDir string // the directory containing f. + env *ProcessEnv // the environment to use for go commands, etc. + loadRealPackageNames bool // if true, load package names from disk rather than guessing them. + otherFiles []*ast.File // sibling files. + + // Intermediate state, generated by load. + existingImports map[string][]*ImportInfo + allRefs references + missingRefs references + + // Inputs to fix. These can be augmented between successive fix calls. + lastTry bool // indicates that this is the last call and fix should clean up as best it can. + candidates []*ImportInfo // candidate imports in priority order. + knownPackages map[string]*packageInfo // information about all known packages. +} + +// loadPackageNames saves the package names for everything referenced by imports. +func (p *pass) loadPackageNames(imports []*ImportInfo) error { + if p.env.Logf != nil { + p.env.Logf("loading package names for %v packages", len(imports)) + defer func() { + p.env.Logf("done loading package names for %v packages", len(imports)) + }() + } + var unknown []string + for _, imp := range imports { + if _, ok := p.knownPackages[imp.ImportPath]; ok { + continue + } + unknown = append(unknown, imp.ImportPath) + } + + resolver, err := p.env.GetResolver() + if err != nil { + return err + } + + names, err := resolver.loadPackageNames(unknown, p.srcDir) + if err != nil { + return err + } + + for path, name := range names { + p.knownPackages[path] = &packageInfo{ + name: name, + exports: map[string]bool{}, + } + } + return nil +} + +// if there is a trailing major version, remove it +func withoutVersion(nm string) string { + if v := path.Base(nm); len(v) > 0 && v[0] == 'v' { + if _, err := strconv.Atoi(v[1:]); err == nil { + // this is, for instance, called with rand/v2 and returns rand + if len(v) < len(nm) { + xnm := nm[:len(nm)-len(v)-1] + return path.Base(xnm) + } + } + } + return nm +} + +// importIdentifier returns the identifier that imp will introduce. It will +// guess if the package name has not been loaded, e.g. because the source +// is not available. +func (p *pass) importIdentifier(imp *ImportInfo) string { + if imp.Name != "" { + return imp.Name + } + known := p.knownPackages[imp.ImportPath] + if known != nil && known.name != "" { + return withoutVersion(known.name) + } + return ImportPathToAssumedName(imp.ImportPath) +} + +// load reads in everything necessary to run a pass, and reports whether the +// file already has all the imports it needs. It fills in p.missingRefs with the +// file's missing symbols, if any, or removes unused imports if not. +func (p *pass) load() ([]*ImportFix, bool) { + p.knownPackages = map[string]*packageInfo{} + p.missingRefs = references{} + p.existingImports = map[string][]*ImportInfo{} + + // Load basic information about the file in question. + p.allRefs = collectReferences(p.f) + + // Load stuff from other files in the same package: + // global variables so we know they don't need resolving, and imports + // that we might want to mimic. + globals := map[string]bool{} + for _, otherFile := range p.otherFiles { + // Don't load globals from files that are in the same directory + // but a different package. Using them to suggest imports is OK. + if p.f.Name.Name == otherFile.Name.Name { + addGlobals(otherFile, globals) + } + p.candidates = append(p.candidates, collectImports(otherFile)...) + } + + // Resolve all the import paths we've seen to package names, and store + // f's imports by the identifier they introduce. + imports := collectImports(p.f) + if p.loadRealPackageNames { + err := p.loadPackageNames(append(imports, p.candidates...)) + if err != nil { + p.env.logf("loading package names: %v", err) + return nil, false + } + } + for _, imp := range imports { + p.existingImports[p.importIdentifier(imp)] = append(p.existingImports[p.importIdentifier(imp)], imp) + } + + // Find missing references. + for left, rights := range p.allRefs { + if globals[left] { + continue + } + _, ok := p.existingImports[left] + if !ok { + p.missingRefs[left] = rights + continue + } + } + if len(p.missingRefs) != 0 { + return nil, false + } + + return p.fix() +} + +// fix attempts to satisfy missing imports using p.candidates. If it finds +// everything, or if p.lastTry is true, it updates fixes to add the imports it found, +// delete anything unused, and update import names, and returns true. +func (p *pass) fix() ([]*ImportFix, bool) { + // Find missing imports. + var selected []*ImportInfo + for left, rights := range p.missingRefs { + if imp := p.findMissingImport(left, rights); imp != nil { + selected = append(selected, imp) + } + } + + if !p.lastTry && len(selected) != len(p.missingRefs) { + return nil, false + } + + // Found everything, or giving up. Add the new imports and remove any unused. + var fixes []*ImportFix + for _, identifierImports := range p.existingImports { + for _, imp := range identifierImports { + // We deliberately ignore globals here, because we can't be sure + // they're in the same package. People do things like put multiple + // main packages in the same directory, and we don't want to + // remove imports if they happen to have the same name as a var in + // a different package. + if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok { + fixes = append(fixes, &ImportFix{ + StmtInfo: *imp, + IdentName: p.importIdentifier(imp), + FixType: DeleteImport, + }) + continue + } + + // An existing import may need to update its import name to be correct. + if name := p.importSpecName(imp); name != imp.Name { + fixes = append(fixes, &ImportFix{ + StmtInfo: ImportInfo{ + Name: name, + ImportPath: imp.ImportPath, + }, + IdentName: p.importIdentifier(imp), + FixType: SetImportName, + }) + } + } + } + // Collecting fixes involved map iteration, so sort for stability. See + // golang/go#59976. + sortFixes(fixes) + + // collect selected fixes in a separate slice, so that it can be sorted + // separately. Note that these fixes must occur after fixes to existing + // imports. TODO(rfindley): figure out why. + var selectedFixes []*ImportFix + for _, imp := range selected { + selectedFixes = append(selectedFixes, &ImportFix{ + StmtInfo: ImportInfo{ + Name: p.importSpecName(imp), + ImportPath: imp.ImportPath, + }, + IdentName: p.importIdentifier(imp), + FixType: AddImport, + }) + } + sortFixes(selectedFixes) + + return append(fixes, selectedFixes...), true +} + +func sortFixes(fixes []*ImportFix) { + sort.Slice(fixes, func(i, j int) bool { + fi, fj := fixes[i], fixes[j] + if fi.StmtInfo.ImportPath != fj.StmtInfo.ImportPath { + return fi.StmtInfo.ImportPath < fj.StmtInfo.ImportPath + } + if fi.StmtInfo.Name != fj.StmtInfo.Name { + return fi.StmtInfo.Name < fj.StmtInfo.Name + } + if fi.IdentName != fj.IdentName { + return fi.IdentName < fj.IdentName + } + return fi.FixType < fj.FixType + }) +} + +// importSpecName gets the import name of imp in the import spec. +// +// When the import identifier matches the assumed import name, the import name does +// not appear in the import spec. +func (p *pass) importSpecName(imp *ImportInfo) string { + // If we did not load the real package names, or the name is already set, + // we just return the existing name. + if !p.loadRealPackageNames || imp.Name != "" { + return imp.Name + } + + ident := p.importIdentifier(imp) + if ident == ImportPathToAssumedName(imp.ImportPath) { + return "" // ident not needed since the assumed and real names are the same. + } + return ident +} + +// apply will perform the fixes on f in order. +func apply(fset *token.FileSet, f *ast.File, fixes []*ImportFix) { + for _, fix := range fixes { + switch fix.FixType { + case DeleteImport: + astutil.DeleteNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath) + case AddImport: + astutil.AddNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath) + case SetImportName: + // Find the matching import path and change the name. + for _, spec := range f.Imports { + path := strings.Trim(spec.Path.Value, `"`) + if path == fix.StmtInfo.ImportPath { + spec.Name = &ast.Ident{ + Name: fix.StmtInfo.Name, + NamePos: spec.Pos(), + } + } + } + } + } +} + +// assumeSiblingImportsValid assumes that siblings' use of packages is valid, +// adding the exports they use. +func (p *pass) assumeSiblingImportsValid() { + for _, f := range p.otherFiles { + refs := collectReferences(f) + imports := collectImports(f) + importsByName := map[string]*ImportInfo{} + for _, imp := range imports { + importsByName[p.importIdentifier(imp)] = imp + } + for left, rights := range refs { + if imp, ok := importsByName[left]; ok { + if m, ok := stdlib.PackageSymbols[imp.ImportPath]; ok { + // We have the stdlib in memory; no need to guess. + rights = symbolNameSet(m) + } + p.addCandidate(imp, &packageInfo{ + // no name; we already know it. + exports: rights, + }) + } + } + } +} + +// addCandidate adds a candidate import to p, and merges in the information +// in pkg. +func (p *pass) addCandidate(imp *ImportInfo, pkg *packageInfo) { + p.candidates = append(p.candidates, imp) + if existing, ok := p.knownPackages[imp.ImportPath]; ok { + if existing.name == "" { + existing.name = pkg.name + } + for export := range pkg.exports { + existing.exports[export] = true + } + } else { + p.knownPackages[imp.ImportPath] = pkg + } +} + +// fixImports adds and removes imports from f so that all its references are +// satisfied and there are no unused imports. +// +// This is declared as a variable rather than a function so goimports can +// easily be extended by adding a file with an init function. +// +// DO NOT REMOVE: used internally at Google. +var fixImports = fixImportsDefault + +func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) error { + fixes, err := getFixes(context.Background(), fset, f, filename, env) + if err != nil { + return err + } + apply(fset, f, fixes) + return err +} + +// getFixes gets the import fixes that need to be made to f in order to fix the imports. +// It does not modify the ast. +func getFixes(ctx context.Context, fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) ([]*ImportFix, error) { + abs, err := filepath.Abs(filename) + if err != nil { + return nil, err + } + srcDir := filepath.Dir(abs) + env.logf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir) + + // First pass: looking only at f, and using the naive algorithm to + // derive package names from import paths, see if the file is already + // complete. We can't add any imports yet, because we don't know + // if missing references are actually package vars. + p := &pass{fset: fset, f: f, srcDir: srcDir, env: env} + if fixes, done := p.load(); done { + return fixes, nil + } + + otherFiles, err := parseOtherFiles(ctx, fset, srcDir, filename) + if err != nil { + return nil, err + } + + // Second pass: add information from other files in the same package, + // like their package vars and imports. + p.otherFiles = otherFiles + if fixes, done := p.load(); done { + return fixes, nil + } + + // Now we can try adding imports from the stdlib. + p.assumeSiblingImportsValid() + addStdlibCandidates(p, p.missingRefs) + if fixes, done := p.fix(); done { + return fixes, nil + } + + // Third pass: get real package names where we had previously used + // the naive algorithm. + p = &pass{fset: fset, f: f, srcDir: srcDir, env: env} + p.loadRealPackageNames = true + p.otherFiles = otherFiles + if fixes, done := p.load(); done { + return fixes, nil + } + + if err := addStdlibCandidates(p, p.missingRefs); err != nil { + return nil, err + } + p.assumeSiblingImportsValid() + if fixes, done := p.fix(); done { + return fixes, nil + } + + // Go look for candidates in $GOPATH, etc. We don't necessarily load + // the real exports of sibling imports, so keep assuming their contents. + if err := addExternalCandidates(ctx, p, p.missingRefs, filename); err != nil { + return nil, err + } + + p.lastTry = true + fixes, _ := p.fix() + return fixes, nil +} + +// MaxRelevance is the highest relevance, used for the standard library. +// Chosen arbitrarily to match pre-existing gopls code. +const MaxRelevance = 7.0 + +// getCandidatePkgs works with the passed callback to find all acceptable packages. +// It deduplicates by import path, and uses a cached stdlib rather than reading +// from disk. +func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filename, filePkg string, env *ProcessEnv) error { + notSelf := func(p *pkg) bool { + return p.packageName != filePkg || p.dir != filepath.Dir(filename) + } + goenv, err := env.goEnv() + if err != nil { + return err + } + + var mu sync.Mutex // to guard asynchronous access to dupCheck + dupCheck := map[string]struct{}{} + + // Start off with the standard library. + for importPath, symbols := range stdlib.PackageSymbols { + p := &pkg{ + dir: filepath.Join(goenv["GOROOT"], "src", importPath), + importPathShort: importPath, + packageName: path.Base(importPath), + relevance: MaxRelevance, + } + dupCheck[importPath] = struct{}{} + if notSelf(p) && wrappedCallback.dirFound(p) && wrappedCallback.packageNameLoaded(p) { + var exports []stdlib.Symbol + for _, sym := range symbols { + switch sym.Kind { + case stdlib.Func, stdlib.Type, stdlib.Var, stdlib.Const: + exports = append(exports, sym) + } + } + wrappedCallback.exportsLoaded(p, exports) + } + } + + scanFilter := &scanCallback{ + rootFound: func(root gopathwalk.Root) bool { + // Exclude goroot results -- getting them is relatively expensive, not cached, + // and generally redundant with the in-memory version. + return root.Type != gopathwalk.RootGOROOT && wrappedCallback.rootFound(root) + }, + dirFound: wrappedCallback.dirFound, + packageNameLoaded: func(pkg *pkg) bool { + mu.Lock() + defer mu.Unlock() + if _, ok := dupCheck[pkg.importPathShort]; ok { + return false + } + dupCheck[pkg.importPathShort] = struct{}{} + return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg) + }, + exportsLoaded: func(pkg *pkg, exports []stdlib.Symbol) { + // If we're an x_test, load the package under test's test variant. + if strings.HasSuffix(filePkg, "_test") && pkg.dir == filepath.Dir(filename) { + var err error + _, exports, err = loadExportsFromFiles(ctx, env, pkg.dir, true) + if err != nil { + return + } + } + wrappedCallback.exportsLoaded(pkg, exports) + }, + } + resolver, err := env.GetResolver() + if err != nil { + return err + } + return resolver.scan(ctx, scanFilter) +} + +func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]float64, error) { + result := make(map[string]float64) + resolver, err := env.GetResolver() + if err != nil { + return nil, err + } + for _, path := range paths { + result[path] = resolver.scoreImportPath(ctx, path) + } + return result, nil +} + +func PrimeCache(ctx context.Context, resolver Resolver) error { + // Fully scan the disk for directories, but don't actually read any Go files. + callback := &scanCallback{ + rootFound: func(root gopathwalk.Root) bool { + // See getCandidatePkgs: walking GOROOT is apparently expensive and + // unnecessary. + return root.Type != gopathwalk.RootGOROOT + }, + dirFound: func(pkg *pkg) bool { + return false + }, + // packageNameLoaded and exportsLoaded must never be called. + } + + return resolver.scan(ctx, callback) +} + +func candidateImportName(pkg *pkg) string { + if ImportPathToAssumedName(pkg.importPathShort) != pkg.packageName { + return pkg.packageName + } + return "" +} + +// GetAllCandidates calls wrapped for each package whose name starts with +// searchPrefix, and can be imported from filename with the package name filePkg. +// +// Beware that the wrapped function may be called multiple times concurrently. +// TODO(adonovan): encapsulate the concurrency. +func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + if !canUse(filename, pkg.dir) { + return false + } + // Try the assumed package name first, then a simpler path match + // in case of packages named vN, which are not uncommon. + return strings.HasPrefix(ImportPathToAssumedName(pkg.importPathShort), searchPrefix) || + strings.HasPrefix(path.Base(pkg.importPathShort), searchPrefix) + }, + packageNameLoaded: func(pkg *pkg) bool { + if !strings.HasPrefix(pkg.packageName, searchPrefix) { + return false + } + wrapped(ImportFix{ + StmtInfo: ImportInfo{ + ImportPath: pkg.importPathShort, + Name: candidateImportName(pkg), + }, + IdentName: pkg.packageName, + FixType: AddImport, + Relevance: pkg.relevance, + }) + return false + }, + } + return getCandidatePkgs(ctx, callback, filename, filePkg, env) +} + +// GetImportPaths calls wrapped for each package whose import path starts with +// searchPrefix, and can be imported from filename with the package name filePkg. +func GetImportPaths(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + if !canUse(filename, pkg.dir) { + return false + } + return strings.HasPrefix(pkg.importPathShort, searchPrefix) + }, + packageNameLoaded: func(pkg *pkg) bool { + wrapped(ImportFix{ + StmtInfo: ImportInfo{ + ImportPath: pkg.importPathShort, + Name: candidateImportName(pkg), + }, + IdentName: pkg.packageName, + FixType: AddImport, + Relevance: pkg.relevance, + }) + return false + }, + } + return getCandidatePkgs(ctx, callback, filename, filePkg, env) +} + +// A PackageExport is a package and its exports. +type PackageExport struct { + Fix *ImportFix + Exports []stdlib.Symbol +} + +// GetPackageExports returns all known packages with name pkg and their exports. +func GetPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error { + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + return pkgIsCandidate(filename, references{searchPkg: nil}, pkg) + }, + packageNameLoaded: func(pkg *pkg) bool { + return pkg.packageName == searchPkg + }, + exportsLoaded: func(pkg *pkg, exports []stdlib.Symbol) { + sortSymbols(exports) + wrapped(PackageExport{ + Fix: &ImportFix{ + StmtInfo: ImportInfo{ + ImportPath: pkg.importPathShort, + Name: candidateImportName(pkg), + }, + IdentName: pkg.packageName, + FixType: AddImport, + Relevance: pkg.relevance, + }, + Exports: exports, + }) + }, + } + return getCandidatePkgs(ctx, callback, filename, filePkg, env) +} + +// TODO(rfindley): we should depend on GOOS and GOARCH, to provide accurate +// imports when doing cross-platform development. +var requiredGoEnvVars = []string{ + "GO111MODULE", + "GOFLAGS", + "GOINSECURE", + "GOMOD", + "GOMODCACHE", + "GONOPROXY", + "GONOSUMDB", + "GOPATH", + "GOPROXY", + "GOROOT", + "GOSUMDB", + "GOWORK", +} + +// ProcessEnv contains environment variables and settings that affect the use of +// the go command, the go/build package, etc. +// +// ...a ProcessEnv *also* overwrites its Env along with derived state in the +// form of the resolver. And because it is lazily initialized, an env may just +// be broken and unusable, but there is no way for the caller to detect that: +// all queries will just fail. +// +// TODO(rfindley): refactor this package so that this type (perhaps renamed to +// just Env or Config) is an immutable configuration struct, to be exchanged +// for an initialized object via a constructor that returns an error. Perhaps +// the signature should be `func NewResolver(*Env) (*Resolver, error)`, where +// resolver is a concrete type used for resolving imports. Via this +// refactoring, we can avoid the need to call ProcessEnv.init and +// ProcessEnv.GoEnv everywhere, and implicitly fix all the places where this +// these are misused. Also, we'd delegate the caller the decision of how to +// handle a broken environment. +type ProcessEnv struct { + GocmdRunner *gocommand.Runner + + BuildFlags []string + ModFlag string + + // SkipPathInScan returns true if the path should be skipped from scans of + // the RootCurrentModule root type. The function argument is a clean, + // absolute path. + SkipPathInScan func(string) bool + + // Env overrides the OS environment, and can be used to specify + // GOPROXY, GO111MODULE, etc. PATH cannot be set here, because + // exec.Command will not honor it. + // Specifying all of requiredGoEnvVars avoids a call to `go env`. + Env map[string]string + + WorkingDir string + + // If Logf is non-nil, debug logging is enabled through this function. + Logf func(format string, args ...interface{}) + + // If set, ModCache holds a shared cache of directory info to use across + // multiple ProcessEnvs. + ModCache *DirInfoCache + + initialized bool // see TODO above + + // resolver and resolverErr are lazily evaluated (see GetResolver). + // This is unclean, but see the big TODO in the docstring for ProcessEnv + // above: for now, we can't be sure that the ProcessEnv is fully initialized. + resolver Resolver + resolverErr error +} + +func (e *ProcessEnv) goEnv() (map[string]string, error) { + if err := e.init(); err != nil { + return nil, err + } + return e.Env, nil +} + +func (e *ProcessEnv) matchFile(dir, name string) (bool, error) { + bctx, err := e.buildContext() + if err != nil { + return false, err + } + return bctx.MatchFile(dir, name) +} + +// CopyConfig copies the env's configuration into a new env. +func (e *ProcessEnv) CopyConfig() *ProcessEnv { + copy := &ProcessEnv{ + GocmdRunner: e.GocmdRunner, + initialized: e.initialized, + BuildFlags: e.BuildFlags, + Logf: e.Logf, + WorkingDir: e.WorkingDir, + resolver: nil, + Env: map[string]string{}, + } + for k, v := range e.Env { + copy.Env[k] = v + } + return copy +} + +func (e *ProcessEnv) init() error { + if e.initialized { + return nil + } + + foundAllRequired := true + for _, k := range requiredGoEnvVars { + if _, ok := e.Env[k]; !ok { + foundAllRequired = false + break + } + } + if foundAllRequired { + e.initialized = true + return nil + } + + if e.Env == nil { + e.Env = map[string]string{} + } + + goEnv := map[string]string{} + stdout, err := e.invokeGo(context.TODO(), "env", append([]string{"-json"}, requiredGoEnvVars...)...) + if err != nil { + return err + } + if err := json.Unmarshal(stdout.Bytes(), &goEnv); err != nil { + return err + } + for k, v := range goEnv { + e.Env[k] = v + } + e.initialized = true + return nil +} + +func (e *ProcessEnv) env() []string { + var env []string // the gocommand package will prepend os.Environ. + for k, v := range e.Env { + env = append(env, k+"="+v) + } + return env +} + +func (e *ProcessEnv) GetResolver() (Resolver, error) { + if err := e.init(); err != nil { + return nil, err + } + + if e.resolver == nil && e.resolverErr == nil { + // TODO(rfindley): we should only use a gopathResolver here if the working + // directory is actually *in* GOPATH. (I seem to recall an open gopls issue + // for this behavior, but I can't find it). + // + // For gopls, we can optionally explicitly choose a resolver type, since we + // already know the view type. + if len(e.Env["GOMOD"]) == 0 && len(e.Env["GOWORK"]) == 0 { + e.resolver = newGopathResolver(e) + e.logf("created gopath resolver") + } else if r, err := newModuleResolver(e, e.ModCache); err != nil { + e.resolverErr = err + e.logf("failed to create module resolver: %v", err) + } else { + e.resolver = Resolver(r) + e.logf("created module resolver") + } + } + + return e.resolver, e.resolverErr +} + +// logf logs if e.Logf is non-nil. +func (e *ProcessEnv) logf(format string, args ...any) { + if e.Logf != nil { + e.Logf(format, args...) + } +} + +// buildContext returns the build.Context to use for matching files. +// +// TODO(rfindley): support dynamic GOOS, GOARCH here, when doing cross-platform +// development. +func (e *ProcessEnv) buildContext() (*build.Context, error) { + ctx := build.Default + goenv, err := e.goEnv() + if err != nil { + return nil, err + } + ctx.GOROOT = goenv["GOROOT"] + ctx.GOPATH = goenv["GOPATH"] + + // As of Go 1.14, build.Context has a Dir field + // (see golang.org/issue/34860). + // Populate it only if present. + rc := reflect.ValueOf(&ctx).Elem() + dir := rc.FieldByName("Dir") + if dir.IsValid() && dir.Kind() == reflect.String { + dir.SetString(e.WorkingDir) + } + + // Since Go 1.11, go/build.Context.Import may invoke 'go list' depending on + // the value in GO111MODULE in the process's environment. We always want to + // run in GOPATH mode when calling Import, so we need to prevent this from + // happening. In Go 1.16, GO111MODULE defaults to "on", so this problem comes + // up more frequently. + // + // HACK: setting any of the Context I/O hooks prevents Import from invoking + // 'go list', regardless of GO111MODULE. This is undocumented, but it's + // unlikely to change before GOPATH support is removed. + ctx.ReadDir = ioutil.ReadDir + + return &ctx, nil +} + +func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) (*bytes.Buffer, error) { + inv := gocommand.Invocation{ + Verb: verb, + Args: args, + BuildFlags: e.BuildFlags, + Env: e.env(), + Logf: e.Logf, + WorkingDir: e.WorkingDir, + } + return e.GocmdRunner.Run(ctx, inv) +} + +func addStdlibCandidates(pass *pass, refs references) error { + goenv, err := pass.env.goEnv() + if err != nil { + return err + } + localbase := func(nm string) string { + ans := path.Base(nm) + if ans[0] == 'v' { + // this is called, for instance, with math/rand/v2 and returns rand/v2 + if _, err := strconv.Atoi(ans[1:]); err == nil { + ix := strings.LastIndex(nm, ans) + more := path.Base(nm[:ix]) + ans = path.Join(more, ans) + } + } + return ans + } + add := func(pkg string) { + // Prevent self-imports. + if path.Base(pkg) == pass.f.Name.Name && filepath.Join(goenv["GOROOT"], "src", pkg) == pass.srcDir { + return + } + exports := symbolNameSet(stdlib.PackageSymbols[pkg]) + pass.addCandidate( + &ImportInfo{ImportPath: pkg}, + &packageInfo{name: localbase(pkg), exports: exports}) + } + for left := range refs { + if left == "rand" { + // Make sure we try crypto/rand before any version of math/rand as both have Int() + // and our policy is to recommend crypto + add("crypto/rand") + // if the user's no later than go1.21, this should be "math/rand" + // but we have no way of figuring out what the user is using + // TODO: investigate using the toolchain version to disambiguate in the stdlib + add("math/rand/v2") + continue + } + for importPath := range stdlib.PackageSymbols { + if path.Base(importPath) == left { + add(importPath) + } + } + } + return nil +} + +// A Resolver does the build-system-specific parts of goimports. +type Resolver interface { + // loadPackageNames loads the package names in importPaths. + loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) + + // scan works with callback to search for packages. See scanCallback for details. + scan(ctx context.Context, callback *scanCallback) error + + // loadExports returns the package name and set of exported symbols in the + // package at dir. loadExports may be called concurrently. + loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) + + // scoreImportPath returns the relevance for an import path. + scoreImportPath(ctx context.Context, path string) float64 + + // ClearForNewScan returns a new Resolver based on the receiver that has + // cleared its internal caches of directory contents. + // + // The new resolver should be primed and then set via + // [ProcessEnv.UpdateResolver]. + ClearForNewScan() Resolver +} + +// A scanCallback controls a call to scan and receives its results. +// In general, minor errors will be silently discarded; a user should not +// expect to receive a full series of calls for everything. +type scanCallback struct { + // rootFound is called before scanning a new root dir. If it returns true, + // the root will be scanned. Returning false will not necessarily prevent + // directories from that root making it to dirFound. + rootFound func(gopathwalk.Root) bool + // dirFound is called when a directory is found that is possibly a Go package. + // pkg will be populated with everything except packageName. + // If it returns true, the package's name will be loaded. + dirFound func(pkg *pkg) bool + // packageNameLoaded is called when a package is found and its name is loaded. + // If it returns true, the package's exports will be loaded. + packageNameLoaded func(pkg *pkg) bool + // exportsLoaded is called when a package's exports have been loaded. + exportsLoaded func(pkg *pkg, exports []stdlib.Symbol) +} + +func addExternalCandidates(ctx context.Context, pass *pass, refs references, filename string) error { + ctx, done := event.Start(ctx, "imports.addExternalCandidates") + defer done() + + var mu sync.Mutex + found := make(map[string][]pkgDistance) + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true // We want everything. + }, + dirFound: func(pkg *pkg) bool { + return pkgIsCandidate(filename, refs, pkg) + }, + packageNameLoaded: func(pkg *pkg) bool { + if _, want := refs[pkg.packageName]; !want { + return false + } + if pkg.dir == pass.srcDir && pass.f.Name.Name == pkg.packageName { + // The candidate is in the same directory and has the + // same package name. Don't try to import ourselves. + return false + } + if !canUse(filename, pkg.dir) { + return false + } + mu.Lock() + defer mu.Unlock() + found[pkg.packageName] = append(found[pkg.packageName], pkgDistance{pkg, distance(pass.srcDir, pkg.dir)}) + return false // We'll do our own loading after we sort. + }, + } + resolver, err := pass.env.GetResolver() + if err != nil { + return err + } + if err = resolver.scan(ctx, callback); err != nil { + return err + } + + // Search for imports matching potential package references. + type result struct { + imp *ImportInfo + pkg *packageInfo + } + results := make([]*result, len(refs)) + + g, ctx := errgroup.WithContext(ctx) + + searcher := symbolSearcher{ + logf: pass.env.logf, + srcDir: pass.srcDir, + xtest: strings.HasSuffix(pass.f.Name.Name, "_test"), + loadExports: resolver.loadExports, + } + + i := 0 + for pkgName, symbols := range refs { + index := i // claim an index in results + i++ + pkgName := pkgName + symbols := symbols + + g.Go(func() error { + found, err := searcher.search(ctx, found[pkgName], pkgName, symbols) + if err != nil { + return err + } + if found == nil { + return nil // No matching package. + } + + imp := &ImportInfo{ + ImportPath: found.importPathShort, + } + pkg := &packageInfo{ + name: pkgName, + exports: symbols, + } + results[index] = &result{imp, pkg} + return nil + }) + } + if err := g.Wait(); err != nil { + return err + } + + for _, result := range results { + if result == nil { + continue + } + // Don't offer completions that would shadow predeclared + // names, such as github.com/coreos/etcd/error. + if types.Universe.Lookup(result.pkg.name) != nil { // predeclared + // Ideally we would skip this candidate only + // if the predeclared name is actually + // referenced by the file, but that's a lot + // trickier to compute and would still create + // an import that is likely to surprise the + // user before long. + continue + } + pass.addCandidate(result.imp, result.pkg) + } + return nil +} + +// notIdentifier reports whether ch is an invalid identifier character. +func notIdentifier(ch rune) bool { + return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || + '0' <= ch && ch <= '9' || + ch == '_' || + ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch))) +} + +// ImportPathToAssumedName returns the assumed package name of an import path. +// It does this using only string parsing of the import path. +// It picks the last element of the path that does not look like a major +// version, and then picks the valid identifier off the start of that element. +// It is used to determine if a local rename should be added to an import for +// clarity. +// This function could be moved to a standard package and exported if we want +// for use in other tools. +func ImportPathToAssumedName(importPath string) string { + base := path.Base(importPath) + if strings.HasPrefix(base, "v") { + if _, err := strconv.Atoi(base[1:]); err == nil { + dir := path.Dir(importPath) + if dir != "." { + base = path.Base(dir) + } + } + } + base = strings.TrimPrefix(base, "go-") + if i := strings.IndexFunc(base, notIdentifier); i >= 0 { + base = base[:i] + } + return base +} + +// gopathResolver implements resolver for GOPATH workspaces. +type gopathResolver struct { + env *ProcessEnv + walked bool + cache *DirInfoCache + scanSema chan struct{} // scanSema prevents concurrent scans. +} + +func newGopathResolver(env *ProcessEnv) *gopathResolver { + r := &gopathResolver{ + env: env, + cache: NewDirInfoCache(), + scanSema: make(chan struct{}, 1), + } + r.scanSema <- struct{}{} + return r +} + +func (r *gopathResolver) ClearForNewScan() Resolver { + return newGopathResolver(r.env) +} + +func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { + names := map[string]string{} + bctx, err := r.env.buildContext() + if err != nil { + return nil, err + } + for _, path := range importPaths { + names[path] = importPathToName(bctx, path, srcDir) + } + return names, nil +} + +// importPathToName finds out the actual package name, as declared in its .go files. +func importPathToName(bctx *build.Context, importPath, srcDir string) string { + // Fast path for standard library without going to disk. + if stdlib.HasPackage(importPath) { + return path.Base(importPath) // stdlib packages always match their paths. + } + + buildPkg, err := bctx.Import(importPath, srcDir, build.FindOnly) + if err != nil { + return "" + } + pkgName, err := packageDirToName(buildPkg.Dir) + if err != nil { + return "" + } + return pkgName +} + +// packageDirToName is a faster version of build.Import if +// the only thing desired is the package name. Given a directory, +// packageDirToName then only parses one file in the package, +// trusting that the files in the directory are consistent. +func packageDirToName(dir string) (packageName string, err error) { + d, err := os.Open(dir) + if err != nil { + return "", err + } + names, err := d.Readdirnames(-1) + d.Close() + if err != nil { + return "", err + } + sort.Strings(names) // to have predictable behavior + var lastErr error + var nfile int + for _, name := range names { + if !strings.HasSuffix(name, ".go") { + continue + } + if strings.HasSuffix(name, "_test.go") { + continue + } + nfile++ + fullFile := filepath.Join(dir, name) + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly) + if err != nil { + lastErr = err + continue + } + pkgName := f.Name.Name + if pkgName == "documentation" { + // Special case from go/build.ImportDir, not + // handled by ctx.MatchFile. + continue + } + if pkgName == "main" { + // Also skip package main, assuming it's a +build ignore generator or example. + // Since you can't import a package main anyway, there's no harm here. + continue + } + return pkgName, nil + } + if lastErr != nil { + return "", lastErr + } + return "", fmt.Errorf("no importable package found in %d Go files", nfile) +} + +type pkg struct { + dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http") + importPathShort string // vendorless import path ("net/http", "a/b") + packageName string // package name loaded from source if requested + relevance float64 // a weakly-defined score of how relevant a package is. 0 is most relevant. +} + +type pkgDistance struct { + pkg *pkg + distance int // relative distance to target +} + +// byDistanceOrImportPathShortLength sorts by relative distance breaking ties +// on the short import path length and then the import string itself. +type byDistanceOrImportPathShortLength []pkgDistance + +func (s byDistanceOrImportPathShortLength) Len() int { return len(s) } +func (s byDistanceOrImportPathShortLength) Less(i, j int) bool { + di, dj := s[i].distance, s[j].distance + if di == -1 { + return false + } + if dj == -1 { + return true + } + if di != dj { + return di < dj + } + + vi, vj := s[i].pkg.importPathShort, s[j].pkg.importPathShort + if len(vi) != len(vj) { + return len(vi) < len(vj) + } + return vi < vj +} +func (s byDistanceOrImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func distance(basepath, targetpath string) int { + p, err := filepath.Rel(basepath, targetpath) + if err != nil { + return -1 + } + if p == "." { + return 0 + } + return strings.Count(p, string(filepath.Separator)) + 1 +} + +func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error { + add := func(root gopathwalk.Root, dir string) { + // We assume cached directories have not changed. We can skip them and their + // children. + if _, ok := r.cache.Load(dir); ok { + return + } + + importpath := filepath.ToSlash(dir[len(root.Path)+len("/"):]) + info := directoryPackageInfo{ + status: directoryScanned, + dir: dir, + rootType: root.Type, + nonCanonicalImportPath: VendorlessPath(importpath), + } + r.cache.Store(dir, info) + } + processDir := func(info directoryPackageInfo) { + // Skip this directory if we were not able to get the package information successfully. + if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { + return + } + + p := &pkg{ + importPathShort: info.nonCanonicalImportPath, + dir: info.dir, + relevance: MaxRelevance - 1, + } + if info.rootType == gopathwalk.RootGOROOT { + p.relevance = MaxRelevance + } + + if !callback.dirFound(p) { + return + } + var err error + p.packageName, err = r.cache.CachePackageName(info) + if err != nil { + return + } + + if !callback.packageNameLoaded(p) { + return + } + if _, exports, err := r.loadExports(ctx, p, false); err == nil { + callback.exportsLoaded(p, exports) + } + } + stop := r.cache.ScanAndListen(ctx, processDir) + defer stop() + + goenv, err := r.env.goEnv() + if err != nil { + return err + } + var roots []gopathwalk.Root + roots = append(roots, gopathwalk.Root{Path: filepath.Join(goenv["GOROOT"], "src"), Type: gopathwalk.RootGOROOT}) + for _, p := range filepath.SplitList(goenv["GOPATH"]) { + roots = append(roots, gopathwalk.Root{Path: filepath.Join(p, "src"), Type: gopathwalk.RootGOPATH}) + } + // The callback is not necessarily safe to use in the goroutine below. Process roots eagerly. + roots = filterRoots(roots, callback.rootFound) + // We can't cancel walks, because we need them to finish to have a usable + // cache. Instead, run them in a separate goroutine and detach. + scanDone := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + return + case <-r.scanSema: + } + defer func() { r.scanSema <- struct{}{} }() + gopathwalk.Walk(roots, add, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: false}) + close(scanDone) + }() + select { + case <-ctx.Done(): + case <-scanDone: + } + return nil +} + +func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) float64 { + if stdlib.HasPackage(path) { + return MaxRelevance + } + return MaxRelevance - 1 +} + +func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []gopathwalk.Root { + var result []gopathwalk.Root + for _, root := range roots { + if !include(root) { + continue + } + result = append(result, root) + } + return result +} + +func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) { + if info, ok := r.cache.Load(pkg.dir); ok && !includeTest { + return r.cache.CacheExports(ctx, r.env, info) + } + return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) +} + +// VendorlessPath returns the devendorized version of the import path ipath. +// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". +func VendorlessPath(ipath string) string { + // Devendorize for use in import statement. + if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { + return ipath[i+len("/vendor/"):] + } + if strings.HasPrefix(ipath, "vendor/") { + return ipath[len("vendor/"):] + } + return ipath +} + +func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []stdlib.Symbol, error) { + // Look for non-test, buildable .go files which could provide exports. + all, err := os.ReadDir(dir) + if err != nil { + return "", nil, err + } + var files []fs.DirEntry + for _, fi := range all { + name := fi.Name() + if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) { + continue + } + match, err := env.matchFile(dir, fi.Name()) + if err != nil || !match { + continue + } + files = append(files, fi) + } + + if len(files) == 0 { + return "", nil, fmt.Errorf("dir %v contains no buildable, non-test .go files", dir) + } + + var pkgName string + var exports []stdlib.Symbol + fset := token.NewFileSet() + for _, fi := range files { + select { + case <-ctx.Done(): + return "", nil, ctx.Err() + default: + } + + fullFile := filepath.Join(dir, fi.Name()) + f, err := parser.ParseFile(fset, fullFile, nil, 0) + if err != nil { + env.logf("error parsing %v: %v", fullFile, err) + continue + } + if f.Name.Name == "documentation" { + // Special case from go/build.ImportDir, not + // handled by MatchFile above. + continue + } + if includeTest && strings.HasSuffix(f.Name.Name, "_test") { + // x_test package. We want internal test files only. + continue + } + pkgName = f.Name.Name + for name, obj := range f.Scope.Objects { + if ast.IsExported(name) { + var kind stdlib.Kind + switch obj.Kind { + case ast.Con: + kind = stdlib.Const + case ast.Typ: + kind = stdlib.Type + case ast.Var: + kind = stdlib.Var + case ast.Fun: + kind = stdlib.Func + } + exports = append(exports, stdlib.Symbol{ + Name: name, + Kind: kind, + Version: 0, // unknown; be permissive + }) + } + } + } + sortSymbols(exports) + + env.logf("loaded exports in dir %v (package %v): %v", dir, pkgName, exports) + return pkgName, exports, nil +} + +func sortSymbols(syms []stdlib.Symbol) { + sort.Slice(syms, func(i, j int) bool { + return syms[i].Name < syms[j].Name + }) +} + +// A symbolSearcher searches for a package with a set of symbols, among a set +// of candidates. See [symbolSearcher.search]. +// +// The search occurs within the scope of a single file, with context captured +// in srcDir and xtest. +type symbolSearcher struct { + logf func(string, ...any) + srcDir string // directory containing the file + xtest bool // if set, the file containing is an x_test file + loadExports func(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) +} + +// search searches the provided candidates for a package containing all +// exported symbols. +// +// If successful, returns the resulting package. +func (s *symbolSearcher) search(ctx context.Context, candidates []pkgDistance, pkgName string, symbols map[string]bool) (*pkg, error) { + // Sort the candidates by their import package length, + // assuming that shorter package names are better than long + // ones. Note that this sorts by the de-vendored name, so + // there's no "penalty" for vendoring. + sort.Sort(byDistanceOrImportPathShortLength(candidates)) + if s.logf != nil { + for i, c := range candidates { + s.logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir) + } + } + + // Arrange rescv so that we can we can await results in order of relevance + // and exit as soon as we find the first match. + // + // Search with bounded concurrency, returning as soon as the first result + // among rescv is non-nil. + rescv := make([]chan *pkg, len(candidates)) + for i := range candidates { + rescv[i] = make(chan *pkg, 1) + } + const maxConcurrentPackageImport = 4 + loadExportsSem := make(chan struct{}, maxConcurrentPackageImport) + + // Ensure that all work is completed at exit. + ctx, cancel := context.WithCancel(ctx) + var wg sync.WaitGroup + defer func() { + cancel() + wg.Wait() + }() + + // Start the search. + wg.Add(1) + go func() { + defer wg.Done() + for i, c := range candidates { + select { + case loadExportsSem <- struct{}{}: + case <-ctx.Done(): + return + } + + i := i + c := c + wg.Add(1) + go func() { + defer func() { + <-loadExportsSem + wg.Done() + }() + if s.logf != nil { + s.logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName) + } + pkg, err := s.searchOne(ctx, c, symbols) + if err != nil { + if s.logf != nil && ctx.Err() == nil { + s.logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) + } + pkg = nil + } + rescv[i] <- pkg // may be nil + }() + } + }() + + // Await the first (best) result. + for _, resc := range rescv { + select { + case r := <-resc: + if r != nil { + return r, nil + } + case <-ctx.Done(): + return nil, ctx.Err() + } + } + return nil, nil +} + +func (s *symbolSearcher) searchOne(ctx context.Context, c pkgDistance, symbols map[string]bool) (*pkg, error) { + if ctx.Err() != nil { + return nil, ctx.Err() + } + // If we're considering the package under test from an x_test, load the + // test variant. + includeTest := s.xtest && c.pkg.dir == s.srcDir + _, exports, err := s.loadExports(ctx, c.pkg, includeTest) + if err != nil { + return nil, err + } + + exportsMap := make(map[string]bool, len(exports)) + for _, sym := range exports { + exportsMap[sym.Name] = true + } + for symbol := range symbols { + if !exportsMap[symbol] { + return nil, nil // no match + } + } + return c.pkg, nil +} + +// pkgIsCandidate reports whether pkg is a candidate for satisfying the +// finding which package pkgIdent in the file named by filename is trying +// to refer to. +// +// This check is purely lexical and is meant to be as fast as possible +// because it's run over all $GOPATH directories to filter out poor +// candidates in order to limit the CPU and I/O later parsing the +// exports in candidate packages. +// +// filename is the file being formatted. +// pkgIdent is the package being searched for, like "client" (if +// searching for "client.New") +func pkgIsCandidate(filename string, refs references, pkg *pkg) bool { + // Check "internal" and "vendor" visibility: + if !canUse(filename, pkg.dir) { + return false + } + + // Speed optimization to minimize disk I/O: + // + // Use the matchesPath heuristic to filter to package paths that could + // reasonably match a dangling reference. + // + // This permits mismatch naming like directory "go-foo" being package "foo", + // or "pkg.v3" being "pkg", or directory + // "google.golang.org/api/cloudbilling/v1" being package "cloudbilling", but + // doesn't permit a directory "foo" to be package "bar", which is strongly + // discouraged anyway. There's no reason goimports needs to be slow just to + // accommodate that. + for pkgIdent := range refs { + if matchesPath(pkgIdent, pkg.importPathShort) { + return true + } + } + return false +} + +// canUse reports whether the package in dir is usable from filename, +// respecting the Go "internal" and "vendor" visibility rules. +func canUse(filename, dir string) bool { + // Fast path check, before any allocations. If it doesn't contain vendor + // or internal, it's not tricky: + // Note that this can false-negative on directories like "notinternal", + // but we check it correctly below. This is just a fast path. + if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") { + return true + } + + dirSlash := filepath.ToSlash(dir) + if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") { + return true + } + // Vendor or internal directory only visible from children of parent. + // That means the path from the current directory to the target directory + // can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal + // or bar/vendor or bar/internal. + // After stripping all the leading ../, the only okay place to see vendor or internal + // is at the very beginning of the path. + absfile, err := filepath.Abs(filename) + if err != nil { + return false + } + absdir, err := filepath.Abs(dir) + if err != nil { + return false + } + rel, err := filepath.Rel(absfile, absdir) + if err != nil { + return false + } + relSlash := filepath.ToSlash(rel) + if i := strings.LastIndex(relSlash, "../"); i >= 0 { + relSlash = relSlash[i+len("../"):] + } + return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal") +} + +// matchesPath reports whether ident may match a potential package name +// referred to by path, using heuristics to filter out unidiomatic package +// names. +// +// Specifically, it checks whether either of the last two '/'- or '\'-delimited +// path segments matches the identifier. The segment-matching heuristic must +// allow for various conventions around segment naming, including go-foo, +// foo-go, and foo.v3. To handle all of these, matching considers both (1) the +// entire segment, ignoring '-' and '.', as well as (2) the last subsegment +// separated by '-' or '.'. So the segment foo-go matches all of the following +// identifiers: foo, go, and foogo. All matches are case insensitive (for ASCII +// identifiers). +// +// See the docstring for [pkgIsCandidate] for an explanation of how this +// heuristic filters potential candidate packages. +func matchesPath(ident, path string) bool { + // Ignore case, for ASCII. + lowerIfASCII := func(b byte) byte { + if 'A' <= b && b <= 'Z' { + return b + ('a' - 'A') + } + return b + } + + // match reports whether path[start:end] matches ident, ignoring [.-]. + match := func(start, end int) bool { + ii := len(ident) - 1 // current byte in ident + pi := end - 1 // current byte in path + for ; pi >= start && ii >= 0; pi-- { + pb := path[pi] + if pb == '-' || pb == '.' { + continue + } + pb = lowerIfASCII(pb) + ib := lowerIfASCII(ident[ii]) + if pb != ib { + return false + } + ii-- + } + return ii < 0 && pi < start // all bytes matched + } + + // segmentEnd and subsegmentEnd hold the end points of the current segment + // and subsegment intervals. + segmentEnd := len(path) + subsegmentEnd := len(path) + + // Count slashes; we only care about the last two segments. + nslash := 0 + + for i := len(path) - 1; i >= 0; i-- { + switch b := path[i]; b { + // TODO(rfindley): we handle backlashes here only because the previous + // heuristic handled backslashes. This is perhaps overly defensive, but is + // the result of many lessons regarding Chesterton's fence and the + // goimports codebase. + // + // However, this function is only ever called with something called an + // 'importPath'. Is it possible that this is a real import path, and + // therefore we need only consider forward slashes? + case '/', '\\': + if match(i+1, segmentEnd) || match(i+1, subsegmentEnd) { + return true + } + nslash++ + if nslash == 2 { + return false // did not match above + } + segmentEnd, subsegmentEnd = i, i // reset + case '-', '.': + if match(i+1, subsegmentEnd) { + return true + } + subsegmentEnd = i + } + } + return match(0, segmentEnd) || match(0, subsegmentEnd) +} + +type visitFn func(node ast.Node) ast.Visitor + +func (fn visitFn) Visit(node ast.Node) ast.Visitor { + return fn(node) +} + +func symbolNameSet(symbols []stdlib.Symbol) map[string]bool { + names := make(map[string]bool) + for _, sym := range symbols { + switch sym.Kind { + case stdlib.Const, stdlib.Var, stdlib.Type, stdlib.Func: + names[sym.Name] = true + } + } + return names +} diff --git a/contribs/gnopls/internal/imports/fix_test.go b/contribs/gnopls/internal/imports/fix_test.go new file mode 100644 index 00000000000..fb5591c7c33 --- /dev/null +++ b/contribs/gnopls/internal/imports/fix_test.go @@ -0,0 +1,3087 @@ +// Copyright 2013 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 imports + +import ( + "context" + "flag" + "fmt" + "go/build" + "log" + "os" + "path" + "path/filepath" + "reflect" + "sort" + "strings" + "sync" + "sync/atomic" + "testing" + + "golang.org/x/tools/go/packages/packagestest" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/stdlib" +) + +var testDebug = flag.Bool("debug", false, "enable debug output") + +var tests = []struct { + name string + formatOnly bool + in, out string +}{ + // Adding an import to an existing parenthesized import + { + name: "factored_imports_add", + in: `package foo +import ( + "fmt" +) +func bar() { +var b bytes.Buffer +fmt.Println(b.String()) +} +`, + out: `package foo + +import ( + "bytes" + "fmt" +) + +func bar() { + var b bytes.Buffer + fmt.Println(b.String()) +} +`, + }, + + // Adding an import to an existing parenthesized import, + // verifying it goes into the first section. + { + name: "factored_imports_add_first_sec", + in: `package foo +import ( + "fmt" + + "github.com/golang/snappy" +) +func bar() { +var b bytes.Buffer +_ = snappy.ErrCorrupt +fmt.Println(b.String()) +} +`, + out: `package foo + +import ( + "bytes" + "fmt" + + "github.com/golang/snappy" +) + +func bar() { + var b bytes.Buffer + _ = snappy.ErrCorrupt + fmt.Println(b.String()) +} +`, + }, + + // Adding an import to an existing parenthesized import, + // verifying it goes into the first section. (test 2) + { + name: "factored_imports_add_first_sec_2", + in: `package foo +import ( + "fmt" + + "github.com/golang/snappy" +) +func bar() { +_ = math.NaN +_ = fmt.Sprintf +_ = snappy.ErrCorrupt +} +`, + out: `package foo + +import ( + "fmt" + "math" + + "github.com/golang/snappy" +) + +func bar() { + _ = math.NaN + _ = fmt.Sprintf + _ = snappy.ErrCorrupt +} +`, + }, + + // Adding a new import line, without parens + { + name: "add_import_section", + in: `package foo +func bar() { +var b bytes.Buffer +} +`, + out: `package foo + +import "bytes" + +func bar() { + var b bytes.Buffer +} +`, + }, + + // Adding two new imports, which should make a parenthesized import decl. + { + name: "add_import_paren_section", + in: `package foo +func bar() { +_, _ := bytes.Buffer, zip.NewReader +} +`, + out: `package foo + +import ( + "archive/zip" + "bytes" +) + +func bar() { + _, _ := bytes.Buffer, zip.NewReader +} +`, + }, + + // Make sure we don't add things twice + { + name: "no_double_add", + in: `package foo +func bar() { +_, _ := bytes.Buffer, bytes.NewReader +} +`, + out: `package foo + +import "bytes" + +func bar() { + _, _ := bytes.Buffer, bytes.NewReader +} +`, + }, + + // Make sure we don't add packages that don't have the right exports + { + name: "no_mismatched_add", + in: `package foo + +func bar() { + _ := bytes.NonexistentSymbol +} +`, + out: `package foo + +func bar() { + _ := bytes.NonexistentSymbol +} +`, + }, + + // Remove unused imports, 1 of a factored block + { + name: "remove_unused_1_of_2", + in: `package foo +import ( +"bytes" +"fmt" +) + +func bar() { +_, _ := bytes.Buffer, bytes.NewReader +} +`, + out: `package foo + +import ( + "bytes" +) + +func bar() { + _, _ := bytes.Buffer, bytes.NewReader +} +`, + }, + + // Remove unused imports, 2 of 2 + { + name: "remove_unused_2_of_2", + in: `package foo +import ( +"bytes" +"fmt" +) + +func bar() { +} +`, + out: `package foo + +func bar() { +} +`, + }, + + // Remove unused imports, 1 of 1 + { + name: "remove_unused_1_of_1", + in: `package foo + +import "fmt" + +func bar() { +} +`, + out: `package foo + +func bar() { +} +`, + }, + + // Don't remove empty imports. + { + name: "dont_remove_empty_imports", + in: `package foo +import ( +_ "image/png" +_ "image/jpeg" +) +`, + out: `package foo + +import ( + _ "image/jpeg" + _ "image/png" +) +`, + }, + + // Don't remove dot imports. + { + name: "dont_remove_dot_imports", + in: `package foo +import ( +. "foo" +. "bar" +) +`, + out: `package foo + +import ( + . "bar" + . "foo" +) +`, + }, + + // Skip refs the parser can resolve. + { + name: "skip_resolved_refs", + in: `package foo + +func f() { + type t struct{ Println func(string) } + fmt := t{Println: func(string) {}} + fmt.Println("foo") +} +`, + out: `package foo + +func f() { + type t struct{ Println func(string) } + fmt := t{Println: func(string) {}} + fmt.Println("foo") +} +`, + }, + + // Do not add a package we already have a resolution for. + { + name: "skip_template", + in: `package foo + +import "html/template" + +func f() { t = template.New("sometemplate") } +`, + out: `package foo + +import "html/template" + +func f() { t = template.New("sometemplate") } +`, + }, + + // Don't touch cgo + { + name: "cgo", + in: `package foo + +/* +#include <foo.h> +*/ +import "C" +`, + out: `package foo + +/* +#include <foo.h> +*/ +import "C" +`, + }, + + // Put some things in their own section + { + name: "make_sections", + in: `package foo + +import ( +"os" +) + +func foo () { +_, _ = os.Args, fmt.Println +_, _ = snappy.ErrCorrupt, p.P +} +`, + out: `package foo + +import ( + "fmt" + "os" + + "github.com/golang/snappy" + "rsc.io/p" +) + +func foo() { + _, _ = os.Args, fmt.Println + _, _ = snappy.ErrCorrupt, p.P +} +`, + }, + // Merge import blocks, even when no additions are required. + { + name: "merge_import_blocks_no_fix", + in: `package foo + +import ( + "fmt" +) +import "os" + +import ( + "rsc.io/p" +) + +var _, _ = os.Args, fmt.Println +var _, _ = snappy.ErrCorrupt, p.P +`, + out: `package foo + +import ( + "fmt" + "os" + + "github.com/golang/snappy" + "rsc.io/p" +) + +var _, _ = os.Args, fmt.Println +var _, _ = snappy.ErrCorrupt, p.P +`, + }, + // Delete existing empty import block + { + name: "delete_empty_import_block", + in: `package foo + +import () +`, + out: `package foo +`, + }, + + // Use existing empty import block + { + name: "use_empty_import_block", + in: `package foo + +import () + +func f() { + _ = fmt.Println +} +`, + out: `package foo + +import "fmt" + +func f() { + _ = fmt.Println +} +`, + }, + + // Blank line before adding new section. + { + name: "blank_line_before_new_group", + in: `package foo + +import ( + "fmt" + "net" +) + +func f() { + _ = net.Dial + _ = fmt.Printf + _ = snappy.ErrCorrupt +} +`, + out: `package foo + +import ( + "fmt" + "net" + + "github.com/golang/snappy" +) + +func f() { + _ = net.Dial + _ = fmt.Printf + _ = snappy.ErrCorrupt +} +`, + }, + + // Blank line between standard library and third-party stuff. + { + name: "blank_line_separating_std_and_third_party", + in: `package foo + +import ( + "github.com/golang/snappy" + "fmt" + "net" +) + +func f() { + _ = net.Dial + _ = fmt.Printf + _ = snappy.Foo +} +`, + out: `package foo + +import ( + "fmt" + "net" + + "github.com/golang/snappy" +) + +func f() { + _ = net.Dial + _ = fmt.Printf + _ = snappy.Foo +} +`, + }, + + // golang.org/issue/6884 + { + name: "new_imports_before_comment", + in: `package main + +// A comment +func main() { + fmt.Println("Hello, world") +} +`, + out: `package main + +import "fmt" + +// A comment +func main() { + fmt.Println("Hello, world") +} +`, + }, + + // golang.org/issue/7132 + { + name: "new_section_for_dotless_import", + in: `package main + +import ( +"fmt" + +"gu" +"manypackages.com/packagea" +) + +var ( +a = packagea.A +b = gu.A +c = fmt.Printf +) +`, + out: `package main + +import ( + "fmt" + + "gu" + + "manypackages.com/packagea" +) + +var ( + a = packagea.A + b = gu.A + c = fmt.Printf +) +`, + }, + + { + name: "fragment_with_main", + in: `func main(){fmt.Println("Hello, world")}`, + out: `package main + +import "fmt" + +func main() { fmt.Println("Hello, world") } +`, + }, + + { + name: "fragment_without_main", + in: `func notmain(){fmt.Println("Hello, world")}`, + out: `import "fmt" + +func notmain() { fmt.Println("Hello, world") }`, + }, + + // Remove first import within in a 2nd/3rd/4th/etc. section. + // golang.org/issue/7679 + { + name: "remove_first_import_in_section", + in: `package main + +import ( + "fmt" + + "manypackages.com/packagea" + "manypackages.com/packageb" +) + +func main() { + var _ = fmt.Println + //var _ = packagea.A + var _ = packageb.B +} +`, + out: `package main + +import ( + "fmt" + + "manypackages.com/packageb" +) + +func main() { + var _ = fmt.Println + //var _ = packagea.A + var _ = packageb.B +} +`, + }, + + // Blank line can be added before all types of import declarations. + // golang.org/issue/7866 + { + name: "new_section_for_all_kinds_of_imports", + in: `package main + +import ( + "fmt" + renamed_packagea "manypackages.com/packagea" + + . "manypackages.com/packageb" + "io" + + _ "manypackages.com/packagec" + "strings" +) + +var _, _, _, _, _ = fmt.Errorf, io.Copy, strings.Contains, renamed_packagea.A, B +`, + out: `package main + +import ( + "fmt" + + renamed_packagea "manypackages.com/packagea" + + "io" + + . "manypackages.com/packageb" + + "strings" + + _ "manypackages.com/packagec" +) + +var _, _, _, _, _ = fmt.Errorf, io.Copy, strings.Contains, renamed_packagea.A, B +`, + }, + + // Blank line can be added even when first import of group has comment with quote + { + name: "new_section_where_trailing_comment_has_quote", + in: `package main + +import ( + "context" + bar "local.com/bar" + baz "local.com/baz" + buzz "local.com/buzz" + "github.com/golang/snappy" // this is a "typical" import +) + +var _, _, _, _, _ = context.Background, bar.B, baz.B, buzz.B, snappy.ErrCorrupt +`, + out: `package main + +import ( + "context" + + "github.com/golang/snappy" // this is a "typical" import + + bar "local.com/bar" + baz "local.com/baz" + buzz "local.com/buzz" +) + +var _, _, _, _, _ = context.Background, bar.B, baz.B, buzz.B, snappy.ErrCorrupt +`, + }, + + // Non-idempotent comment formatting + // golang.org/issue/8035 + { + name: "comments_formatted", + in: `package main + +import ( + "fmt" // A + "go/ast" // B + _ "manypackages.com/packagec" // C +) + +func main() { _, _ = fmt.Print, ast.Walk } +`, + out: `package main + +import ( + "fmt" // A + "go/ast" // B + + _ "manypackages.com/packagec" // C +) + +func main() { _, _ = fmt.Print, ast.Walk } +`, + }, + + // Failure to delete all duplicate imports + // golang.org/issue/8459 + { + name: "remove_duplicates", + in: `package main + +import ( + "fmt" + "log" + "log" + "math" +) + +func main() { fmt.Println("pi:", math.Pi) } +`, + out: `package main + +import ( + "fmt" + "math" +) + +func main() { fmt.Println("pi:", math.Pi) } +`, + }, + + // Too aggressive prefix matching + // golang.org/issue/9961 + { + name: "no_extra_groups", + in: `package p + +import ( + "zip" + + "rsc.io/p" +) + +var ( + _ = fmt.Print + _ = zip.Store + _ p.P + _ = regexp.Compile +) +`, + out: `package p + +import ( + "fmt" + "regexp" + "zip" + + "rsc.io/p" +) + +var ( + _ = fmt.Print + _ = zip.Store + _ p.P + _ = regexp.Compile +) +`, + }, + + // Unused named import is mistaken for unnamed import + // golang.org/issue/8149 + { + name: "named_import_doesnt_provide_package_name", + in: `package main + +import foo "fmt" + +func main() { fmt.Println() } +`, + out: `package main + +import "fmt" + +func main() { fmt.Println() } +`, + }, + + // Unused named import is mistaken for unnamed import + // golang.org/issue/8149 + { + name: "unused_named_import_removed", + in: `package main + +import ( + "fmt" + x "fmt" +) + +func main() { fmt.Println() } +`, + out: `package main + +import ( + "fmt" +) + +func main() { fmt.Println() } +`, + }, + + { + name: "ignore_unexported_identifier", + in: `package main +var _ = fmt.unexported`, + out: `package main + +var _ = fmt.unexported +`, + }, + + // FormatOnly + { + name: "formatonly_works", + formatOnly: true, + in: `package main + +import ( +"fmt" +"manypackages.com/packagea" +) + +func main() {} +`, + out: `package main + +import ( + "fmt" + + "manypackages.com/packagea" +) + +func main() {} +`, + }, + + { + name: "preserve_import_group", + in: `package p + +import ( + "bytes" + "fmt" +) + +var _ = fmt.Sprintf +`, + out: `package p + +import ( + "fmt" +) + +var _ = fmt.Sprintf +`, + }, + { + name: "import_grouping_not_path_dependent_no_groups", + in: `package main + +import ( + "time" +) + +func main() { + _ = snappy.ErrCorrupt + _ = p.P + _ = time.Parse +} +`, + out: `package main + +import ( + "time" + + "github.com/golang/snappy" + "rsc.io/p" +) + +func main() { + _ = snappy.ErrCorrupt + _ = p.P + _ = time.Parse +} +`, + }, + + { + name: "import_grouping_not_path_dependent_existing_group", + in: `package main + +import ( + "time" + + "github.com/golang/snappy" +) + +func main() { + _ = snappy.ErrCorrupt + _ = p.P + _ = time.Parse +} +`, + out: `package main + +import ( + "time" + + "github.com/golang/snappy" + "rsc.io/p" +) + +func main() { + _ = snappy.ErrCorrupt + _ = p.P + _ = time.Parse +} +`, + }, + + // golang.org/issue/12097 + { + name: "package_statement_insertion_preserves_comments", + in: `// a +// b +// c + +func main() { + _ = fmt.Println +}`, + out: `package main + +import "fmt" + +// a +// b +// c + +func main() { + _ = fmt.Println +} +`, + }, + + { + name: "import_comment_stays_on_import", + in: `package main + +import ( + "math" // fun +) + +func main() { + x := math.MaxInt64 + fmt.Println(strings.Join(",", []string{"hi"}), x) +}`, + out: `package main + +import ( + "fmt" + "math" // fun + "strings" +) + +func main() { + x := math.MaxInt64 + fmt.Println(strings.Join(",", []string{"hi"}), x) +} +`, + }, + + { + name: "no_blank_after_comment", + in: `package main + +import ( + _ "io" + _ "net/http" + _ "net/http/pprof" // install the pprof http handlers + _ "strings" +) + +func main() { +} +`, + out: `package main + +import ( + _ "io" + _ "net/http" + _ "net/http/pprof" // install the pprof http handlers + _ "strings" +) + +func main() { +} +`, + }, + + { + name: "no_blank_after_comment_reordered", + in: `package main + +import ( + _ "io" + _ "net/http/pprof" // install the pprof http handlers + _ "net/http" + _ "strings" +) + +func main() { +} +`, + out: `package main + +import ( + _ "io" + _ "net/http" + _ "net/http/pprof" // install the pprof http handlers + _ "strings" +) + +func main() { +} +`, + }, + + { + name: "no_blank_after_comment_unnamed", + in: `package main + +import ( + "encoding/json" + "io" + "net/http" + _ "net/http/pprof" // install the pprof http handlers + "strings" + + "manypackages.com/packagea" +) + +func main() { + _ = strings.ToUpper("hello") + _ = io.EOF + var ( + _ json.Number + _ *http.Request + _ packagea.A + ) +} +`, + out: `package main + +import ( + "encoding/json" + "io" + "net/http" + _ "net/http/pprof" // install the pprof http handlers + "strings" + + "manypackages.com/packagea" +) + +func main() { + _ = strings.ToUpper("hello") + _ = io.EOF + var ( + _ json.Number + _ *http.Request + _ packagea.A + ) +} +`, + }, + + { + name: "blank_after_package_statement_with_comment", + in: `package p // comment + +import "math" + +var _ = fmt.Printf +`, + out: `package p // comment + +import "fmt" + +var _ = fmt.Printf +`, + }, + + { + name: "blank_after_package_statement_no_comment", + in: `package p + +import "math" + +var _ = fmt.Printf +`, + out: `package p + +import "fmt" + +var _ = fmt.Printf +`, + }, + + { + name: "cryptorand_preferred_easy_possible", + in: `package p + +var _ = rand.Read +`, + out: `package p + +import "crypto/rand" + +var _ = rand.Read +`, + }, + + { + name: "cryptorand_preferred_easy_impossible", + in: `package p + +var _ = rand.NewZipf +`, + out: `package p + +import "math/rand/v2" + +var _ = rand.NewZipf +`, + }, + + { + name: "cryptorand_preferred_complex_possible", + in: `package p + +var _, _ = rand.Read, rand.Prime +`, + out: `package p + +import "crypto/rand" + +var _, _ = rand.Read, rand.Prime +`, + }, + + { + name: "cryptorand_preferred_complex_impossible", + in: `package p + +var _, _ = rand.Read, rand.NewZipf +`, + out: `package p + +import "math/rand" + +var _, _ = rand.Read, rand.NewZipf +`, + }, + { + name: "unused_duplicate_imports_remove", + in: `package main + +import ( + "errors" + + "github.com/pkg/errors" +) +`, + out: `package main +`, + }, +} + +func TestSimpleCases(t *testing.T) { + const localPrefix = "local.com,github.com/local" + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testConfig{ + modules: []packagestest.Module{ + { + Name: "golang.org/fake", + Files: fm{"x.go": tt.in}, + }, + // Skeleton non-stdlib packages for use during testing. + // Each includes one arbitrary symbol, e.g. the first declaration in the first file. + // Try not to add more without a good reason. + // DO NOT USE PACKAGES NOT LISTED HERE -- they will be downloaded! + { + Name: "rsc.io", + Files: fm{"p/x.go": "package p\nfunc P(){}\n"}, + }, + { + Name: "github.com/golang/snappy", + Files: fm{"x.go": "package snappy\nvar ErrCorrupt error\n"}, + }, + { + Name: "manypackages.com", + Files: fm{ + "packagea/x.go": "package packagea\nfunc A(){}\n", + "packageb/x.go": "package packageb\nfunc B(){}\n", + "packagec/x.go": "package packagec\nfunc C(){}\n", + "packaged/x.go": "package packaged\nfunc D(){}\n", + }, + }, + { + Name: "local.com", + Files: fm{"foo/x.go": "package foo\nfunc Foo(){}\n"}, + }, + { + Name: "github.com/local", + Files: fm{"bar/x.go": "package bar\nfunc Bar(){}\n"}, + }, + }, + }.test(t, func(t *goimportTest) { + options := &Options{ + LocalPrefix: localPrefix, + TabWidth: 8, + TabIndent: true, + Comments: true, + Fragment: true, + FormatOnly: tt.formatOnly, + } + t.assertProcessEquals("golang.org/fake", "x.go", nil, options, tt.out) + }) + + }) + } +} + +func TestAppengine(t *testing.T) { + const input = `package p + +var _, _, _ = fmt.Printf, appengine.Main, datastore.ErrInvalidEntityType +` + + const want = `package p + +import ( + "fmt" + + "appengine" + "appengine/datastore" +) + +var _, _, _ = fmt.Printf, appengine.Main, datastore.ErrInvalidEntityType +` + + testConfig{ + gopathOnly: true, // can't create a module named appengine, so no module tests. + modules: []packagestest.Module{ + { + Name: "golang.org/fake", + Files: fm{"x.go": input}, + }, + { + Name: "appengine", + Files: fm{ + "x.go": "package appengine\nfunc Main(){}\n", + "datastore/x.go": "package datastore\nvar ErrInvalidEntityType error\n", + }, + }, + }, + }.processTest(t, "golang.org/fake", "x.go", nil, nil, want) +} + +func TestReadFromFilesystem(t *testing.T) { + tests := []struct { + name string + in, out string + }{ + { + name: "works", + in: `package foo +func bar() { +fmt.Println("hi") +} +`, + out: `package foo + +import "fmt" + +func bar() { + fmt.Println("hi") +} +`, + }, + { + name: "missing_package", + in: ` +func bar() { +fmt.Println("hi") +} +`, + out: ` +import "fmt" + +func bar() { + fmt.Println("hi") +} +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + options := &Options{ + TabWidth: 8, + TabIndent: true, + Comments: true, + Fragment: true, + } + testConfig{ + module: packagestest.Module{ + Name: "golang.org/fake", + Files: fm{"x.go": tt.in}, + }, + }.processTest(t, "golang.org/fake", "x.go", nil, options, tt.out) + }) + } + +} + +// Test support for packages in GOPATH that are actually symlinks. +// Also test that a symlink loop does not block the process. +func TestImportSymlinks(t *testing.T) { + const input = `package p + +var ( + _ = fmt.Print + _ = mypkg.Foo +) +` + const want = `package p + +import ( + "fmt" + + "golang.org/fake/x/y/mypkg" +) + +var ( + _ = fmt.Print + _ = mypkg.Foo +) +` + + testConfig{ + module: packagestest.Module{ + Name: "golang.org/fake", + Files: fm{ + "target/f.go": "package mypkg\nvar Foo = 123\n", + "x/y/mypkg": packagestest.Symlink("../../target"), // valid symlink + "x/y/apkg": packagestest.Symlink(".."), // symlink loop + "myotherpackage/toformat.go": input, + }, + }, + }.processTest(t, "golang.org/fake", "myotherpackage/toformat.go", nil, nil, want) +} + +// Test support for packages in GOPATH whose files are symlinks. +func TestImportSymlinkFiles(t *testing.T) { + const input = `package p + +var ( + _ = fmt.Print + _ = mypkg.Foo +) +` + const want = `package p + +import ( + "fmt" + + "golang.org/fake/x/y/mypkg" +) + +var ( + _ = fmt.Print + _ = mypkg.Foo +) +` + + testConfig{ + module: packagestest.Module{ + Name: "golang.org/fake", + Files: fm{ + "target/f.go": "package mypkg\nvar Foo = 123\n", + "x/y/mypkg/f.go": packagestest.Symlink("../../../target/f.go"), + "myotherpackage/toformat.go": input, + }, + }, + }.processTest(t, "golang.org/fake", "myotherpackage/toformat.go", nil, nil, want) +} + +func TestImportSymlinksWithIgnore(t *testing.T) { + const input = `package p + +var ( + _ = fmt.Print + _ = mypkg.Foo +) +` + const want = `package p + +import "fmt" + +var ( + _ = fmt.Print + _ = mypkg.Foo +) +` + + testConfig{ + gopathOnly: true, + module: packagestest.Module{ + Name: "golang.org/fake", + Files: fm{ + "target/f.go": "package mypkg\nvar Foo = 123\n", + "x/y/mypkg": packagestest.Symlink("../../target"), // valid symlink + "x/y/apkg": packagestest.Symlink(".."), // symlink loop + "myotherpkg/toformat.go": input, + "../../.goimportsignore": "golang.org/fake/x/y/mypkg\n" + + "golang.org/fake/x/y/apkg\n", + }, + }, + }.processTest(t, "golang.org/fake", "myotherpkg/toformat.go", nil, nil, want) +} + +// Test for x/y/v2 convention for package y. +func TestModuleVersion(t *testing.T) { + const input = `package p + +import ( + "fmt" + + "github.com/foo/v2" +) + +var ( + _ = fmt.Print + _ = foo.Foo +) +` + + testConfig{ + modules: []packagestest.Module{ + { + Name: "mypkg.com/outpkg", + Files: fm{"toformat.go": input}, + }, + { + Name: "github.com/foo/v2", + Files: fm{"x.go": "package foo\n func Foo(){}\n"}, + }, + }, + }.processTest(t, "mypkg.com/outpkg", "toformat.go", nil, nil, input) +} + +// Test for correctly identifying the name of a vendored package when it +// differs from its directory name. In this test, the import line +// "mypkg.com/mypkg_v1" would be removed if goimports wasn't able to detect +// that the package name is "mypkg". +func TestVendorPackage(t *testing.T) { + const input = `package p +import ( + "fmt" + "mypkg.com/mypkg_v1" +) +var _, _ = fmt.Print, mypkg.Foo +` + + const want = `package p + +import ( + "fmt" + + mypkg "mypkg.com/mypkg_v1" +) + +var _, _ = fmt.Print, mypkg.Foo +` + + testConfig{ + gopathOnly: true, + module: packagestest.Module{ + Name: "mypkg.com/outpkg", + Files: fm{ + "vendor/mypkg.com/mypkg_v1/f.go": "package mypkg\nvar Foo = 123\n", + "toformat.go": input, + }, + }, + }.processTest(t, "mypkg.com/outpkg", "toformat.go", nil, nil, want) +} + +func TestInternal(t *testing.T) { + const input = `package bar + +var _ = race.Acquire +` + const importAdded = `package bar + +import "foo.com/internal/race" + +var _ = race.Acquire +` + + // Packages under the same directory should be able to use internal packages. + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "internal/race/x.go": "package race\n func Acquire(){}\n", + "bar/x.go": input, + }, + }, + }.processTest(t, "foo.com", "bar/x.go", nil, nil, importAdded) + + // Packages outside the same directory should not. + testConfig{ + modules: []packagestest.Module{ + { + Name: "foo.com", + Files: fm{"internal/race/x.go": "package race\n func Acquire(){}\n"}, + }, + { + Name: "bar.com", + Files: fm{"x.go": input}, + }, + }, + }.processTest(t, "bar.com", "x.go", nil, nil, input) +} + +func TestProcessVendor(t *testing.T) { + const input = `package p + +var _ = hpack.HuffmanDecode +` + const want = `package p + +import "golang.org/x/net/http2/hpack" + +var _ = hpack.HuffmanDecode +` + testConfig{ + gopathOnly: true, + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "vendor/golang.org/x/net/http2/hpack/huffman.go": "package hpack\nfunc HuffmanDecode() { }\n", + "bar/x.go": input, + }, + }, + }.processTest(t, "foo.com", "bar/x.go", nil, nil, want) +} + +func TestFindStdlib(t *testing.T) { + tests := []struct { + pkg string + symbols []string + want string + }{ + {"http", []string{"Get"}, "net/http"}, + {"http", []string{"Get", "Post"}, "net/http"}, + {"http", []string{"Get", "Foo"}, ""}, + {"bytes", []string{"Buffer"}, "bytes"}, + {"ioutil", []string{"Discard"}, "io/ioutil"}, + } + for _, tt := range tests { + input := "package p\n" + for _, sym := range tt.symbols { + input += fmt.Sprintf("var _ = %s.%s\n", tt.pkg, sym) + } + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{"x.go": input}, + }, + }.test(t, func(t *goimportTest) { + buf, err := t.process("foo.com", "x.go", nil, nil) + if err != nil { + t.Fatal(err) + } + if got := string(buf); !strings.Contains(got, tt.want) { + t.Errorf("Process(%q) = %q, wanted it to contain %q", input, buf, tt.want) + } + }) + } +} + +// https://golang.org/issue/31814 +func TestStdlibNotPrefixed(t *testing.T) { + const input = `package p +var _ = bytes.Buffer +` + const want = `package p + +import "bytes" + +var _ = bytes.Buffer +` + // Force a scan of the stdlib. + savedStdlib := stdlib.PackageSymbols + defer func() { stdlib.PackageSymbols = savedStdlib }() + stdlib.PackageSymbols = nil + + testConfig{ + module: packagestest.Module{ + Name: "ignored.com", + Files: fm{"x.go": "package x"}, + }, + }.test(t, func(t *goimportTest) { + // Run in GOROOT/src so that the std module shows up in go list -m all. + t.env.WorkingDir = filepath.Join(t.goroot, "src") + got, err := t.processNonModule(filepath.Join(t.goroot, "src/x.go"), []byte(input), nil) + if err != nil { + t.Fatalf("Process() = %v", err) + } + if string(got) != want { + t.Errorf("Got:\n%s\nWant:\n%s", got, want) + } + }) +} + +func TestStdlibSelfImports(t *testing.T) { + const input = `package ecdsa + +var _ = ecdsa.GenerateKey +` + + testConfig{ + module: packagestest.Module{ + Name: "ignored.com", + Files: fm{"x.go": "package x"}, + }, + }.test(t, func(t *goimportTest) { + got, err := t.processNonModule(filepath.Join(t.goroot, "src/crypto/ecdsa/foo.go"), []byte(input), nil) + if err != nil { + t.Fatalf("Process() = %v", err) + } + if string(got) != input { + t.Errorf("Got:\n%s\nWant:\n%s", got, input) + } + }) +} + +type testConfig struct { + gopathOnly bool + module packagestest.Module + modules []packagestest.Module +} + +// fm is the type for a packagestest.Module's Files, abbreviated for shorter lines. +type fm map[string]interface{} + +func (c testConfig) test(t *testing.T, fn func(*goimportTest)) { + t.Helper() + + if c.module.Name != "" { + c.modules = []packagestest.Module{c.module} + } + + for _, exporter := range packagestest.All { + t.Run(exporter.Name(), func(t *testing.T) { + t.Helper() + if c.gopathOnly && exporter.Name() == "Modules" { + t.Skip("test marked GOPATH-only") + } + exported := packagestest.Export(t, exporter, c.modules) + defer exported.Cleanup() + + env := map[string]string{} + for _, kv := range exported.Config.Env { + split := strings.SplitN(kv, "=", 2) + env[split[0]] = split[1] + } + it := &goimportTest{ + T: t, + env: &ProcessEnv{ + Env: env, + WorkingDir: exported.Config.Dir, + GocmdRunner: &gocommand.Runner{}, + }, + exported: exported, + } + if *testDebug { + it.env.Logf = log.Printf + } + // packagestest clears out GOROOT to work around golang/go#32849, + // which isn't relevant here. Fill it back in so we can find the standard library. + it.env.Env["GOROOT"] = build.Default.GOROOT + it.goroot = build.Default.GOROOT + + fn(it) + }) + } +} + +func (c testConfig) processTest(t *testing.T, module, file string, contents []byte, opts *Options, want string) { + t.Helper() + c.test(t, func(t *goimportTest) { + t.Helper() + t.assertProcessEquals(module, file, contents, opts, want) + }) +} + +type goimportTest struct { + *testing.T + goroot string + env *ProcessEnv + exported *packagestest.Exported +} + +func (t *goimportTest) process(module, file string, contents []byte, opts *Options) ([]byte, error) { + t.Helper() + f := t.exported.File(module, file) + if f == "" { + t.Fatalf("%v not found in exported files (typo in filename?)", file) + } + return t.processNonModule(f, contents, opts) +} + +func (t *goimportTest) processNonModule(file string, contents []byte, opts *Options) ([]byte, error) { + if contents == nil { + var err error + contents, err = os.ReadFile(file) + if err != nil { + return nil, err + } + } + if opts == nil { + opts = &Options{Comments: true, TabIndent: true, TabWidth: 8} + } + // ProcessEnv is not safe for concurrent use. Make a copy. + opts.Env = t.env.CopyConfig() + return Process(file, contents, opts) +} + +func (t *goimportTest) assertProcessEquals(module, file string, contents []byte, opts *Options, want string) { + buf, err := t.process(module, file, contents, opts) + if err != nil { + t.Fatalf("Process() = %v", err) + } + if string(buf) != want { + t.Errorf("Got:\n'%s'\nWant:\n'%s'", buf, want) // 's show empty lines + } +} + +// Tests that added imports are renamed when the import path's base doesn't +// match its package name. +func TestRenameWhenPackageNameMismatch(t *testing.T) { + const input = `package main + const Y = bar.X` + + const want = `package main + +import bar "foo.com/foo/bar/baz" + +const Y = bar.X +` + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "foo/bar/baz/x.go": "package bar \n const X = 1", + "test/t.go": input, + }, + }, + }.processTest(t, "foo.com", "test/t.go", nil, nil, want) +} + +func TestPanicAstutils(t *testing.T) { + t.Skip("panic in ast/astutil/imports.go, should be PostionFor(,false) at lines 273, 274, at least") + const input = `package main +//line mah.go:600 + +import ( +"foo.com/a.thing" +"foo.com/surprise" +"foo.com/v1" +"foo.com/other/v2" +"foo.com/other/v3" +) +` + + const want = `package main + +//line mah.go:600 + +import ( + "foo.com/a.thing" + "foo.com/go-thing" + gow "foo.com/go-wrong" + v2 "foo.com/other/v2" + "foo.com/other/v3" + bar "foo.com/surprise" + v1 "foo.com/v1" +) + +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "test/t.go": input, + }, + }, + }.processTest(t, "foo.com", "test/t.go", nil, nil, want) +} + +// without PositionFor in sortImports this test panics +func TestPanic51916(t *testing.T) { + const input = `package main +//line mah.go:600 + +import ( +"foo.com/a.thing" +"foo.com/surprise" +"foo.com/v1" +"foo.com/other/v2" +"foo.com/other/v3" +"foo.com/go-thing" +"foo.com/go-wrong" +) + +var _ = []interface{}{bar.X, v1.Y, a.A, v2.V2, other.V3, thing.Thing, gow.Wrong}` + + const want = `package main + +//line mah.go:600 + +import ( + "foo.com/a.thing" + "foo.com/go-thing" + gow "foo.com/go-wrong" + v2 "foo.com/other/v2" + "foo.com/other/v3" + bar "foo.com/surprise" + v1 "foo.com/v1" +) + +var _ = []interface{}{bar.X, v1.Y, a.A, v2.V2, other.V3, thing.Thing, gow.Wrong} +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "a.thing/a.go": "package a \n const A = 1", + "surprise/x.go": "package bar \n const X = 1", + "v1/x.go": "package v1 \n const Y = 1", + "other/v2/y.go": "package v2 \n const V2 = 1", + "other/v3/z.go": "package other \n const V3 = 1", + "go-thing/b.go": "package thing \n const Thing = 1", + "go-wrong/b.go": "package gow \n const Wrong = 1", + "test/t.go": input, + }, + }, + }.processTest(t, "foo.com", "test/t.go", nil, nil, want) +} + +// Tests that an existing import with badly mismatched path/name has its name +// correctly added. See #28645 and #29041. +// and check that //line directives are ignored (#51916) +func TestAddNameToMismatchedImport(t *testing.T) { + const input = `package main + +import ( +"foo.com/a.thing" +"foo.com/surprise" +"foo.com/v1" +"foo.com/other/v2" +"foo.com/other/v3" +"foo.com/go-thing" +"foo.com/go-wrong" +) + +var _ = []interface{}{bar.X, v1.Y, a.A, v2.V2, other.V3, thing.Thing, gow.Wrong}` + + const want = `package main + +import ( + "foo.com/a.thing" + "foo.com/go-thing" + gow "foo.com/go-wrong" + v2 "foo.com/other/v2" + "foo.com/other/v3" + bar "foo.com/surprise" + v1 "foo.com/v1" +) + +var _ = []interface{}{bar.X, v1.Y, a.A, v2.V2, other.V3, thing.Thing, gow.Wrong} +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "a.thing/a.go": "package a \n const A = 1", + "surprise/x.go": "package bar \n const X = 1", + "v1/x.go": "package v1 \n const Y = 1", + "other/v2/y.go": "package v2 \n const V2 = 1", + "other/v3/z.go": "package other \n const V3 = 1", + "go-thing/b.go": "package thing \n const Thing = 1", + "go-wrong/b.go": "package gow \n const Wrong = 1", + "test/t.go": input, + }, + }, + }.processTest(t, "foo.com", "test/t.go", nil, nil, want) +} + +// Tests that the LocalPrefix option causes imports +// to be added into a later group (num=3). +func TestLocalPrefix(t *testing.T) { + tests := []struct { + name string + modules []packagestest.Module + localPrefix string + src string + want string + }{ + { + name: "one_local", + modules: []packagestest.Module{ + { + Name: "foo.com", + Files: fm{ + "bar/bar.go": "package bar \n const X = 1", + }, + }, + }, + localPrefix: "foo.com/", + src: "package main \n const Y = bar.X \n const _ = runtime.GOOS", + want: `package main + +import ( + "runtime" + + "foo.com/bar" +) + +const Y = bar.X +const _ = runtime.GOOS +`, + }, + { + name: "two_local", + modules: []packagestest.Module{ + { + Name: "foo.com", + Files: fm{ + "foo/foo.go": "package foo \n const X = 1", + "foo/bar/bar.go": "package bar \n const X = 1", + }, + }, + }, + localPrefix: "foo.com/foo", + src: "package main \n const Y = bar.X \n const Z = foo.X \n const _ = runtime.GOOS", + want: `package main + +import ( + "runtime" + + "foo.com/foo" + "foo.com/foo/bar" +) + +const Y = bar.X +const Z = foo.X +const _ = runtime.GOOS +`, + }, + { + name: "three_prefixes", + modules: []packagestest.Module{ + { + Name: "example.org/pkg", + Files: fm{"pkg.go": "package pkg \n const A = 1"}, + }, + { + Name: "foo.com", + Files: fm{"bar/bar.go": "package bar \n const B = 1"}, + }, + { + Name: "code.org/r/p", + Files: fm{"expproj/expproj.go": "package expproj \n const C = 1"}, + }, + }, + localPrefix: "example.org/pkg,foo.com/,code.org", + src: "package main \n const X = pkg.A \n const Y = bar.B \n const Z = expproj.C \n const _ = runtime.GOOS", + want: `package main + +import ( + "runtime" + + "code.org/r/p/expproj" + "example.org/pkg" + "foo.com/bar" +) + +const X = pkg.A +const Y = bar.B +const Z = expproj.C +const _ = runtime.GOOS +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testConfig{ + // The module being processed has to be first so it's the primary module. + modules: append([]packagestest.Module{{ + Name: "test.com", + Files: fm{"t.go": tt.src}, + }}, tt.modules...), + }.test(t, func(t *goimportTest) { + options := &Options{ + LocalPrefix: tt.localPrefix, + TabWidth: 8, + TabIndent: true, + Comments: true, + Fragment: true, + } + t.assertProcessEquals("test.com", "t.go", nil, options, tt.want) + }) + }) + } +} + +// Tests that "package documentation" files are ignored. +func TestIgnoreDocumentationPackage(t *testing.T) { + const input = `package x + +const Y = foo.X +` + const want = `package x + +import "foo.com/foo" + +const Y = foo.X +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "foo/foo.go": "package foo\nconst X = 1\n", + "foo/doc.go": "package documentation \n // just to confuse things\n", + "x/x.go": input, + }, + }, + }.processTest(t, "foo.com", "x/x.go", nil, nil, want) +} + +// Tests importPathToNameGoPathParse and in particular that it stops +// after finding the first non-documentation package name, not +// reporting an error on inconsistent package names (since it should +// never make it that far). +func TestImportPathToNameGoPathParse(t *testing.T) { + testConfig{ + module: packagestest.Module{ + Name: "example.net/pkg", + Files: fm{ + "doc.go": "package documentation\n", // ignored + "gen.go": "package main\n", // also ignored + "pkg.go": "package the_pkg_name_to_find\n and this syntax error is ignored because of parser.PackageClauseOnly", + "z.go": "package inconsistent\n", // inconsistent but ignored + }, + }, + }.test(t, func(t *goimportTest) { + if strings.Contains(t.Name(), "GoPackages") { + t.Skip("go/packages does not ignore package main") + } + r, err := t.env.GetResolver() + if err != nil { + t.Fatal(err) + } + srcDir := filepath.Dir(t.exported.File("example.net/pkg", "z.go")) + names, err := r.loadPackageNames([]string{"example.net/pkg"}, srcDir) + if err != nil { + t.Fatal(err) + } + const want = "the_pkg_name_to_find" + if got := names["example.net/pkg"]; got != want { + t.Errorf("loadPackageNames(..) = %q; want %q", got, want) + } + }) +} + +func TestIgnoreConfiguration(t *testing.T) { + const input = `package x + +const _ = pkg.X +` + const want = `package x + +import "foo.com/otherwise-longer-so-worse-example/foo/pkg" + +const _ = pkg.X +` + + testConfig{ + gopathOnly: true, + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "../.goimportsignore": "# comment line\n\n foo.com/example", // tests comment, blank line, whitespace trimming + "example/pkg/pkg.go": "package pkg\nconst X = 1", + "otherwise-longer-so-worse-example/foo/pkg/pkg.go": "package pkg\nconst X = 1", + "x/x.go": input, + }, + }, + }.processTest(t, "foo.com", "x/x.go", nil, nil, want) +} + +// Skip "node_modules" directory. +func TestSkipNodeModules(t *testing.T) { + const input = `package x + +const _ = pkg.X +` + const want = `package x + +import "foo.com/otherwise-longer/not_modules/pkg" + +const _ = pkg.X +` + + testConfig{ + gopathOnly: true, + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "example/node_modules/pkg/a.go": "package pkg\nconst X = 1", + "otherwise-longer/not_modules/pkg/a.go": "package pkg\nconst X = 1", + "x/x.go": input, + }, + }, + }.processTest(t, "foo.com", "x/x.go", nil, nil, want) +} + +// Tests that package global variables with the same name and function name as +// a function in a separate package do not result in an import which masks +// the global variable +func TestGlobalImports(t *testing.T) { + const usesGlobal = `package pkg + +func doSomething() { + t := time.Now() +} +` + + const declaresGlobal = `package pkg + +type Time struct{} + +func (t Time) Now() Time { + return Time{} +} + +var time Time +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "pkg/uses.go": usesGlobal, + "pkg/global.go": declaresGlobal, + }, + }, + }.processTest(t, "foo.com", "pkg/uses.go", nil, nil, usesGlobal) +} + +// Some people put multiple packages' files in the same directory. Globals +// declared in other packages should be ignored. +func TestGlobalImports_DifferentPackage(t *testing.T) { + const declaresGlobal = `package main +var fmt int +` + const input = `package pkg +var _ = fmt.Printf +` + const want = `package pkg + +import "fmt" + +var _ = fmt.Printf +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "pkg/main.go": declaresGlobal, + "pkg/uses.go": input, + }, + }, + }.processTest(t, "foo.com", "pkg/uses.go", nil, nil, want) +} + +func TestGlobalImports_MultipleMains(t *testing.T) { + const declaresGlobal = `package main +var fmt int +` + const input = `package main +import "fmt" +var _, _ = fmt.Printf, bytes.Equal +` + const want = `package main + +import ( + "bytes" + "fmt" +) + +var _, _ = fmt.Printf, bytes.Equal +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "pkg/main.go": declaresGlobal, + "pkg/uses.go": input, + }, + }, + }.processTest(t, "foo.com", "pkg/uses.go", nil, nil, want) +} + +// Tests that sibling files - other files in the same package - can provide an +// import that may not be the default one otherwise. +func TestSiblingImports(t *testing.T) { + + // provide is the sibling file that provides the desired import. + const provide = `package siblingimporttest + +import "local/log" +import "my/bytes" +import renamed "fmt" + +func LogSomething() { + log.Print("Something") + bytes.SomeFunc() + renamed.Println("Something") +} +` + + // need is the file being tested that needs the import. + const need = `package siblingimporttest + +var _ = bytes.Buffer{} + +func LogSomethingElse() { + log.Print("Something else") + renamed.Println("Yet another") +} +` + + // want is the expected result file + const want = `package siblingimporttest + +import ( + "bytes" + renamed "fmt" + "local/log" +) + +var _ = bytes.Buffer{} + +func LogSomethingElse() { + log.Print("Something else") + renamed.Println("Yet another") +} +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "p/needs_import.go": need, + "p/provides_import.go": provide, + }, + }, + }.processTest(t, "foo.com", "p/needs_import.go", nil, nil, want) +} + +// Tests #29180: a sibling import of the right package with the wrong name is used. +func TestSiblingImport_Misnamed(t *testing.T) { + const sibling = `package main +import renamed "fmt" +var _ = renamed.Printf +` + const input = `package pkg +var _ = fmt.Printf +` + const want = `package pkg + +import "fmt" + +var _ = fmt.Printf +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "pkg/main.go": sibling, + "pkg/uses.go": input, + }, + }, + }.processTest(t, "foo.com", "pkg/uses.go", nil, nil, want) + +} + +// Tests that an input file's own package is ignored. +func TestIgnoreOwnPackage(t *testing.T) { + const input = `package pkg + +const _ = pkg.X +` + const want = `package pkg + +const _ = pkg.X +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "pkg/a.go": "package pkg\nconst X = 1", + "pkg/b.go": input, + }, + }, + }.processTest(t, "foo.com", "pkg/b.go", nil, nil, want) +} + +func TestExternalTestImportsPackageUnderTest(t *testing.T) { + const provide = `package pkg +func DoIt(){} +` + const input = `package pkg_test + +var _ = pkg.DoIt` + + const want = `package pkg_test + +import "foo.com/pkg" + +var _ = pkg.DoIt +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "pkg/provide.go": provide, + "pkg/x_test.go": input, + }, + }, + }.processTest(t, "foo.com", "pkg/x_test.go", nil, nil, want) +} + +func TestPkgIsCandidate(t *testing.T) { + tests := []struct { + name string + filename string + pkgIdent string + pkg *pkg + want bool + }{ + { + name: "normal_match", + filename: "/gopath/src/my/pkg/pkg.go", + pkgIdent: "client", + pkg: &pkg{ + dir: "/gopath/src/client", + importPathShort: "client", + }, + want: true, + }, + { + name: "no_match", + filename: "/gopath/src/my/pkg/pkg.go", + pkgIdent: "zzz", + pkg: &pkg{ + dir: "/gopath/src/client", + importPathShort: "client", + }, + want: false, + }, + { + name: "match_too_early", + filename: "/gopath/src/my/pkg/pkg.go", + pkgIdent: "client", + pkg: &pkg{ + dir: "/gopath/src/client/foo/foo/foo", + importPathShort: "client/foo/foo", + }, + want: false, + }, + { + name: "substring_match", + filename: "/gopath/src/my/pkg/pkg.go", + pkgIdent: "client", + pkg: &pkg{ + dir: "/gopath/src/foo/go-client", + importPathShort: "foo/go-client", + }, + want: true, + }, + { + name: "hidden_internal", + filename: "/gopath/src/my/pkg/pkg.go", + pkgIdent: "client", + pkg: &pkg{ + dir: "/gopath/src/foo/internal/client", + importPathShort: "foo/internal/client", + }, + want: false, + }, + { + name: "visible_internal", + filename: "/gopath/src/foo/bar.go", + pkgIdent: "client", + pkg: &pkg{ + dir: "/gopath/src/foo/internal/client", + importPathShort: "foo/internal/client", + }, + want: true, + }, + { + name: "invisible_vendor", + filename: "/gopath/src/foo/bar.go", + pkgIdent: "client", + pkg: &pkg{ + dir: "/gopath/src/other/vendor/client", + importPathShort: "client", + }, + want: false, + }, + { + name: "visible_vendor", + filename: "/gopath/src/foo/bar.go", + pkgIdent: "client", + pkg: &pkg{ + dir: "/gopath/src/foo/vendor/client", + importPathShort: "client", + }, + want: true, + }, + { + name: "match_with_hyphens", + filename: "/gopath/src/foo/bar.go", + pkgIdent: "socketio", + pkg: &pkg{ + dir: "/gopath/src/foo/socket-io", + importPathShort: "foo/socket-io", + }, + want: true, + }, + { + name: "match_with_mixed_case", + filename: "/gopath/src/foo/bar.go", + pkgIdent: "fooprod", + pkg: &pkg{ + dir: "/gopath/src/foo/FooPROD", + importPathShort: "foo/FooPROD", + }, + want: true, + }, + { + name: "matches_with_hyphen_and_caps", + filename: "/gopath/src/foo/bar.go", + pkgIdent: "fooprod", + pkg: &pkg{ + dir: "/gopath/src/foo/Foo-PROD", + importPathShort: "foo/Foo-PROD", + }, + want: true, + }, + } + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + refs := references{tt.pkgIdent: nil} + got := pkgIsCandidate(tt.filename, refs, tt.pkg) + if got != tt.want { + t.Errorf("test %d. pkgIsCandidate(%q, %q, %+v) = %v; want %v", + i, tt.filename, tt.pkgIdent, *tt.pkg, got, tt.want) + } + }) + } +} + +// Issue 20941: this used to panic on Windows. +func TestProcessStdin(t *testing.T) { + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + }, + }.test(t, func(t *goimportTest) { + got, err := t.processNonModule("<standard input>", []byte("package main\nfunc main() {\n\tfmt.Println(123)\n}\n"), nil) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(got), `"fmt"`) { + t.Errorf("expected fmt import; got: %s", got) + } + }) +} + +// Tests LocalPackagePromotion when there is a local package that matches, it +// should be the closest match. +// https://golang.org/issues/17557 +func TestLocalPackagePromotion(t *testing.T) { + const input = `package main +var c = &config.SystemConfig{} +` + const want = `package main + +import "mycompany.net/tool/config" + +var c = &config.SystemConfig{} +` + + testConfig{ + modules: []packagestest.Module{ + { + Name: "config.net/config", + Files: fm{"config.go": "package config\n type SystemConfig struct {}"}, // Will match but should not be first choice + }, + { + Name: "mycompany.net/config", + Files: fm{"config.go": "package config\n type SystemConfig struct {}"}, // Will match but should not be first choice + }, + { + Name: "mycompany.net/tool", + Files: fm{ + "config/config.go": "package config\n type SystemConfig struct {}", // Local package should be promoted over shorter package + "main.go": input, + }, + }, + }, + }.processTest(t, "mycompany.net/tool", "main.go", nil, nil, want) +} + +// Tests FindImportInLocalGoFiles looks at the import lines for other Go files in the +// local directory, since the user is likely to import the same packages in the current +// Go file. If an import is found that satisfies the need, it should be used over the +// standard library. +// https://golang.org/issues/17557 +func TestFindImportInLocalGoFiles(t *testing.T) { + const input = `package main + var _ = &bytes.Buffer{}` + + const want = `package main + +import "bytes.net/bytes" + +var _ = &bytes.Buffer{} +` + testConfig{ + modules: []packagestest.Module{ + { + Name: "mycompany.net/tool", + Files: fm{ + "io.go": "package main\n import \"bytes.net/bytes\"\n var _ = &bytes.Buffer{}", // Contains package import that will cause stdlib to be ignored + "main.go": input, + }, + }, + { + Name: "bytes.net/bytes", + Files: fm{"bytes.go": "package bytes\n type Buffer struct {}"}, // Should be selected over standard library + }, + }, + }.processTest(t, "mycompany.net/tool", "main.go", nil, nil, want) +} + +func TestInMemoryFile(t *testing.T) { + const input = `package main + var _ = &bytes.Buffer{}` + + const want = `package main + +import "bytes" + +var _ = &bytes.Buffer{} +` + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{"x.go": "package x\n"}, + }, + }.processTest(t, "foo.com", "x.go", []byte(input), nil, want) +} + +func TestImportNoGoFiles(t *testing.T) { + const input = `package main + var _ = &bytes.Buffer{}` + + const want = `package main + +import "bytes" + +var _ = &bytes.Buffer{} +` + testConfig{ + module: packagestest.Module{ + Name: "mycompany.net", + }, + }.test(t, func(t *goimportTest) { + buf, err := t.processNonModule("mycompany.net/tool/main.go", []byte(input), nil) + if err != nil { + t.Fatalf("Process() = %v", err) + } + if string(buf) != want { + t.Errorf("Got:\n%s\nWant:\n%s", buf, want) + } + }) + +} + +// Ensures a token as large as 500000 bytes can be handled +// https://golang.org/issues/18201 +func TestProcessLargeToken(t *testing.T) { + largeString := strings.Repeat("x", 500000) + + input := `package testimports + +import ( + "bytes" +) + +const s = fmt.Sprintf("%s", "` + largeString + `") +var _ = bytes.Buffer{} + +// end +` + + want := `package testimports + +import ( + "bytes" + "fmt" +) + +const s = fmt.Sprintf("%s", "` + largeString + `") + +var _ = bytes.Buffer{} + +// end +` + + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{"foo.go": input}, + }, + }.processTest(t, "foo.com", "foo.go", nil, nil, want) +} + +// Tests that an external test package will import the package under test if it +// also uses symbols exported only in test files. +// https://golang.org/issues/29979 +func TestExternalTest(t *testing.T) { + const input = `package a_test +func TestX() { + a.X() + a.Y() +} +` + const want = `package a_test + +import "foo.com/a" + +func TestX() { + a.X() + a.Y() +} +` + + testConfig{ + modules: []packagestest.Module{ + { + Name: "foo.com/a", + Files: fm{ + "a.go": "package a\n func X() {}", + "export_test.go": "package a\n func Y() {}", + "a_test.go": input, + }, + }, + }, + }.processTest(t, "foo.com/a", "a_test.go", nil, nil, want) +} + +// TestGetCandidates tests that get packages finds packages +// with correct priorities. +func TestGetCandidates(t *testing.T) { + type res struct { + relevance float64 + name, path string + } + want := []res{ + {0, "bytes", "bytes"}, + {0, "http", "net/http"}, + {0, "rand", "crypto/rand"}, + {0, "bar", "bar.com/bar"}, + {0, "foo", "foo.com/foo"}, + } + + testConfig{ + modules: []packagestest.Module{ + { + Name: "bar.com", + Files: fm{"bar/bar.go": "package bar\n"}, + }, + { + Name: "foo.com", + Files: fm{"foo/foo.go": "package foo\n"}, + }, + }, + }.test(t, func(t *goimportTest) { + var mu sync.Mutex + var got []res + add := func(c ImportFix) { + mu.Lock() + defer mu.Unlock() + for _, w := range want { + if c.StmtInfo.ImportPath == w.path { + got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath}) + } + } + } + if err := GetAllCandidates(context.Background(), add, "", "x.go", "x", t.env); err != nil { + t.Fatalf("GetAllCandidates() = %v", err) + } + // Sort, then clear out relevance so it doesn't mess up the DeepEqual. + sort.Slice(got, func(i, j int) bool { + ri, rj := got[i], got[j] + if ri.relevance != rj.relevance { + return ri.relevance > rj.relevance // Highest first. + } + return ri.name < rj.name + }) + for i := range got { + got[i].relevance = 0 + } + if !reflect.DeepEqual(want, got) { + t.Errorf("wanted results in order %v, got %v", want, got) + } + }) +} + +func TestGetImportPaths(t *testing.T) { + type res struct { + relevance float64 + name, path string + } + want := []res{ + {0, "http", "net/http"}, + {0, "net", "net"}, + {0, "neta", "neta.com/neta"}, + } + + testConfig{ + modules: []packagestest.Module{ + { + Name: "neta.com", + Files: fm{"neta/neta.go": "package neta\n"}, + }, + }, + }.test(t, func(t *goimportTest) { + var mu sync.Mutex + var got []res + add := func(c ImportFix) { + mu.Lock() + defer mu.Unlock() + for _, w := range want { + if c.StmtInfo.ImportPath == w.path { + got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath}) + } + } + } + if err := GetImportPaths(context.Background(), add, "ne", "x.go", "x", t.env); err != nil { + t.Fatalf("GetImportPaths() = %v", err) + } + // Sort, then clear out relevance so it doesn't mess up the DeepEqual. + sort.Slice(got, func(i, j int) bool { + ri, rj := got[i], got[j] + if ri.relevance != rj.relevance { + return ri.relevance > rj.relevance // Highest first. + } + return ri.name < rj.name + }) + for i := range got { + got[i].relevance = 0 + } + if !reflect.DeepEqual(want, got) { + t.Errorf("wanted results in order %v, got %v", want, got) + } + }) +} + +func TestGetPackageCompletions(t *testing.T) { + type res struct { + relevance float64 + name, path, symbol string + } + want := []res{ + {0, "rand", "math/rand", "Seed"}, + {0, "rand", "bar.com/rand", "Bar"}, + } + + testConfig{ + modules: []packagestest.Module{ + { + Name: "bar.com", + Files: fm{"rand/bar.go": "package rand\nvar Bar int\n"}, + }, + }, + }.test(t, func(t *goimportTest) { + var mu sync.Mutex + var got []res + add := func(c PackageExport) { + mu.Lock() + defer mu.Unlock() + for _, csym := range c.Exports { + for _, w := range want { + if c.Fix.StmtInfo.ImportPath == w.path && csym.Name == w.symbol { + got = append(got, res{c.Fix.Relevance, c.Fix.IdentName, c.Fix.StmtInfo.ImportPath, csym.Name}) + } + } + } + } + if err := GetPackageExports(context.Background(), add, "rand", "x.go", "x", t.env); err != nil { + t.Fatalf("getPackageCompletions() = %v", err) + } + // Sort, then clear out relevance so it doesn't mess up the DeepEqual. + sort.Slice(got, func(i, j int) bool { + ri, rj := got[i], got[j] + if ri.relevance != rj.relevance { + return ri.relevance > rj.relevance // Highest first. + } + return ri.name < rj.name + }) + for i := range got { + got[i].relevance = 0 + } + if !reflect.DeepEqual(want, got) { + t.Errorf("wanted results in order %v, got %v", want, got) + } + }) +} + +// Tests #34895: process should not panic on concurrent calls. +func TestConcurrentProcess(t *testing.T) { + testConfig{ + module: packagestest.Module{ + Name: "foo.com", + Files: fm{ + "p/first.go": `package foo + +func _() { + fmt.Println() +} +`, + "p/second.go": `package foo + +import "fmt" + +func _() { + fmt.Println() + imports.Bar() // not imported. +} +`, + }, + }, + }.test(t, func(t *goimportTest) { + var ( + n = 10 + wg sync.WaitGroup + ) + wg.Add(n) + for i := 0; i < n; i++ { + go func() { + defer wg.Done() + _, err := t.process("foo.com", "p/first.go", nil, nil) + if err != nil { + t.Error(err) + } + }() + } + wg.Wait() + }) +} + +func TestNonlocalDot(t *testing.T) { + const input = `package main +import ( + "fmt" +) +var _, _ = fmt.Sprintf, dot.Dot +` + const want = `package main + +import ( + "fmt" + "noninternet/dot.v1/dot" +) + +var _, _ = fmt.Sprintf, dot.Dot +` + testConfig{ + modules: []packagestest.Module{ + { + Name: "golang.org/fake", + Files: fm{"x.go": input}, + }, + { + Name: "noninternet/dot.v1", + Files: fm{ + "dot/dot.go": "package dot\nfunc Dot(){}\n", + }, + }, + }, + gopathOnly: true, // our modules testing setup doesn't allow modules without dots. + }.processTest(t, "golang.org/fake", "x.go", nil, nil, want) +} + +func TestSymbolSearchStarvation(t *testing.T) { + // This test verifies the fix for golang/go#67923: searching through + // candidates should not starve when the context is cancelled. + // + // To reproduce the conditions that led to starvation, cancel the context + // half way through the search, by leveraging the loadExports callback. + const candCount = 100 + var loaded atomic.Int32 + ctx, cancel := context.WithCancel(context.Background()) + searcher := symbolSearcher{ + logf: t.Logf, + srcDir: "/path/to/foo", + loadExports: func(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) { + if loaded.Add(1) > candCount/2 { + cancel() + } + return "bar", []stdlib.Symbol{ + {Name: "A", Kind: stdlib.Var}, + {Name: "B", Kind: stdlib.Var}, + // Missing: "C", so that none of these packages match. + }, nil + }, + } + + var candidates []pkgDistance + for i := 0; i < candCount; i++ { + name := fmt.Sprintf("bar%d", i) + candidates = append(candidates, pkgDistance{ + pkg: &pkg{ + dir: path.Join(searcher.srcDir, name), + importPathShort: "foo/" + name, + packageName: name, + relevance: 1, + }, + distance: 1, + }) + } + + // We don't actually care what happens, as long as it doesn't deadlock! + _, err := searcher.search(ctx, candidates, "bar", map[string]bool{"A": true, "B": true, "C": true}) + t.Logf("search completed with err: %v", err) +} + +func TestMatchesPath(t *testing.T) { + tests := []struct { + ident string + path string + want bool + }{ + // degenerate cases + {"", "", true}, + {"", "x", false}, + {"x", "", false}, + + // full segment matching + {"x", "x", true}, + {"x", "y", false}, + {"x", "wx", false}, + {"x", "path/to/x", true}, + {"mypkg", "path/to/mypkg", true}, + {"x", "path/to/xy", false}, + {"x", "path/to/x/y", true}, + {"mypkg", "path/to/mypkg/y", true}, + {"x", "path/to/x/v3", true}, + + // subsegment matching + {"x", "path/to/x-go", true}, + {"foo", "path/to/go-foo", true}, + {"go", "path/to/go-foo", true}, + {"gofoo", "path/to/go-foo", true}, + {"gofoo", "path/to/go-foo-bar", false}, + {"foo", "path/to/go-foo-bar", true}, + {"bar", "path/to/go-foo-bar", true}, + {"gofoobar", "path/to/go-foo-bar", true}, + {"x", "path/to/x.v3", true}, + {"x", "path/to/xy.v3", false}, + {"x", "path/to/wx.v3", false}, + + // case insensitivity + {"MyPkg", "path/to/mypkg", true}, + {"myPkg", "path/to/MyPkg", true}, + + // multi-byte runes + {"世界", "path/to/世界", true}, + {"世界", "path/to/世界/foo", true}, + {"世界", "path/to/go-世界/foo", true}, + {"世界", "path/to/世/foo", false}, + } + + for _, test := range tests { + if got := matchesPath(test.ident, test.path); got != test.want { + t.Errorf("matchesPath(%q, %q) = %v, want %v", test.ident, test.path, got, test.want) + } + } +} + +func BenchmarkMatchesPath(b *testing.B) { + // A collection of calls that exercise different kinds of matching. + tests := map[string][]struct { + ident string + path string + want bool + }{ + "easy": { // lower case ascii + {"mypkg", "path/to/mypkg/y", true}, + {"foo", "path/to/go-foo-bar", true}, + {"gofoo", "path/to/go-foo-bar-baz", false}, + }, + "hard": { + {"MyPkg", "path/to/mypkg", true}, + {"世界", "path/to/go-世界-pkg/foo", true}, + {"longpkgname", "cloud.google.com/Go/Spanner/Admin/Database/longpkgname", true}, + }, + } + + for name, tests := range tests { + b.Run(name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, test := range tests { + if got := matchesPath(test.ident, test.path); got != test.want { + b.Errorf("matchesPath(%q, %q) = %v, want %v", test.ident, test.path, got, test.want) + } + } + } + }) + } +} diff --git a/contribs/gnopls/internal/imports/imports.go b/contribs/gnopls/internal/imports/imports.go new file mode 100644 index 00000000000..d291f253fe2 --- /dev/null +++ b/contribs/gnopls/internal/imports/imports.go @@ -0,0 +1,354 @@ +// Copyright 2013 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 imports implements a Go pretty-printer (like package "go/format") +// that also adds or removes import statements as necessary. +package imports + +import ( + "bufio" + "bytes" + "context" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/printer" + "go/token" + "io" + "regexp" + "strconv" + "strings" + + "golang.org/x/tools/go/ast/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/event" +) + +// Options is golang.org/x/tools/imports.Options with extra internal-only options. +type Options struct { + Env *ProcessEnv // The environment to use. Note: this contains the cached module and filesystem state. + + // LocalPrefix is a comma-separated string of import path prefixes, which, if + // set, instructs Process to sort the import paths with the given prefixes + // into another group after 3rd-party packages. + LocalPrefix string + + Fragment bool // Accept fragment of a source file (no package statement) + AllErrors bool // Report all errors (not just the first 10 on different lines) + + Comments bool // Print comments (true if nil *Options provided) + TabIndent bool // Use tabs for indent (true if nil *Options provided) + TabWidth int // Tab width (8 if nil *Options provided) + + FormatOnly bool // Disable the insertion and deletion of imports +} + +// Process implements golang.org/x/tools/imports.Process with explicit context in opt.Env. +func Process(filename string, src []byte, opt *Options) (formatted []byte, err error) { + fileSet := token.NewFileSet() + file, adjust, err := parse(fileSet, filename, src, opt) + if err != nil { + return nil, err + } + + if !opt.FormatOnly { + if err := fixImports(fileSet, file, filename, opt.Env); err != nil { + return nil, err + } + } + return formatFile(fileSet, file, src, adjust, opt) +} + +// FixImports returns a list of fixes to the imports that, when applied, +// will leave the imports in the same state as Process. src and opt must +// be specified. +// +// Note that filename's directory influences which imports can be chosen, +// so it is important that filename be accurate. +func FixImports(ctx context.Context, filename string, src []byte, opt *Options) (fixes []*ImportFix, err error) { + ctx, done := event.Start(ctx, "imports.FixImports") + defer done() + + fileSet := token.NewFileSet() + file, _, err := parse(fileSet, filename, src, opt) + if err != nil { + return nil, err + } + + return getFixes(ctx, fileSet, file, filename, opt.Env) +} + +// ApplyFixes applies all of the fixes to the file and formats it. extraMode +// is added in when parsing the file. src and opts must be specified, but no +// env is needed. +func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, extraMode parser.Mode) (formatted []byte, err error) { + // Don't use parse() -- we don't care about fragments or statement lists + // here, and we need to work with unparseable files. + fileSet := token.NewFileSet() + parserMode := parser.Mode(0) + if opt.Comments { + parserMode |= parser.ParseComments + } + if opt.AllErrors { + parserMode |= parser.AllErrors + } + parserMode |= extraMode + + file, err := parser.ParseFile(fileSet, filename, src, parserMode) + if file == nil { + return nil, err + } + + // Apply the fixes to the file. + apply(fileSet, file, fixes) + + return formatFile(fileSet, file, src, nil, opt) +} + +// formatFile formats the file syntax tree. +// It may mutate the token.FileSet and the ast.File. +// +// If an adjust function is provided, it is called after formatting +// with the original source (formatFile's src parameter) and the +// formatted file, and returns the postpocessed result. +func formatFile(fset *token.FileSet, file *ast.File, src []byte, adjust func(orig []byte, src []byte) []byte, opt *Options) ([]byte, error) { + mergeImports(file) + sortImports(opt.LocalPrefix, fset.File(file.Pos()), file) + var spacesBefore []string // import paths we need spaces before + for _, impSection := range astutil.Imports(fset, file) { + // Within each block of contiguous imports, see if any + // import lines are in different group numbers. If so, + // we'll need to put a space between them so it's + // compatible with gofmt. + lastGroup := -1 + for _, importSpec := range impSection { + importPath, _ := strconv.Unquote(importSpec.Path.Value) + groupNum := importGroup(opt.LocalPrefix, importPath) + if groupNum != lastGroup && lastGroup != -1 { + spacesBefore = append(spacesBefore, importPath) + } + lastGroup = groupNum + } + + } + + printerMode := printer.UseSpaces + if opt.TabIndent { + printerMode |= printer.TabIndent + } + printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth} + + var buf bytes.Buffer + err := printConfig.Fprint(&buf, fset, file) + if err != nil { + return nil, err + } + out := buf.Bytes() + if adjust != nil { + out = adjust(src, out) + } + if len(spacesBefore) > 0 { + out, err = addImportSpaces(bytes.NewReader(out), spacesBefore) + if err != nil { + return nil, err + } + } + + out, err = format.Source(out) + if err != nil { + return nil, err + } + return out, nil +} + +// parse parses src, which was read from filename, +// as a Go source file or statement list. +func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) { + parserMode := parser.Mode(0) + if opt.Comments { + parserMode |= parser.ParseComments + } + if opt.AllErrors { + parserMode |= parser.AllErrors + } + + // Try as whole source file. + file, err := parser.ParseFile(fset, filename, src, parserMode) + if err == nil { + return file, nil, nil + } + // If the error is that the source file didn't begin with a + // package line and we accept fragmented input, fall through to + // try as a source fragment. Stop and return on any other error. + if !opt.Fragment || !strings.Contains(err.Error(), "expected 'package'") { + return nil, nil, err + } + + // If this is a declaration list, make it a source file + // by inserting a package clause. + // Insert using a ;, not a newline, so that parse errors are on + // the correct line. + const prefix = "package main;" + psrc := append([]byte(prefix), src...) + file, err = parser.ParseFile(fset, filename, psrc, parserMode) + if err == nil { + // Gofmt will turn the ; into a \n. + // Do that ourselves now and update the file contents, + // so that positions and line numbers are correct going forward. + psrc[len(prefix)-1] = '\n' + fset.File(file.Package).SetLinesForContent(psrc) + + // If a main function exists, we will assume this is a main + // package and leave the file. + if containsMainFunc(file) { + return file, nil, nil + } + + adjust := func(orig, src []byte) []byte { + // Remove the package clause. + src = src[len(prefix):] + return matchSpace(orig, src) + } + return file, adjust, nil + } + // If the error is that the source file didn't begin with a + // declaration, fall through to try as a statement list. + // Stop and return on any other error. + if !strings.Contains(err.Error(), "expected declaration") { + return nil, nil, err + } + + // If this is a statement list, make it a source file + // by inserting a package clause and turning the list + // into a function body. This handles expressions too. + // Insert using a ;, not a newline, so that the line numbers + // in fsrc match the ones in src. + fsrc := append(append([]byte("package p; func _() {"), src...), '}') + file, err = parser.ParseFile(fset, filename, fsrc, parserMode) + if err == nil { + adjust := func(orig, src []byte) []byte { + // Remove the wrapping. + // Gofmt has turned the ; into a \n\n. + src = src[len("package p\n\nfunc _() {"):] + src = src[:len(src)-len("}\n")] + // Gofmt has also indented the function body one level. + // Remove that indent. + src = bytes.ReplaceAll(src, []byte("\n\t"), []byte("\n")) + return matchSpace(orig, src) + } + return file, adjust, nil + } + + // Failed, and out of options. + return nil, nil, err +} + +// containsMainFunc checks if a file contains a function declaration with the +// function signature 'func main()' +func containsMainFunc(file *ast.File) bool { + for _, decl := range file.Decls { + if f, ok := decl.(*ast.FuncDecl); ok { + if f.Name.Name != "main" { + continue + } + + if len(f.Type.Params.List) != 0 { + continue + } + + if f.Type.Results != nil && len(f.Type.Results.List) != 0 { + continue + } + + return true + } + } + + return false +} + +func cutSpace(b []byte) (before, middle, after []byte) { + i := 0 + for i < len(b) && (b[i] == ' ' || b[i] == '\t' || b[i] == '\n') { + i++ + } + j := len(b) + for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') { + j-- + } + if i <= j { + return b[:i], b[i:j], b[j:] + } + return nil, nil, b[j:] +} + +// matchSpace reformats src to use the same space context as orig. +// 1. If orig begins with blank lines, matchSpace inserts them at the beginning of src. +// 2. matchSpace copies the indentation of the first non-blank line in orig +// to every non-blank line in src. +// 3. matchSpace copies the trailing space from orig and uses it in place +// of src's trailing space. +func matchSpace(orig []byte, src []byte) []byte { + before, _, after := cutSpace(orig) + i := bytes.LastIndex(before, []byte{'\n'}) + before, indent := before[:i+1], before[i+1:] + + _, src, _ = cutSpace(src) + + var b bytes.Buffer + b.Write(before) + for len(src) > 0 { + line := src + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, src = line[:i+1], line[i+1:] + } else { + src = nil + } + if len(line) > 0 && line[0] != '\n' { // not blank + b.Write(indent) + } + b.Write(line) + } + b.Write(after) + return b.Bytes() +} + +var impLine = regexp.MustCompile(`^\s+(?:[\w\.]+\s+)?"(.+?)"`) + +func addImportSpaces(r io.Reader, breaks []string) ([]byte, error) { + var out bytes.Buffer + in := bufio.NewReader(r) + inImports := false + done := false + for { + s, err := in.ReadString('\n') + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + + if !inImports && !done && strings.HasPrefix(s, "import") { + inImports = true + } + if inImports && (strings.HasPrefix(s, "var") || + strings.HasPrefix(s, "func") || + strings.HasPrefix(s, "const") || + strings.HasPrefix(s, "type")) { + done = true + inImports = false + } + if inImports && len(breaks) > 0 { + if m := impLine.FindStringSubmatch(s); m != nil { + if m[1] == breaks[0] { + out.WriteByte('\n') + breaks = breaks[1:] + } + } + } + + fmt.Fprint(&out, s) + } + return out.Bytes(), nil +} diff --git a/contribs/gnopls/internal/imports/imports_test.go b/contribs/gnopls/internal/imports/imports_test.go new file mode 100644 index 00000000000..4da1a2ccd53 --- /dev/null +++ b/contribs/gnopls/internal/imports/imports_test.go @@ -0,0 +1,17 @@ +// 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. + +package imports + +import ( + "os" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +func TestMain(m *testing.M) { + testenv.ExitIfSmallMachine() + os.Exit(m.Run()) +} diff --git a/contribs/gnopls/internal/imports/mkindex.go b/contribs/gnopls/internal/imports/mkindex.go new file mode 100644 index 00000000000..2ecc9e45e9f --- /dev/null +++ b/contribs/gnopls/internal/imports/mkindex.go @@ -0,0 +1,173 @@ +//go:build ignore +// +build ignore + +// Copyright 2013 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. + +// Command mkindex creates the file "pkgindex.go" containing an index of the Go +// standard library. The file is intended to be built as part of the imports +// package, so that the package may be used in environments where a GOROOT is +// not available (such as App Engine). +package imports + +import ( + "bytes" + "fmt" + "go/ast" + "go/build" + "go/format" + "go/parser" + "go/token" + "log" + "os" + "path" + "path/filepath" + "strings" +) + +var ( + pkgIndex = make(map[string][]pkg) + exports = make(map[string]map[string]bool) +) + +func main() { + // Don't use GOPATH. + ctx := build.Default + ctx.GOPATH = "" + + // Populate pkgIndex global from GOROOT. + for _, path := range ctx.SrcDirs() { + f, err := os.Open(path) + if err != nil { + log.Print(err) + continue + } + children, err := f.Readdir(-1) + f.Close() + if err != nil { + log.Print(err) + continue + } + for _, child := range children { + if child.IsDir() { + loadPkg(path, child.Name()) + } + } + } + // Populate exports global. + for _, ps := range pkgIndex { + for _, p := range ps { + e := loadExports(p.dir) + if e != nil { + exports[p.dir] = e + } + } + } + + // Construct source file. + var buf bytes.Buffer + fmt.Fprint(&buf, pkgIndexHead) + fmt.Fprintf(&buf, "var pkgIndexMaster = %#v\n", pkgIndex) + fmt.Fprintf(&buf, "var exportsMaster = %#v\n", exports) + src := buf.Bytes() + + // Replace main.pkg type name with pkg. + src = bytes.Replace(src, []byte("main.pkg"), []byte("pkg"), -1) + // Replace actual GOROOT with "/go". + src = bytes.Replace(src, []byte(ctx.GOROOT), []byte("/go"), -1) + // Add some line wrapping. + src = bytes.Replace(src, []byte("}, "), []byte("},\n"), -1) + src = bytes.Replace(src, []byte("true, "), []byte("true,\n"), -1) + + var err error + src, err = format.Source(src) + if err != nil { + log.Fatal(err) + } + + // Write out source file. + err = os.WriteFile("pkgindex.go", src, 0644) + if err != nil { + log.Fatal(err) + } +} + +const pkgIndexHead = `package imports + +func init() { + pkgIndexOnce.Do(func() { + pkgIndex.m = pkgIndexMaster + }) + loadExports = func(dir string) map[string]bool { + return exportsMaster[dir] + } +} +` + +type pkg struct { + importpath string // full pkg import path, e.g. "net/http" + dir string // absolute file path to pkg directory e.g. "/usr/lib/go/src/fmt" +} + +var fset = token.NewFileSet() + +func loadPkg(root, importpath string) { + shortName := path.Base(importpath) + if shortName == "testdata" { + return + } + + dir := filepath.Join(root, importpath) + pkgIndex[shortName] = append(pkgIndex[shortName], pkg{ + importpath: importpath, + dir: dir, + }) + + pkgDir, err := os.Open(dir) + if err != nil { + return + } + children, err := pkgDir.Readdir(-1) + pkgDir.Close() + if err != nil { + return + } + for _, child := range children { + name := child.Name() + if name == "" { + continue + } + if c := name[0]; c == '.' || ('0' <= c && c <= '9') { + continue + } + if child.IsDir() { + loadPkg(root, filepath.Join(importpath, name)) + } + } +} + +func loadExports(dir string) map[string]bool { + exports := make(map[string]bool) + buildPkg, err := build.ImportDir(dir, 0) + if err != nil { + if strings.Contains(err.Error(), "no buildable Go source files in") { + return nil + } + log.Printf("could not import %q: %v", dir, err) + return nil + } + for _, file := range buildPkg.GoFiles { + f, err := parser.ParseFile(fset, filepath.Join(dir, file), nil, 0) + if err != nil { + log.Printf("could not parse %q: %v", file, err) + continue + } + for name := range f.Scope.Objects { + if ast.IsExported(name) { + exports[name] = true + } + } + } + return exports +} diff --git a/contribs/gnopls/internal/imports/mod.go b/contribs/gnopls/internal/imports/mod.go new file mode 100644 index 00000000000..d66b9d3abd7 --- /dev/null +++ b/contribs/gnopls/internal/imports/mod.go @@ -0,0 +1,840 @@ +// 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. + +package imports + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + + "golang.org/x/mod/module" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/gopathwalk" + "github.com/gnolang/gno/contribs/gnopls/internal/stdlib" +) + +// Notes(rfindley): ModuleResolver appears to be heavily optimized for scanning +// as fast as possible, which is desirable for a call to goimports from the +// command line, but it doesn't work as well for gopls, where it suffers from +// slow startup (golang/go#44863) and intermittent hanging (golang/go#59216), +// both caused by populating the cache, albeit in slightly different ways. +// +// A high level list of TODOs: +// - Optimize the scan itself, as there is some redundancy statting and +// reading go.mod files. +// - Invert the relationship between ProcessEnv and Resolver (see the +// docstring of ProcessEnv). +// - Make it easier to use an external resolver implementation. +// +// Smaller TODOs are annotated in the code below. + +// ModuleResolver implements the Resolver interface for a workspace using +// modules. +// +// A goal of the ModuleResolver is to invoke the Go command as little as +// possible. To this end, it runs the Go command only for listing module +// information (i.e. `go list -m -e -json ...`). Package scanning, the process +// of loading package information for the modules, is implemented internally +// via the scan method. +// +// It has two types of state: the state derived from the go command, which +// is populated by init, and the state derived from scans, which is populated +// via scan. A root is considered scanned if it has been walked to discover +// directories. However, if the scan did not require additional information +// from the directory (such as package name or exports), the directory +// information itself may be partially populated. It will be lazily filled in +// as needed by scans, using the scanCallback. +type ModuleResolver struct { + env *ProcessEnv + + // Module state, populated during construction + dummyVendorMod *gocommand.ModuleJSON // if vendoring is enabled, a pseudo-module to represent the /vendor directory + moduleCacheDir string // GOMODCACHE, inferred from GOPATH if unset + roots []gopathwalk.Root // roots to scan, in approximate order of importance + mains []*gocommand.ModuleJSON // main modules + mainByDir map[string]*gocommand.ModuleJSON // module information by dir, to join with roots + modsByModPath []*gocommand.ModuleJSON // all modules, ordered by # of path components in their module path + modsByDir []*gocommand.ModuleJSON // ...or by the number of path components in their Dir. + + // Scanning state, populated by scan + + // scanSema prevents concurrent scans, and guards scannedRoots and the cache + // fields below (though the caches themselves are concurrency safe). + // Receive to acquire, send to release. + scanSema chan struct{} + scannedRoots map[gopathwalk.Root]bool // if true, root has been walked + + // Caches of directory info, populated by scans and scan callbacks + // + // moduleCacheCache stores cached information about roots in the module + // cache, which are immutable and therefore do not need to be invalidated. + // + // otherCache stores information about all other roots (even GOROOT), which + // may change. + moduleCacheCache *DirInfoCache + otherCache *DirInfoCache +} + +// newModuleResolver returns a new module-aware goimports resolver. +// +// Note: use caution when modifying this constructor: changes must also be +// reflected in ModuleResolver.ClearForNewScan. +func newModuleResolver(e *ProcessEnv, moduleCacheCache *DirInfoCache) (*ModuleResolver, error) { + r := &ModuleResolver{ + env: e, + scanSema: make(chan struct{}, 1), + } + r.scanSema <- struct{}{} // release + + goenv, err := r.env.goEnv() + if err != nil { + return nil, err + } + + // TODO(rfindley): can we refactor to share logic with r.env.invokeGo? + inv := gocommand.Invocation{ + BuildFlags: r.env.BuildFlags, + ModFlag: r.env.ModFlag, + Env: r.env.env(), + Logf: r.env.Logf, + WorkingDir: r.env.WorkingDir, + } + + vendorEnabled := false + var mainModVendor *gocommand.ModuleJSON // for module vendoring + var mainModsVendor []*gocommand.ModuleJSON // for workspace vendoring + + goWork := r.env.Env["GOWORK"] + if len(goWork) == 0 { + // TODO(rfindley): VendorEnabled runs the go command to get GOFLAGS, but + // they should be available from the ProcessEnv. Can we avoid the redundant + // invocation? + vendorEnabled, mainModVendor, err = gocommand.VendorEnabled(context.TODO(), inv, r.env.GocmdRunner) + if err != nil { + return nil, err + } + } else { + vendorEnabled, mainModsVendor, err = gocommand.WorkspaceVendorEnabled(context.Background(), inv, r.env.GocmdRunner) + if err != nil { + return nil, err + } + } + + if vendorEnabled { + if mainModVendor != nil { + // Module vendor mode is on, so all the non-Main modules are irrelevant, + // and we need to search /vendor for everything. + r.mains = []*gocommand.ModuleJSON{mainModVendor} + r.dummyVendorMod = &gocommand.ModuleJSON{ + Path: "", + Dir: filepath.Join(mainModVendor.Dir, "vendor"), + } + r.modsByModPath = []*gocommand.ModuleJSON{mainModVendor, r.dummyVendorMod} + r.modsByDir = []*gocommand.ModuleJSON{mainModVendor, r.dummyVendorMod} + } else { + // Workspace vendor mode is on, so all the non-Main modules are irrelevant, + // and we need to search /vendor for everything. + r.mains = mainModsVendor + r.dummyVendorMod = &gocommand.ModuleJSON{ + Path: "", + Dir: filepath.Join(filepath.Dir(goWork), "vendor"), + } + r.modsByModPath = append(append([]*gocommand.ModuleJSON{}, mainModsVendor...), r.dummyVendorMod) + r.modsByDir = append(append([]*gocommand.ModuleJSON{}, mainModsVendor...), r.dummyVendorMod) + } + } else { + // Vendor mode is off, so run go list -m ... to find everything. + err := r.initAllMods() + // We expect an error when running outside of a module with + // GO111MODULE=on. Other errors are fatal. + if err != nil { + if errMsg := err.Error(); !strings.Contains(errMsg, "working directory is not part of a module") && !strings.Contains(errMsg, "go.mod file not found") { + return nil, err + } + } + } + + r.moduleCacheDir = gomodcacheForEnv(goenv) + if r.moduleCacheDir == "" { + return nil, fmt.Errorf("cannot resolve GOMODCACHE") + } + + sort.Slice(r.modsByModPath, func(i, j int) bool { + count := func(x int) int { + return strings.Count(r.modsByModPath[x].Path, "/") + } + return count(j) < count(i) // descending order + }) + sort.Slice(r.modsByDir, func(i, j int) bool { + count := func(x int) int { + return strings.Count(r.modsByDir[x].Dir, string(filepath.Separator)) + } + return count(j) < count(i) // descending order + }) + + r.roots = []gopathwalk.Root{} + if goenv["GOROOT"] != "" { // "" happens in tests + r.roots = append(r.roots, gopathwalk.Root{Path: filepath.Join(goenv["GOROOT"], "/src"), Type: gopathwalk.RootGOROOT}) + } + r.mainByDir = make(map[string]*gocommand.ModuleJSON) + for _, main := range r.mains { + r.roots = append(r.roots, gopathwalk.Root{Path: main.Dir, Type: gopathwalk.RootCurrentModule}) + r.mainByDir[main.Dir] = main + } + if vendorEnabled { + r.roots = append(r.roots, gopathwalk.Root{Path: r.dummyVendorMod.Dir, Type: gopathwalk.RootOther}) + } else { + addDep := func(mod *gocommand.ModuleJSON) { + if mod.Replace == nil { + // This is redundant with the cache, but we'll skip it cheaply enough + // when we encounter it in the module cache scan. + // + // Including it at a lower index in r.roots than the module cache dir + // helps prioritize matches from within existing dependencies. + r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootModuleCache}) + } else { + r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootOther}) + } + } + // Walk dependent modules before scanning the full mod cache, direct deps first. + for _, mod := range r.modsByModPath { + if !mod.Indirect && !mod.Main { + addDep(mod) + } + } + for _, mod := range r.modsByModPath { + if mod.Indirect && !mod.Main { + addDep(mod) + } + } + // If provided, share the moduleCacheCache. + // + // TODO(rfindley): The module cache is immutable. However, the loaded + // exports do depend on GOOS and GOARCH. Fortunately, the + // ProcessEnv.buildContext does not adjust these from build.DefaultContext + // (even though it should). So for now, this is OK to share, but we need to + // add logic for handling GOOS/GOARCH. + r.moduleCacheCache = moduleCacheCache + r.roots = append(r.roots, gopathwalk.Root{Path: r.moduleCacheDir, Type: gopathwalk.RootModuleCache}) + } + + r.scannedRoots = map[gopathwalk.Root]bool{} + if r.moduleCacheCache == nil { + r.moduleCacheCache = NewDirInfoCache() + } + r.otherCache = NewDirInfoCache() + return r, nil +} + +// gomodcacheForEnv returns the GOMODCACHE value to use based on the given env +// map, which must have GOMODCACHE and GOPATH populated. +// +// TODO(rfindley): this is defensive refactoring. +// 1. Is this even relevant anymore? Can't we just read GOMODCACHE. +// 2. Use this to separate module cache scanning from other scanning. +func gomodcacheForEnv(goenv map[string]string) string { + if gmc := goenv["GOMODCACHE"]; gmc != "" { + // golang/go#67156: ensure that the module cache is clean, since it is + // assumed as a prefix to directories scanned by gopathwalk, which are + // themselves clean. + return filepath.Clean(gmc) + } + gopaths := filepath.SplitList(goenv["GOPATH"]) + if len(gopaths) == 0 { + return "" + } + return filepath.Join(gopaths[0], "/pkg/mod") +} + +func (r *ModuleResolver) initAllMods() error { + stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-e", "-json", "...") + if err != nil { + return err + } + for dec := json.NewDecoder(stdout); dec.More(); { + mod := &gocommand.ModuleJSON{} + if err := dec.Decode(mod); err != nil { + return err + } + if mod.Dir == "" { + r.env.logf("module %v has not been downloaded and will be ignored", mod.Path) + // Can't do anything with a module that's not downloaded. + continue + } + // golang/go#36193: the go command doesn't always clean paths. + mod.Dir = filepath.Clean(mod.Dir) + r.modsByModPath = append(r.modsByModPath, mod) + r.modsByDir = append(r.modsByDir, mod) + if mod.Main { + r.mains = append(r.mains, mod) + } + } + return nil +} + +// ClearForNewScan invalidates the last scan. +// +// It preserves the set of roots, but forgets about the set of directories. +// Though it forgets the set of module cache directories, it remembers their +// contents, since they are assumed to be immutable. +func (r *ModuleResolver) ClearForNewScan() Resolver { + <-r.scanSema // acquire r, to guard scannedRoots + r2 := &ModuleResolver{ + env: r.env, + dummyVendorMod: r.dummyVendorMod, + moduleCacheDir: r.moduleCacheDir, + roots: r.roots, + mains: r.mains, + mainByDir: r.mainByDir, + modsByModPath: r.modsByModPath, + + scanSema: make(chan struct{}, 1), + scannedRoots: make(map[gopathwalk.Root]bool), + otherCache: NewDirInfoCache(), + moduleCacheCache: r.moduleCacheCache, + } + r2.scanSema <- struct{}{} // r2 must start released + // Invalidate root scans. We don't need to invalidate module cache roots, + // because they are immutable. + // (We don't support a use case where GOMODCACHE is cleaned in the middle of + // e.g. a gopls session: the user must restart gopls to get accurate + // imports.) + // + // Scanning for new directories in GOMODCACHE should be handled elsewhere, + // via a call to ScanModuleCache. + for _, root := range r.roots { + if root.Type == gopathwalk.RootModuleCache && r.scannedRoots[root] { + r2.scannedRoots[root] = true + } + } + r.scanSema <- struct{}{} // release r + return r2 +} + +// ClearModuleInfo invalidates resolver state that depends on go.mod file +// contents (essentially, the output of go list -m -json ...). +// +// Notably, it does not forget directory contents, which are reset +// asynchronously via ClearForNewScan. +// +// If the ProcessEnv is a GOPATH environment, ClearModuleInfo is a no op. +// +// TODO(rfindley): move this to a new env.go, consolidating ProcessEnv methods. +func (e *ProcessEnv) ClearModuleInfo() { + if r, ok := e.resolver.(*ModuleResolver); ok { + resolver, err := newModuleResolver(e, e.ModCache) + if err != nil { + e.resolver = nil + e.resolverErr = err + return + } + + <-r.scanSema // acquire (guards caches) + resolver.moduleCacheCache = r.moduleCacheCache + resolver.otherCache = r.otherCache + r.scanSema <- struct{}{} // release + + e.UpdateResolver(resolver) + } +} + +// UpdateResolver sets the resolver for the ProcessEnv to use in imports +// operations. Only for use with the result of [Resolver.ClearForNewScan]. +// +// TODO(rfindley): this awkward API is a result of the (arguably) inverted +// relationship between configuration and state described in the doc comment +// for [ProcessEnv]. +func (e *ProcessEnv) UpdateResolver(r Resolver) { + e.resolver = r + e.resolverErr = nil +} + +// findPackage returns the module and directory from within the main modules +// and their dependencies that contains the package at the given import path, +// or returns nil, "" if no module is in scope. +func (r *ModuleResolver) findPackage(importPath string) (*gocommand.ModuleJSON, string) { + // This can't find packages in the stdlib, but that's harmless for all + // the existing code paths. + for _, m := range r.modsByModPath { + if !strings.HasPrefix(importPath, m.Path) { + continue + } + pathInModule := importPath[len(m.Path):] + pkgDir := filepath.Join(m.Dir, pathInModule) + if r.dirIsNestedModule(pkgDir, m) { + continue + } + + if info, ok := r.cacheLoad(pkgDir); ok { + if loaded, err := info.reachedStatus(nameLoaded); loaded { + if err != nil { + continue // No package in this dir. + } + return m, pkgDir + } + if scanned, err := info.reachedStatus(directoryScanned); scanned && err != nil { + continue // Dir is unreadable, etc. + } + // This is slightly wrong: a directory doesn't have to have an + // importable package to count as a package for package-to-module + // resolution. package main or _test files should count but + // don't. + // TODO(heschi): fix this. + if _, err := r.cachePackageName(info); err == nil { + return m, pkgDir + } + } + + // Not cached. Read the filesystem. + pkgFiles, err := os.ReadDir(pkgDir) + if err != nil { + continue + } + // A module only contains a package if it has buildable go + // files in that directory. If not, it could be provided by an + // outer module. See #29736. + for _, fi := range pkgFiles { + if ok, _ := r.env.matchFile(pkgDir, fi.Name()); ok { + return m, pkgDir + } + } + } + return nil, "" +} + +func (r *ModuleResolver) cacheLoad(dir string) (directoryPackageInfo, bool) { + if info, ok := r.moduleCacheCache.Load(dir); ok { + return info, ok + } + return r.otherCache.Load(dir) +} + +func (r *ModuleResolver) cacheStore(info directoryPackageInfo) { + if info.rootType == gopathwalk.RootModuleCache { + r.moduleCacheCache.Store(info.dir, info) + } else { + r.otherCache.Store(info.dir, info) + } +} + +// cachePackageName caches the package name for a dir already in the cache. +func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) { + if info.rootType == gopathwalk.RootModuleCache { + return r.moduleCacheCache.CachePackageName(info) + } + return r.otherCache.CachePackageName(info) +} + +func (r *ModuleResolver) cacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []stdlib.Symbol, error) { + if info.rootType == gopathwalk.RootModuleCache { + return r.moduleCacheCache.CacheExports(ctx, env, info) + } + return r.otherCache.CacheExports(ctx, env, info) +} + +// findModuleByDir returns the module that contains dir, or nil if no such +// module is in scope. +func (r *ModuleResolver) findModuleByDir(dir string) *gocommand.ModuleJSON { + // This is quite tricky and may not be correct. dir could be: + // - a package in the main module. + // - a replace target underneath the main module's directory. + // - a nested module in the above. + // - a replace target somewhere totally random. + // - a nested module in the above. + // - in the mod cache. + // - in /vendor/ in -mod=vendor mode. + // - nested module? Dunno. + // Rumor has it that replace targets cannot contain other replace targets. + // + // Note that it is critical here that modsByDir is sorted to have deeper dirs + // first. This ensures that findModuleByDir finds the innermost module. + // See also golang/go#56291. + for _, m := range r.modsByDir { + if !strings.HasPrefix(dir, m.Dir) { + continue + } + + if r.dirIsNestedModule(dir, m) { + continue + } + + return m + } + return nil +} + +// dirIsNestedModule reports if dir is contained in a nested module underneath +// mod, not actually in mod. +func (r *ModuleResolver) dirIsNestedModule(dir string, mod *gocommand.ModuleJSON) bool { + if !strings.HasPrefix(dir, mod.Dir) { + return false + } + if r.dirInModuleCache(dir) { + // Nested modules in the module cache are pruned, + // so it cannot be a nested module. + return false + } + if mod != nil && mod == r.dummyVendorMod { + // The /vendor pseudomodule is flattened and doesn't actually count. + return false + } + modDir, _ := r.modInfo(dir) + if modDir == "" { + return false + } + return modDir != mod.Dir +} + +func readModName(modFile string) string { + modBytes, err := os.ReadFile(modFile) + if err != nil { + return "" + } + return modulePath(modBytes) +} + +func (r *ModuleResolver) modInfo(dir string) (modDir, modName string) { + if r.dirInModuleCache(dir) { + if matches := modCacheRegexp.FindStringSubmatch(dir); len(matches) == 3 { + index := strings.Index(dir, matches[1]+"@"+matches[2]) + modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2]) + return modDir, readModName(filepath.Join(modDir, "go.mod")) + } + } + for { + if info, ok := r.cacheLoad(dir); ok { + return info.moduleDir, info.moduleName + } + f := filepath.Join(dir, "go.mod") + info, err := os.Stat(f) + if err == nil && !info.IsDir() { + return dir, readModName(f) + } + + d := filepath.Dir(dir) + if len(d) >= len(dir) { + return "", "" // reached top of file system, no go.mod + } + dir = d + } +} + +func (r *ModuleResolver) dirInModuleCache(dir string) bool { + if r.moduleCacheDir == "" { + return false + } + return strings.HasPrefix(dir, r.moduleCacheDir) +} + +func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { + names := map[string]string{} + for _, path := range importPaths { + // TODO(rfindley): shouldn't this use the dirInfoCache? + _, packageDir := r.findPackage(path) + if packageDir == "" { + continue + } + name, err := packageDirToName(packageDir) + if err != nil { + continue + } + names[path] = name + } + return names, nil +} + +func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error { + ctx, done := event.Start(ctx, "imports.ModuleResolver.scan") + defer done() + + processDir := func(info directoryPackageInfo) { + // Skip this directory if we were not able to get the package information successfully. + if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { + return + } + pkg, err := r.canonicalize(info) + if err != nil { + return + } + if !callback.dirFound(pkg) { + return + } + + pkg.packageName, err = r.cachePackageName(info) + if err != nil { + return + } + if !callback.packageNameLoaded(pkg) { + return + } + + _, exports, err := r.loadExports(ctx, pkg, false) + if err != nil { + return + } + callback.exportsLoaded(pkg, exports) + } + + // Start processing everything in the cache, and listen for the new stuff + // we discover in the walk below. + stop1 := r.moduleCacheCache.ScanAndListen(ctx, processDir) + defer stop1() + stop2 := r.otherCache.ScanAndListen(ctx, processDir) + defer stop2() + + // We assume cached directories are fully cached, including all their + // children, and have not changed. We can skip them. + skip := func(root gopathwalk.Root, dir string) bool { + if r.env.SkipPathInScan != nil && root.Type == gopathwalk.RootCurrentModule { + if root.Path == dir { + return false + } + + if r.env.SkipPathInScan(filepath.Clean(dir)) { + return true + } + } + + info, ok := r.cacheLoad(dir) + if !ok { + return false + } + // This directory can be skipped as long as we have already scanned it. + // Packages with errors will continue to have errors, so there is no need + // to rescan them. + packageScanned, _ := info.reachedStatus(directoryScanned) + return packageScanned + } + + add := func(root gopathwalk.Root, dir string) { + r.cacheStore(r.scanDirForPackage(root, dir)) + } + + // r.roots and the callback are not necessarily safe to use in the + // goroutine below. Process them eagerly. + roots := filterRoots(r.roots, callback.rootFound) + // We can't cancel walks, because we need them to finish to have a usable + // cache. Instead, run them in a separate goroutine and detach. + scanDone := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + return + case <-r.scanSema: // acquire + } + defer func() { r.scanSema <- struct{}{} }() // release + // We have the lock on r.scannedRoots, and no other scans can run. + for _, root := range roots { + if ctx.Err() != nil { + return + } + + if r.scannedRoots[root] { + continue + } + gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: true}) + r.scannedRoots[root] = true + } + close(scanDone) + }() + select { + case <-ctx.Done(): + case <-scanDone: + } + return nil +} + +func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) float64 { + if stdlib.HasPackage(path) { + return MaxRelevance + } + mod, _ := r.findPackage(path) + return modRelevance(mod) +} + +func modRelevance(mod *gocommand.ModuleJSON) float64 { + var relevance float64 + switch { + case mod == nil: // out of scope + return MaxRelevance - 4 + case mod.Indirect: + relevance = MaxRelevance - 3 + case !mod.Main: + relevance = MaxRelevance - 2 + default: + relevance = MaxRelevance - 1 // main module ties with stdlib + } + + _, versionString, ok := module.SplitPathVersion(mod.Path) + if ok { + index := strings.Index(versionString, "v") + if index == -1 { + return relevance + } + if versionNumber, err := strconv.ParseFloat(versionString[index+1:], 64); err == nil { + relevance += versionNumber / 1000 + } + } + + return relevance +} + +// canonicalize gets the result of canonicalizing the packages using the results +// of initializing the resolver from 'go list -m'. +func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) { + // Packages in GOROOT are already canonical, regardless of the std/cmd modules. + if info.rootType == gopathwalk.RootGOROOT { + return &pkg{ + importPathShort: info.nonCanonicalImportPath, + dir: info.dir, + packageName: path.Base(info.nonCanonicalImportPath), + relevance: MaxRelevance, + }, nil + } + + importPath := info.nonCanonicalImportPath + mod := r.findModuleByDir(info.dir) + // Check if the directory is underneath a module that's in scope. + if mod != nil { + // It is. If dir is the target of a replace directive, + // our guessed import path is wrong. Use the real one. + if mod.Dir == info.dir { + importPath = mod.Path + } else { + dirInMod := info.dir[len(mod.Dir)+len("/"):] + importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod)) + } + } else if !strings.HasPrefix(importPath, info.moduleName) { + // The module's name doesn't match the package's import path. It + // probably needs a replace directive we don't have. + return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir) + } + + res := &pkg{ + importPathShort: importPath, + dir: info.dir, + relevance: modRelevance(mod), + } + // We may have discovered a package that has a different version + // in scope already. Canonicalize to that one if possible. + if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" { + res.dir = canonicalDir + } + return res, nil +} + +func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []stdlib.Symbol, error) { + if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest { + return r.cacheExports(ctx, r.env, info) + } + return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) +} + +func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo { + subdir := "" + if prefix := root.Path + string(filepath.Separator); strings.HasPrefix(dir, prefix) { + subdir = dir[len(prefix):] + } + importPath := filepath.ToSlash(subdir) + if strings.HasPrefix(importPath, "vendor/") { + // Only enter vendor directories if they're explicitly requested as a root. + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("unwanted vendor directory"), + } + } + switch root.Type { + case gopathwalk.RootCurrentModule: + importPath = path.Join(r.mainByDir[root.Path].Path, filepath.ToSlash(subdir)) + case gopathwalk.RootModuleCache: + matches := modCacheRegexp.FindStringSubmatch(subdir) + if len(matches) == 0 { + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("invalid module cache path: %v", subdir), + } + } + modPath, err := module.UnescapePath(filepath.ToSlash(matches[1])) + if err != nil { + r.env.logf("decoding module cache path %q: %v", subdir, err) + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("decoding module cache path %q: %v", subdir, err), + } + } + importPath = path.Join(modPath, filepath.ToSlash(matches[3])) + } + + modDir, modName := r.modInfo(dir) + result := directoryPackageInfo{ + status: directoryScanned, + dir: dir, + rootType: root.Type, + nonCanonicalImportPath: importPath, + moduleDir: modDir, + moduleName: modName, + } + if root.Type == gopathwalk.RootGOROOT { + // stdlib packages are always in scope, despite the confusing go.mod + return result + } + return result +} + +// modCacheRegexp splits a path in a module cache into module, module version, and package. +var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) + +var ( + slashSlash = []byte("//") + moduleStr = []byte("module") +) + +// modulePath returns the module path from the gomod file text. +// If it cannot find a module path, it returns an empty string. +// It is tolerant of unrelated problems in the go.mod file. +// +// Copied from cmd/go/internal/modfile. +func modulePath(mod []byte) string { + for len(mod) > 0 { + line := mod + mod = nil + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, mod = line[:i], line[i+1:] + } + if i := bytes.Index(line, slashSlash); i >= 0 { + line = line[:i] + } + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, moduleStr) { + continue + } + line = line[len(moduleStr):] + n := len(line) + line = bytes.TrimSpace(line) + if len(line) == n || len(line) == 0 { + continue + } + + if line[0] == '"' || line[0] == '`' { + p, err := strconv.Unquote(string(line)) + if err != nil { + return "" // malformed quoted string or multiline module path + } + return p + } + + return string(line) + } + return "" // missing module path +} diff --git a/contribs/gnopls/internal/imports/mod_cache.go b/contribs/gnopls/internal/imports/mod_cache.go new file mode 100644 index 00000000000..e4369db317b --- /dev/null +++ b/contribs/gnopls/internal/imports/mod_cache.go @@ -0,0 +1,331 @@ +// 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. + +package imports + +import ( + "context" + "fmt" + "path" + "path/filepath" + "strings" + "sync" + + "golang.org/x/mod/module" + "github.com/gnolang/gno/contribs/gnopls/internal/gopathwalk" + "github.com/gnolang/gno/contribs/gnopls/internal/stdlib" +) + +// To find packages to import, the resolver needs to know about all of +// the packages that could be imported. This includes packages that are +// already in modules that are in (1) the current module, (2) replace targets, +// and (3) packages in the module cache. Packages in (1) and (2) may change over +// time, as the client may edit the current module and locally replaced modules. +// The module cache (which includes all of the packages in (3)) can only +// ever be added to. +// +// The resolver can thus save state about packages in the module cache +// and guarantee that this will not change over time. To obtain information +// about new modules added to the module cache, the module cache should be +// rescanned. +// +// It is OK to serve information about modules that have been deleted, +// as they do still exist. +// TODO(suzmue): can we share information with the caller about +// what module needs to be downloaded to import this package? + +type directoryPackageStatus int + +const ( + _ directoryPackageStatus = iota + directoryScanned + nameLoaded + exportsLoaded +) + +// directoryPackageInfo holds (possibly incomplete) information about packages +// contained in a given directory. +type directoryPackageInfo struct { + // status indicates the extent to which this struct has been filled in. + status directoryPackageStatus + // err is non-nil when there was an error trying to reach status. + err error + + // Set when status >= directoryScanned. + + // dir is the absolute directory of this package. + dir string + rootType gopathwalk.RootType + // nonCanonicalImportPath is the package's expected import path. It may + // not actually be importable at that path. + nonCanonicalImportPath string + + // Module-related information. + moduleDir string // The directory that is the module root of this dir. + moduleName string // The module name that contains this dir. + + // Set when status >= nameLoaded. + + packageName string // the package name, as declared in the source. + + // Set when status >= exportsLoaded. + // TODO(rfindley): it's hard to see this, but exports depend implicitly on + // the default build context GOOS and GOARCH. + // + // We can make this explicit, and key exports by GOOS, GOARCH. + exports []stdlib.Symbol +} + +// reachedStatus returns true when info has a status at least target and any error associated with +// an attempt to reach target. +func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) (bool, error) { + if info.err == nil { + return info.status >= target, nil + } + if info.status == target { + return true, info.err + } + return true, nil +} + +// DirInfoCache is a concurrency-safe map for storing information about +// directories that may contain packages. +// +// The information in this cache is built incrementally. Entries are initialized in scan. +// No new keys should be added in any other functions, as all directories containing +// packages are identified in scan. +// +// Other functions, including loadExports and findPackage, may update entries in this cache +// as they discover new things about the directory. +// +// The information in the cache is not expected to change for the cache's +// lifetime, so there is no protection against competing writes. Users should +// take care not to hold the cache across changes to the underlying files. +type DirInfoCache struct { + mu sync.Mutex + // dirs stores information about packages in directories, keyed by absolute path. + dirs map[string]*directoryPackageInfo + listeners map[*int]cacheListener +} + +func NewDirInfoCache() *DirInfoCache { + return &DirInfoCache{ + dirs: make(map[string]*directoryPackageInfo), + listeners: make(map[*int]cacheListener), + } +} + +type cacheListener func(directoryPackageInfo) + +// ScanAndListen calls listener on all the items in the cache, and on anything +// newly added. The returned stop function waits for all in-flight callbacks to +// finish and blocks new ones. +func (d *DirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() { + ctx, cancel := context.WithCancel(ctx) + + // Flushing out all the callbacks is tricky without knowing how many there + // are going to be. Setting an arbitrary limit makes it much easier. + const maxInFlight = 10 + sema := make(chan struct{}, maxInFlight) + for i := 0; i < maxInFlight; i++ { + sema <- struct{}{} + } + + cookie := new(int) // A unique ID we can use for the listener. + + // We can't hold mu while calling the listener. + d.mu.Lock() + var keys []string + for key := range d.dirs { + keys = append(keys, key) + } + d.listeners[cookie] = func(info directoryPackageInfo) { + select { + case <-ctx.Done(): + return + case <-sema: + } + listener(info) + sema <- struct{}{} + } + d.mu.Unlock() + + stop := func() { + cancel() + d.mu.Lock() + delete(d.listeners, cookie) + d.mu.Unlock() + for i := 0; i < maxInFlight; i++ { + <-sema + } + } + + // Process the pre-existing keys. + for _, k := range keys { + select { + case <-ctx.Done(): + return stop + default: + } + if v, ok := d.Load(k); ok { + listener(v) + } + } + + return stop +} + +// Store stores the package info for dir. +func (d *DirInfoCache) Store(dir string, info directoryPackageInfo) { + d.mu.Lock() + // TODO(rfindley, golang/go#59216): should we overwrite an existing entry? + // That seems incorrect as the cache should be idempotent. + _, old := d.dirs[dir] + d.dirs[dir] = &info + var listeners []cacheListener + for _, l := range d.listeners { + listeners = append(listeners, l) + } + d.mu.Unlock() + + if !old { + for _, l := range listeners { + l(info) + } + } +} + +// Load returns a copy of the directoryPackageInfo for absolute directory dir. +func (d *DirInfoCache) Load(dir string) (directoryPackageInfo, bool) { + d.mu.Lock() + defer d.mu.Unlock() + info, ok := d.dirs[dir] + if !ok { + return directoryPackageInfo{}, false + } + return *info, true +} + +// Keys returns the keys currently present in d. +func (d *DirInfoCache) Keys() (keys []string) { + d.mu.Lock() + defer d.mu.Unlock() + for key := range d.dirs { + keys = append(keys, key) + } + return keys +} + +func (d *DirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) { + if loaded, err := info.reachedStatus(nameLoaded); loaded { + return info.packageName, err + } + if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { + return "", fmt.Errorf("cannot read package name, scan error: %v", err) + } + info.packageName, info.err = packageDirToName(info.dir) + info.status = nameLoaded + d.Store(info.dir, info) + return info.packageName, info.err +} + +func (d *DirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []stdlib.Symbol, error) { + if reached, _ := info.reachedStatus(exportsLoaded); reached { + return info.packageName, info.exports, info.err + } + if reached, err := info.reachedStatus(nameLoaded); reached && err != nil { + return "", nil, err + } + info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false) + if info.err == context.Canceled || info.err == context.DeadlineExceeded { + return info.packageName, info.exports, info.err + } + // The cache structure wants things to proceed linearly. We can skip a + // step here, but only if we succeed. + if info.status == nameLoaded || info.err == nil { + info.status = exportsLoaded + } else { + info.status = nameLoaded + } + d.Store(info.dir, info) + return info.packageName, info.exports, info.err +} + +// ScanModuleCache walks the given directory, which must be a GOMODCACHE value, +// for directory package information, storing the results in cache. +func ScanModuleCache(dir string, cache *DirInfoCache, logf func(string, ...any)) { + // Note(rfindley): it's hard to see, but this function attempts to implement + // just the side effects on cache of calling PrimeCache with a ProcessEnv + // that has the given dir as its GOMODCACHE. + // + // Teasing out the control flow, we see that we can avoid any handling of + // vendor/ and can infer module info entirely from the path, simplifying the + // logic here. + + root := gopathwalk.Root{ + Path: filepath.Clean(dir), + Type: gopathwalk.RootModuleCache, + } + + directoryInfo := func(root gopathwalk.Root, dir string) directoryPackageInfo { + // This is a copy of ModuleResolver.scanDirForPackage, trimmed down to + // logic that applies to a module cache directory. + + subdir := "" + if dir != root.Path { + subdir = dir[len(root.Path)+len("/"):] + } + + matches := modCacheRegexp.FindStringSubmatch(subdir) + if len(matches) == 0 { + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("invalid module cache path: %v", subdir), + } + } + modPath, err := module.UnescapePath(filepath.ToSlash(matches[1])) + if err != nil { + if logf != nil { + logf("decoding module cache path %q: %v", subdir, err) + } + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("decoding module cache path %q: %v", subdir, err), + } + } + importPath := path.Join(modPath, filepath.ToSlash(matches[3])) + index := strings.Index(dir, matches[1]+"@"+matches[2]) + modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2]) + modName := readModName(filepath.Join(modDir, "go.mod")) + return directoryPackageInfo{ + status: directoryScanned, + dir: dir, + rootType: root.Type, + nonCanonicalImportPath: importPath, + moduleDir: modDir, + moduleName: modName, + } + } + + add := func(root gopathwalk.Root, dir string) { + info := directoryInfo(root, dir) + cache.Store(info.dir, info) + } + + skip := func(_ gopathwalk.Root, dir string) bool { + // Skip directories that have already been scanned. + // + // Note that gopathwalk only adds "package" directories, which must contain + // a .go file, and all such package directories in the module cache are + // immutable. So if we can load a dir, it can be skipped. + info, ok := cache.Load(dir) + if !ok { + return false + } + packageScanned, _ := info.reachedStatus(directoryScanned) + return packageScanned + } + + gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: logf, ModulesEnabled: true}) +} diff --git a/contribs/gnopls/internal/imports/mod_cache_test.go b/contribs/gnopls/internal/imports/mod_cache_test.go new file mode 100644 index 00000000000..3af85fb7f56 --- /dev/null +++ b/contribs/gnopls/internal/imports/mod_cache_test.go @@ -0,0 +1,144 @@ +// 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. + +package imports + +import ( + "fmt" + "os/exec" + "reflect" + "sort" + "strings" + "testing" + "time" +) + +func TestDirectoryPackageInfoReachedStatus(t *testing.T) { + tests := []struct { + info directoryPackageInfo + target directoryPackageStatus + wantStatus bool + wantError bool + }{ + { + info: directoryPackageInfo{ + status: directoryScanned, + err: nil, + }, + target: directoryScanned, + wantStatus: true, + }, + { + info: directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("error getting to directory scanned"), + }, + target: directoryScanned, + wantStatus: true, + wantError: true, + }, + { + info: directoryPackageInfo{}, + target: directoryScanned, + wantStatus: false, + }, + } + + for _, tt := range tests { + gotStatus, gotErr := tt.info.reachedStatus(tt.target) + if gotErr != nil { + if !tt.wantError { + t.Errorf("unexpected error: %s", gotErr) + } + continue + } + + if tt.wantStatus != gotStatus { + t.Errorf("reached status expected: %v, got: %v", tt.wantStatus, gotStatus) + } + } +} + +func TestModCacheInfo(t *testing.T) { + m := NewDirInfoCache() + + dirInfo := []struct { + dir string + info directoryPackageInfo + }{ + { + dir: "mypackage", + info: directoryPackageInfo{ + status: directoryScanned, + dir: "mypackage", + nonCanonicalImportPath: "example.com/mypackage", + }, + }, + { + dir: "bad package", + info: directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("bad package"), + }, + }, + { + dir: "mypackage/other", + info: directoryPackageInfo{ + dir: "mypackage/other", + nonCanonicalImportPath: "example.com/mypackage/other", + }, + }, + } + + for _, d := range dirInfo { + m.Store(d.dir, d.info) + } + + for _, d := range dirInfo { + val, ok := m.Load(d.dir) + if !ok { + t.Errorf("directory not loaded: %s", d.dir) + } + + if !reflect.DeepEqual(d.info, val) { + t.Errorf("expected: %v, got: %v", d.info, val) + } + } + + var wantKeys []string + for _, d := range dirInfo { + wantKeys = append(wantKeys, d.dir) + } + sort.Strings(wantKeys) + + gotKeys := m.Keys() + sort.Strings(gotKeys) + + if len(gotKeys) != len(wantKeys) { + t.Errorf("different length of keys. expected: %d, got: %d", len(wantKeys), len(gotKeys)) + } + + for i, want := range wantKeys { + if want != gotKeys[i] { + t.Errorf("%d: expected %s, got %s", i, want, gotKeys[i]) + } + } +} + +func BenchmarkScanModuleCache(b *testing.B) { + output, err := exec.Command("go", "env", "GOMODCACHE").Output() + if err != nil { + b.Fatal(err) + } + gomodcache := strings.TrimSpace(string(output)) + cache := NewDirInfoCache() + start := time.Now() + ScanModuleCache(gomodcache, cache, nil) + b.Logf("initial scan took %v", time.Since(start)) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + ScanModuleCache(gomodcache, cache, nil) + } +} diff --git a/contribs/gnopls/internal/imports/mod_go122_test.go b/contribs/gnopls/internal/imports/mod_go122_test.go new file mode 100644 index 00000000000..af8feef713d --- /dev/null +++ b/contribs/gnopls/internal/imports/mod_go122_test.go @@ -0,0 +1,58 @@ +// 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. + +//go:build go1.22 +// +build go1.22 + +package imports + +import ( + "context" + "testing" +) + +// Tests that go.work files and vendor directory are respected. +func TestModWorkspaceVendoring(t *testing.T) { + mt := setup(t, nil, ` +-- go.work -- +go 1.22 + +use ( + ./a + ./b +) +-- a/go.mod -- +module example.com/a + +go 1.22 + +require rsc.io/sampler v1.3.1 +-- a/a.go -- +package a + +import _ "rsc.io/sampler" +-- b/go.mod -- +module example.com/b + +go 1.22 +-- b/b.go -- +package b +`, "") + defer mt.cleanup() + + // generate vendor directory + if _, err := mt.env.invokeGo(context.Background(), "work", "vendor"); err != nil { + t.Fatal(err) + } + + // update module resolver + mt.env.ClearModuleInfo() + mt.env.UpdateResolver(mt.env.resolver.ClearForNewScan()) + + mt.assertModuleFoundInDir("example.com/a", "a", `main/a$`) + mt.assertScanFinds("example.com/a", "a") + mt.assertModuleFoundInDir("example.com/b", "b", `main/b$`) + mt.assertScanFinds("example.com/b", "b") + mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `/vendor/`) +} diff --git a/contribs/gnopls/internal/imports/mod_test.go b/contribs/gnopls/internal/imports/mod_test.go new file mode 100644 index 00000000000..16dd982bc0d --- /dev/null +++ b/contribs/gnopls/internal/imports/mod_test.go @@ -0,0 +1,1325 @@ +// 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. + +package imports + +import ( + "archive/zip" + "context" + "fmt" + "log" + "os" + "path/filepath" + "reflect" + "regexp" + "sort" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/mod/module" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/gopathwalk" + "github.com/gnolang/gno/contribs/gnopls/internal/proxydir" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "golang.org/x/tools/txtar" +) + +// Tests that we can find packages in the stdlib. +func TestScanStdlib(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x +`, "") + defer mt.cleanup() + + mt.assertScanFinds("fmt", "fmt") +} + +// Tests that we handle a nested module. This is different from other tests +// where the module is in scope -- here we have to figure out the import path +// without any help from go list. +func TestScanOutOfScopeNestedModule(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x + +-- x.go -- +package x + +-- v2/go.mod -- +module x + +-- v2/x.go -- +package x`, "") + defer mt.cleanup() + + pkg := mt.assertScanFinds("x/v2", "x") + if pkg != nil && !strings.HasSuffix(filepath.ToSlash(pkg.dir), "main/v2") { + t.Errorf("x/v2 was found in %v, wanted .../main/v2", pkg.dir) + } + // We can't load the package name from the import path, but that should + // be okay -- if we end up adding this result, we'll add it with a name + // if necessary. +} + +// Tests that we don't find a nested module contained in a local replace target. +// The code for this case is too annoying to write, so it's just ignored. +func TestScanNestedModuleInLocalReplace(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x + +require y v0.0.0 +replace y => ./y + +-- x.go -- +package x + +-- y/go.mod -- +module y + +-- y/y.go -- +package y + +-- y/z/go.mod -- +module y/z + +-- y/z/z.go -- +package z +`, "") + defer mt.cleanup() + + mt.assertFound("y", "y") + + scan, err := scanToSlice(mt.env.resolver, nil) + if err != nil { + t.Fatal(err) + } + for _, pkg := range scan { + if strings.HasSuffix(filepath.ToSlash(pkg.dir), "main/y/z") { + t.Errorf("scan found a package %v in dir main/y/z, wanted none", pkg.importPathShort) + } + } +} + +// Tests that path encoding is handled correctly. Adapted from mod_case.txt. +func TestModCase(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x + +require rsc.io/QUOTE v1.5.2 + +-- x.go -- +package x + +import _ "rsc.io/QUOTE/QUOTE" +`, "") + defer mt.cleanup() + mt.assertFound("rsc.io/QUOTE/QUOTE", "QUOTE") +} + +// Not obviously relevant to goimports. Adapted from mod_domain_root.txt anyway. +func TestModDomainRoot(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x + +require example.com v1.0.0 + +-- x.go -- +package x +import _ "example.com" +`, "") + defer mt.cleanup() + mt.assertFound("example.com", "x") +} + +// Tests that scanning the module cache > 1 time is able to find the same module. +func TestModMultipleScans(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x + +require example.com v1.0.0 + +-- x.go -- +package x +import _ "example.com" +`, "") + defer mt.cleanup() + + mt.assertScanFinds("example.com", "x") + mt.assertScanFinds("example.com", "x") +} + +// Tests that scanning the module cache > 1 time is able to find the same module +// in the module cache. +func TestModMultipleScansWithSubdirs(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x + +require rsc.io/quote v1.5.2 + +-- x.go -- +package x +import _ "rsc.io/quote" +`, "") + defer mt.cleanup() + + mt.assertScanFinds("rsc.io/quote", "quote") + mt.assertScanFinds("rsc.io/quote", "quote") +} + +// Tests that scanning the module cache > 1 after changing a package in module cache to make it unimportable +// is able to find the same module. +func TestModCacheEditModFile(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x + +require rsc.io/quote v1.5.2 +-- x.go -- +package x +import _ "rsc.io/quote" +`, "") + defer mt.cleanup() + found := mt.assertScanFinds("rsc.io/quote", "quote") + if found == nil { + t.Fatal("rsc.io/quote not found in initial scan.") + } + + // Update the go.mod file of example.com so that it changes its module path (not allowed). + if err := os.Chmod(filepath.Join(found.dir, "go.mod"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(found.dir, "go.mod"), []byte("module bad.com\n"), 0644); err != nil { + t.Fatal(err) + } + + // Test that with its cache of module packages it still finds the package. + mt.assertScanFinds("rsc.io/quote", "quote") + + // Rewrite the main package so that rsc.io/quote is not in scope. + if err := os.WriteFile(filepath.Join(mt.env.WorkingDir, "go.mod"), []byte("module x\n"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(mt.env.WorkingDir, "x.go"), []byte("package x\n"), 0644); err != nil { + t.Fatal(err) + } + + // Uninitialize the go.mod dependent cached information and make sure it still finds the package. + mt.env.ClearModuleInfo() + mt.assertScanFinds("rsc.io/quote", "quote") +} + +// Tests that -mod=vendor works. Adapted from mod_vendor_build.txt. +func TestModVendorBuild(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module m +go 1.12 +require rsc.io/sampler v1.3.1 +-- x.go -- +package x +import _ "rsc.io/sampler" +`, "") + defer mt.cleanup() + + // Sanity-check the setup. + mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.*mod.*/sampler@.*$`) + + // Populate vendor/ and clear out the mod cache so we can't cheat. + if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil { + t.Fatal(err) + } + if _, err := mt.env.invokeGo(context.Background(), "clean", "-modcache"); err != nil { + t.Fatal(err) + } + + // Clear out the resolver's cache, since we've changed the environment. + mt.env.Env["GOFLAGS"] = "-mod=vendor" + mt.env.ClearModuleInfo() + mt.env.UpdateResolver(mt.env.resolver.ClearForNewScan()) + mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `/vendor/`) +} + +// Tests that -mod=vendor is auto-enabled only for go1.14 and higher. +// Vaguely inspired by mod_vendor_auto.txt. +func TestModVendorAuto(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module m +go 1.14 +require rsc.io/sampler v1.3.1 +-- x.go -- +package x +import _ "rsc.io/sampler" +`, "") + defer mt.cleanup() + + // Populate vendor/. + if _, err := mt.env.invokeGo(context.Background(), "mod", "vendor"); err != nil { + t.Fatal(err) + } + + wantDir := `pkg.*mod.*/sampler@.*$` + if testenv.Go1Point() >= 14 { + wantDir = `/vendor/` + } + + // Clear out the resolver's module info, since we've changed the environment. + // (the presence of a /vendor directory affects `go list -m`). + mt.env.ClearModuleInfo() + mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", wantDir) +} + +// Tests that a module replace works. Adapted from mod_list.txt. We start with +// go.mod2; the first part of the test is irrelevant. +func TestModList(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x +require rsc.io/quote v1.5.1 +replace rsc.io/sampler v1.3.0 => rsc.io/sampler v1.3.1 + +-- x.go -- +package x +import _ "rsc.io/quote" +`, "") + defer mt.cleanup() + + mt.assertModuleFoundInDir("rsc.io/sampler", "sampler", `pkg.mod.*/sampler@v1.3.1$`) +} + +// Tests that a local replace works. Adapted from mod_local_replace.txt. +func TestModLocalReplace(t *testing.T) { + mt := setup(t, nil, ` +-- x/y/go.mod -- +module x/y +require zz v1.0.0 +replace zz v1.0.0 => ../z + +-- x/y/y.go -- +package y +import _ "zz" + +-- x/z/go.mod -- +module x/z + +-- x/z/z.go -- +package z +`, "x/y") + defer mt.cleanup() + + mt.assertFound("zz", "z") +} + +// Tests that the package at the root of the main module can be found. +// Adapted from the first part of mod_multirepo.txt. +func TestModMultirepo1(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module rsc.io/quote + +-- x.go -- +package quote +`, "") + defer mt.cleanup() + + mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`) +} + +// Tests that a simple module dependency is found. Adapted from the third part +// of mod_multirepo.txt (We skip the case where it doesn't have a go.mod +// entry -- we just don't work in that case.) +func TestModMultirepo3(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module rsc.io/quote + +require rsc.io/quote/v2 v2.0.1 +-- x.go -- +package quote + +import _ "rsc.io/quote/v2" +`, "") + defer mt.cleanup() + + mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`) + mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`) +} + +// Tests that a nested module is found in the module cache, even though +// it's checked out. Adapted from the fourth part of mod_multirepo.txt. +func TestModMultirepo4(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module rsc.io/quote +require rsc.io/quote/v2 v2.0.1 + +-- x.go -- +package quote +import _ "rsc.io/quote/v2" + +-- v2/go.mod -- +package rsc.io/quote/v2 + +-- v2/x.go -- +package quote +import _ "rsc.io/quote/v2" +`, "") + defer mt.cleanup() + + mt.assertModuleFoundInDir("rsc.io/quote", "quote", `/main`) + mt.assertModuleFoundInDir("rsc.io/quote/v2", "quote", `pkg.mod.*/v2@v2.0.1$`) +} + +// Tests a simple module dependency. Adapted from the first part of mod_replace.txt. +func TestModReplace1(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module quoter + +require rsc.io/quote/v3 v3.0.0 + +-- main.go -- + +package main +`, "") + defer mt.cleanup() + mt.assertFound("rsc.io/quote/v3", "quote") +} + +// Tests a local replace. Adapted from the second part of mod_replace.txt. +func TestModReplace2(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module quoter + +require rsc.io/quote/v3 v3.0.0 +replace rsc.io/quote/v3 => ./local/rsc.io/quote/v3 +-- main.go -- +package main + +-- local/rsc.io/quote/v3/go.mod -- +module rsc.io/quote/v3 + +require rsc.io/sampler v1.3.0 + +-- local/rsc.io/quote/v3/quote.go -- +package quote + +import "rsc.io/sampler" +`, "") + defer mt.cleanup() + mt.assertModuleFoundInDir("rsc.io/quote/v3", "quote", `/local/rsc.io/quote/v3`) +} + +// Tests that a module can be replaced by a different module path. Adapted +// from the third part of mod_replace.txt. +func TestModReplace3(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module quoter + +require not-rsc.io/quote/v3 v3.1.0 +replace not-rsc.io/quote/v3 v3.1.0 => ./local/rsc.io/quote/v3 + +-- usenewmodule/main.go -- +package main + +-- local/rsc.io/quote/v3/go.mod -- +module rsc.io/quote/v3 + +require rsc.io/sampler v1.3.0 + +-- local/rsc.io/quote/v3/quote.go -- +package quote + +-- local/not-rsc.io/quote/v3/go.mod -- +module not-rsc.io/quote/v3 + +-- local/not-rsc.io/quote/v3/quote.go -- +package quote +`, "") + defer mt.cleanup() + mt.assertModuleFoundInDir("not-rsc.io/quote/v3", "quote", "local/rsc.io/quote/v3") +} + +// Tests more local replaces, notably the case where an outer module provides +// a package that could also be provided by an inner module. Adapted from +// mod_replace_import.txt, with example.com/v changed to /vv because Go 1.11 +// thinks /v is an invalid major version. +func TestModReplaceImport(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module example.com/m + +replace ( + example.com/a => ./a + example.com/a/b => ./b +) + +replace ( + example.com/x => ./x + example.com/x/v3 => ./v3 +) + +replace ( + example.com/y/z/w => ./w + example.com/y => ./y +) + +replace ( + example.com/vv v1.11.0 => ./v11 + example.com/vv v1.12.0 => ./v12 + example.com/vv => ./vv +) + +require ( + example.com/a/b v0.0.0 + example.com/x/v3 v3.0.0 + example.com/y v0.0.0 + example.com/y/z/w v0.0.0 + example.com/vv v1.12.0 +) +-- m.go -- +package main +import ( + _ "example.com/a/b" + _ "example.com/x/v3" + _ "example.com/y/z/w" + _ "example.com/vv" +) +func main() {} + +-- a/go.mod -- +module a.localhost +-- a/a.go -- +package a +-- a/b/b.go-- +package b + +-- b/go.mod -- +module a.localhost/b +-- b/b.go -- +package b + +-- x/go.mod -- +module x.localhost +-- x/x.go -- +package x +-- x/v3.go -- +package v3 +import _ "x.localhost/v3" + +-- v3/go.mod -- +module x.localhost/v3 +-- v3/x.go -- +package x + +-- w/go.mod -- +module w.localhost +-- w/skip/skip.go -- +// Package skip is nested below nonexistent package w. +package skip + +-- y/go.mod -- +module y.localhost +-- y/z/w/w.go -- +package w + +-- v12/go.mod -- +module v.localhost +-- v12/v.go -- +package v + +-- v11/go.mod -- +module v.localhost +-- v11/v.go -- +package v + +-- vv/go.mod -- +module v.localhost +-- vv/v.go -- +package v +`, "") + defer mt.cleanup() + + mt.assertModuleFoundInDir("example.com/a/b", "b", `main/b$`) + mt.assertModuleFoundInDir("example.com/x/v3", "x", `main/v3$`) + mt.assertModuleFoundInDir("example.com/y/z/w", "w", `main/y/z/w$`) + mt.assertModuleFoundInDir("example.com/vv", "v", `main/v12$`) +} + +// Tests that go.work files are respected. +func TestModWorkspace(t *testing.T) { + mt := setup(t, nil, ` +-- go.work -- +go 1.18 + +use ( + ./a + ./b +) +-- a/go.mod -- +module example.com/a + +go 1.18 +-- a/a.go -- +package a +-- b/go.mod -- +module example.com/b + +go 1.18 +-- b/b.go -- +package b +`, "") + defer mt.cleanup() + + mt.assertModuleFoundInDir("example.com/a", "a", `main/a$`) + mt.assertModuleFoundInDir("example.com/b", "b", `main/b$`) + mt.assertScanFinds("example.com/a", "a") + mt.assertScanFinds("example.com/b", "b") +} + +// Tests replaces in workspaces. Uses the directory layout in the cmd/go +// work_replace test. It tests both that replaces in go.work files are +// respected and that a wildcard replace in go.work overrides a versioned replace +// in go.mod. +func TestModWorkspaceReplace(t *testing.T) { + mt := setup(t, nil, ` +-- go.work -- +use m + +replace example.com/dep => ./dep +replace example.com/other => ./other2 + +-- m/go.mod -- +module example.com/m + +require example.com/dep v1.0.0 +require example.com/other v1.0.0 + +replace example.com/other v1.0.0 => ./other +-- m/m.go -- +package m + +import "example.com/dep" +import "example.com/other" + +func F() { + dep.G() + other.H() +} +-- dep/go.mod -- +module example.com/dep +-- dep/dep.go -- +package dep + +func G() { +} +-- other/go.mod -- +module example.com/other +-- other/dep.go -- +package other + +func G() { +} +-- other2/go.mod -- +module example.com/other +-- other2/dep.go -- +package other2 + +func G() { +} +`, "") + defer mt.cleanup() + + mt.assertScanFinds("example.com/m", "m") + mt.assertScanFinds("example.com/dep", "dep") + mt.assertModuleFoundInDir("example.com/other", "other2", "main/other2$") + mt.assertScanFinds("example.com/other", "other2") +} + +// Tests a case where conflicting replaces are overridden by a replace +// in the go.work file. +func TestModWorkspaceReplaceOverride(t *testing.T) { + mt := setup(t, nil, `-- go.work -- +use m +use n +replace example.com/dep => ./dep3 +-- m/go.mod -- +module example.com/m + +require example.com/dep v1.0.0 +replace example.com/dep => ./dep1 +-- m/m.go -- +package m + +import "example.com/dep" + +func F() { + dep.G() +} +-- n/go.mod -- +module example.com/n + +require example.com/dep v1.0.0 +replace example.com/dep => ./dep2 +-- n/n.go -- +package n + +import "example.com/dep" + +func F() { + dep.G() +} +-- dep1/go.mod -- +module example.com/dep +-- dep1/dep.go -- +package dep + +func G() { +} +-- dep2/go.mod -- +module example.com/dep +-- dep2/dep.go -- +package dep + +func G() { +} +-- dep3/go.mod -- +module example.com/dep +-- dep3/dep.go -- +package dep + +func G() { +} +`, "") + + mt.assertScanFinds("example.com/m", "m") + mt.assertScanFinds("example.com/n", "n") + mt.assertScanFinds("example.com/dep", "dep") + mt.assertModuleFoundInDir("example.com/dep", "dep", "main/dep3$") +} + +// Tests that the correct versions of modules are found in +// workspaces with module pruning. This is based on the +// cmd/go mod_prune_all script test. +func TestModWorkspacePrune(t *testing.T) { + mt := setup(t, nil, ` +-- go.work -- +go 1.18 + +use ( + ./a + ./p +) + +replace example.com/b v1.0.0 => ./b +replace example.com/q v1.0.0 => ./q1_0_0 +replace example.com/q v1.0.5 => ./q1_0_5 +replace example.com/q v1.1.0 => ./q1_1_0 +replace example.com/r v1.0.0 => ./r +replace example.com/w v1.0.0 => ./w +replace example.com/x v1.0.0 => ./x +replace example.com/y v1.0.0 => ./y +replace example.com/z v1.0.0 => ./z1_0_0 +replace example.com/z v1.1.0 => ./z1_1_0 + +-- a/go.mod -- +module example.com/a + +go 1.18 + +require example.com/b v1.0.0 +require example.com/z v1.0.0 +-- a/foo.go -- +package main + +import "example.com/b" + +func main() { + b.B() +} +-- b/go.mod -- +module example.com/b + +go 1.18 + +require example.com/q v1.1.0 +-- b/b.go -- +package b + +func B() { +} +-- p/go.mod -- +module example.com/p + +go 1.18 + +require example.com/q v1.0.0 + +replace example.com/q v1.0.0 => ../q1_0_0 +replace example.com/q v1.1.0 => ../q1_1_0 +-- p/main.go -- +package main + +import "example.com/q" + +func main() { + q.PrintVersion() +} +-- q1_0_0/go.mod -- +module example.com/q + +go 1.18 +-- q1_0_0/q.go -- +package q + +import "fmt" + +func PrintVersion() { + fmt.Println("version 1.0.0") +} +-- q1_0_5/go.mod -- +module example.com/q + +go 1.18 + +require example.com/r v1.0.0 +-- q1_0_5/q.go -- +package q + +import _ "example.com/r" +-- q1_1_0/go.mod -- +module example.com/q + +require example.com/w v1.0.0 +require example.com/z v1.1.0 + +go 1.18 +-- q1_1_0/q.go -- +package q + +import _ "example.com/w" +import _ "example.com/z" + +import "fmt" + +func PrintVersion() { + fmt.Println("version 1.1.0") +} +-- r/go.mod -- +module example.com/r + +go 1.18 + +require example.com/r v1.0.0 +-- r/r.go -- +package r +-- w/go.mod -- +module example.com/w + +go 1.18 + +require example.com/x v1.0.0 +-- w/w.go -- +package w +-- w/w_test.go -- +package w + +import _ "example.com/x" +-- x/go.mod -- +module example.com/x + +go 1.18 +-- x/x.go -- +package x +-- x/x_test.go -- +package x +import _ "example.com/y" +-- y/go.mod -- +module example.com/y + +go 1.18 +-- y/y.go -- +package y +-- z1_0_0/go.mod -- +module example.com/z + +go 1.18 + +require example.com/q v1.0.5 +-- z1_0_0/z.go -- +package z + +import _ "example.com/q" +-- z1_1_0/go.mod -- +module example.com/z + +go 1.18 +-- z1_1_0/z.go -- +package z +`, "") + + mt.assertScanFinds("example.com/w", "w") + mt.assertScanFinds("example.com/q", "q") + mt.assertScanFinds("example.com/x", "x") + mt.assertScanFinds("example.com/z", "z") + mt.assertModuleFoundInDir("example.com/w", "w", "main/w$") + mt.assertModuleFoundInDir("example.com/q", "q", "main/q1_1_0$") + mt.assertModuleFoundInDir("example.com/x", "x", "main/x$") + mt.assertModuleFoundInDir("example.com/z", "z", "main/z1_1_0$") +} + +// Tests that we handle GO111MODULE=on with no go.mod file. See #30855. +func TestNoMainModule(t *testing.T) { + mt := setup(t, map[string]string{"GO111MODULE": "on"}, ` +-- x.go -- +package x +`, "") + defer mt.cleanup() + if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote@v1.5.1"); err != nil { + t.Fatal(err) + } + + mt.assertScanFinds("rsc.io/quote", "quote") +} + +// assertFound asserts that the package at importPath is found to have pkgName, +// and that scanning for pkgName finds it at importPath. +func (t *modTest) assertFound(importPath, pkgName string) (string, *pkg) { + t.Helper() + + names, err := t.env.resolver.loadPackageNames([]string{importPath}, t.env.WorkingDir) + if err != nil { + t.Errorf("loading package name for %v: %v", importPath, err) + } + if names[importPath] != pkgName { + t.Errorf("package name for %v = %v, want %v", importPath, names[importPath], pkgName) + } + pkg := t.assertScanFinds(importPath, pkgName) + + _, foundDir := t.env.resolver.(*ModuleResolver).findPackage(importPath) + return foundDir, pkg +} + +func (t *modTest) assertScanFinds(importPath, pkgName string) *pkg { + t.Helper() + scan, err := scanToSlice(t.env.resolver, nil) + if err != nil { + t.Errorf("scan failed: %v", err) + } + for _, pkg := range scan { + if pkg.importPathShort == importPath { + return pkg + } + } + t.Errorf("scanning for %v did not find %v", pkgName, importPath) + return nil +} + +func scanToSlice(resolver Resolver, exclude []gopathwalk.RootType) ([]*pkg, error) { + var mu sync.Mutex + var result []*pkg + filter := &scanCallback{ + rootFound: func(root gopathwalk.Root) bool { + for _, rt := range exclude { + if root.Type == rt { + return false + } + } + return true + }, + dirFound: func(pkg *pkg) bool { + return true + }, + packageNameLoaded: func(pkg *pkg) bool { + mu.Lock() + defer mu.Unlock() + result = append(result, pkg) + return false + }, + } + err := resolver.scan(context.Background(), filter) + return result, err +} + +// assertModuleFoundInDir is the same as assertFound, but also checks that the +// package was found in an active module whose Dir matches dirRE. +func (t *modTest) assertModuleFoundInDir(importPath, pkgName, dirRE string) { + t.Helper() + dir, pkg := t.assertFound(importPath, pkgName) + re, err := regexp.Compile(dirRE) + if err != nil { + t.Fatal(err) + } + + if dir == "" { + t.Errorf("import path %v not found in active modules", importPath) + } else { + if !re.MatchString(filepath.ToSlash(dir)) { + t.Errorf("finding dir for %s: dir = %q did not match regex %q", importPath, dir, dirRE) + } + } + if pkg != nil { + if !re.MatchString(filepath.ToSlash(pkg.dir)) { + t.Errorf("scanning for %s: dir = %q did not match regex %q", pkgName, pkg.dir, dirRE) + } + } +} + +var proxyOnce sync.Once +var proxyDir string + +type modTest struct { + *testing.T + env *ProcessEnv + gopath string + cleanup func() +} + +// setup builds a test environment from a txtar and supporting modules +// in testdata/mod, along the lines of TestScript in cmd/go. +// +// extraEnv is applied on top of the default test env. +func setup(t *testing.T, extraEnv map[string]string, main, wd string) *modTest { + t.Helper() + testenv.NeedsTool(t, "go") + + proxyOnce.Do(func() { + var err error + proxyDir, err = os.MkdirTemp("", "proxy-") + if err != nil { + t.Fatal(err) + } + if err := writeProxy(proxyDir, "testdata/mod"); err != nil { + t.Fatal(err) + } + }) + + dir, err := os.MkdirTemp("", t.Name()) + if err != nil { + t.Fatal(err) + } + + mainDir := filepath.Join(dir, "main") + if err := writeModule(mainDir, main); err != nil { + t.Fatal(err) + } + + env := &ProcessEnv{ + Env: map[string]string{ + "GOPATH": filepath.Join(dir, "gopath"), + "GOMODCACHE": "", + "GO111MODULE": "auto", + "GOSUMDB": "off", + "GOPROXY": proxydir.ToURL(proxyDir), + }, + WorkingDir: filepath.Join(mainDir, wd), + GocmdRunner: &gocommand.Runner{}, + } + for k, v := range extraEnv { + env.Env[k] = v + } + if *testDebug { + env.Logf = log.Printf + } + // go mod download gets mad if we don't have a go.mod, so make sure we do. + _, err = os.Stat(filepath.Join(mainDir, "go.mod")) + if err != nil && !os.IsNotExist(err) { + t.Fatalf("checking if go.mod exists: %v", err) + } + if err == nil { + if _, err := env.invokeGo(context.Background(), "mod", "download", "all"); err != nil { + t.Fatal(err) + } + } + + // Ensure the resolver is set for tests that (unsafely) access env.resolver + // directly. + // + // TODO(rfindley): fix this after addressing the TODO in the ProcessEnv + // docstring. + if _, err := env.GetResolver(); err != nil { + t.Fatal(err) + } + + return &modTest{ + T: t, + gopath: env.Env["GOPATH"], + env: env, + cleanup: func() { removeDir(dir) }, + } +} + +// writeModule writes the module in the ar, a txtar, to dir. +func writeModule(dir, ar string) error { + a := txtar.Parse([]byte(ar)) + + for _, f := range a.Files { + fpath := filepath.Join(dir, f.Name) + if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil { + return err + } + + if err := os.WriteFile(fpath, f.Data, 0644); err != nil { + return err + } + } + return nil +} + +// writeProxy writes all the txtar-formatted modules in arDir to a proxy +// directory in dir. +func writeProxy(dir, arDir string) error { + files, err := os.ReadDir(arDir) + if err != nil { + return err + } + + for _, fi := range files { + if err := writeProxyModule(dir, filepath.Join(arDir, fi.Name())); err != nil { + return err + } + } + return nil +} + +// writeProxyModule writes a txtar-formatted module at arPath to the module +// proxy in base. +func writeProxyModule(base, arPath string) error { + arName := filepath.Base(arPath) + i := strings.LastIndex(arName, "_v") + ver := strings.TrimSuffix(arName[i+1:], ".txt") + modDir := strings.ReplaceAll(arName[:i], "_", "/") + modPath, err := module.UnescapePath(modDir) + if err != nil { + return err + } + + dir := filepath.Join(base, modDir, "@v") + a, err := txtar.ParseFile(arPath) + + if err != nil { + return err + } + + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + z := zip.NewWriter(f) + for _, f := range a.Files { + if f.Name[0] == '.' { + if err := os.WriteFile(filepath.Join(dir, ver+f.Name), f.Data, 0644); err != nil { + return err + } + } else { + zf, err := z.Create(modPath + "@" + ver + "/" + f.Name) + if err != nil { + return err + } + if _, err := zf.Write(f.Data); err != nil { + return err + } + } + } + if err := z.Close(); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + + list, err := os.OpenFile(filepath.Join(dir, "list"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return err + } + if _, err := fmt.Fprintf(list, "%s\n", ver); err != nil { + return err + } + if err := list.Close(); err != nil { + return err + } + return nil +} + +func removeDir(dir string) { + _ = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + if info.IsDir() { + _ = os.Chmod(path, 0777) + } + return nil + }) + _ = os.RemoveAll(dir) // ignore errors +} + +// Tests that findModFile can find the mod files from a path in the module cache. +func TestFindModFileModCache(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module x + +require rsc.io/quote v1.5.2 +-- x.go -- +package x +import _ "rsc.io/quote" +`, "") + defer mt.cleanup() + want := filepath.Join(mt.gopath, "pkg/mod", "rsc.io/quote@v1.5.2") + + found := mt.assertScanFinds("rsc.io/quote", "quote") + modDir, _ := mt.env.resolver.(*ModuleResolver).modInfo(found.dir) + if modDir != want { + t.Errorf("expected: %s, got: %s", want, modDir) + } +} + +// Tests that crud in the module cache is ignored. +func TestInvalidModCache(t *testing.T) { + testenv.NeedsTool(t, "go") + + dir, err := os.MkdirTemp("", t.Name()) + if err != nil { + t.Fatal(err) + } + defer removeDir(dir) + + // This doesn't have module@version like it should. + if err := os.MkdirAll(filepath.Join(dir, "gopath/pkg/mod/sabotage"), 0777); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "gopath/pkg/mod/sabotage/x.go"), []byte("package foo\n"), 0777); err != nil { + t.Fatal(err) + } + env := &ProcessEnv{ + Env: map[string]string{ + "GOPATH": filepath.Join(dir, "gopath"), + "GO111MODULE": "on", + "GOSUMDB": "off", + }, + GocmdRunner: &gocommand.Runner{}, + WorkingDir: dir, + } + resolver, err := env.GetResolver() + if err != nil { + t.Fatal(err) + } + scanToSlice(resolver, nil) +} + +func TestGetCandidatesRanking(t *testing.T) { + mt := setup(t, nil, ` +-- go.mod -- +module example.com + +require rsc.io/quote v1.5.1 +require rsc.io/quote/v3 v3.0.0 + +-- rpackage/x.go -- +package rpackage +import ( + _ "rsc.io/quote" + _ "rsc.io/quote/v3" +) +`, "") + defer mt.cleanup() + + if _, err := mt.env.invokeGo(context.Background(), "mod", "download", "rsc.io/quote/v2@v2.0.1"); err != nil { + t.Fatal(err) + } + + type res struct { + relevance float64 + name, path string + } + want := []res{ + // Stdlib + {7, "bytes", "bytes"}, + {7, "http", "net/http"}, + // Main module + {6, "rpackage", "example.com/rpackage"}, + // Direct module deps with v2+ major version + {5.003, "quote", "rsc.io/quote/v3"}, + // Direct module deps + {5, "quote", "rsc.io/quote"}, + // Indirect deps + {4, "language", "golang.org/x/text/language"}, + // Out of scope modules + {3, "quote", "rsc.io/quote/v2"}, + } + var mu sync.Mutex + var got []res + add := func(c ImportFix) { + mu.Lock() + defer mu.Unlock() + for _, w := range want { + if c.StmtInfo.ImportPath == w.path { + got = append(got, res{c.Relevance, c.IdentName, c.StmtInfo.ImportPath}) + } + } + } + if err := GetAllCandidates(context.Background(), add, "", "foo.go", "foo", mt.env); err != nil { + t.Fatalf("getAllCandidates() = %v", err) + } + sort.Slice(got, func(i, j int) bool { + ri, rj := got[i], got[j] + if ri.relevance != rj.relevance { + return ri.relevance > rj.relevance // Highest first. + } + return ri.name < rj.name + }) + if !reflect.DeepEqual(want, got) { + t.Errorf("wanted candidates in order %v, got %v", want, got) + } +} + +func BenchmarkModuleResolver_RescanModCache(b *testing.B) { + env := &ProcessEnv{ + GocmdRunner: &gocommand.Runner{}, + // Uncomment for verbose logging (too verbose to enable by default). + // Logf: b.Logf, + } + exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT} + resolver, err := env.GetResolver() + if err != nil { + b.Fatal(err) + } + start := time.Now() + scanToSlice(resolver, exclude) + b.Logf("warming the mod cache took %v", time.Since(start)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + scanToSlice(resolver, exclude) + resolver = resolver.ClearForNewScan() + } +} + +func BenchmarkModuleResolver_InitialScan(b *testing.B) { + for i := 0; i < b.N; i++ { + env := &ProcessEnv{ + GocmdRunner: &gocommand.Runner{}, + } + exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT} + resolver, err := env.GetResolver() + if err != nil { + b.Fatal(err) + } + scanToSlice(resolver, exclude) + } +} diff --git a/contribs/gnopls/internal/imports/sortimports.go b/contribs/gnopls/internal/imports/sortimports.go new file mode 100644 index 00000000000..da8194fd965 --- /dev/null +++ b/contribs/gnopls/internal/imports/sortimports.go @@ -0,0 +1,297 @@ +// Copyright 2013 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. + +// Hacked up copy of go/ast/import.go +// Modified to use a single token.File in preference to a FileSet. + +package imports + +import ( + "go/ast" + "go/token" + "log" + "sort" + "strconv" +) + +// sortImports sorts runs of consecutive import lines in import blocks in f. +// It also removes duplicate imports when it is possible to do so without data loss. +// +// It may mutate the token.File and the ast.File. +func sortImports(localPrefix string, tokFile *token.File, f *ast.File) { + for i, d := range f.Decls { + d, ok := d.(*ast.GenDecl) + if !ok || d.Tok != token.IMPORT { + // Not an import declaration, so we're done. + // Imports are always first. + break + } + + if len(d.Specs) == 0 { + // Empty import block, remove it. + f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) + } + + if !d.Lparen.IsValid() { + // Not a block: sorted by default. + continue + } + + // Identify and sort runs of specs on successive lines. + i := 0 + specs := d.Specs[:0] + for j, s := range d.Specs { + if j > i && tokFile.Line(s.Pos()) > 1+tokFile.Line(d.Specs[j-1].End()) { + // j begins a new run. End this one. + specs = append(specs, sortSpecs(localPrefix, tokFile, f, d.Specs[i:j])...) + i = j + } + } + specs = append(specs, sortSpecs(localPrefix, tokFile, f, d.Specs[i:])...) + d.Specs = specs + + // Deduping can leave a blank line before the rparen; clean that up. + // Ignore line directives. + if len(d.Specs) > 0 { + lastSpec := d.Specs[len(d.Specs)-1] + lastLine := tokFile.PositionFor(lastSpec.Pos(), false).Line + if rParenLine := tokFile.PositionFor(d.Rparen, false).Line; rParenLine > lastLine+1 { + tokFile.MergeLine(rParenLine - 1) // has side effects! + } + } + } +} + +// mergeImports merges all the import declarations into the first one. +// Taken from golang.org/x/tools/ast/astutil. +// This does not adjust line numbers properly +func mergeImports(f *ast.File) { + if len(f.Decls) <= 1 { + return + } + + // Merge all the import declarations into the first one. + var first *ast.GenDecl + for i := 0; i < len(f.Decls); i++ { + decl := f.Decls[i] + gen, ok := decl.(*ast.GenDecl) + if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { + continue + } + if first == nil { + first = gen + continue // Don't touch the first one. + } + // We now know there is more than one package in this import + // declaration. Ensure that it ends up parenthesized. + first.Lparen = first.Pos() + // Move the imports of the other import declaration to the first one. + for _, spec := range gen.Specs { + spec.(*ast.ImportSpec).Path.ValuePos = first.Pos() + first.Specs = append(first.Specs, spec) + } + f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) + i-- + } +} + +// declImports reports whether gen contains an import of path. +// Taken from golang.org/x/tools/ast/astutil. +func declImports(gen *ast.GenDecl, path string) bool { + if gen.Tok != token.IMPORT { + return false + } + for _, spec := range gen.Specs { + impspec := spec.(*ast.ImportSpec) + if importPath(impspec) == path { + return true + } + } + return false +} + +func importPath(s ast.Spec) string { + t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value) + if err == nil { + return t + } + return "" +} + +func importName(s ast.Spec) string { + n := s.(*ast.ImportSpec).Name + if n == nil { + return "" + } + return n.Name +} + +func importComment(s ast.Spec) string { + c := s.(*ast.ImportSpec).Comment + if c == nil { + return "" + } + return c.Text() +} + +// collapse indicates whether prev may be removed, leaving only next. +func collapse(prev, next ast.Spec) bool { + if importPath(next) != importPath(prev) || importName(next) != importName(prev) { + return false + } + return prev.(*ast.ImportSpec).Comment == nil +} + +type posSpan struct { + Start token.Pos + End token.Pos +} + +// sortSpecs sorts the import specs within each import decl. +// It may mutate the token.File. +func sortSpecs(localPrefix string, tokFile *token.File, f *ast.File, specs []ast.Spec) []ast.Spec { + // Can't short-circuit here even if specs are already sorted, + // since they might yet need deduplication. + // A lone import, however, may be safely ignored. + if len(specs) <= 1 { + return specs + } + + // Record positions for specs. + pos := make([]posSpan, len(specs)) + for i, s := range specs { + pos[i] = posSpan{s.Pos(), s.End()} + } + + // Identify comments in this range. + // Any comment from pos[0].Start to the final line counts. + lastLine := tokFile.Line(pos[len(pos)-1].End) + cstart := len(f.Comments) + cend := len(f.Comments) + for i, g := range f.Comments { + if g.Pos() < pos[0].Start { + continue + } + if i < cstart { + cstart = i + } + if tokFile.Line(g.End()) > lastLine { + cend = i + break + } + } + comments := f.Comments[cstart:cend] + + // Assign each comment to the import spec preceding it. + importComment := map[*ast.ImportSpec][]*ast.CommentGroup{} + specIndex := 0 + for _, g := range comments { + for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { + specIndex++ + } + s := specs[specIndex].(*ast.ImportSpec) + importComment[s] = append(importComment[s], g) + } + + // Sort the import specs by import path. + // Remove duplicates, when possible without data loss. + // Reassign the import paths to have the same position sequence. + // Reassign each comment to abut the end of its spec. + // Sort the comments by new position. + sort.Sort(byImportSpec{localPrefix, specs}) + + // Dedup. Thanks to our sorting, we can just consider + // adjacent pairs of imports. + deduped := specs[:0] + for i, s := range specs { + if i == len(specs)-1 || !collapse(s, specs[i+1]) { + deduped = append(deduped, s) + } else { + p := s.Pos() + tokFile.MergeLine(tokFile.Line(p)) // has side effects! + } + } + specs = deduped + + // Fix up comment positions + for i, s := range specs { + s := s.(*ast.ImportSpec) + if s.Name != nil { + s.Name.NamePos = pos[i].Start + } + s.Path.ValuePos = pos[i].Start + s.EndPos = pos[i].End + nextSpecPos := pos[i].End + + for _, g := range importComment[s] { + for _, c := range g.List { + c.Slash = pos[i].End + nextSpecPos = c.End() + } + } + if i < len(specs)-1 { + pos[i+1].Start = nextSpecPos + pos[i+1].End = nextSpecPos + } + } + + sort.Sort(byCommentPos(comments)) + + // Fixup comments can insert blank lines, because import specs are on different lines. + // We remove those blank lines here by merging import spec to the first import spec line. + firstSpecLine := tokFile.Line(specs[0].Pos()) + for _, s := range specs[1:] { + p := s.Pos() + line := tokFile.Line(p) + for previousLine := line - 1; previousLine >= firstSpecLine; { + // MergeLine can panic. Avoid the panic at the cost of not removing the blank line + // golang/go#50329 + if previousLine > 0 && previousLine < tokFile.LineCount() { + tokFile.MergeLine(previousLine) // has side effects! + previousLine-- + } else { + // try to gather some data to diagnose how this could happen + req := "Please report what the imports section of your go file looked like." + log.Printf("panic avoided: first:%d line:%d previous:%d max:%d. %s", + firstSpecLine, line, previousLine, tokFile.LineCount(), req) + } + } + } + return specs +} + +type byImportSpec struct { + localPrefix string + specs []ast.Spec // slice of *ast.ImportSpec +} + +func (x byImportSpec) Len() int { return len(x.specs) } +func (x byImportSpec) Swap(i, j int) { x.specs[i], x.specs[j] = x.specs[j], x.specs[i] } +func (x byImportSpec) Less(i, j int) bool { + ipath := importPath(x.specs[i]) + jpath := importPath(x.specs[j]) + + igroup := importGroup(x.localPrefix, ipath) + jgroup := importGroup(x.localPrefix, jpath) + if igroup != jgroup { + return igroup < jgroup + } + + if ipath != jpath { + return ipath < jpath + } + iname := importName(x.specs[i]) + jname := importName(x.specs[j]) + + if iname != jname { + return iname < jname + } + return importComment(x.specs[i]) < importComment(x.specs[j]) +} + +type byCommentPos []*ast.CommentGroup + +func (x byCommentPos) Len() int { return len(x) } +func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() } diff --git a/contribs/gnopls/internal/imports/testdata/mod/example.com_v1.0.0.txt b/contribs/gnopls/internal/imports/testdata/mod/example.com_v1.0.0.txt new file mode 100644 index 00000000000..263287d9e2c --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/example.com_v1.0.0.txt @@ -0,0 +1,9 @@ +Written by hand. +Test case for module at root of domain. + +-- .mod -- +module example.com +-- .info -- +{"Version": "v1.0.0"} +-- x.go -- +package x diff --git a/contribs/gnopls/internal/imports/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt b/contribs/gnopls/internal/imports/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt new file mode 100644 index 00000000000..f4f50cdedb6 --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/golang.org_x_text_v0.0.0-20170915032832-14c0d48ead0c.txt @@ -0,0 +1,47 @@ +written by hand - just enough to compile rsc.io/sampler, rsc.io/quote + +-- .mod -- +module golang.org/x/text +-- .info -- +{"Version":"v0.0.0-20170915032832-14c0d48ead0c","Name":"v0.0.0-20170915032832-14c0d48ead0c","Short":"14c0d48ead0c","Time":"2017-09-15T03:28:32Z"} +-- go.mod -- +module golang.org/x/text +-- unused/unused.go -- +package unused +-- language/lang.go -- +// Copyright 2018 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 is a tiny version of golang.org/x/text. + +package language + +import "strings" + +type Tag string + +func Make(s string) Tag { return Tag(s) } + +func (t Tag) String() string { return string(t) } + +func NewMatcher(tags []Tag) Matcher { return &matcher{tags} } + +type Matcher interface { + Match(...Tag) (Tag, int, int) +} + +type matcher struct { + tags []Tag +} + +func (m *matcher) Match(prefs ...Tag) (Tag, int, int) { + for _, pref := range prefs { + for _, tag := range m.tags { + if tag == pref || strings.HasPrefix(string(pref), string(tag+"-")) || strings.HasPrefix(string(tag), string(pref+"-")) { + return tag, 0, 0 + } + } + } + return m.tags[0], 0, 0 +} diff --git a/contribs/gnopls/internal/imports/testdata/mod/rsc.io_!q!u!o!t!e_v1.5.2.txt b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_!q!u!o!t!e_v1.5.2.txt new file mode 100644 index 00000000000..21185c39f33 --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_!q!u!o!t!e_v1.5.2.txt @@ -0,0 +1,88 @@ +rsc.io/QUOTE v1.5.2 + +-- .mod -- +module rsc.io/QUOTE + +require rsc.io/quote v1.5.2 +-- .info -- +{"Version":"v1.5.2","Name":"","Short":"","Time":"2018-07-15T16:25:34Z"} +-- go.mod -- +module rsc.io/QUOTE + +require rsc.io/quote v1.5.2 +-- QUOTE/quote.go -- +// Copyright 2018 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 QUOTE COLLECTS LOUD SAYINGS. +package QUOTE + +import ( + "strings" + + "rsc.io/quote" +) + +// HELLO RETURNS A GREETING. +func HELLO() string { + return strings.ToUpper(quote.Hello()) +} + +// GLASS RETURNS A USEFUL PHRASE FOR WORLD TRAVELERS. +func GLASS() string { + return strings.ToUpper(quote.GLASS()) +} + +// GO RETURNS A GO PROVERB. +func GO() string { + return strings.ToUpper(quote.GO()) +} + +// OPT RETURNS AN OPTIMIZATION TRUTH. +func OPT() string { + return strings.ToUpper(quote.OPT()) +} +-- QUOTE/quote_test.go -- +// Copyright 2018 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 QUOTE + +import ( + "os" + "testing" +) + +func init() { + os.Setenv("LC_ALL", "en") +} + +func TestHELLO(t *testing.T) { + hello := "HELLO, WORLD" + if out := HELLO(); out != hello { + t.Errorf("HELLO() = %q, want %q", out, hello) + } +} + +func TestGLASS(t *testing.T) { + glass := "I CAN EAT GLASS AND IT DOESN'T HURT ME." + if out := GLASS(); out != glass { + t.Errorf("GLASS() = %q, want %q", out, glass) + } +} + +func TestGO(t *testing.T) { + go1 := "DON'T COMMUNICATE BY SHARING MEMORY, SHARE MEMORY BY COMMUNICATING." + if out := GO(); out != go1 { + t.Errorf("GO() = %q, want %q", out, go1) + } +} + +func TestOPT(t *testing.T) { + opt := "IF A PROGRAM IS TOO SLOW, IT MUST HAVE A LOOP." + if out := OPT(); out != opt { + t.Errorf("OPT() = %q, want %q", out, opt) + } +} diff --git a/contribs/gnopls/internal/imports/testdata/mod/rsc.io_!q!u!o!t!e_v1.5.3-!p!r!e.txt b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_!q!u!o!t!e_v1.5.3-!p!r!e.txt new file mode 100644 index 00000000000..54bac2df7bb --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_!q!u!o!t!e_v1.5.3-!p!r!e.txt @@ -0,0 +1,88 @@ +rsc.io/QUOTE v1.5.3-PRE (sigh) + +-- .mod -- +module rsc.io/QUOTE + +require rsc.io/quote v1.5.2 +-- .info -- +{"Version":"v1.5.3-PRE","Name":"","Short":"","Time":"2018-07-15T16:25:34Z"} +-- go.mod -- +module rsc.io/QUOTE + +require rsc.io/quote v1.5.2 +-- QUOTE/quote.go -- +// Copyright 2018 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 QUOTE COLLECTS LOUD SAYINGS. +package QUOTE + +import ( + "strings" + + "rsc.io/quote" +) + +// HELLO RETURNS A GREETING. +func HELLO() string { + return strings.ToUpper(quote.Hello()) +} + +// GLASS RETURNS A USEFUL PHRASE FOR WORLD TRAVELERS. +func GLASS() string { + return strings.ToUpper(quote.GLASS()) +} + +// GO RETURNS A GO PROVERB. +func GO() string { + return strings.ToUpper(quote.GO()) +} + +// OPT RETURNS AN OPTIMIZATION TRUTH. +func OPT() string { + return strings.ToUpper(quote.OPT()) +} +-- QUOTE/quote_test.go -- +// Copyright 2018 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 QUOTE + +import ( + "os" + "testing" +) + +func init() { + os.Setenv("LC_ALL", "en") +} + +func TestHELLO(t *testing.T) { + hello := "HELLO, WORLD" + if out := HELLO(); out != hello { + t.Errorf("HELLO() = %q, want %q", out, hello) + } +} + +func TestGLASS(t *testing.T) { + glass := "I CAN EAT GLASS AND IT DOESN'T HURT ME." + if out := GLASS(); out != glass { + t.Errorf("GLASS() = %q, want %q", out, glass) + } +} + +func TestGO(t *testing.T) { + go1 := "DON'T COMMUNICATE BY SHARING MEMORY, SHARE MEMORY BY COMMUNICATING." + if out := GO(); out != go1 { + t.Errorf("GO() = %q, want %q", out, go1) + } +} + +func TestOPT(t *testing.T) { + opt := "IF A PROGRAM IS TOO SLOW, IT MUST HAVE A LOOP." + if out := OPT(); out != opt { + t.Errorf("OPT() = %q, want %q", out, opt) + } +} diff --git a/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v1.5.1.txt b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v1.5.1.txt new file mode 100644 index 00000000000..eed051bea04 --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v1.5.1.txt @@ -0,0 +1,86 @@ +rsc.io/quote@23179ee8a569 + +-- .mod -- +module "rsc.io/quote" + +require "rsc.io/sampler" v1.3.0 +-- .info -- +{"Version":"v1.5.1","Name":"23179ee8a569bb05d896ae05c6503ec69a19f99f","Short":"23179ee8a569","Time":"2018-02-14T00:58:40Z"} +-- go.mod -- +module "rsc.io/quote" + +require "rsc.io/sampler" v1.3.0 +-- quote.go -- +// Copyright 2018 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 quote collects pithy sayings. +package quote // import "rsc.io/quote" + +import "rsc.io/sampler" + +// Hello returns a greeting. +func Hello() string { + return sampler.Hello() +} + +// Glass returns a useful phrase for world travelers. +func Glass() string { + // See http://www.oocities.org/nodotus/hbglass.html. + return "I can eat glass and it doesn't hurt me." +} + +// Go returns a Go proverb. +func Go() string { + return "Don't communicate by sharing memory, share memory by communicating." +} + +// Opt returns an optimization truth. +func Opt() string { + // Wisdom from ken. + return "If a program is too slow, it must have a loop." +} +-- quote_test.go -- +// Copyright 2018 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 quote + +import ( + "os" + "testing" +) + +func init() { + os.Setenv("LC_ALL", "en") +} + +func TestHello(t *testing.T) { + hello := "Hello, world." + if out := Hello(); out != hello { + t.Errorf("Hello() = %q, want %q", out, hello) + } +} + +func TestGlass(t *testing.T) { + glass := "I can eat glass and it doesn't hurt me." + if out := Glass(); out != glass { + t.Errorf("Glass() = %q, want %q", out, glass) + } +} + +func TestGo(t *testing.T) { + go1 := "Don't communicate by sharing memory, share memory by communicating." + if out := Go(); out != go1 { + t.Errorf("Go() = %q, want %q", out, go1) + } +} + +func TestOpt(t *testing.T) { + opt := "If a program is too slow, it must have a loop." + if out := Opt(); out != opt { + t.Errorf("Opt() = %q, want %q", out, opt) + } +} diff --git a/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v1.5.2.txt b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v1.5.2.txt new file mode 100644 index 00000000000..8671f6fe772 --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v1.5.2.txt @@ -0,0 +1,98 @@ +rsc.io/quote@v1.5.2 + +-- .mod -- +module "rsc.io/quote" + +require "rsc.io/sampler" v1.3.0 +-- .info -- +{"Version":"v1.5.2","Name":"c4d4236f92427c64bfbcf1cc3f8142ab18f30b22","Short":"c4d4236f9242","Time":"2018-02-14T15:44:20Z"} +-- buggy/buggy_test.go -- +// Copyright 2018 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 buggy + +import "testing" + +func Test(t *testing.T) { + t.Fatal("buggy!") +} +-- go.mod -- +module "rsc.io/quote" + +require "rsc.io/sampler" v1.3.0 +-- quote.go -- +// Copyright 2018 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 quote collects pithy sayings. +package quote // import "rsc.io/quote" + +import "rsc.io/sampler" + +// Hello returns a greeting. +func Hello() string { + return sampler.Hello() +} + +// Glass returns a useful phrase for world travelers. +func Glass() string { + // See http://www.oocities.org/nodotus/hbglass.html. + return "I can eat glass and it doesn't hurt me." +} + +// Go returns a Go proverb. +func Go() string { + return "Don't communicate by sharing memory, share memory by communicating." +} + +// Opt returns an optimization truth. +func Opt() string { + // Wisdom from ken. + return "If a program is too slow, it must have a loop." +} +-- quote_test.go -- +// Copyright 2018 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 quote + +import ( + "os" + "testing" +) + +func init() { + os.Setenv("LC_ALL", "en") +} + +func TestHello(t *testing.T) { + hello := "Hello, world." + if out := Hello(); out != hello { + t.Errorf("Hello() = %q, want %q", out, hello) + } +} + +func TestGlass(t *testing.T) { + glass := "I can eat glass and it doesn't hurt me." + if out := Glass(); out != glass { + t.Errorf("Glass() = %q, want %q", out, glass) + } +} + +func TestGo(t *testing.T) { + go1 := "Don't communicate by sharing memory, share memory by communicating." + if out := Go(); out != go1 { + t.Errorf("Go() = %q, want %q", out, go1) + } +} + +func TestOpt(t *testing.T) { + opt := "If a program is too slow, it must have a loop." + if out := Opt(); out != opt { + t.Errorf("Opt() = %q, want %q", out, opt) + } +} diff --git a/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v2_v2.0.1.txt b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v2_v2.0.1.txt new file mode 100644 index 00000000000..d51128c46b9 --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v2_v2.0.1.txt @@ -0,0 +1,86 @@ +rsc.io/quote/v2@v2.0.1 + +-- .mod -- +module rsc.io/quote/v2 + +require rsc.io/sampler v1.3.0 +-- .info -- +{"Version":"v2.0.1","Name":"754f68430672776c84704e2d10209a6ec700cd64","Short":"754f68430672","Time":"2018-07-09T16:25:34Z"} +-- go.mod -- +module rsc.io/quote/v2 + +require rsc.io/sampler v1.3.0 +-- quote.go -- +// Copyright 2018 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 quote collects pithy sayings. +package quote // import "rsc.io/quote" + +import "rsc.io/sampler" + +// Hello returns a greeting. +func HelloV2() string { + return sampler.Hello() +} + +// Glass returns a useful phrase for world travelers. +func GlassV2() string { + // See http://www.oocities.org/nodotus/hbglass.html. + return "I can eat glass and it doesn't hurt me." +} + +// Go returns a Go proverb. +func GoV2() string { + return "Don't communicate by sharing memory, share memory by communicating." +} + +// Opt returns an optimization truth. +func OptV2() string { + // Wisdom from ken. + return "If a program is too slow, it must have a loop." +} +-- quote_test.go -- +// Copyright 2018 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 quote + +import ( + "os" + "testing" +) + +func init() { + os.Setenv("LC_ALL", "en") +} + +func TestHello(t *testing.T) { + hello := "Hello, world." + if out := Hello(); out != hello { + t.Errorf("Hello() = %q, want %q", out, hello) + } +} + +func TestGlass(t *testing.T) { + glass := "I can eat glass and it doesn't hurt me." + if out := Glass(); out != glass { + t.Errorf("Glass() = %q, want %q", out, glass) + } +} + +func TestGo(t *testing.T) { + go1 := "Don't communicate by sharing memory, share memory by communicating." + if out := Go(); out != go1 { + t.Errorf("Go() = %q, want %q", out, go1) + } +} + +func TestOpt(t *testing.T) { + opt := "If a program is too slow, it must have a loop." + if out := Opt(); out != opt { + t.Errorf("Opt() = %q, want %q", out, opt) + } +} diff --git a/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v3_v3.0.0.txt b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v3_v3.0.0.txt new file mode 100644 index 00000000000..0afe1f05199 --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_quote_v3_v3.0.0.txt @@ -0,0 +1,45 @@ +rsc.io/quote/v3@v3.0.0 + +-- .mod -- +module rsc.io/quote/v3 + +require rsc.io/sampler v1.3.0 + +-- .info -- +{"Version":"v3.0.0","Name":"d88915d7e77ed0fd35d0a022a2f244e2202fd8c8","Short":"d88915d7e77e","Time":"2018-07-09T15:34:46Z"} +-- go.mod -- +module rsc.io/quote/v3 + +require rsc.io/sampler v1.3.0 + +-- quote.go -- +// Copyright 2018 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 quote collects pithy sayings. +package quote // import "rsc.io/quote" + +import "rsc.io/sampler" + +// Hello returns a greeting. +func HelloV3() string { + return sampler.Hello() +} + +// Glass returns a useful phrase for world travelers. +func GlassV3() string { + // See http://www.oocities.org/nodotus/hbglass.html. + return "I can eat glass and it doesn't hurt me." +} + +// Go returns a Go proverb. +func GoV3() string { + return "Don't communicate by sharing memory, share memory by communicating." +} + +// Opt returns an optimization truth. +func OptV3() string { + // Wisdom from ken. + return "If a program is too slow, it must have a loop." +} diff --git a/contribs/gnopls/internal/imports/testdata/mod/rsc.io_sampler_v1.3.0.txt b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_sampler_v1.3.0.txt new file mode 100644 index 00000000000..febe51fd9a9 --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_sampler_v1.3.0.txt @@ -0,0 +1,202 @@ +rsc.io/sampler@v1.3.0 + +-- .mod -- +module "rsc.io/sampler" + +require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c +-- .info -- +{"Version":"v1.3.0","Name":"0cc034b51e57ed7832d4c67d526f75a900996e5c","Short":"0cc034b51e57","Time":"2018-02-13T19:05:03Z"} +-- glass.go -- +// Copyright 2018 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. + +// Translations from Frank da Cruz, Ethan Mollick, and many others. +// See http://kermitproject.org/utf8.html. +// http://www.oocities.org/nodotus/hbglass.html +// https://en.wikipedia.org/wiki/I_Can_Eat_Glass + +package sampler + +var glass = newText(` + +English: en: I can eat glass and it doesn't hurt me. +French: fr: Je peux manger du verre, ça ne me fait pas mal. +Spanish: es: Puedo comer vidrio, no me hace daño. + +`) +-- glass_test.go -- +// Copyright 2018 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 sampler + +import ( + "testing" + + "golang.org/x/text/language" + _ "rsc.io/testonly" +) + +var glassTests = []struct { + prefs []language.Tag + text string +}{ + { + []language.Tag{language.Make("en-US"), language.Make("fr")}, + "I can eat glass and it doesn't hurt me.", + }, + { + []language.Tag{language.Make("fr"), language.Make("en-US")}, + "Je peux manger du verre, ça ne me fait pas mal.", + }, +} + +func TestGlass(t *testing.T) { + for _, tt := range glassTests { + text := Glass(tt.prefs...) + if text != tt.text { + t.Errorf("Glass(%v) = %q, want %q", tt.prefs, text, tt.text) + } + } +} +-- go.mod -- +module "rsc.io/sampler" + +require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c +-- hello.go -- +// Copyright 2018 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. + +// Translations by Google Translate. + +package sampler + +var hello = newText(` + +English: en: Hello, world. +French: fr: Bonjour le monde. +Spanish: es: Hola Mundo. + +`) +-- hello_test.go -- +// Copyright 2018 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 sampler + +import ( + "testing" + + "golang.org/x/text/language" +) + +var helloTests = []struct { + prefs []language.Tag + text string +}{ + { + []language.Tag{language.Make("en-US"), language.Make("fr")}, + "Hello, world.", + }, + { + []language.Tag{language.Make("fr"), language.Make("en-US")}, + "Bonjour le monde.", + }, +} + +func TestHello(t *testing.T) { + for _, tt := range helloTests { + text := Hello(tt.prefs...) + if text != tt.text { + t.Errorf("Hello(%v) = %q, want %q", tt.prefs, text, tt.text) + } + } +} +-- sampler.go -- +// Copyright 2018 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 sampler shows simple texts. +package sampler // import "rsc.io/sampler" + +import ( + "os" + "strings" + + "golang.org/x/text/language" +) + +// DefaultUserPrefs returns the default user language preferences. +// It consults the $LC_ALL, $LC_MESSAGES, and $LANG environment +// variables, in that order. +func DefaultUserPrefs() []language.Tag { + var prefs []language.Tag + for _, k := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} { + if env := os.Getenv(k); env != "" { + prefs = append(prefs, language.Make(env)) + } + } + return prefs +} + +// Hello returns a localized greeting. +// If no prefs are given, Hello uses DefaultUserPrefs. +func Hello(prefs ...language.Tag) string { + if len(prefs) == 0 { + prefs = DefaultUserPrefs() + } + return hello.find(prefs) +} + +// Glass returns a localized silly phrase. +// If no prefs are given, Glass uses DefaultUserPrefs. +func Glass(prefs ...language.Tag) string { + if len(prefs) == 0 { + prefs = DefaultUserPrefs() + } + return glass.find(prefs) +} + +// A text is a localized text. +type text struct { + byTag map[string]string + matcher language.Matcher +} + +// newText creates a new localized text, given a list of translations. +func newText(s string) *text { + t := &text{ + byTag: make(map[string]string), + } + var tags []language.Tag + for _, line := range strings.Split(s, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + f := strings.Split(line, ": ") + if len(f) != 3 { + continue + } + tag := language.Make(f[1]) + tags = append(tags, tag) + t.byTag[tag.String()] = f[2] + } + t.matcher = language.NewMatcher(tags) + return t +} + +// find finds the text to use for the given language tag preferences. +func (t *text) find(prefs []language.Tag) string { + tag, _, _ := t.matcher.Match(prefs...) + s := t.byTag[tag.String()] + if strings.HasPrefix(s, "RTL ") { + s = "\u200F" + strings.TrimPrefix(s, "RTL ") + "\u200E" + } + return s +} diff --git a/contribs/gnopls/internal/imports/testdata/mod/rsc.io_sampler_v1.3.1.txt b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_sampler_v1.3.1.txt new file mode 100644 index 00000000000..a293f108696 --- /dev/null +++ b/contribs/gnopls/internal/imports/testdata/mod/rsc.io_sampler_v1.3.1.txt @@ -0,0 +1,201 @@ +rsc.io/sampler@v1.3.1 + +-- .mod -- +module "rsc.io/sampler" + +require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c +-- .info -- +{"Version":"v1.3.1","Name":"f545d0289d06e2add4556ea6a15fc4938014bf87","Short":"f545d0289d06","Time":"2018-02-14T16:34:12Z"} +-- glass.go -- +// Copyright 2018 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. + +// Translations from Frank da Cruz, Ethan Mollick, and many others. +// See http://kermitproject.org/utf8.html. +// http://www.oocities.org/nodotus/hbglass.html +// https://en.wikipedia.org/wiki/I_Can_Eat_Glass + +package sampler + +var glass = newText(` + +English: en: I can eat glass and it doesn't hurt me. +French: fr: Je peux manger du verre, ça ne me fait pas mal. +Spanish: es: Puedo comer vidrio, no me hace daño. + +`) +-- glass_test.go -- +// Copyright 2018 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 sampler + +import ( + "testing" + + "golang.org/x/text/language" +) + +var glassTests = []struct { + prefs []language.Tag + text string +}{ + { + []language.Tag{language.Make("en-US"), language.Make("fr")}, + "I can eat glass and it doesn't hurt me.", + }, + { + []language.Tag{language.Make("fr"), language.Make("en-US")}, + "Je peux manger du verre, ça ne me fait pas mal.", + }, +} + +func TestGlass(t *testing.T) { + for _, tt := range glassTests { + text := Glass(tt.prefs...) + if text != tt.text { + t.Errorf("Glass(%v) = %q, want %q", tt.prefs, text, tt.text) + } + } +} +-- go.mod -- +module "rsc.io/sampler" + +require "golang.org/x/text" v0.0.0-20170915032832-14c0d48ead0c +-- hello.go -- +// Copyright 2018 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. + +// Translations by Google Translate. + +package sampler + +var hello = newText(` + +English: en: Hello, world. +French: fr: Bonjour le monde. +Spanish: es: Hola Mundo. + +`) +-- hello_test.go -- +// Copyright 2018 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 sampler + +import ( + "testing" + + "golang.org/x/text/language" +) + +var helloTests = []struct { + prefs []language.Tag + text string +}{ + { + []language.Tag{language.Make("en-US"), language.Make("fr")}, + "Hello, world.", + }, + { + []language.Tag{language.Make("fr"), language.Make("en-US")}, + "Bonjour le monde.", + }, +} + +func TestHello(t *testing.T) { + for _, tt := range helloTests { + text := Hello(tt.prefs...) + if text != tt.text { + t.Errorf("Hello(%v) = %q, want %q", tt.prefs, text, tt.text) + } + } +} +-- sampler.go -- +// Copyright 2018 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 sampler shows simple texts in a variety of languages. +package sampler // import "rsc.io/sampler" + +import ( + "os" + "strings" + + "golang.org/x/text/language" +) + +// DefaultUserPrefs returns the default user language preferences. +// It consults the $LC_ALL, $LC_MESSAGES, and $LANG environment +// variables, in that order. +func DefaultUserPrefs() []language.Tag { + var prefs []language.Tag + for _, k := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} { + if env := os.Getenv(k); env != "" { + prefs = append(prefs, language.Make(env)) + } + } + return prefs +} + +// Hello returns a localized greeting. +// If no prefs are given, Hello uses DefaultUserPrefs. +func Hello(prefs ...language.Tag) string { + if len(prefs) == 0 { + prefs = DefaultUserPrefs() + } + return hello.find(prefs) +} + +// Glass returns a localized silly phrase. +// If no prefs are given, Glass uses DefaultUserPrefs. +func Glass(prefs ...language.Tag) string { + if len(prefs) == 0 { + prefs = DefaultUserPrefs() + } + return glass.find(prefs) +} + +// A text is a localized text. +type text struct { + byTag map[string]string + matcher language.Matcher +} + +// newText creates a new localized text, given a list of translations. +func newText(s string) *text { + t := &text{ + byTag: make(map[string]string), + } + var tags []language.Tag + for _, line := range strings.Split(s, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + f := strings.Split(line, ": ") + if len(f) != 3 { + continue + } + tag := language.Make(f[1]) + tags = append(tags, tag) + t.byTag[tag.String()] = f[2] + } + t.matcher = language.NewMatcher(tags) + return t +} + +// find finds the text to use for the given language tag preferences. +func (t *text) find(prefs []language.Tag) string { + tag, _, _ := t.matcher.Match(prefs...) + s := t.byTag[tag.String()] + if strings.HasPrefix(s, "RTL ") { + s = "\u200F" + strings.TrimPrefix(s, "RTL ") + "\u200E" + } + return s +} diff --git a/contribs/gnopls/internal/jsonrpc2/conn.go b/contribs/gnopls/internal/jsonrpc2/conn.go new file mode 100644 index 00000000000..60b1b286424 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/conn.go @@ -0,0 +1,261 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "context" + "encoding/json" + "fmt" + "sync" + "sync/atomic" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" +) + +// Conn is the common interface to jsonrpc clients and servers. +// Conn is bidirectional; it does not have a designated server or client end. +// It manages the jsonrpc2 protocol, connecting responses back to their calls. +type Conn interface { + // Call invokes the target method and waits for a response. + // The params will be marshaled to JSON before sending over the wire, and will + // be handed to the method invoked. + // The response will be unmarshaled from JSON into the result. + // The id returned will be unique from this connection, and can be used for + // logging or tracking. + Call(ctx context.Context, method string, params, result interface{}) (ID, error) + + // Notify invokes the target method but does not wait for a response. + // The params will be marshaled to JSON before sending over the wire, and will + // be handed to the method invoked. + Notify(ctx context.Context, method string, params interface{}) error + + // Go starts a goroutine to handle the connection. + // It must be called exactly once for each Conn. + // It returns immediately. + // You must block on Done() to wait for the connection to shut down. + // This is a temporary measure, this should be started automatically in the + // future. + Go(ctx context.Context, handler Handler) + + // Close closes the connection and it's underlying stream. + // It does not wait for the close to complete, use the Done() channel for + // that. + Close() error + + // Done returns a channel that will be closed when the processing goroutine + // has terminated, which will happen if Close() is called or an underlying + // stream is closed. + Done() <-chan struct{} + + // Err returns an error if there was one from within the processing goroutine. + // If err returns non nil, the connection will be already closed or closing. + Err() error +} + +type conn struct { + seq int64 // must only be accessed using atomic operations + writeMu sync.Mutex // protects writes to the stream + stream Stream + pendingMu sync.Mutex // protects the pending map + pending map[ID]chan *Response + + done chan struct{} + err atomic.Value +} + +// NewConn creates a new connection object around the supplied stream. +func NewConn(s Stream) Conn { + conn := &conn{ + stream: s, + pending: make(map[ID]chan *Response), + done: make(chan struct{}), + } + return conn +} + +func (c *conn) Notify(ctx context.Context, method string, params interface{}) (err error) { + notify, err := NewNotification(method, params) + if err != nil { + return fmt.Errorf("marshaling notify parameters: %v", err) + } + ctx, done := event.Start(ctx, method, + Method.Of(method), + RPCDirection.Of(Outbound), + ) + defer func() { + recordStatus(ctx, err) + done() + }() + + event.Metric(ctx, Started.Of(1)) + n, err := c.write(ctx, notify) + event.Metric(ctx, SentBytes.Of(n)) + return err +} + +func (c *conn) Call(ctx context.Context, method string, params, result interface{}) (_ ID, err error) { + // generate a new request identifier + id := ID{number: atomic.AddInt64(&c.seq, 1)} + call, err := NewCall(id, method, params) + if err != nil { + return id, fmt.Errorf("marshaling call parameters: %v", err) + } + ctx, done := event.Start(ctx, method, + Method.Of(method), + RPCDirection.Of(Outbound), + RPCID.Of(fmt.Sprintf("%q", id)), + ) + defer func() { + recordStatus(ctx, err) + done() + }() + event.Metric(ctx, Started.Of(1)) + // We have to add ourselves to the pending map before we send, otherwise we + // are racing the response. Also add a buffer to rchan, so that if we get a + // wire response between the time this call is cancelled and id is deleted + // from c.pending, the send to rchan will not block. + rchan := make(chan *Response, 1) + c.pendingMu.Lock() + c.pending[id] = rchan + c.pendingMu.Unlock() + defer func() { + c.pendingMu.Lock() + delete(c.pending, id) + c.pendingMu.Unlock() + }() + // now we are ready to send + n, err := c.write(ctx, call) + event.Metric(ctx, SentBytes.Of(n)) + if err != nil { + // sending failed, we will never get a response, so don't leave it pending + return id, err + } + // now wait for the response + select { + case response := <-rchan: + // is it an error response? + if response.err != nil { + return id, response.err + } + if result == nil || len(response.result) == 0 { + return id, nil + } + if err := json.Unmarshal(response.result, result); err != nil { + return id, fmt.Errorf("unmarshaling result: %v", err) + } + return id, nil + case <-ctx.Done(): + return id, ctx.Err() + } +} + +func (c *conn) replier(req Request, spanDone func()) Replier { + return func(ctx context.Context, result interface{}, err error) error { + defer func() { + recordStatus(ctx, err) + spanDone() + }() + call, ok := req.(*Call) + if !ok { + // request was a notify, no need to respond + return nil + } + response, err := NewResponse(call.id, result, err) + if err != nil { + return err + } + n, err := c.write(ctx, response) + event.Metric(ctx, SentBytes.Of(n)) + if err != nil { + // TODO(iancottrell): if a stream write fails, we really need to shut down + // the whole stream + return err + } + return nil + } +} + +func (c *conn) write(ctx context.Context, msg Message) (int64, error) { + c.writeMu.Lock() + defer c.writeMu.Unlock() + return c.stream.Write(ctx, msg) +} + +func (c *conn) Go(ctx context.Context, handler Handler) { + go c.run(ctx, handler) +} + +func (c *conn) run(ctx context.Context, handler Handler) { + defer close(c.done) + for { + // get the next message + msg, n, err := c.stream.Read(ctx) + if err != nil { + // The stream failed, we cannot continue. + c.fail(err) + return + } + switch msg := msg.(type) { + case Request: + labels := []label.Label{ + Method.Of(msg.Method()), + RPCDirection.Of(Inbound), + {}, // reserved for ID if present + } + if call, ok := msg.(*Call); ok { + labels[len(labels)-1] = RPCID.Of(fmt.Sprintf("%q", call.ID())) + } else { + labels = labels[:len(labels)-1] + } + reqCtx, spanDone := event.Start(ctx, msg.Method(), labels...) + event.Metric(reqCtx, + Started.Of(1), + ReceivedBytes.Of(n)) + if err := handler(reqCtx, c.replier(msg, spanDone), msg); err != nil { + // delivery failed, not much we can do + event.Error(reqCtx, "jsonrpc2 message delivery failed", err) + } + case *Response: + // If method is not set, this should be a response, in which case we must + // have an id to send the response back to the caller. + c.pendingMu.Lock() + rchan, ok := c.pending[msg.id] + c.pendingMu.Unlock() + if ok { + rchan <- msg + } + } + } +} + +func (c *conn) Close() error { + return c.stream.Close() +} + +func (c *conn) Done() <-chan struct{} { + return c.done +} + +func (c *conn) Err() error { + if err := c.err.Load(); err != nil { + return err.(error) + } + return nil +} + +// fail sets a failure condition on the stream and closes it. +func (c *conn) fail(err error) { + c.err.Store(err) + c.stream.Close() +} + +func recordStatus(ctx context.Context, err error) { + if err != nil { + event.Label(ctx, StatusCode.Of("ERROR")) + } else { + event.Label(ctx, StatusCode.Of("OK")) + } +} diff --git a/contribs/gnopls/internal/jsonrpc2/handler.go b/contribs/gnopls/internal/jsonrpc2/handler.go new file mode 100644 index 00000000000..cfe86afe2d8 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/handler.go @@ -0,0 +1,109 @@ +// 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. + +package jsonrpc2 + +import ( + "context" + "fmt" + "sync" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" +) + +// Handler is invoked to handle incoming requests. +// The Replier sends a reply to the request and must be called exactly once. +type Handler func(ctx context.Context, reply Replier, req Request) error + +// Replier is passed to handlers to allow them to reply to the request. +// If err is set then result will be ignored. +type Replier func(ctx context.Context, result interface{}, err error) error + +// MethodNotFound is a Handler that replies to all call requests with the +// standard method not found response. +// This should normally be the final handler in a chain. +func MethodNotFound(ctx context.Context, reply Replier, req Request) error { + return reply(ctx, nil, fmt.Errorf("%w: %q", ErrMethodNotFound, req.Method())) +} + +// MustReplyHandler creates a Handler that panics if the wrapped handler does +// not call Reply for every request that it is passed. +func MustReplyHandler(handler Handler) Handler { + return func(ctx context.Context, reply Replier, req Request) error { + called := false + err := handler(ctx, func(ctx context.Context, result interface{}, err error) error { + if called { + panic(fmt.Errorf("request %q replied to more than once", req.Method())) + } + called = true + return reply(ctx, result, err) + }, req) + if !called { + panic(fmt.Errorf("request %q was never replied to", req.Method())) + } + return err + } +} + +// CancelHandler returns a handler that supports cancellation, and a function +// that can be used to trigger canceling in progress requests. +func CancelHandler(handler Handler) (Handler, func(id ID)) { + var mu sync.Mutex + handling := make(map[ID]context.CancelFunc) + wrapped := func(ctx context.Context, reply Replier, req Request) error { + if call, ok := req.(*Call); ok { + cancelCtx, cancel := context.WithCancel(ctx) + ctx = cancelCtx + mu.Lock() + handling[call.ID()] = cancel + mu.Unlock() + innerReply := reply + reply = func(ctx context.Context, result interface{}, err error) error { + mu.Lock() + delete(handling, call.ID()) + mu.Unlock() + return innerReply(ctx, result, err) + } + } + return handler(ctx, reply, req) + } + return wrapped, func(id ID) { + mu.Lock() + cancel, found := handling[id] + mu.Unlock() + if found { + cancel() + } + } +} + +// AsyncHandler returns a handler that processes each request goes in its own +// goroutine. +// The handler returns immediately, without the request being processed. +// Each request then waits for the previous request to finish before it starts. +// This allows the stream to unblock at the cost of unbounded goroutines +// all stalled on the previous one. +func AsyncHandler(handler Handler) Handler { + nextRequest := make(chan struct{}) + close(nextRequest) + return func(ctx context.Context, reply Replier, req Request) error { + waitForPrevious := nextRequest + nextRequest = make(chan struct{}) + unlockNext := nextRequest + innerReply := reply + reply = func(ctx context.Context, result interface{}, err error) error { + close(unlockNext) + return innerReply(ctx, result, err) + } + _, queueDone := event.Start(ctx, "queued") + go func() { + <-waitForPrevious + queueDone() + if err := handler(ctx, reply, req); err != nil { + event.Error(ctx, "jsonrpc2 async message delivery failed", err) + } + }() + return nil + } +} diff --git a/contribs/gnopls/internal/jsonrpc2/jsonrpc2.go b/contribs/gnopls/internal/jsonrpc2/jsonrpc2.go new file mode 100644 index 00000000000..5a52995014a --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/jsonrpc2.go @@ -0,0 +1,17 @@ +// Copyright 2018 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 jsonrpc2 is a minimal implementation of the JSON RPC 2 spec. +// https://www.jsonrpc.org/specification +// It is intended to be compatible with other implementations at the wire level. +package jsonrpc2 + +const ( + // ErrIdleTimeout is returned when serving timed out waiting for new connections. + ErrIdleTimeout = constError("timed out waiting for new connections") +) + +type constError string + +func (e constError) Error() string { return string(e) } diff --git a/contribs/gnopls/internal/jsonrpc2/jsonrpc2_test.go b/contribs/gnopls/internal/jsonrpc2/jsonrpc2_test.go new file mode 100644 index 00000000000..cdd660f6511 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/jsonrpc2_test.go @@ -0,0 +1,148 @@ +// Copyright 2018 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 jsonrpc2_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "net" + "path" + "reflect" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/eventtest" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/stack/stacktest" +) + +var logRPC = flag.Bool("logrpc", false, "Enable jsonrpc2 communication logging") + +type callTest struct { + method string + params interface{} + expect interface{} +} + +var callTests = []callTest{ + {"no_args", nil, true}, + {"one_string", "fish", "got:fish"}, + {"one_number", 10, "got:10"}, + {"join", []string{"a", "b", "c"}, "a/b/c"}, + //TODO: expand the test cases +} + +func (test *callTest) newResults() interface{} { + switch e := test.expect.(type) { + case []interface{}: + var r []interface{} + for _, v := range e { + r = append(r, reflect.New(reflect.TypeOf(v)).Interface()) + } + return r + case nil: + return nil + default: + return reflect.New(reflect.TypeOf(test.expect)).Interface() + } +} + +func (test *callTest) verifyResults(t *testing.T, results interface{}) { + if results == nil { + return + } + val := reflect.Indirect(reflect.ValueOf(results)).Interface() + if !reflect.DeepEqual(val, test.expect) { + t.Errorf("%v:Results are incorrect, got %+v expect %+v", test.method, val, test.expect) + } +} + +func TestCall(t *testing.T) { + stacktest.NoLeak(t) + ctx := eventtest.NewContext(context.Background(), t) + for _, headers := range []bool{false, true} { + name := "Plain" + if headers { + name = "Headers" + } + t.Run(name, func(t *testing.T) { + ctx := eventtest.NewContext(ctx, t) + a, b, done := prepare(ctx, t, headers) + defer done() + for _, test := range callTests { + t.Run(test.method, func(t *testing.T) { + ctx := eventtest.NewContext(ctx, t) + results := test.newResults() + if _, err := a.Call(ctx, test.method, test.params, results); err != nil { + t.Fatalf("%v:Call failed: %v", test.method, err) + } + test.verifyResults(t, results) + if _, err := b.Call(ctx, test.method, test.params, results); err != nil { + t.Fatalf("%v:Call failed: %v", test.method, err) + } + test.verifyResults(t, results) + }) + } + }) + } +} + +func prepare(ctx context.Context, t *testing.T, withHeaders bool) (jsonrpc2.Conn, jsonrpc2.Conn, func()) { + // make a wait group that can be used to wait for the system to shut down + aPipe, bPipe := net.Pipe() + a := run(ctx, withHeaders, aPipe) + b := run(ctx, withHeaders, bPipe) + return a, b, func() { + a.Close() + b.Close() + <-a.Done() + <-b.Done() + } +} + +func run(ctx context.Context, withHeaders bool, nc net.Conn) jsonrpc2.Conn { + var stream jsonrpc2.Stream + if withHeaders { + stream = jsonrpc2.NewHeaderStream(nc) + } else { + stream = jsonrpc2.NewRawStream(nc) + } + conn := jsonrpc2.NewConn(stream) + conn.Go(ctx, testHandler(*logRPC)) + return conn +} + +func testHandler(log bool) jsonrpc2.Handler { + return func(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + switch req.Method() { + case "no_args": + if len(req.Params()) > 0 { + return reply(ctx, nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams)) + } + return reply(ctx, true, nil) + case "one_string": + var v string + if err := json.Unmarshal(req.Params(), &v); err != nil { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) + } + return reply(ctx, "got:"+v, nil) + case "one_number": + var v int + if err := json.Unmarshal(req.Params(), &v); err != nil { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) + } + return reply(ctx, fmt.Sprintf("got:%d", v), nil) + case "join": + var v []string + if err := json.Unmarshal(req.Params(), &v); err != nil { + return reply(ctx, nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)) + } + return reply(ctx, path.Join(v...), nil) + default: + return jsonrpc2.MethodNotFound(ctx, reply, req) + } + } +} diff --git a/contribs/gnopls/internal/jsonrpc2/labels.go b/contribs/gnopls/internal/jsonrpc2/labels.go new file mode 100644 index 00000000000..4e581de0cfa --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/labels.go @@ -0,0 +1,24 @@ +// 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 jsonrpc2 + +import "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + +// These keys are used for creating labels to instrument jsonrpc2 events. +var ( + Method = keys.NewString("method", "") + RPCID = keys.NewString("id", "") + RPCDirection = keys.NewString("direction", "") + Started = keys.NewInt64("started", "Count of started RPCs.") + SentBytes = keys.NewInt64("sent_bytes", "Bytes sent.") //, unit.Bytes) + ReceivedBytes = keys.NewInt64("received_bytes", "Bytes received.") //, unit.Bytes) + StatusCode = keys.NewString("status.code", "") + Latency = keys.NewFloat64("latency_ms", "Elapsed time in milliseconds") //, unit.Milliseconds) +) + +const ( + Inbound = "in" + Outbound = "out" +) diff --git a/contribs/gnopls/internal/jsonrpc2/messages.go b/contribs/gnopls/internal/jsonrpc2/messages.go new file mode 100644 index 00000000000..721168fd4f2 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/messages.go @@ -0,0 +1,238 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "encoding/json" + "errors" + "fmt" +) + +// Message is the interface to all jsonrpc2 message types. +// They share no common functionality, but are a closed set of concrete types +// that are allowed to implement this interface. The message types are *Call, +// *Notification and *Response. +type Message interface { + // isJSONRPC2Message is used to make the set of message implementations a + // closed set. + isJSONRPC2Message() +} + +// Request is the shared interface to jsonrpc2 messages that request +// a method be invoked. +// The request types are a closed set of *Call and *Notification. +type Request interface { + Message + // Method is a string containing the method name to invoke. + Method() string + // Params is an JSON value (object, array, null, or "") with the parameters of the method. + Params() json.RawMessage + // isJSONRPC2Request is used to make the set of request implementations closed. + isJSONRPC2Request() +} + +// Notification is a request for which a response cannot occur, and as such +// it has not ID. +type Notification struct { + // Method is a string containing the method name to invoke. + method string + params json.RawMessage +} + +// Call is a request that expects a response. +// The response will have a matching ID. +type Call struct { + // Method is a string containing the method name to invoke. + method string + // Params is a JSON value (object, array, null, or "") with the parameters of the method. + params json.RawMessage + // id of this request, used to tie the Response back to the request. + id ID +} + +// Response is a reply to a Call. +// It will have the same ID as the call it is a response to. +type Response struct { + // result is the content of the response. + result json.RawMessage + // err is set only if the call failed. + err error + // ID of the request this is a response to. + id ID +} + +// NewNotification constructs a new Notification message for the supplied +// method and parameters. +func NewNotification(method string, params interface{}) (*Notification, error) { + p, merr := marshalToRaw(params) + return &Notification{method: method, params: p}, merr +} + +func (msg *Notification) Method() string { return msg.method } +func (msg *Notification) Params() json.RawMessage { return msg.params } +func (msg *Notification) isJSONRPC2Message() {} +func (msg *Notification) isJSONRPC2Request() {} + +func (n *Notification) MarshalJSON() ([]byte, error) { + msg := wireRequest{Method: n.method, Params: &n.params} + data, err := json.Marshal(msg) + if err != nil { + return data, fmt.Errorf("marshaling notification: %w", err) + } + return data, nil +} + +func (n *Notification) UnmarshalJSON(data []byte) error { + msg := wireRequest{} + if err := json.Unmarshal(data, &msg); err != nil { + return fmt.Errorf("unmarshaling notification: %w", err) + } + n.method = msg.Method + if msg.Params != nil { + n.params = *msg.Params + } + return nil +} + +// NewCall constructs a new Call message for the supplied ID, method and +// parameters. +func NewCall(id ID, method string, params interface{}) (*Call, error) { + p, merr := marshalToRaw(params) + return &Call{id: id, method: method, params: p}, merr +} + +func (msg *Call) Method() string { return msg.method } +func (msg *Call) Params() json.RawMessage { return msg.params } +func (msg *Call) ID() ID { return msg.id } +func (msg *Call) isJSONRPC2Message() {} +func (msg *Call) isJSONRPC2Request() {} + +func (c *Call) MarshalJSON() ([]byte, error) { + msg := wireRequest{Method: c.method, Params: &c.params, ID: &c.id} + data, err := json.Marshal(msg) + if err != nil { + return data, fmt.Errorf("marshaling call: %w", err) + } + return data, nil +} + +func (c *Call) UnmarshalJSON(data []byte) error { + msg := wireRequest{} + if err := json.Unmarshal(data, &msg); err != nil { + return fmt.Errorf("unmarshaling call: %w", err) + } + c.method = msg.Method + if msg.Params != nil { + c.params = *msg.Params + } + if msg.ID != nil { + c.id = *msg.ID + } + return nil +} + +// NewResponse constructs a new Response message that is a reply to the +// supplied. If err is set result may be ignored. +func NewResponse(id ID, result interface{}, err error) (*Response, error) { + r, merr := marshalToRaw(result) + return &Response{id: id, result: r, err: err}, merr +} + +func (msg *Response) ID() ID { return msg.id } +func (msg *Response) Result() json.RawMessage { return msg.result } +func (msg *Response) Err() error { return msg.err } +func (msg *Response) isJSONRPC2Message() {} + +func (r *Response) MarshalJSON() ([]byte, error) { + msg := &wireResponse{Error: toWireError(r.err), ID: &r.id} + if msg.Error == nil { + msg.Result = &r.result + } + data, err := json.Marshal(msg) + if err != nil { + return data, fmt.Errorf("marshaling notification: %w", err) + } + return data, nil +} + +func toWireError(err error) *WireError { + if err == nil { + // no error, the response is complete + return nil + } + if err, ok := err.(*WireError); ok { + // already a wire error, just use it + return err + } + result := &WireError{Message: err.Error()} + var wrapped *WireError + if errors.As(err, &wrapped) { + // if we wrapped a wire error, keep the code from the wrapped error + // but the message from the outer error + result.Code = wrapped.Code + } + return result +} + +func (r *Response) UnmarshalJSON(data []byte) error { + msg := wireResponse{} + if err := json.Unmarshal(data, &msg); err != nil { + return fmt.Errorf("unmarshaling jsonrpc response: %w", err) + } + if msg.Result != nil { + r.result = *msg.Result + } + if msg.Error != nil { + r.err = msg.Error + } + if msg.ID != nil { + r.id = *msg.ID + } + return nil +} + +func DecodeMessage(data []byte) (Message, error) { + msg := wireCombined{} + if err := json.Unmarshal(data, &msg); err != nil { + return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err) + } + if msg.Method == "" { + // no method, should be a response + if msg.ID == nil { + return nil, ErrInvalidRequest + } + response := &Response{id: *msg.ID} + if msg.Error != nil { + response.err = msg.Error + } + if msg.Result != nil { + response.result = *msg.Result + } + return response, nil + } + // has a method, must be a request + if msg.ID == nil { + // request with no ID is a notify + notify := &Notification{method: msg.Method} + if msg.Params != nil { + notify.params = *msg.Params + } + return notify, nil + } + // request with an ID, must be a call + call := &Call{method: msg.Method, id: *msg.ID} + if msg.Params != nil { + call.params = *msg.Params + } + return call, nil +} + +func marshalToRaw(obj interface{}) (json.RawMessage, error) { + data, err := json.Marshal(obj) + if err != nil { + return json.RawMessage{}, err + } + return json.RawMessage(data), nil +} diff --git a/contribs/gnopls/internal/jsonrpc2/serve.go b/contribs/gnopls/internal/jsonrpc2/serve.go new file mode 100644 index 00000000000..91104989ce6 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/serve.go @@ -0,0 +1,163 @@ +// 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 jsonrpc2 + +import ( + "context" + "errors" + "io" + "math" + "net" + "os" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" +) + +// NOTE: This file provides an experimental API for serving multiple remote +// jsonrpc2 clients over the network. For now, it is intentionally similar to +// net/http, but that may change in the future as we figure out the correct +// semantics. + +// A StreamServer is used to serve incoming jsonrpc2 clients communicating over +// a newly created connection. +type StreamServer interface { + ServeStream(context.Context, Conn) error +} + +// The ServerFunc type is an adapter that implements the StreamServer interface +// using an ordinary function. +type ServerFunc func(context.Context, Conn) error + +// ServeStream calls f(ctx, s). +func (f ServerFunc) ServeStream(ctx context.Context, c Conn) error { + return f(ctx, c) +} + +// HandlerServer returns a StreamServer that handles incoming streams using the +// provided handler. +func HandlerServer(h Handler) StreamServer { + return ServerFunc(func(ctx context.Context, conn Conn) error { + conn.Go(ctx, h) + <-conn.Done() + return conn.Err() + }) +} + +// ListenAndServe starts an jsonrpc2 server on the given address. If +// idleTimeout is non-zero, ListenAndServe exits after there are no clients for +// this duration, otherwise it exits only on error. +func ListenAndServe(ctx context.Context, network, addr string, server StreamServer, idleTimeout time.Duration) error { + ln, err := net.Listen(network, addr) + if err != nil { + return err + } + defer ln.Close() + if network == "unix" { + defer os.Remove(addr) + } + return Serve(ctx, ln, server, idleTimeout) +} + +// Serve accepts incoming connections from the network, and handles them using +// the provided server. If idleTimeout is non-zero, ListenAndServe exits after +// there are no clients for this duration, otherwise it exits only on error. +func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeout time.Duration) error { + newConns := make(chan net.Conn) + closedConns := make(chan error) + activeConns := 0 + var acceptErr error + go func() { + defer close(newConns) + for { + var nc net.Conn + nc, acceptErr = ln.Accept() + if acceptErr != nil { + return + } + newConns <- nc + } + }() + + ctx, cancel := context.WithCancel(ctx) + defer func() { + // Signal the Accept goroutine to stop immediately + // and terminate all newly-accepted connections until it returns. + ln.Close() + for nc := range newConns { + nc.Close() + } + // Cancel pending ServeStream callbacks and wait for them to finish. + cancel() + for activeConns > 0 { + err := <-closedConns + if !isClosingError(err) { + event.Error(ctx, "closed a connection", err) + } + activeConns-- + } + }() + + // Max duration: ~290 years; surely that's long enough. + const forever = math.MaxInt64 + if idleTimeout <= 0 { + idleTimeout = forever + } + connTimer := time.NewTimer(idleTimeout) + defer connTimer.Stop() + + for { + select { + case netConn, ok := <-newConns: + if !ok { + return acceptErr + } + if activeConns == 0 && !connTimer.Stop() { + // connTimer.C may receive a value even after Stop returns. + // (See https://golang.org/issue/37196.) + <-connTimer.C + } + activeConns++ + stream := NewHeaderStream(netConn) + go func() { + conn := NewConn(stream) + err := server.ServeStream(ctx, conn) + stream.Close() + closedConns <- err + }() + + case err := <-closedConns: + if !isClosingError(err) { + event.Error(ctx, "closed a connection", err) + } + activeConns-- + if activeConns == 0 { + connTimer.Reset(idleTimeout) + } + + case <-connTimer.C: + return ErrIdleTimeout + + case <-ctx.Done(): + return nil + } + } +} + +// isClosingError reports if the error occurs normally during the process of +// closing a network connection. It uses imperfect heuristics that err on the +// side of false negatives, and should not be used for anything critical. +func isClosingError(err error) bool { + if errors.Is(err, io.EOF) { + return true + } + // Per https://github.com/golang/go/issues/4373, this error string should not + // change. This is not ideal, but since the worst that could happen here is + // some superfluous logging, it is acceptable. + if err.Error() == "use of closed network connection" { + return true + } + return false +} diff --git a/contribs/gnopls/internal/jsonrpc2/serve_test.go b/contribs/gnopls/internal/jsonrpc2/serve_test.go new file mode 100644 index 00000000000..5bc7293f859 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/serve_test.go @@ -0,0 +1,65 @@ +// 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 jsonrpc2 + +import ( + "context" + "net" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/stack/stacktest" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +func TestIdleTimeout(t *testing.T) { + testenv.NeedsLocalhostNet(t) + + stacktest.NoLeak(t) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + ln, err := net.Listen("tcp", "localhost:0") + if err != nil { + t.Fatal(err) + } + defer ln.Close() + + connect := func() net.Conn { + conn, err := net.DialTimeout("tcp", ln.Addr().String(), 5*time.Second) + if err != nil { + panic(err) + } + return conn + } + + server := HandlerServer(MethodNotFound) + // connTimer := &fakeTimer{c: make(chan time.Time, 1)} + var ( + runErr error + wg sync.WaitGroup + ) + wg.Add(1) + go func() { + defer wg.Done() + runErr = Serve(ctx, ln, server, 100*time.Millisecond) + }() + + // Exercise some connection/disconnection patterns, and then assert that when + // our timer fires, the server exits. + conn1 := connect() + conn2 := connect() + conn1.Close() + conn2.Close() + conn3 := connect() + conn3.Close() + + wg.Wait() + + if runErr != ErrIdleTimeout { + t.Errorf("run() returned error %v, want %v", runErr, ErrIdleTimeout) + } +} diff --git a/contribs/gnopls/internal/jsonrpc2/servertest/servertest.go b/contribs/gnopls/internal/jsonrpc2/servertest/servertest.go new file mode 100644 index 00000000000..9662f31f8f2 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/servertest/servertest.go @@ -0,0 +1,119 @@ +// 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 servertest provides utilities for running tests against a remote LSP +// server. +package servertest + +import ( + "context" + "fmt" + "net" + "strings" + "sync" + + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" +) + +// Connector is the interface used to connect to a server. +type Connector interface { + Connect(context.Context) jsonrpc2.Conn +} + +// TCPServer is a helper for executing tests against a remote jsonrpc2 +// connection. Once initialized, its Addr field may be used to connect a +// jsonrpc2 client. +type TCPServer struct { + *connList + + Addr string + + ln net.Listener + framer jsonrpc2.Framer +} + +// NewTCPServer returns a new test server listening on local tcp port and +// serving incoming jsonrpc2 streams using the provided stream server. It +// panics on any error. +func NewTCPServer(ctx context.Context, server jsonrpc2.StreamServer, framer jsonrpc2.Framer) *TCPServer { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + panic(fmt.Sprintf("servertest: failed to listen: %v", err)) + } + if framer == nil { + framer = jsonrpc2.NewHeaderStream + } + go jsonrpc2.Serve(ctx, ln, server, 0) + return &TCPServer{Addr: ln.Addr().String(), ln: ln, framer: framer, connList: &connList{}} +} + +// Connect dials the test server and returns a jsonrpc2 Connection that is +// ready for use. +func (s *TCPServer) Connect(_ context.Context) jsonrpc2.Conn { + netConn, err := net.Dial("tcp", s.Addr) + if err != nil { + panic(fmt.Sprintf("servertest: failed to connect to test instance: %v", err)) + } + conn := jsonrpc2.NewConn(s.framer(netConn)) + s.add(conn) + return conn +} + +// PipeServer is a test server that handles connections over io.Pipes. +type PipeServer struct { + *connList + server jsonrpc2.StreamServer + framer jsonrpc2.Framer +} + +// NewPipeServer returns a test server that can be connected to via io.Pipes. +func NewPipeServer(server jsonrpc2.StreamServer, framer jsonrpc2.Framer) *PipeServer { + if framer == nil { + framer = jsonrpc2.NewRawStream + } + return &PipeServer{server: server, framer: framer, connList: &connList{}} +} + +// Connect creates new io.Pipes and binds them to the underlying StreamServer. +func (s *PipeServer) Connect(ctx context.Context) jsonrpc2.Conn { + sPipe, cPipe := net.Pipe() + serverStream := s.framer(sPipe) + serverConn := jsonrpc2.NewConn(serverStream) + s.add(serverConn) + go s.server.ServeStream(ctx, serverConn) + + clientStream := s.framer(cPipe) + clientConn := jsonrpc2.NewConn(clientStream) + s.add(clientConn) + return clientConn +} + +// connList tracks closers to run when a testserver is closed. This is a +// convenience, so that callers don't have to worry about closing each +// connection. +type connList struct { + mu sync.Mutex + conns []jsonrpc2.Conn +} + +func (l *connList) add(conn jsonrpc2.Conn) { + l.mu.Lock() + defer l.mu.Unlock() + l.conns = append(l.conns, conn) +} + +func (l *connList) Close() error { + l.mu.Lock() + defer l.mu.Unlock() + var errmsgs []string + for _, conn := range l.conns { + if err := conn.Close(); err != nil { + errmsgs = append(errmsgs, err.Error()) + } + } + if len(errmsgs) > 0 { + return fmt.Errorf("closing errors:\n%s", strings.Join(errmsgs, "\n")) + } + return nil +} diff --git a/contribs/gnopls/internal/jsonrpc2/servertest/servertest_test.go b/contribs/gnopls/internal/jsonrpc2/servertest/servertest_test.go new file mode 100644 index 00000000000..670090f13aa --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/servertest/servertest_test.go @@ -0,0 +1,53 @@ +// 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 servertest + +import ( + "context" + "testing" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" +) + +type msg struct { + Msg string +} + +func fakeHandler(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) error { + return reply(ctx, &msg{"pong"}, nil) +} + +func TestTestServer(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + server := jsonrpc2.HandlerServer(fakeHandler) + tcpTS := NewTCPServer(ctx, server, nil) + defer tcpTS.Close() + pipeTS := NewPipeServer(server, nil) + defer pipeTS.Close() + + tests := []struct { + name string + connector Connector + }{ + {"tcp", tcpTS}, + {"pipe", pipeTS}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + conn := test.connector.Connect(ctx) + conn.Go(ctx, jsonrpc2.MethodNotFound) + var got msg + if _, err := conn.Call(ctx, "ping", &msg{"ping"}, &got); err != nil { + t.Fatal(err) + } + if want := "pong"; got.Msg != want { + t.Errorf("conn.Call(...): returned %q, want %q", got, want) + } + }) + } +} diff --git a/contribs/gnopls/internal/jsonrpc2/stream.go b/contribs/gnopls/internal/jsonrpc2/stream.go new file mode 100644 index 00000000000..b22be7ee3cd --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/stream.go @@ -0,0 +1,170 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net" + "strconv" + "strings" +) + +// Stream abstracts the transport mechanics from the JSON RPC protocol. +// A Conn reads and writes messages using the stream it was provided on +// construction, and assumes that each call to Read or Write fully transfers +// a single message, or returns an error. +// A stream is not safe for concurrent use, it is expected it will be used by +// a single Conn in a safe manner. +type Stream interface { + // Read gets the next message from the stream. + Read(context.Context) (Message, int64, error) + // Write sends a message to the stream. + Write(context.Context, Message) (int64, error) + // Close closes the connection. + // Any blocked Read or Write operations will be unblocked and return errors. + Close() error +} + +// Framer wraps a network connection up into a Stream. +// It is responsible for the framing and encoding of messages into wire form. +// NewRawStream and NewHeaderStream are implementations of a Framer. +type Framer func(conn net.Conn) Stream + +// NewRawStream returns a Stream built on top of a net.Conn. +// The messages are sent with no wrapping, and rely on json decode consistency +// to determine message boundaries. +func NewRawStream(conn net.Conn) Stream { + return &rawStream{ + conn: conn, + in: json.NewDecoder(conn), + } +} + +type rawStream struct { + conn net.Conn + in *json.Decoder +} + +func (s *rawStream) Read(ctx context.Context) (Message, int64, error) { + select { + case <-ctx.Done(): + return nil, 0, ctx.Err() + default: + } + var raw json.RawMessage + if err := s.in.Decode(&raw); err != nil { + return nil, 0, err + } + msg, err := DecodeMessage(raw) + return msg, int64(len(raw)), err +} + +func (s *rawStream) Write(ctx context.Context, msg Message) (int64, error) { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + data, err := json.Marshal(msg) + if err != nil { + return 0, fmt.Errorf("marshaling message: %v", err) + } + n, err := s.conn.Write(data) + return int64(n), err +} + +func (s *rawStream) Close() error { + return s.conn.Close() +} + +// NewHeaderStream returns a Stream built on top of a net.Conn. +// The messages are sent with HTTP content length and MIME type headers. +// This is the format used by LSP and others. +func NewHeaderStream(conn net.Conn) Stream { + return &headerStream{ + conn: conn, + in: bufio.NewReader(conn), + } +} + +type headerStream struct { + conn net.Conn + in *bufio.Reader +} + +func (s *headerStream) Read(ctx context.Context) (Message, int64, error) { + select { + case <-ctx.Done(): + return nil, 0, ctx.Err() + default: + } + var total, length int64 + // read the header, stop on the first empty line + for { + line, err := s.in.ReadString('\n') + total += int64(len(line)) + if err != nil { + return nil, total, fmt.Errorf("failed reading header line: %w", err) + } + line = strings.TrimSpace(line) + // check we have a header line + if line == "" { + break + } + colon := strings.IndexRune(line, ':') + if colon < 0 { + return nil, total, fmt.Errorf("invalid header line %q", line) + } + name, value := line[:colon], strings.TrimSpace(line[colon+1:]) + switch name { + case "Content-Length": + if length, err = strconv.ParseInt(value, 10, 32); err != nil { + return nil, total, fmt.Errorf("failed parsing Content-Length: %v", value) + } + if length <= 0 { + return nil, total, fmt.Errorf("invalid Content-Length: %v", length) + } + default: + // ignoring unknown headers + } + } + if length == 0 { + return nil, total, fmt.Errorf("missing Content-Length header") + } + data := make([]byte, length) + if _, err := io.ReadFull(s.in, data); err != nil { + return nil, total, err + } + total += length + msg, err := DecodeMessage(data) + return msg, total, err +} + +func (s *headerStream) Write(ctx context.Context, msg Message) (int64, error) { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + data, err := json.Marshal(msg) + if err != nil { + return 0, fmt.Errorf("marshaling message: %v", err) + } + n, err := fmt.Fprintf(s.conn, "Content-Length: %v\r\n\r\n", len(data)) + total := int64(n) + if err == nil { + n, err = s.conn.Write(data) + total += int64(n) + } + return total, err +} + +func (s *headerStream) Close() error { + return s.conn.Close() +} diff --git a/contribs/gnopls/internal/jsonrpc2/wire.go b/contribs/gnopls/internal/jsonrpc2/wire.go new file mode 100644 index 00000000000..f2aa2d63e8c --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/wire.go @@ -0,0 +1,159 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "encoding/json" + "fmt" +) + +// this file contains the go forms of the wire specification +// see http://www.jsonrpc.org/specification for details + +var ( + // ErrUnknown should be used for all non coded errors. + ErrUnknown = NewError(-32001, "JSON RPC unknown error") + // ErrParse is used when invalid JSON was received by the server. + ErrParse = NewError(-32700, "JSON RPC parse error") + //ErrInvalidRequest is used when the JSON sent is not a valid Request object. + ErrInvalidRequest = NewError(-32600, "JSON RPC invalid request") + // ErrMethodNotFound should be returned by the handler when the method does + // not exist / is not available. + ErrMethodNotFound = NewError(-32601, "JSON RPC method not found") + // ErrInvalidParams should be returned by the handler when method + // parameter(s) were invalid. + ErrInvalidParams = NewError(-32602, "JSON RPC invalid params") + // ErrInternal is not currently returned but defined for completeness. + ErrInternal = NewError(-32603, "JSON RPC internal error") + + //ErrServerOverloaded is returned when a message was refused due to a + //server being temporarily unable to accept any new messages. + ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded") +) + +// wireRequest is sent to a server to represent a Call or Notify operation. +type wireRequest struct { + // VersionTag is always encoded as the string "2.0" + VersionTag wireVersionTag `json:"jsonrpc"` + // Method is a string containing the method name to invoke. + Method string `json:"method"` + // Params is either a struct or an array with the parameters of the method. + Params *json.RawMessage `json:"params,omitempty"` + // The id of this request, used to tie the Response back to the request. + // Will be either a string or a number. If not set, the Request is a notify, + // and no response is possible. + ID *ID `json:"id,omitempty"` +} + +// WireResponse is a reply to a Request. +// It will always have the ID field set to tie it back to a request, and will +// have either the Result or Error fields set depending on whether it is a +// success or failure response. +type wireResponse struct { + // VersionTag is always encoded as the string "2.0" + VersionTag wireVersionTag `json:"jsonrpc"` + // Result is the response value, and is required on success. + Result *json.RawMessage `json:"result,omitempty"` + // Error is a structured error response if the call fails. + Error *WireError `json:"error,omitempty"` + // ID must be set and is the identifier of the Request this is a response to. + ID *ID `json:"id,omitempty"` +} + +// wireCombined has all the fields of both Request and Response. +// We can decode this and then work out which it is. +type wireCombined struct { + VersionTag wireVersionTag `json:"jsonrpc"` + ID *ID `json:"id,omitempty"` + Method string `json:"method"` + Params *json.RawMessage `json:"params,omitempty"` + Result *json.RawMessage `json:"result,omitempty"` + Error *WireError `json:"error,omitempty"` +} + +// WireError represents a structured error in a Response. +type WireError struct { + // Code is an error code indicating the type of failure. + Code int64 `json:"code"` + // Message is a short description of the error. + Message string `json:"message"` + // Data is optional structured data containing additional information about the error. + Data *json.RawMessage `json:"data,omitempty"` +} + +// wireVersionTag is a special 0 sized struct that encodes as the jsonrpc version +// tag. +// It will fail during decode if it is not the correct version tag in the +// stream. +type wireVersionTag struct{} + +// ID is a Request identifier. +type ID struct { + name string + number int64 +} + +func NewError(code int64, message string) error { + return &WireError{ + Code: code, + Message: message, + } +} + +func (err *WireError) Error() string { + return err.Message +} + +func (wireVersionTag) MarshalJSON() ([]byte, error) { + return json.Marshal("2.0") +} + +func (wireVersionTag) UnmarshalJSON(data []byte) error { + version := "" + if err := json.Unmarshal(data, &version); err != nil { + return err + } + if version != "2.0" { + return fmt.Errorf("invalid RPC version %v", version) + } + return nil +} + +// NewIntID returns a new numerical request ID. +func NewIntID(v int64) ID { return ID{number: v} } + +// NewStringID returns a new string request ID. +func NewStringID(v string) ID { return ID{name: v} } + +// Format writes the ID to the formatter. +// If the rune is q the representation is non ambiguous, +// string forms are quoted, number forms are preceded by a # +func (id ID) Format(f fmt.State, r rune) { + numF, strF := `%d`, `%s` + if r == 'q' { + numF, strF = `#%d`, `%q` + } + switch { + case id.name != "": + fmt.Fprintf(f, strF, id.name) + default: + fmt.Fprintf(f, numF, id.number) + } +} + +func (id *ID) MarshalJSON() ([]byte, error) { + if id.name != "" { + return json.Marshal(id.name) + } + return json.Marshal(id.number) +} + +func (id *ID) UnmarshalJSON(data []byte) error { + *id = ID{} + if err := json.Unmarshal(data, &id.number); err == nil { + return nil + } + return json.Unmarshal(data, &id.name) +} diff --git a/contribs/gnopls/internal/jsonrpc2/wire_test.go b/contribs/gnopls/internal/jsonrpc2/wire_test.go new file mode 100644 index 00000000000..eb304f8f510 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2/wire_test.go @@ -0,0 +1,126 @@ +// 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 jsonrpc2_test + +import ( + "bytes" + "encoding/json" + "fmt" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" +) + +var wireIDTestData = []struct { + name string + id jsonrpc2.ID + encoded []byte + plain string + quoted string +}{ + { + name: `empty`, + encoded: []byte(`0`), + plain: `0`, + quoted: `#0`, + }, { + name: `number`, + id: jsonrpc2.NewIntID(43), + encoded: []byte(`43`), + plain: `43`, + quoted: `#43`, + }, { + name: `string`, + id: jsonrpc2.NewStringID("life"), + encoded: []byte(`"life"`), + plain: `life`, + quoted: `"life"`, + }, +} + +func TestIDFormat(t *testing.T) { + for _, test := range wireIDTestData { + t.Run(test.name, func(t *testing.T) { + if got := fmt.Sprint(test.id); got != test.plain { + t.Errorf("got %s expected %s", got, test.plain) + } + if got := fmt.Sprintf("%q", test.id); got != test.quoted { + t.Errorf("got %s want %s", got, test.quoted) + } + }) + } +} + +func TestIDEncode(t *testing.T) { + for _, test := range wireIDTestData { + t.Run(test.name, func(t *testing.T) { + data, err := json.Marshal(&test.id) + if err != nil { + t.Fatal(err) + } + checkJSON(t, data, test.encoded) + }) + } +} + +func TestIDDecode(t *testing.T) { + for _, test := range wireIDTestData { + t.Run(test.name, func(t *testing.T) { + var got *jsonrpc2.ID + if err := json.Unmarshal(test.encoded, &got); err != nil { + t.Fatal(err) + } + if got == nil { + t.Errorf("got nil want %s", test.id) + } else if *got != test.id { + t.Errorf("got %s want %s", got, test.id) + } + }) + } +} + +func TestErrorEncode(t *testing.T) { + b, err := json.Marshal(jsonrpc2.NewError(0, "")) + if err != nil { + t.Fatal(err) + } + checkJSON(t, b, []byte(`{ + "code": 0, + "message": "" + }`)) +} + +func TestErrorResponse(t *testing.T) { + // originally reported in #39719, this checks that result is not present if + // it is an error response + r, _ := jsonrpc2.NewResponse(jsonrpc2.NewIntID(3), nil, fmt.Errorf("computing fix edits")) + data, err := json.Marshal(r) + if err != nil { + t.Fatal(err) + } + checkJSON(t, data, []byte(`{ + "jsonrpc":"2.0", + "error":{ + "code":0, + "message":"computing fix edits" + }, + "id":3 + }`)) +} + +func checkJSON(t *testing.T, got, want []byte) { + // compare the compact form, to allow for formatting differences + g := &bytes.Buffer{} + if err := json.Compact(g, []byte(got)); err != nil { + t.Fatal(err) + } + w := &bytes.Buffer{} + if err := json.Compact(w, []byte(want)); err != nil { + t.Fatal(err) + } + if g.String() != w.String() { + t.Fatalf("Got:\n%s\nWant:\n%s", g, w) + } +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/conn.go b/contribs/gnopls/internal/jsonrpc2_v2/conn.go new file mode 100644 index 00000000000..8b6e8082198 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/conn.go @@ -0,0 +1,812 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "sync" + "sync/atomic" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" +) + +// Binder builds a connection configuration. +// This may be used in servers to generate a new configuration per connection. +// ConnectionOptions itself implements Binder returning itself unmodified, to +// allow for the simple cases where no per connection information is needed. +type Binder interface { + // Bind returns the ConnectionOptions to use when establishing the passed-in + // Connection. + // + // The connection is not ready to use when Bind is called, + // but Bind may close it without reading or writing to it. + Bind(context.Context, *Connection) ConnectionOptions +} + +// A BinderFunc implements the Binder interface for a standalone Bind function. +type BinderFunc func(context.Context, *Connection) ConnectionOptions + +func (f BinderFunc) Bind(ctx context.Context, c *Connection) ConnectionOptions { + return f(ctx, c) +} + +var _ Binder = BinderFunc(nil) + +// ConnectionOptions holds the options for new connections. +type ConnectionOptions struct { + // Framer allows control over the message framing and encoding. + // If nil, HeaderFramer will be used. + Framer Framer + // Preempter allows registration of a pre-queue message handler. + // If nil, no messages will be preempted. + Preempter Preempter + // Handler is used as the queued message handler for inbound messages. + // If nil, all responses will be ErrNotHandled. + Handler Handler + // OnInternalError, if non-nil, is called with any internal errors that occur + // while serving the connection, such as protocol errors or invariant + // violations. (If nil, internal errors result in panics.) + OnInternalError func(error) +} + +// Connection manages the jsonrpc2 protocol, connecting responses back to their +// calls. +// Connection is bidirectional; it does not have a designated server or client +// end. +type Connection struct { + seq int64 // must only be accessed using atomic operations + + stateMu sync.Mutex + state inFlightState // accessed only in updateInFlight + done chan struct{} // closed (under stateMu) when state.closed is true and all goroutines have completed + + writer chan Writer // 1-buffered; stores the writer when not in use + + handler Handler + + onInternalError func(error) + onDone func() +} + +// inFlightState records the state of the incoming and outgoing calls on a +// Connection. +type inFlightState struct { + connClosing bool // true when the Connection's Close method has been called + reading bool // true while the readIncoming goroutine is running + readErr error // non-nil when the readIncoming goroutine exits (typically io.EOF) + writeErr error // non-nil if a call to the Writer has failed with a non-canceled Context + + // closer shuts down and cleans up the Reader and Writer state, ideally + // interrupting any Read or Write call that is currently blocked. It is closed + // when the state is idle and one of: connClosing is true, readErr is non-nil, + // or writeErr is non-nil. + // + // After the closer has been invoked, the closer field is set to nil + // and the closeErr field is simultaneously set to its result. + closer io.Closer + closeErr error // error returned from closer.Close + + outgoingCalls map[ID]*AsyncCall // calls only + outgoingNotifications int // # of notifications awaiting "write" + + // incoming stores the total number of incoming calls and notifications + // that have not yet written or processed a result. + incoming int + + incomingByID map[ID]*incomingRequest // calls only + + // handlerQueue stores the backlog of calls and notifications that were not + // already handled by a preempter. + // The queue does not include the request currently being handled (if any). + handlerQueue []*incomingRequest + handlerRunning bool +} + +// updateInFlight locks the state of the connection's in-flight requests, allows +// f to mutate that state, and closes the connection if it is idle and either +// is closing or has a read or write error. +func (c *Connection) updateInFlight(f func(*inFlightState)) { + c.stateMu.Lock() + defer c.stateMu.Unlock() + + s := &c.state + + f(s) + + select { + case <-c.done: + // The connection was already completely done at the start of this call to + // updateInFlight, so it must remain so. (The call to f should have noticed + // that and avoided making any updates that would cause the state to be + // non-idle.) + if !s.idle() { + panic("jsonrpc2_v2: updateInFlight transitioned to non-idle when already done") + } + return + default: + } + + if s.idle() && s.shuttingDown(ErrUnknown) != nil { + if s.closer != nil { + s.closeErr = s.closer.Close() + s.closer = nil // prevent duplicate Close calls + } + if s.reading { + // The readIncoming goroutine is still running. Our call to Close should + // cause it to exit soon, at which point it will make another call to + // updateInFlight, set s.reading to false, and mark the Connection done. + } else { + // The readIncoming goroutine has exited, or never started to begin with. + // Since everything else is idle, we're completely done. + if c.onDone != nil { + c.onDone() + } + close(c.done) + } + } +} + +// idle reports whether the connection is in a state with no pending calls or +// notifications. +// +// If idle returns true, the readIncoming goroutine may still be running, +// but no other goroutines are doing work on behalf of the connection. +func (s *inFlightState) idle() bool { + return len(s.outgoingCalls) == 0 && s.outgoingNotifications == 0 && s.incoming == 0 && !s.handlerRunning +} + +// shuttingDown reports whether the connection is in a state that should +// disallow new (incoming and outgoing) calls. It returns either nil or +// an error that is or wraps the provided errClosing. +func (s *inFlightState) shuttingDown(errClosing error) error { + if s.connClosing { + // If Close has been called explicitly, it doesn't matter what state the + // Reader and Writer are in: we shouldn't be starting new work because the + // caller told us not to start new work. + return errClosing + } + if s.readErr != nil { + // If the read side of the connection is broken, we cannot read new call + // requests, and cannot read responses to our outgoing calls. + return fmt.Errorf("%w: %v", errClosing, s.readErr) + } + if s.writeErr != nil { + // If the write side of the connection is broken, we cannot write responses + // for incoming calls, and cannot write requests for outgoing calls. + return fmt.Errorf("%w: %v", errClosing, s.writeErr) + } + return nil +} + +// incomingRequest is used to track an incoming request as it is being handled +type incomingRequest struct { + *Request // the request being processed + ctx context.Context + cancel context.CancelFunc + endSpan func() // called (and set to nil) when the response is sent +} + +// Bind returns the options unmodified. +func (o ConnectionOptions) Bind(context.Context, *Connection) ConnectionOptions { + return o +} + +// newConnection creates a new connection and runs it. +// +// This is used by the Dial and Serve functions to build the actual connection. +// +// The connection is closed automatically (and its resources cleaned up) when +// the last request has completed after the underlying ReadWriteCloser breaks, +// but it may be stopped earlier by calling Close (for a clean shutdown). +func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binder, onDone func()) *Connection { + // TODO: Should we create a new event span here? + // This will propagate cancellation from ctx; should it? + ctx := notDone{bindCtx} + + c := &Connection{ + state: inFlightState{closer: rwc}, + done: make(chan struct{}), + writer: make(chan Writer, 1), + onDone: onDone, + } + // It's tempting to set a finalizer on c to verify that the state has gone + // idle when the connection becomes unreachable. Unfortunately, the Binder + // interface makes that unsafe: it allows the Handler to close over the + // Connection, which could create a reference cycle that would cause the + // Connection to become uncollectable. + + options := binder.Bind(bindCtx, c) + framer := options.Framer + if framer == nil { + framer = HeaderFramer() + } + c.handler = options.Handler + if c.handler == nil { + c.handler = defaultHandler{} + } + c.onInternalError = options.OnInternalError + + c.writer <- framer.Writer(rwc) + reader := framer.Reader(rwc) + + c.updateInFlight(func(s *inFlightState) { + select { + case <-c.done: + // Bind already closed the connection; don't start a goroutine to read it. + return + default: + } + + // The goroutine started here will continue until the underlying stream is closed. + // + // (If the Binder closed the Connection already, this should error out and + // return almost immediately.) + s.reading = true + go c.readIncoming(ctx, reader, options.Preempter) + }) + return c +} + +// Notify invokes the target method but does not wait for a response. +// The params will be marshaled to JSON before sending over the wire, and will +// be handed to the method invoked. +func (c *Connection) Notify(ctx context.Context, method string, params interface{}) (err error) { + ctx, done := event.Start(ctx, method, + jsonrpc2.Method.Of(method), + jsonrpc2.RPCDirection.Of(jsonrpc2.Outbound), + ) + attempted := false + + defer func() { + labelStatus(ctx, err) + done() + if attempted { + c.updateInFlight(func(s *inFlightState) { + s.outgoingNotifications-- + }) + } + }() + + c.updateInFlight(func(s *inFlightState) { + // If the connection is shutting down, allow outgoing notifications only if + // there is at least one call still in flight. The number of calls in flight + // cannot increase once shutdown begins, and allowing outgoing notifications + // may permit notifications that will cancel in-flight calls. + if len(s.outgoingCalls) == 0 && len(s.incomingByID) == 0 { + err = s.shuttingDown(ErrClientClosing) + if err != nil { + return + } + } + s.outgoingNotifications++ + attempted = true + }) + if err != nil { + return err + } + + notify, err := NewNotification(method, params) + if err != nil { + return fmt.Errorf("marshaling notify parameters: %v", err) + } + + event.Metric(ctx, jsonrpc2.Started.Of(1)) + return c.write(ctx, notify) +} + +// Call invokes the target method and returns an object that can be used to await the response. +// The params will be marshaled to JSON before sending over the wire, and will +// be handed to the method invoked. +// You do not have to wait for the response, it can just be ignored if not needed. +// If sending the call failed, the response will be ready and have the error in it. +func (c *Connection) Call(ctx context.Context, method string, params interface{}) *AsyncCall { + // Generate a new request identifier. + id := Int64ID(atomic.AddInt64(&c.seq, 1)) + ctx, endSpan := event.Start(ctx, method, + jsonrpc2.Method.Of(method), + jsonrpc2.RPCDirection.Of(jsonrpc2.Outbound), + jsonrpc2.RPCID.Of(fmt.Sprintf("%q", id)), + ) + + ac := &AsyncCall{ + id: id, + ready: make(chan struct{}), + ctx: ctx, + endSpan: endSpan, + } + // When this method returns, either ac is retired, or the request has been + // written successfully and the call is awaiting a response (to be provided by + // the readIncoming goroutine). + + call, err := NewCall(ac.id, method, params) + if err != nil { + ac.retire(&Response{ID: id, Error: fmt.Errorf("marshaling call parameters: %w", err)}) + return ac + } + + c.updateInFlight(func(s *inFlightState) { + err = s.shuttingDown(ErrClientClosing) + if err != nil { + return + } + if s.outgoingCalls == nil { + s.outgoingCalls = make(map[ID]*AsyncCall) + } + s.outgoingCalls[ac.id] = ac + }) + if err != nil { + ac.retire(&Response{ID: id, Error: err}) + return ac + } + + event.Metric(ctx, jsonrpc2.Started.Of(1)) + if err := c.write(ctx, call); err != nil { + // Sending failed. We will never get a response, so deliver a fake one if it + // wasn't already retired by the connection breaking. + c.updateInFlight(func(s *inFlightState) { + if s.outgoingCalls[ac.id] == ac { + delete(s.outgoingCalls, ac.id) + ac.retire(&Response{ID: id, Error: err}) + } else { + // ac was already retired by the readIncoming goroutine: + // perhaps our write raced with the Read side of the connection breaking. + } + }) + } + return ac +} + +type AsyncCall struct { + id ID + ready chan struct{} // closed after response has been set and span has been ended + response *Response + ctx context.Context // for event logging only + endSpan func() // close the tracing span when all processing for the message is complete +} + +// ID used for this call. +// This can be used to cancel the call if needed. +func (ac *AsyncCall) ID() ID { return ac.id } + +// IsReady can be used to check if the result is already prepared. +// This is guaranteed to return true on a result for which Await has already +// returned, or a call that failed to send in the first place. +func (ac *AsyncCall) IsReady() bool { + select { + case <-ac.ready: + return true + default: + return false + } +} + +// retire processes the response to the call. +func (ac *AsyncCall) retire(response *Response) { + select { + case <-ac.ready: + panic(fmt.Sprintf("jsonrpc2: retire called twice for ID %v", ac.id)) + default: + } + + ac.response = response + labelStatus(ac.ctx, response.Error) + ac.endSpan() + // Allow the trace context, which may retain a lot of reachable values, + // to be garbage-collected. + ac.ctx, ac.endSpan = nil, nil + + close(ac.ready) +} + +// Await waits for (and decodes) the results of a Call. +// The response will be unmarshaled from JSON into the result. +func (ac *AsyncCall) Await(ctx context.Context, result interface{}) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-ac.ready: + } + if ac.response.Error != nil { + return ac.response.Error + } + if result == nil { + return nil + } + return json.Unmarshal(ac.response.Result, result) +} + +// Respond delivers a response to an incoming Call. +// +// Respond must be called exactly once for any message for which a handler +// returns ErrAsyncResponse. It must not be called for any other message. +func (c *Connection) Respond(id ID, result interface{}, err error) error { + var req *incomingRequest + c.updateInFlight(func(s *inFlightState) { + req = s.incomingByID[id] + }) + if req == nil { + return c.internalErrorf("Request not found for ID %v", id) + } + + if err == ErrAsyncResponse { + // Respond is supposed to supply the asynchronous response, so it would be + // confusing to call Respond with an error that promises to call Respond + // again. + err = c.internalErrorf("Respond called with ErrAsyncResponse for %q", req.Method) + } + return c.processResult("Respond", req, result, err) +} + +// Cancel cancels the Context passed to the Handle call for the inbound message +// with the given ID. +// +// Cancel will not complain if the ID is not a currently active message, and it +// will not cause any messages that have not arrived yet with that ID to be +// cancelled. +func (c *Connection) Cancel(id ID) { + var req *incomingRequest + c.updateInFlight(func(s *inFlightState) { + req = s.incomingByID[id] + }) + if req != nil { + req.cancel() + } +} + +// Wait blocks until the connection is fully closed, but does not close it. +func (c *Connection) Wait() error { + var err error + <-c.done + c.updateInFlight(func(s *inFlightState) { + err = s.closeErr + }) + return err +} + +// Close stops accepting new requests, waits for in-flight requests and enqueued +// Handle calls to complete, and then closes the underlying stream. +// +// After the start of a Close, notification requests (that lack IDs and do not +// receive responses) will continue to be passed to the Preempter, but calls +// with IDs will receive immediate responses with ErrServerClosing, and no new +// requests (not even notifications!) will be enqueued to the Handler. +func (c *Connection) Close() error { + // Stop handling new requests, and interrupt the reader (by closing the + // connection) as soon as the active requests finish. + c.updateInFlight(func(s *inFlightState) { s.connClosing = true }) + + return c.Wait() +} + +// readIncoming collects inbound messages from the reader and delivers them, either responding +// to outgoing calls or feeding requests to the queue. +func (c *Connection) readIncoming(ctx context.Context, reader Reader, preempter Preempter) { + var err error + for { + var ( + msg Message + n int64 + ) + msg, n, err = reader.Read(ctx) + if err != nil { + break + } + + switch msg := msg.(type) { + case *Request: + c.acceptRequest(ctx, msg, n, preempter) + + case *Response: + c.updateInFlight(func(s *inFlightState) { + if ac, ok := s.outgoingCalls[msg.ID]; ok { + delete(s.outgoingCalls, msg.ID) + ac.retire(msg) + } else { + // TODO: How should we report unexpected responses? + } + }) + + default: + c.internalErrorf("Read returned an unexpected message of type %T", msg) + } + } + + c.updateInFlight(func(s *inFlightState) { + s.reading = false + s.readErr = err + + // Retire any outgoing requests that were still in flight: with the Reader no + // longer being processed, they necessarily cannot receive a response. + for id, ac := range s.outgoingCalls { + ac.retire(&Response{ID: id, Error: err}) + } + s.outgoingCalls = nil + }) +} + +// acceptRequest either handles msg synchronously or enqueues it to be handled +// asynchronously. +func (c *Connection) acceptRequest(ctx context.Context, msg *Request, msgBytes int64, preempter Preempter) { + // Add a span to the context for this request. + labels := append(make([]label.Label, 0, 3), // Make space for the ID if present. + jsonrpc2.Method.Of(msg.Method), + jsonrpc2.RPCDirection.Of(jsonrpc2.Inbound), + ) + if msg.IsCall() { + labels = append(labels, jsonrpc2.RPCID.Of(fmt.Sprintf("%q", msg.ID))) + } + ctx, endSpan := event.Start(ctx, msg.Method, labels...) + event.Metric(ctx, + jsonrpc2.Started.Of(1), + jsonrpc2.ReceivedBytes.Of(msgBytes)) + + // In theory notifications cannot be cancelled, but we build them a cancel + // context anyway. + ctx, cancel := context.WithCancel(ctx) + req := &incomingRequest{ + Request: msg, + ctx: ctx, + cancel: cancel, + endSpan: endSpan, + } + + // If the request is a call, add it to the incoming map so it can be + // cancelled (or responded) by ID. + var err error + c.updateInFlight(func(s *inFlightState) { + s.incoming++ + + if req.IsCall() { + if s.incomingByID[req.ID] != nil { + err = fmt.Errorf("%w: request ID %v already in use", ErrInvalidRequest, req.ID) + req.ID = ID{} // Don't misattribute this error to the existing request. + return + } + + if s.incomingByID == nil { + s.incomingByID = make(map[ID]*incomingRequest) + } + s.incomingByID[req.ID] = req + + // When shutting down, reject all new Call requests, even if they could + // theoretically be handled by the preempter. The preempter could return + // ErrAsyncResponse, which would increase the amount of work in flight + // when we're trying to ensure that it strictly decreases. + err = s.shuttingDown(ErrServerClosing) + } + }) + if err != nil { + c.processResult("acceptRequest", req, nil, err) + return + } + + if preempter != nil { + result, err := preempter.Preempt(req.ctx, req.Request) + + if req.IsCall() && errors.Is(err, ErrAsyncResponse) { + // This request will remain in flight until Respond is called for it. + return + } + + if !errors.Is(err, ErrNotHandled) { + c.processResult("Preempt", req, result, err) + return + } + } + + c.updateInFlight(func(s *inFlightState) { + // If the connection is shutting down, don't enqueue anything to the + // handler — not even notifications. That ensures that if the handler + // continues to make progress, it will eventually become idle and + // close the connection. + err = s.shuttingDown(ErrServerClosing) + if err != nil { + return + } + + // We enqueue requests that have not been preempted to an unbounded slice. + // Unfortunately, we cannot in general limit the size of the handler + // queue: we have to read every response that comes in on the wire + // (because it may be responding to a request issued by, say, an + // asynchronous handler), and in order to get to that response we have + // to read all of the requests that came in ahead of it. + s.handlerQueue = append(s.handlerQueue, req) + if !s.handlerRunning { + // We start the handleAsync goroutine when it has work to do, and let it + // exit when the queue empties. + // + // Otherwise, in order to synchronize the handler we would need some other + // goroutine (probably readIncoming?) to explicitly wait for handleAsync + // to finish, and that would complicate error reporting: either the error + // report from the goroutine would be blocked on the handler emptying its + // queue (which was tried, and introduced a deadlock detected by + // TestCloseCallRace), or the error would need to be reported separately + // from synchronizing completion. Allowing the handler goroutine to exit + // when idle seems simpler than trying to implement either of those + // alternatives correctly. + s.handlerRunning = true + go c.handleAsync() + } + }) + if err != nil { + c.processResult("acceptRequest", req, nil, err) + } +} + +// handleAsync invokes the handler on the requests in the handler queue +// sequentially until the queue is empty. +func (c *Connection) handleAsync() { + for { + var req *incomingRequest + c.updateInFlight(func(s *inFlightState) { + if len(s.handlerQueue) > 0 { + req, s.handlerQueue = s.handlerQueue[0], s.handlerQueue[1:] + } else { + s.handlerRunning = false + } + }) + if req == nil { + return + } + + // Only deliver to the Handler if not already canceled. + if err := req.ctx.Err(); err != nil { + c.updateInFlight(func(s *inFlightState) { + if s.writeErr != nil { + // Assume that req.ctx was canceled due to s.writeErr. + // TODO(#51365): use a Context API to plumb this through req.ctx. + err = fmt.Errorf("%w: %v", ErrServerClosing, s.writeErr) + } + }) + c.processResult("handleAsync", req, nil, err) + continue + } + + result, err := c.handler.Handle(req.ctx, req.Request) + c.processResult(c.handler, req, result, err) + } +} + +// processResult processes the result of a request and, if appropriate, sends a response. +func (c *Connection) processResult(from interface{}, req *incomingRequest, result interface{}, err error) error { + switch err { + case ErrAsyncResponse: + if !req.IsCall() { + return c.internalErrorf("%#v returned ErrAsyncResponse for a %q Request without an ID", from, req.Method) + } + return nil // This request is still in flight, so don't record the result yet. + case ErrNotHandled, ErrMethodNotFound: + // Add detail describing the unhandled method. + err = fmt.Errorf("%w: %q", ErrMethodNotFound, req.Method) + } + + if req.endSpan == nil { + return c.internalErrorf("%#v produced a duplicate %q Response", from, req.Method) + } + + if result != nil && err != nil { + c.internalErrorf("%#v returned a non-nil result with a non-nil error for %s:\n%v\n%#v", from, req.Method, err, result) + result = nil // Discard the spurious result and respond with err. + } + + if req.IsCall() { + if result == nil && err == nil { + err = c.internalErrorf("%#v returned a nil result and nil error for a %q Request that requires a Response", from, req.Method) + } + + response, respErr := NewResponse(req.ID, result, err) + + // The caller could theoretically reuse the request's ID as soon as we've + // sent the response, so ensure that it is removed from the incoming map + // before sending. + c.updateInFlight(func(s *inFlightState) { + delete(s.incomingByID, req.ID) + }) + if respErr == nil { + writeErr := c.write(notDone{req.ctx}, response) + if err == nil { + err = writeErr + } + } else { + err = c.internalErrorf("%#v returned a malformed result for %q: %w", from, req.Method, respErr) + } + } else { // req is a notification + if result != nil { + err = c.internalErrorf("%#v returned a non-nil result for a %q Request without an ID", from, req.Method) + } else if err != nil { + err = fmt.Errorf("%w: %q notification failed: %v", ErrInternal, req.Method, err) + } + if err != nil { + // TODO: can/should we do anything with this error beyond writing it to the event log? + // (Is this the right label to attach to the log?) + event.Label(req.ctx, keys.Err.Of(err)) + } + } + + labelStatus(req.ctx, err) + + // Cancel the request and finalize the event span to free any associated resources. + req.cancel() + req.endSpan() + req.endSpan = nil + c.updateInFlight(func(s *inFlightState) { + if s.incoming == 0 { + panic("jsonrpc2_v2: processResult called when incoming count is already zero") + } + s.incoming-- + }) + return nil +} + +// write is used by all things that write outgoing messages, including replies. +// it makes sure that writes are atomic +func (c *Connection) write(ctx context.Context, msg Message) error { + writer := <-c.writer + defer func() { c.writer <- writer }() + n, err := writer.Write(ctx, msg) + event.Metric(ctx, jsonrpc2.SentBytes.Of(n)) + + if err != nil && ctx.Err() == nil { + // The call to Write failed, and since ctx.Err() is nil we can't attribute + // the failure (even indirectly) to Context cancellation. The writer appears + // to be broken, and future writes are likely to also fail. + // + // If the read side of the connection is also broken, we might not even be + // able to receive cancellation notifications. Since we can't reliably write + // the results of incoming calls and can't receive explicit cancellations, + // cancel the calls now. + c.updateInFlight(func(s *inFlightState) { + if s.writeErr == nil { + s.writeErr = err + for _, r := range s.incomingByID { + r.cancel() + } + } + }) + } + + return err +} + +// internalErrorf reports an internal error. By default it panics, but if +// c.onInternalError is non-nil it instead calls that and returns an error +// wrapping ErrInternal. +func (c *Connection) internalErrorf(format string, args ...interface{}) error { + err := fmt.Errorf(format, args...) + if c.onInternalError == nil { + panic("jsonrpc2: " + err.Error()) + } + c.onInternalError(err) + + return fmt.Errorf("%w: %v", ErrInternal, err) +} + +// labelStatus labels the status of the event in ctx based on whether err is nil. +func labelStatus(ctx context.Context, err error) { + if err == nil { + event.Label(ctx, jsonrpc2.StatusCode.Of("OK")) + } else { + event.Label(ctx, jsonrpc2.StatusCode.Of("ERROR")) + } +} + +// notDone is a context.Context wrapper that returns a nil Done channel. +type notDone struct{ ctx context.Context } + +func (ic notDone) Value(key interface{}) interface{} { + return ic.ctx.Value(key) +} + +func (notDone) Done() <-chan struct{} { return nil } +func (notDone) Err() error { return nil } +func (notDone) Deadline() (time.Time, bool) { return time.Time{}, false } diff --git a/contribs/gnopls/internal/jsonrpc2_v2/frame.go b/contribs/gnopls/internal/jsonrpc2_v2/frame.go new file mode 100644 index 00000000000..e4248328132 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/frame.go @@ -0,0 +1,183 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "strconv" + "strings" +) + +// Reader abstracts the transport mechanics from the JSON RPC protocol. +// A Conn reads messages from the reader it was provided on construction, +// and assumes that each call to Read fully transfers a single message, +// or returns an error. +// A reader is not safe for concurrent use, it is expected it will be used by +// a single Conn in a safe manner. +type Reader interface { + // Read gets the next message from the stream. + Read(context.Context) (Message, int64, error) +} + +// Writer abstracts the transport mechanics from the JSON RPC protocol. +// A Conn writes messages using the writer it was provided on construction, +// and assumes that each call to Write fully transfers a single message, +// or returns an error. +// A writer is not safe for concurrent use, it is expected it will be used by +// a single Conn in a safe manner. +type Writer interface { + // Write sends a message to the stream. + Write(context.Context, Message) (int64, error) +} + +// Framer wraps low level byte readers and writers into jsonrpc2 message +// readers and writers. +// It is responsible for the framing and encoding of messages into wire form. +type Framer interface { + // Reader wraps a byte reader into a message reader. + Reader(rw io.Reader) Reader + // Writer wraps a byte writer into a message writer. + Writer(rw io.Writer) Writer +} + +// RawFramer returns a new Framer. +// The messages are sent with no wrapping, and rely on json decode consistency +// to determine message boundaries. +func RawFramer() Framer { return rawFramer{} } + +type rawFramer struct{} +type rawReader struct{ in *json.Decoder } +type rawWriter struct{ out io.Writer } + +func (rawFramer) Reader(rw io.Reader) Reader { + return &rawReader{in: json.NewDecoder(rw)} +} + +func (rawFramer) Writer(rw io.Writer) Writer { + return &rawWriter{out: rw} +} + +func (r *rawReader) Read(ctx context.Context) (Message, int64, error) { + select { + case <-ctx.Done(): + return nil, 0, ctx.Err() + default: + } + var raw json.RawMessage + if err := r.in.Decode(&raw); err != nil { + return nil, 0, err + } + msg, err := DecodeMessage(raw) + return msg, int64(len(raw)), err +} + +func (w *rawWriter) Write(ctx context.Context, msg Message) (int64, error) { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + data, err := EncodeMessage(msg) + if err != nil { + return 0, fmt.Errorf("marshaling message: %v", err) + } + n, err := w.out.Write(data) + return int64(n), err +} + +// HeaderFramer returns a new Framer. +// The messages are sent with HTTP content length and MIME type headers. +// This is the format used by LSP and others. +func HeaderFramer() Framer { return headerFramer{} } + +type headerFramer struct{} +type headerReader struct{ in *bufio.Reader } +type headerWriter struct{ out io.Writer } + +func (headerFramer) Reader(rw io.Reader) Reader { + return &headerReader{in: bufio.NewReader(rw)} +} + +func (headerFramer) Writer(rw io.Writer) Writer { + return &headerWriter{out: rw} +} + +func (r *headerReader) Read(ctx context.Context) (Message, int64, error) { + select { + case <-ctx.Done(): + return nil, 0, ctx.Err() + default: + } + var total, length int64 + // read the header, stop on the first empty line + for { + line, err := r.in.ReadString('\n') + total += int64(len(line)) + if err != nil { + if err == io.EOF { + if total == 0 { + return nil, 0, io.EOF + } + err = io.ErrUnexpectedEOF + } + return nil, total, fmt.Errorf("failed reading header line: %w", err) + } + line = strings.TrimSpace(line) + // check we have a header line + if line == "" { + break + } + colon := strings.IndexRune(line, ':') + if colon < 0 { + return nil, total, fmt.Errorf("invalid header line %q", line) + } + name, value := line[:colon], strings.TrimSpace(line[colon+1:]) + switch name { + case "Content-Length": + if length, err = strconv.ParseInt(value, 10, 32); err != nil { + return nil, total, fmt.Errorf("failed parsing Content-Length: %v", value) + } + if length <= 0 { + return nil, total, fmt.Errorf("invalid Content-Length: %v", length) + } + default: + // ignoring unknown headers + } + } + if length == 0 { + return nil, total, fmt.Errorf("missing Content-Length header") + } + data := make([]byte, length) + n, err := io.ReadFull(r.in, data) + total += int64(n) + if err != nil { + return nil, total, err + } + msg, err := DecodeMessage(data) + return msg, total, err +} + +func (w *headerWriter) Write(ctx context.Context, msg Message) (int64, error) { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + data, err := EncodeMessage(msg) + if err != nil { + return 0, fmt.Errorf("marshaling message: %v", err) + } + n, err := fmt.Fprintf(w.out, "Content-Length: %v\r\n\r\n", len(data)) + total := int64(n) + if err == nil { + n, err = w.out.Write(data) + total += int64(n) + } + return total, err +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/jsonrpc2.go b/contribs/gnopls/internal/jsonrpc2_v2/jsonrpc2.go new file mode 100644 index 00000000000..e9164b0bc95 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/jsonrpc2.go @@ -0,0 +1,137 @@ +// Copyright 2018 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 jsonrpc2 is a minimal implementation of the JSON RPC 2 spec. +// https://www.jsonrpc.org/specification +// It is intended to be compatible with other implementations at the wire level. +package jsonrpc2 + +import ( + "context" + "errors" +) + +var ( + // ErrIdleTimeout is returned when serving timed out waiting for new connections. + ErrIdleTimeout = errors.New("timed out waiting for new connections") + + // ErrNotHandled is returned from a Handler or Preempter to indicate it did + // not handle the request. + // + // If a Handler returns ErrNotHandled, the server replies with + // ErrMethodNotFound. + ErrNotHandled = errors.New("JSON RPC not handled") + + // ErrAsyncResponse is returned from a handler to indicate it will generate a + // response asynchronously. + // + // ErrAsyncResponse must not be returned for notifications, + // which do not receive responses. + ErrAsyncResponse = errors.New("JSON RPC asynchronous response") +) + +// Preempter handles messages on a connection before they are queued to the main +// handler. +// Primarily this is used for cancel handlers or notifications for which out of +// order processing is not an issue. +type Preempter interface { + // Preempt is invoked for each incoming request before it is queued for handling. + // + // If Preempt returns ErrNotHandled, the request will be queued, + // and eventually passed to a Handle call. + // + // Otherwise, the result and error are processed as if returned by Handle. + // + // Preempt must not block. (The Context passed to it is for Values only.) + Preempt(ctx context.Context, req *Request) (result interface{}, err error) +} + +// A PreempterFunc implements the Preempter interface for a standalone Preempt function. +type PreempterFunc func(ctx context.Context, req *Request) (interface{}, error) + +func (f PreempterFunc) Preempt(ctx context.Context, req *Request) (interface{}, error) { + return f(ctx, req) +} + +var _ Preempter = PreempterFunc(nil) + +// Handler handles messages on a connection. +type Handler interface { + // Handle is invoked sequentially for each incoming request that has not + // already been handled by a Preempter. + // + // If the Request has a nil ID, Handle must return a nil result, + // and any error may be logged but will not be reported to the caller. + // + // If the Request has a non-nil ID, Handle must return either a + // non-nil, JSON-marshalable result, or a non-nil error. + // + // The Context passed to Handle will be canceled if the + // connection is broken or the request is canceled or completed. + // (If Handle returns ErrAsyncResponse, ctx will remain uncanceled + // until either Cancel or Respond is called for the request's ID.) + Handle(ctx context.Context, req *Request) (result interface{}, err error) +} + +type defaultHandler struct{} + +func (defaultHandler) Preempt(context.Context, *Request) (interface{}, error) { + return nil, ErrNotHandled +} + +func (defaultHandler) Handle(context.Context, *Request) (interface{}, error) { + return nil, ErrNotHandled +} + +// A HandlerFunc implements the Handler interface for a standalone Handle function. +type HandlerFunc func(ctx context.Context, req *Request) (interface{}, error) + +func (f HandlerFunc) Handle(ctx context.Context, req *Request) (interface{}, error) { + return f(ctx, req) +} + +var _ Handler = HandlerFunc(nil) + +// async is a small helper for operations with an asynchronous result that you +// can wait for. +type async struct { + ready chan struct{} // closed when done + firstErr chan error // 1-buffered; contains either nil or the first non-nil error +} + +func newAsync() *async { + var a async + a.ready = make(chan struct{}) + a.firstErr = make(chan error, 1) + a.firstErr <- nil + return &a +} + +func (a *async) done() { + close(a.ready) +} + +func (a *async) isDone() bool { + select { + case <-a.ready: + return true + default: + return false + } +} + +func (a *async) wait() error { + <-a.ready + err := <-a.firstErr + a.firstErr <- err + return err +} + +func (a *async) setError(err error) { + storedErr := <-a.firstErr + if storedErr == nil { + storedErr = err + } + a.firstErr <- storedErr +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/jsonrpc2_test.go b/contribs/gnopls/internal/jsonrpc2_v2/jsonrpc2_test.go new file mode 100644 index 00000000000..409280e22d2 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/jsonrpc2_test.go @@ -0,0 +1,388 @@ +// Copyright 2018 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 jsonrpc2_test + +import ( + "context" + "encoding/json" + "fmt" + "path" + "reflect" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/event/export/eventtest" + jsonrpc2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" + "github.com/gnolang/gno/contribs/gnopls/internal/stack/stacktest" +) + +var callTests = []invoker{ + call{"no_args", nil, true}, + call{"one_string", "fish", "got:fish"}, + call{"one_number", 10, "got:10"}, + call{"join", []string{"a", "b", "c"}, "a/b/c"}, + sequence{"notify", []invoker{ + notify{"set", 3}, + notify{"add", 5}, + call{"get", nil, 8}, + }}, + sequence{"preempt", []invoker{ + async{"a", "wait", "a"}, + notify{"unblock", "a"}, + collect{"a", true, false}, + }}, + sequence{"basic cancel", []invoker{ + async{"b", "wait", "b"}, + cancel{"b"}, + collect{"b", nil, true}, + }}, + sequence{"queue", []invoker{ + async{"a", "wait", "a"}, + notify{"set", 1}, + notify{"add", 2}, + notify{"add", 3}, + notify{"add", 4}, + call{"peek", nil, 0}, // accumulator will not have any adds yet + notify{"unblock", "a"}, + collect{"a", true, false}, + call{"get", nil, 10}, // accumulator now has all the adds + }}, + sequence{"fork", []invoker{ + async{"a", "fork", "a"}, + notify{"set", 1}, + notify{"add", 2}, + notify{"add", 3}, + notify{"add", 4}, + call{"get", nil, 10}, // fork will not have blocked the adds + notify{"unblock", "a"}, + collect{"a", true, false}, + }}, + sequence{"concurrent", []invoker{ + async{"a", "fork", "a"}, + notify{"unblock", "a"}, + async{"b", "fork", "b"}, + notify{"unblock", "b"}, + collect{"a", true, false}, + collect{"b", true, false}, + }}, +} + +type binder struct { + framer jsonrpc2.Framer + runTest func(*handler) +} + +type handler struct { + conn *jsonrpc2.Connection + accumulator int + waiters chan map[string]chan struct{} + calls map[string]*jsonrpc2.AsyncCall +} + +type invoker interface { + Name() string + Invoke(t *testing.T, ctx context.Context, h *handler) +} + +type notify struct { + method string + params interface{} +} + +type call struct { + method string + params interface{} + expect interface{} +} + +type async struct { + name string + method string + params interface{} +} + +type collect struct { + name string + expect interface{} + fails bool +} + +type cancel struct { + name string +} + +type sequence struct { + name string + tests []invoker +} + +type echo call + +type cancelParams struct{ ID int64 } + +func TestConnectionRaw(t *testing.T) { + testConnection(t, jsonrpc2.RawFramer()) +} + +func TestConnectionHeader(t *testing.T) { + testConnection(t, jsonrpc2.HeaderFramer()) +} + +func testConnection(t *testing.T, framer jsonrpc2.Framer) { + stacktest.NoLeak(t) + ctx := eventtest.NewContext(context.Background(), t) + listener, err := jsonrpc2.NetPipeListener(ctx) + if err != nil { + t.Fatal(err) + } + server := jsonrpc2.NewServer(ctx, listener, binder{framer, nil}) + defer func() { + listener.Close() + server.Wait() + }() + + for _, test := range callTests { + t.Run(test.Name(), func(t *testing.T) { + client, err := jsonrpc2.Dial(ctx, + listener.Dialer(), binder{framer, func(h *handler) { + defer h.conn.Close() + ctx := eventtest.NewContext(ctx, t) + test.Invoke(t, ctx, h) + if call, ok := test.(*call); ok { + // also run all simple call tests in echo mode + (*echo)(call).Invoke(t, ctx, h) + } + }}) + if err != nil { + t.Fatal(err) + } + client.Wait() + }) + } +} + +func (test notify) Name() string { return test.method } +func (test notify) Invoke(t *testing.T, ctx context.Context, h *handler) { + if err := h.conn.Notify(ctx, test.method, test.params); err != nil { + t.Fatalf("%v:Notify failed: %v", test.method, err) + } +} + +func (test call) Name() string { return test.method } +func (test call) Invoke(t *testing.T, ctx context.Context, h *handler) { + results := newResults(test.expect) + if err := h.conn.Call(ctx, test.method, test.params).Await(ctx, results); err != nil { + t.Fatalf("%v:Call failed: %v", test.method, err) + } + verifyResults(t, test.method, results, test.expect) +} + +func (test echo) Invoke(t *testing.T, ctx context.Context, h *handler) { + results := newResults(test.expect) + if err := h.conn.Call(ctx, "echo", []interface{}{test.method, test.params}).Await(ctx, results); err != nil { + t.Fatalf("%v:Echo failed: %v", test.method, err) + } + verifyResults(t, test.method, results, test.expect) +} + +func (test async) Name() string { return test.name } +func (test async) Invoke(t *testing.T, ctx context.Context, h *handler) { + h.calls[test.name] = h.conn.Call(ctx, test.method, test.params) +} + +func (test collect) Name() string { return test.name } +func (test collect) Invoke(t *testing.T, ctx context.Context, h *handler) { + o := h.calls[test.name] + results := newResults(test.expect) + err := o.Await(ctx, results) + switch { + case test.fails && err == nil: + t.Fatalf("%v:Collect was supposed to fail", test.name) + case !test.fails && err != nil: + t.Fatalf("%v:Collect failed: %v", test.name, err) + } + verifyResults(t, test.name, results, test.expect) +} + +func (test cancel) Name() string { return test.name } +func (test cancel) Invoke(t *testing.T, ctx context.Context, h *handler) { + o := h.calls[test.name] + if err := h.conn.Notify(ctx, "cancel", &cancelParams{o.ID().Raw().(int64)}); err != nil { + t.Fatalf("%v:Collect failed: %v", test.name, err) + } +} + +func (test sequence) Name() string { return test.name } +func (test sequence) Invoke(t *testing.T, ctx context.Context, h *handler) { + for _, child := range test.tests { + child.Invoke(t, ctx, h) + } +} + +// newResults makes a new empty copy of the expected type to put the results into +func newResults(expect interface{}) interface{} { + switch e := expect.(type) { + case []interface{}: + var r []interface{} + for _, v := range e { + r = append(r, reflect.New(reflect.TypeOf(v)).Interface()) + } + return r + case nil: + return nil + default: + return reflect.New(reflect.TypeOf(expect)).Interface() + } +} + +// verifyResults compares the results to the expected values +func verifyResults(t *testing.T, method string, results interface{}, expect interface{}) { + if expect == nil { + if results != nil { + t.Errorf("%v:Got results %+v where none expected", method, expect) + } + return + } + val := reflect.Indirect(reflect.ValueOf(results)).Interface() + if !reflect.DeepEqual(val, expect) { + t.Errorf("%v:Results are incorrect, got %+v expect %+v", method, val, expect) + } +} + +func (b binder) Bind(ctx context.Context, conn *jsonrpc2.Connection) jsonrpc2.ConnectionOptions { + h := &handler{ + conn: conn, + waiters: make(chan map[string]chan struct{}, 1), + calls: make(map[string]*jsonrpc2.AsyncCall), + } + h.waiters <- make(map[string]chan struct{}) + if b.runTest != nil { + go b.runTest(h) + } + return jsonrpc2.ConnectionOptions{ + Framer: b.framer, + Preempter: h, + Handler: h, + } +} + +func (h *handler) waiter(name string) chan struct{} { + waiters := <-h.waiters + defer func() { h.waiters <- waiters }() + waiter, found := waiters[name] + if !found { + waiter = make(chan struct{}) + waiters[name] = waiter + } + return waiter +} + +func (h *handler) Preempt(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) { + switch req.Method { + case "unblock": + var name string + if err := json.Unmarshal(req.Params, &name); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + close(h.waiter(name)) + return nil, nil + case "peek": + if len(req.Params) > 0 { + return nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams) + } + return h.accumulator, nil + case "cancel": + var params cancelParams + if err := json.Unmarshal(req.Params, ¶ms); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + h.conn.Cancel(jsonrpc2.Int64ID(params.ID)) + return nil, nil + default: + return nil, jsonrpc2.ErrNotHandled + } +} + +func (h *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) { + switch req.Method { + case "no_args": + if len(req.Params) > 0 { + return nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams) + } + return true, nil + case "one_string": + var v string + if err := json.Unmarshal(req.Params, &v); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + return "got:" + v, nil + case "one_number": + var v int + if err := json.Unmarshal(req.Params, &v); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + return fmt.Sprintf("got:%d", v), nil + case "set": + var v int + if err := json.Unmarshal(req.Params, &v); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + h.accumulator = v + return nil, nil + case "add": + var v int + if err := json.Unmarshal(req.Params, &v); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + h.accumulator += v + return nil, nil + case "get": + if len(req.Params) > 0 { + return nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams) + } + return h.accumulator, nil + case "join": + var v []string + if err := json.Unmarshal(req.Params, &v); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + return path.Join(v...), nil + case "echo": + var v []interface{} + if err := json.Unmarshal(req.Params, &v); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + var result interface{} + err := h.conn.Call(ctx, v[0].(string), v[1]).Await(ctx, &result) + return result, err + case "wait": + var name string + if err := json.Unmarshal(req.Params, &name); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + select { + case <-h.waiter(name): + return true, nil + case <-ctx.Done(): + return nil, ctx.Err() + } + case "fork": + var name string + if err := json.Unmarshal(req.Params, &name); err != nil { + return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) + } + waitFor := h.waiter(name) + go func() { + select { + case <-waitFor: + h.conn.Respond(req.ID, true, nil) + case <-ctx.Done(): + h.conn.Respond(req.ID, nil, ctx.Err()) + } + }() + return nil, jsonrpc2.ErrAsyncResponse + default: + return nil, jsonrpc2.ErrNotHandled + } +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/messages.go b/contribs/gnopls/internal/jsonrpc2_v2/messages.go new file mode 100644 index 00000000000..f02b879c3f2 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/messages.go @@ -0,0 +1,181 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "encoding/json" + "errors" + "fmt" +) + +// ID is a Request identifier. +type ID struct { + value interface{} +} + +// Message is the interface to all jsonrpc2 message types. +// They share no common functionality, but are a closed set of concrete types +// that are allowed to implement this interface. The message types are *Request +// and *Response. +type Message interface { + // marshal builds the wire form from the API form. + // It is private, which makes the set of Message implementations closed. + marshal(to *wireCombined) +} + +// Request is a Message sent to a peer to request behavior. +// If it has an ID it is a call, otherwise it is a notification. +type Request struct { + // ID of this request, used to tie the Response back to the request. + // This will be nil for notifications. + ID ID + // Method is a string containing the method name to invoke. + Method string + // Params is either a struct or an array with the parameters of the method. + Params json.RawMessage +} + +// Response is a Message used as a reply to a call Request. +// It will have the same ID as the call it is a response to. +type Response struct { + // result is the content of the response. + Result json.RawMessage + // err is set only if the call failed. + Error error + // id of the request this is a response to. + ID ID +} + +// StringID creates a new string request identifier. +func StringID(s string) ID { return ID{value: s} } + +// Int64ID creates a new integer request identifier. +func Int64ID(i int64) ID { return ID{value: i} } + +// IsValid returns true if the ID is a valid identifier. +// The default value for ID will return false. +func (id ID) IsValid() bool { return id.value != nil } + +// Raw returns the underlying value of the ID. +func (id ID) Raw() interface{} { return id.value } + +// NewNotification constructs a new Notification message for the supplied +// method and parameters. +func NewNotification(method string, params interface{}) (*Request, error) { + p, merr := marshalToRaw(params) + return &Request{Method: method, Params: p}, merr +} + +// NewCall constructs a new Call message for the supplied ID, method and +// parameters. +func NewCall(id ID, method string, params interface{}) (*Request, error) { + p, merr := marshalToRaw(params) + return &Request{ID: id, Method: method, Params: p}, merr +} + +func (msg *Request) IsCall() bool { return msg.ID.IsValid() } + +func (msg *Request) marshal(to *wireCombined) { + to.ID = msg.ID.value + to.Method = msg.Method + to.Params = msg.Params +} + +// NewResponse constructs a new Response message that is a reply to the +// supplied. If err is set result may be ignored. +func NewResponse(id ID, result interface{}, rerr error) (*Response, error) { + r, merr := marshalToRaw(result) + return &Response{ID: id, Result: r, Error: rerr}, merr +} + +func (msg *Response) marshal(to *wireCombined) { + to.ID = msg.ID.value + to.Error = toWireError(msg.Error) + to.Result = msg.Result +} + +func toWireError(err error) *WireError { + if err == nil { + // no error, the response is complete + return nil + } + if err, ok := err.(*WireError); ok { + // already a wire error, just use it + return err + } + result := &WireError{Message: err.Error()} + var wrapped *WireError + if errors.As(err, &wrapped) { + // if we wrapped a wire error, keep the code from the wrapped error + // but the message from the outer error + result.Code = wrapped.Code + } + return result +} + +func EncodeMessage(msg Message) ([]byte, error) { + wire := wireCombined{VersionTag: wireVersion} + msg.marshal(&wire) + data, err := json.Marshal(&wire) + if err != nil { + return data, fmt.Errorf("marshaling jsonrpc message: %w", err) + } + return data, nil +} + +func DecodeMessage(data []byte) (Message, error) { + msg := wireCombined{} + if err := json.Unmarshal(data, &msg); err != nil { + return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err) + } + if msg.VersionTag != wireVersion { + return nil, fmt.Errorf("invalid message version tag %s expected %s", msg.VersionTag, wireVersion) + } + id := ID{} + switch v := msg.ID.(type) { + case nil: + case float64: + // coerce the id type to int64 if it is float64, the spec does not allow fractional parts + id = Int64ID(int64(v)) + case int64: + id = Int64ID(v) + case string: + id = StringID(v) + default: + return nil, fmt.Errorf("invalid message id type <%T>%v", v, v) + } + if msg.Method != "" { + // has a method, must be a call + return &Request{ + Method: msg.Method, + ID: id, + Params: msg.Params, + }, nil + } + // no method, should be a response + if !id.IsValid() { + return nil, ErrInvalidRequest + } + resp := &Response{ + ID: id, + Result: msg.Result, + } + // we have to check if msg.Error is nil to avoid a typed error + if msg.Error != nil { + resp.Error = msg.Error + } + return resp, nil +} + +func marshalToRaw(obj interface{}) (json.RawMessage, error) { + if obj == nil { + return nil, nil + } + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + return json.RawMessage(data), nil +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/net.go b/contribs/gnopls/internal/jsonrpc2_v2/net.go new file mode 100644 index 00000000000..15d0aea3af0 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/net.go @@ -0,0 +1,138 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "context" + "io" + "net" + "os" +) + +// This file contains implementations of the transport primitives that use the standard network +// package. + +// NetListenOptions is the optional arguments to the NetListen function. +type NetListenOptions struct { + NetListenConfig net.ListenConfig + NetDialer net.Dialer +} + +// NetListener returns a new Listener that listens on a socket using the net package. +func NetListener(ctx context.Context, network, address string, options NetListenOptions) (Listener, error) { + ln, err := options.NetListenConfig.Listen(ctx, network, address) + if err != nil { + return nil, err + } + return &netListener{net: ln}, nil +} + +// netListener is the implementation of Listener for connections made using the net package. +type netListener struct { + net net.Listener +} + +// Accept blocks waiting for an incoming connection to the listener. +func (l *netListener) Accept(context.Context) (io.ReadWriteCloser, error) { + return l.net.Accept() +} + +// Close will cause the listener to stop listening. It will not close any connections that have +// already been accepted. +func (l *netListener) Close() error { + addr := l.net.Addr() + err := l.net.Close() + if addr.Network() == "unix" { + rerr := os.Remove(addr.String()) + if rerr != nil && err == nil { + err = rerr + } + } + return err +} + +// Dialer returns a dialer that can be used to connect to the listener. +func (l *netListener) Dialer() Dialer { + return NetDialer(l.net.Addr().Network(), l.net.Addr().String(), net.Dialer{}) +} + +// NetDialer returns a Dialer using the supplied standard network dialer. +func NetDialer(network, address string, nd net.Dialer) Dialer { + return &netDialer{ + network: network, + address: address, + dialer: nd, + } +} + +type netDialer struct { + network string + address string + dialer net.Dialer +} + +func (n *netDialer) Dial(ctx context.Context) (io.ReadWriteCloser, error) { + return n.dialer.DialContext(ctx, n.network, n.address) +} + +// NetPipeListener returns a new Listener that listens using net.Pipe. +// It is only possibly to connect to it using the Dialer returned by the +// Dialer method, each call to that method will generate a new pipe the other +// side of which will be returned from the Accept call. +func NetPipeListener(ctx context.Context) (Listener, error) { + return &netPiper{ + done: make(chan struct{}), + dialed: make(chan io.ReadWriteCloser), + }, nil +} + +// netPiper is the implementation of Listener build on top of net.Pipes. +type netPiper struct { + done chan struct{} + dialed chan io.ReadWriteCloser +} + +// Accept blocks waiting for an incoming connection to the listener. +func (l *netPiper) Accept(context.Context) (io.ReadWriteCloser, error) { + // Block until the pipe is dialed or the listener is closed, + // preferring the latter if already closed at the start of Accept. + select { + case <-l.done: + return nil, errClosed + default: + } + select { + case rwc := <-l.dialed: + return rwc, nil + case <-l.done: + return nil, errClosed + } +} + +// Close will cause the listener to stop listening. It will not close any connections that have +// already been accepted. +func (l *netPiper) Close() error { + // unblock any accept calls that are pending + close(l.done) + return nil +} + +func (l *netPiper) Dialer() Dialer { + return l +} + +func (l *netPiper) Dial(ctx context.Context) (io.ReadWriteCloser, error) { + client, server := net.Pipe() + + select { + case l.dialed <- server: + return client, nil + + case <-l.done: + client.Close() + server.Close() + return nil, errClosed + } +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/serve.go b/contribs/gnopls/internal/jsonrpc2_v2/serve.go new file mode 100644 index 00000000000..7bac0103e8f --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/serve.go @@ -0,0 +1,328 @@ +// 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 jsonrpc2 + +import ( + "context" + "fmt" + "io" + "runtime" + "sync" + "sync/atomic" + "time" +) + +// Listener is implemented by protocols to accept new inbound connections. +type Listener interface { + // Accept accepts an inbound connection to a server. + // It blocks until either an inbound connection is made, or the listener is closed. + Accept(context.Context) (io.ReadWriteCloser, error) + + // Close closes the listener. + // Any blocked Accept or Dial operations will unblock and return errors. + Close() error + + // Dialer returns a dialer that can be used to connect to this listener + // locally. + // If a listener does not implement this it will return nil. + Dialer() Dialer +} + +// Dialer is used by clients to dial a server. +type Dialer interface { + // Dial returns a new communication byte stream to a listening server. + Dial(ctx context.Context) (io.ReadWriteCloser, error) +} + +// Server is a running server that is accepting incoming connections. +type Server struct { + listener Listener + binder Binder + async *async + + shutdownOnce sync.Once + closing int32 // atomic: set to nonzero when Shutdown is called +} + +// Dial uses the dialer to make a new connection, wraps the returned +// reader and writer using the framer to make a stream, and then builds +// a connection on top of that stream using the binder. +// +// The returned Connection will operate independently using the Preempter and/or +// Handler provided by the Binder, and will release its own resources when the +// connection is broken, but the caller may Close it earlier to stop accepting +// (or sending) new requests. +func Dial(ctx context.Context, dialer Dialer, binder Binder) (*Connection, error) { + // dial a server + rwc, err := dialer.Dial(ctx) + if err != nil { + return nil, err + } + return newConnection(ctx, rwc, binder, nil), nil +} + +// NewServer starts a new server listening for incoming connections and returns +// it. +// This returns a fully running and connected server, it does not block on +// the listener. +// You can call Wait to block on the server, or Shutdown to get the sever to +// terminate gracefully. +// To notice incoming connections, use an intercepting Binder. +func NewServer(ctx context.Context, listener Listener, binder Binder) *Server { + server := &Server{ + listener: listener, + binder: binder, + async: newAsync(), + } + go server.run(ctx) + return server +} + +// Wait returns only when the server has shut down. +func (s *Server) Wait() error { + return s.async.wait() +} + +// Shutdown informs the server to stop accepting new connections. +func (s *Server) Shutdown() { + s.shutdownOnce.Do(func() { + atomic.StoreInt32(&s.closing, 1) + s.listener.Close() + }) +} + +// run accepts incoming connections from the listener, +// If IdleTimeout is non-zero, run exits after there are no clients for this +// duration, otherwise it exits only on error. +func (s *Server) run(ctx context.Context) { + defer s.async.done() + + var activeConns sync.WaitGroup + for { + rwc, err := s.listener.Accept(ctx) + if err != nil { + // Only Shutdown closes the listener. If we get an error after Shutdown is + // called, assume that was the cause and don't report the error; + // otherwise, report the error in case it is unexpected. + if atomic.LoadInt32(&s.closing) == 0 { + s.async.setError(err) + } + // We are done generating new connections for good. + break + } + + // A new inbound connection. + activeConns.Add(1) + _ = newConnection(ctx, rwc, s.binder, activeConns.Done) // unregisters itself when done + } + activeConns.Wait() +} + +// NewIdleListener wraps a listener with an idle timeout. +// +// When there are no active connections for at least the timeout duration, +// calls to Accept will fail with ErrIdleTimeout. +// +// A connection is considered inactive as soon as its Close method is called. +func NewIdleListener(timeout time.Duration, wrap Listener) Listener { + l := &idleListener{ + wrapped: wrap, + timeout: timeout, + active: make(chan int, 1), + timedOut: make(chan struct{}), + idleTimer: make(chan *time.Timer, 1), + } + l.idleTimer <- time.AfterFunc(l.timeout, l.timerExpired) + return l +} + +type idleListener struct { + wrapped Listener + timeout time.Duration + + // Only one of these channels is receivable at any given time. + active chan int // count of active connections; closed when Close is called if not timed out + timedOut chan struct{} // closed when the idle timer expires + idleTimer chan *time.Timer // holds the timer only when idle +} + +// Accept accepts an incoming connection. +// +// If an incoming connection is accepted concurrent to the listener being closed +// due to idleness, the new connection is immediately closed. +func (l *idleListener) Accept(ctx context.Context) (io.ReadWriteCloser, error) { + rwc, err := l.wrapped.Accept(ctx) + + select { + case n, ok := <-l.active: + if err != nil { + if ok { + l.active <- n + } + return nil, err + } + if ok { + l.active <- n + 1 + } else { + // l.wrapped.Close Close has been called, but Accept returned a + // connection. This race can occur with concurrent Accept and Close calls + // with any net.Listener, and it is benign: since the listener was closed + // explicitly, it can't have also timed out. + } + return l.newConn(rwc), nil + + case <-l.timedOut: + if err == nil { + // Keeping the connection open would leave the listener simultaneously + // active and closed due to idleness, which would be contradictory and + // confusing. Close the connection and pretend that it never happened. + rwc.Close() + } else { + // In theory the timeout could have raced with an unrelated error return + // from Accept. However, ErrIdleTimeout is arguably still valid (since we + // would have closed due to the timeout independent of the error), and the + // harm from returning a spurious ErrIdleTimeout is negligible anyway. + } + return nil, ErrIdleTimeout + + case timer := <-l.idleTimer: + if err != nil { + // The idle timer doesn't run until it receives itself from the idleTimer + // channel, so it can't have called l.wrapped.Close yet and thus err can't + // be ErrIdleTimeout. Leave the idle timer as it was and return whatever + // error we got. + l.idleTimer <- timer + return nil, err + } + + if !timer.Stop() { + // Failed to stop the timer — the timer goroutine is in the process of + // firing. Send the timer back to the timer goroutine so that it can + // safely close the timedOut channel, and then wait for the listener to + // actually be closed before we return ErrIdleTimeout. + l.idleTimer <- timer + rwc.Close() + <-l.timedOut + return nil, ErrIdleTimeout + } + + l.active <- 1 + return l.newConn(rwc), nil + } +} + +func (l *idleListener) Close() error { + select { + case _, ok := <-l.active: + if ok { + close(l.active) + } + + case <-l.timedOut: + // Already closed by the timer; take care not to double-close if the caller + // only explicitly invokes this Close method once, since the io.Closer + // interface explicitly leaves doubled Close calls undefined. + return ErrIdleTimeout + + case timer := <-l.idleTimer: + if !timer.Stop() { + // Couldn't stop the timer. It shouldn't take long to run, so just wait + // (so that the Listener is guaranteed to be closed before we return) + // and pretend that this call happened afterward. + // That way we won't leak any timers or goroutines when Close returns. + l.idleTimer <- timer + <-l.timedOut + return ErrIdleTimeout + } + close(l.active) + } + + return l.wrapped.Close() +} + +func (l *idleListener) Dialer() Dialer { + return l.wrapped.Dialer() +} + +func (l *idleListener) timerExpired() { + select { + case n, ok := <-l.active: + if ok { + panic(fmt.Sprintf("jsonrpc2: idleListener idle timer fired with %d connections still active", n)) + } else { + panic("jsonrpc2: Close finished with idle timer still running") + } + + case <-l.timedOut: + panic("jsonrpc2: idleListener idle timer fired more than once") + + case <-l.idleTimer: + // The timer for this very call! + } + + // Close the Listener with all channels still blocked to ensure that this call + // to l.wrapped.Close doesn't race with the one in l.Close. + defer close(l.timedOut) + l.wrapped.Close() +} + +func (l *idleListener) connClosed() { + select { + case n, ok := <-l.active: + if !ok { + // l is already closed, so it can't close due to idleness, + // and we don't need to track the number of active connections any more. + return + } + n-- + if n == 0 { + l.idleTimer <- time.AfterFunc(l.timeout, l.timerExpired) + } else { + l.active <- n + } + + case <-l.timedOut: + panic("jsonrpc2: idleListener idle timer fired before last active connection was closed") + + case <-l.idleTimer: + panic("jsonrpc2: idleListener idle timer active before last active connection was closed") + } +} + +type idleListenerConn struct { + wrapped io.ReadWriteCloser + l *idleListener + closeOnce sync.Once +} + +func (l *idleListener) newConn(rwc io.ReadWriteCloser) *idleListenerConn { + c := &idleListenerConn{ + wrapped: rwc, + l: l, + } + + // A caller that forgets to call Close may disrupt the idleListener's + // accounting, even though the file descriptor for the underlying connection + // may eventually be garbage-collected anyway. + // + // Set a (best-effort) finalizer to verify that a Close call always occurs. + // (We will clear the finalizer explicitly in Close.) + runtime.SetFinalizer(c, func(c *idleListenerConn) { + panic("jsonrpc2: IdleListener connection became unreachable without a call to Close") + }) + + return c +} + +func (c *idleListenerConn) Read(p []byte) (int, error) { return c.wrapped.Read(p) } +func (c *idleListenerConn) Write(p []byte) (int, error) { return c.wrapped.Write(p) } + +func (c *idleListenerConn) Close() error { + defer c.closeOnce.Do(func() { + c.l.connClosed() + runtime.SetFinalizer(c, nil) + }) + return c.wrapped.Close() +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/serve_go116.go b/contribs/gnopls/internal/jsonrpc2_v2/serve_go116.go new file mode 100644 index 00000000000..29549f1059d --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/serve_go116.go @@ -0,0 +1,19 @@ +// 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.16 +// +build go1.16 + +package jsonrpc2 + +import ( + "errors" + "net" +) + +var errClosed = net.ErrClosed + +func isErrClosed(err error) bool { + return errors.Is(err, errClosed) +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/serve_pre116.go b/contribs/gnopls/internal/jsonrpc2_v2/serve_pre116.go new file mode 100644 index 00000000000..a1801d8a200 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/serve_pre116.go @@ -0,0 +1,30 @@ +// 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. + +//go:build !go1.16 +// +build !go1.16 + +package jsonrpc2 + +import ( + "errors" + "strings" +) + +// errClosed is an error with the same string as net.ErrClosed, +// which was added in Go 1.16. +var errClosed = errors.New("use of closed network connection") + +// isErrClosed reports whether err ends in the same string as errClosed. +func isErrClosed(err error) bool { + // As of Go 1.16, this could be 'errors.Is(err, net.ErrClosing)', but + // unfortunately gopls still requires compatibility with + // (otherwise-unsupported) older Go versions. + // + // In the meantime, this error string has not changed on any supported Go + // version, and is not expected to change in the future. + // This is not ideal, but since the worst that could happen here is some + // superfluous logging, it is acceptable. + return strings.HasSuffix(err.Error(), "use of closed network connection") +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/serve_test.go b/contribs/gnopls/internal/jsonrpc2_v2/serve_test.go new file mode 100644 index 00000000000..a6edf717900 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/serve_test.go @@ -0,0 +1,347 @@ +// 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 jsonrpc2_test + +import ( + "context" + "errors" + "fmt" + "runtime/debug" + "testing" + "time" + + jsonrpc2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" + "github.com/gnolang/gno/contribs/gnopls/internal/stack/stacktest" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +func TestIdleTimeout(t *testing.T) { + testenv.NeedsLocalhostNet(t) + stacktest.NoLeak(t) + + // Use a panicking time.AfterFunc instead of context.WithTimeout so that we + // get a goroutine dump on failure. We expect the test to take on the order of + // a few tens of milliseconds at most, so 10s should be several orders of + // magnitude of headroom. + timer := time.AfterFunc(10*time.Second, func() { + debug.SetTraceback("all") + panic("TestIdleTimeout deadlocked") + }) + defer timer.Stop() + + ctx := context.Background() + + try := func(d time.Duration) (longEnough bool) { + listener, err := jsonrpc2.NetListener(ctx, "tcp", "localhost:0", jsonrpc2.NetListenOptions{}) + if err != nil { + t.Fatal(err) + } + + idleStart := time.Now() + listener = jsonrpc2.NewIdleListener(d, listener) + defer listener.Close() + + server := jsonrpc2.NewServer(ctx, listener, jsonrpc2.ConnectionOptions{}) + + // Exercise some connection/disconnection patterns, and then assert that when + // our timer fires, the server exits. + conn1, err := jsonrpc2.Dial(ctx, listener.Dialer(), jsonrpc2.ConnectionOptions{}) + if err != nil { + if since := time.Since(idleStart); since < d { + t.Fatalf("conn1 failed to connect after %v: %v", since, err) + } + t.Log("jsonrpc2.Dial:", err) + return false // Took to long to dial, so the failure could have been due to the idle timeout. + } + // On the server side, Accept can race with the connection timing out. + // Send a call and wait for the response to ensure that the connection was + // actually fully accepted. + ac := conn1.Call(ctx, "ping", nil) + if err := ac.Await(ctx, nil); !errors.Is(err, jsonrpc2.ErrMethodNotFound) { + if since := time.Since(idleStart); since < d { + t.Fatalf("conn1 broken after %v: %v", since, err) + } + t.Log(`conn1.Call(ctx, "ping", nil):`, err) + conn1.Close() + return false + } + + // Since conn1 was successfully accepted and remains open, the server is + // definitely non-idle. Dialing another simultaneous connection should + // succeed. + conn2, err := jsonrpc2.Dial(ctx, listener.Dialer(), jsonrpc2.ConnectionOptions{}) + if err != nil { + conn1.Close() + t.Fatalf("conn2 failed to connect while non-idle after %v: %v", time.Since(idleStart), err) + return false + } + // Ensure that conn2 is also accepted on the server side before we close + // conn1. Otherwise, the connection can appear idle if the server processes + // the closure of conn1 and the idle timeout before it finally notices conn2 + // in the accept queue. + // (That failure mode may explain the failure noted in + // https://go.dev/issue/49387#issuecomment-1303979877.) + ac = conn2.Call(ctx, "ping", nil) + if err := ac.Await(ctx, nil); !errors.Is(err, jsonrpc2.ErrMethodNotFound) { + t.Fatalf("conn2 broken while non-idle after %v: %v", time.Since(idleStart), err) + } + + if err := conn1.Close(); err != nil { + t.Fatalf("conn1.Close failed with error: %v", err) + } + idleStart = time.Now() + if err := conn2.Close(); err != nil { + t.Fatalf("conn2.Close failed with error: %v", err) + } + + conn3, err := jsonrpc2.Dial(ctx, listener.Dialer(), jsonrpc2.ConnectionOptions{}) + if err != nil { + if since := time.Since(idleStart); since < d { + t.Fatalf("conn3 failed to connect after %v: %v", since, err) + } + t.Log("jsonrpc2.Dial:", err) + return false // Took to long to dial, so the failure could have been due to the idle timeout. + } + + ac = conn3.Call(ctx, "ping", nil) + if err := ac.Await(ctx, nil); !errors.Is(err, jsonrpc2.ErrMethodNotFound) { + if since := time.Since(idleStart); since < d { + t.Fatalf("conn3 broken after %v: %v", since, err) + } + t.Log(`conn3.Call(ctx, "ping", nil):`, err) + conn3.Close() + return false + } + + idleStart = time.Now() + if err := conn3.Close(); err != nil { + t.Fatalf("conn3.Close failed with error: %v", err) + } + + serverError := server.Wait() + + if !errors.Is(serverError, jsonrpc2.ErrIdleTimeout) { + t.Errorf("run() returned error %v, want %v", serverError, jsonrpc2.ErrIdleTimeout) + } + if since := time.Since(idleStart); since < d { + t.Errorf("server shut down after %v idle; want at least %v", since, d) + } + return true + } + + d := 1 * time.Millisecond + for { + t.Logf("testing with idle timeout %v", d) + if !try(d) { + d *= 2 + continue + } + break + } +} + +type msg struct { + Msg string +} + +type fakeHandler struct{} + +func (fakeHandler) Handle(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) { + switch req.Method { + case "ping": + return &msg{"pong"}, nil + default: + return nil, jsonrpc2.ErrNotHandled + } +} + +func TestServe(t *testing.T) { + stacktest.NoLeak(t) + ctx := context.Background() + + tests := []struct { + name string + factory func(context.Context, testing.TB) (jsonrpc2.Listener, error) + }{ + {"tcp", func(ctx context.Context, t testing.TB) (jsonrpc2.Listener, error) { + testenv.NeedsLocalhostNet(t) + return jsonrpc2.NetListener(ctx, "tcp", "localhost:0", jsonrpc2.NetListenOptions{}) + }}, + {"pipe", func(ctx context.Context, t testing.TB) (jsonrpc2.Listener, error) { + return jsonrpc2.NetPipeListener(ctx) + }}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fake, err := test.factory(ctx, t) + if err != nil { + t.Fatal(err) + } + conn, shutdown, err := newFake(t, ctx, fake) + if err != nil { + t.Fatal(err) + } + defer shutdown() + var got msg + if err := conn.Call(ctx, "ping", &msg{"ting"}).Await(ctx, &got); err != nil { + t.Fatal(err) + } + if want := "pong"; got.Msg != want { + t.Errorf("conn.Call(...): returned %q, want %q", got, want) + } + }) + } +} + +func newFake(t *testing.T, ctx context.Context, l jsonrpc2.Listener) (*jsonrpc2.Connection, func(), error) { + server := jsonrpc2.NewServer(ctx, l, jsonrpc2.ConnectionOptions{ + Handler: fakeHandler{}, + }) + + client, err := jsonrpc2.Dial(ctx, + l.Dialer(), + jsonrpc2.ConnectionOptions{ + Handler: fakeHandler{}, + }) + if err != nil { + return nil, nil, err + } + return client, func() { + if err := l.Close(); err != nil { + t.Fatal(err) + } + if err := client.Close(); err != nil { + t.Fatal(err) + } + server.Wait() + }, nil +} + +// TestIdleListenerAcceptCloseRace checks for the Accept/Close race fixed in CL 388597. +// +// (A bug in the idleListener implementation caused a successful Accept to block +// on sending to a background goroutine that could have already exited.) +func TestIdleListenerAcceptCloseRace(t *testing.T) { + ctx := context.Background() + + n := 10 + + // Each iteration of the loop appears to take around a millisecond, so to + // avoid spurious failures we'll set the watchdog for three orders of + // magnitude longer. When the bug was present, this reproduced the deadlock + // reliably on a Linux workstation when run with -count=100, which should be + // frequent enough to show up on the Go build dashboard if it regresses. + watchdog := time.Duration(n) * 1000 * time.Millisecond + timer := time.AfterFunc(watchdog, func() { + debug.SetTraceback("all") + panic(fmt.Sprintf("%s deadlocked after %v", t.Name(), watchdog)) + }) + defer timer.Stop() + + for ; n > 0; n-- { + listener, err := jsonrpc2.NetPipeListener(ctx) + if err != nil { + t.Fatal(err) + } + listener = jsonrpc2.NewIdleListener(24*time.Hour, listener) + + done := make(chan struct{}) + go func() { + conn, err := jsonrpc2.Dial(ctx, listener.Dialer(), jsonrpc2.ConnectionOptions{}) + listener.Close() + if err == nil { + conn.Close() + } + close(done) + }() + + // Accept may return a non-nil error if Close closes the underlying network + // connection before the wrapped Accept call unblocks. However, it must not + // deadlock! + c, err := listener.Accept(ctx) + if err == nil { + c.Close() + } + <-done + } +} + +// TestCloseCallRace checks for a race resulting in a deadlock when a Call on +// one side of the connection races with a Close (or otherwise broken +// connection) initiated from the other side. +// +// (The Call method was waiting for a result from the Read goroutine to +// determine which error value to return, but the Read goroutine was waiting for +// in-flight calls to complete before reporting that result.) +func TestCloseCallRace(t *testing.T) { + ctx := context.Background() + n := 10 + + watchdog := time.Duration(n) * 1000 * time.Millisecond + timer := time.AfterFunc(watchdog, func() { + debug.SetTraceback("all") + panic(fmt.Sprintf("%s deadlocked after %v", t.Name(), watchdog)) + }) + defer timer.Stop() + + for ; n > 0; n-- { + listener, err := jsonrpc2.NetPipeListener(ctx) + if err != nil { + t.Fatal(err) + } + + pokec := make(chan *jsonrpc2.AsyncCall, 1) + + s := jsonrpc2.NewServer(ctx, listener, jsonrpc2.BinderFunc(func(_ context.Context, srvConn *jsonrpc2.Connection) jsonrpc2.ConnectionOptions { + h := jsonrpc2.HandlerFunc(func(ctx context.Context, _ *jsonrpc2.Request) (interface{}, error) { + // Start a concurrent call from the server to the client. + // The point of this test is to ensure this doesn't deadlock + // if the client shuts down the connection concurrently. + // + // The racing Call may or may not receive a response: it should get a + // response if it is sent before the client closes the connection, and + // it should fail with some kind of "connection closed" error otherwise. + go func() { + pokec <- srvConn.Call(ctx, "poke", nil) + }() + + return &msg{"pong"}, nil + }) + return jsonrpc2.ConnectionOptions{Handler: h} + })) + + dialConn, err := jsonrpc2.Dial(ctx, listener.Dialer(), jsonrpc2.ConnectionOptions{}) + if err != nil { + listener.Close() + s.Wait() + t.Fatal(err) + } + + // Calling any method on the server should provoke it to asynchronously call + // us back. While it is starting that call, we will close the connection. + if err := dialConn.Call(ctx, "ping", nil).Await(ctx, nil); err != nil { + t.Error(err) + } + if err := dialConn.Close(); err != nil { + t.Error(err) + } + + // Ensure that the Call on the server side did not block forever when the + // connection closed. + pokeCall := <-pokec + if err := pokeCall.Await(ctx, nil); err == nil { + t.Errorf("unexpected nil error from server-initited call") + } else if errors.Is(err, jsonrpc2.ErrMethodNotFound) { + // The call completed before the Close reached the handler. + } else { + // The error was something else. + t.Logf("server-initiated call completed with expected error: %v", err) + } + + listener.Close() + s.Wait() + } +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/wire.go b/contribs/gnopls/internal/jsonrpc2_v2/wire.go new file mode 100644 index 00000000000..8f60fc62766 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/wire.go @@ -0,0 +1,86 @@ +// Copyright 2018 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 jsonrpc2 + +import ( + "encoding/json" +) + +// This file contains the go forms of the wire specification. +// see http://www.jsonrpc.org/specification for details + +var ( + // ErrParse is used when invalid JSON was received by the server. + ErrParse = NewError(-32700, "JSON RPC parse error") + // ErrInvalidRequest is used when the JSON sent is not a valid Request object. + ErrInvalidRequest = NewError(-32600, "JSON RPC invalid request") + // ErrMethodNotFound should be returned by the handler when the method does + // not exist / is not available. + ErrMethodNotFound = NewError(-32601, "JSON RPC method not found") + // ErrInvalidParams should be returned by the handler when method + // parameter(s) were invalid. + ErrInvalidParams = NewError(-32602, "JSON RPC invalid params") + // ErrInternal indicates a failure to process a call correctly + ErrInternal = NewError(-32603, "JSON RPC internal error") + + // The following errors are not part of the json specification, but + // compliant extensions specific to this implementation. + + // ErrServerOverloaded is returned when a message was refused due to a + // server being temporarily unable to accept any new messages. + ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded") + // ErrUnknown should be used for all non coded errors. + ErrUnknown = NewError(-32001, "JSON RPC unknown error") + // ErrServerClosing is returned for calls that arrive while the server is closing. + ErrServerClosing = NewError(-32002, "JSON RPC server is closing") + // ErrClientClosing is a dummy error returned for calls initiated while the client is closing. + ErrClientClosing = NewError(-32003, "JSON RPC client is closing") +) + +const wireVersion = "2.0" + +// wireCombined has all the fields of both Request and Response. +// We can decode this and then work out which it is. +type wireCombined struct { + VersionTag string `json:"jsonrpc"` + ID interface{} `json:"id,omitempty"` + Method string `json:"method,omitempty"` + Params json.RawMessage `json:"params,omitempty"` + Result json.RawMessage `json:"result,omitempty"` + Error *WireError `json:"error,omitempty"` +} + +// WireError represents a structured error in a Response. +type WireError struct { + // Code is an error code indicating the type of failure. + Code int64 `json:"code"` + // Message is a short description of the error. + Message string `json:"message"` + // Data is optional structured data containing additional information about the error. + Data json.RawMessage `json:"data,omitempty"` +} + +// NewError returns an error that will encode on the wire correctly. +// The standard codes are made available from this package, this function should +// only be used to build errors for application specific codes as allowed by the +// specification. +func NewError(code int64, message string) error { + return &WireError{ + Code: code, + Message: message, + } +} + +func (err *WireError) Error() string { + return err.Message +} + +func (err *WireError) Is(other error) bool { + w, ok := other.(*WireError) + if !ok { + return false + } + return err.Code == w.Code +} diff --git a/contribs/gnopls/internal/jsonrpc2_v2/wire_test.go b/contribs/gnopls/internal/jsonrpc2_v2/wire_test.go new file mode 100644 index 00000000000..0c380257e88 --- /dev/null +++ b/contribs/gnopls/internal/jsonrpc2_v2/wire_test.go @@ -0,0 +1,118 @@ +// 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 jsonrpc2_test + +import ( + "bytes" + "encoding/json" + "reflect" + "testing" + + jsonrpc2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" +) + +func TestWireMessage(t *testing.T) { + for _, test := range []struct { + name string + msg jsonrpc2.Message + encoded []byte + }{{ + name: "notification", + msg: newNotification("alive", nil), + encoded: []byte(`{"jsonrpc":"2.0","method":"alive"}`), + }, { + name: "call", + msg: newCall("msg1", "ping", nil), + encoded: []byte(`{"jsonrpc":"2.0","id":"msg1","method":"ping"}`), + }, { + name: "response", + msg: newResponse("msg2", "pong", nil), + encoded: []byte(`{"jsonrpc":"2.0","id":"msg2","result":"pong"}`), + }, { + name: "numerical id", + msg: newCall(1, "poke", nil), + encoded: []byte(`{"jsonrpc":"2.0","id":1,"method":"poke"}`), + }, { + // originally reported in #39719, this checks that result is not present if + // it is an error response + name: "computing fix edits", + msg: newResponse(3, nil, jsonrpc2.NewError(0, "computing fix edits")), + encoded: []byte(`{ + "jsonrpc":"2.0", + "id":3, + "error":{ + "code":0, + "message":"computing fix edits" + } + }`), + }} { + b, err := jsonrpc2.EncodeMessage(test.msg) + if err != nil { + t.Fatal(err) + } + checkJSON(t, b, test.encoded) + msg, err := jsonrpc2.DecodeMessage(test.encoded) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(msg, test.msg) { + t.Errorf("decoded message does not match\nGot:\n%+#v\nWant:\n%+#v", msg, test.msg) + } + } +} + +func newNotification(method string, params interface{}) jsonrpc2.Message { + msg, err := jsonrpc2.NewNotification(method, params) + if err != nil { + panic(err) + } + return msg +} + +func newID(id interface{}) jsonrpc2.ID { + switch v := id.(type) { + case nil: + return jsonrpc2.ID{} + case string: + return jsonrpc2.StringID(v) + case int: + return jsonrpc2.Int64ID(int64(v)) + case int64: + return jsonrpc2.Int64ID(v) + default: + panic("invalid ID type") + } +} + +func newCall(id interface{}, method string, params interface{}) jsonrpc2.Message { + msg, err := jsonrpc2.NewCall(newID(id), method, params) + if err != nil { + panic(err) + } + return msg +} + +func newResponse(id interface{}, result interface{}, rerr error) jsonrpc2.Message { + msg, err := jsonrpc2.NewResponse(newID(id), result, rerr) + if err != nil { + panic(err) + } + return msg +} + +func checkJSON(t *testing.T, got, want []byte) { + // compare the compact form, to allow for formatting differences + g := &bytes.Buffer{} + if err := json.Compact(g, []byte(got)); err != nil { + t.Fatal(err) + } + w := &bytes.Buffer{} + if err := json.Compact(w, []byte(want)); err != nil { + t.Fatal(err) + } + if g.String() != w.String() { + t.Errorf("encoded message does not match\nGot:\n%s\nWant:\n%s", g, w) + } +} diff --git a/contribs/gnopls/internal/label/keys.go b/contribs/gnopls/internal/label/keys.go index 1ef3b1786e5..6fec0a5db67 100644 --- a/contribs/gnopls/internal/label/keys.go +++ b/contribs/gnopls/internal/label/keys.go @@ -6,7 +6,7 @@ // and events. package label -import "golang.org/x/tools/internal/event/keys" +import "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" var ( File = keys.NewString("file", "") diff --git a/contribs/gnopls/internal/licenses/gen-licenses.sh b/contribs/gnopls/internal/licenses/gen-licenses.sh index a39f87ce845..3d73ef5be7a 100755 --- a/contribs/gnopls/internal/licenses/gen-licenses.sh +++ b/contribs/gnopls/internal/licenses/gen-licenses.sh @@ -23,7 +23,7 @@ END # List all the modules gopls depends on, except other golang.org modules, which # are known to have the same license. -mods=$(go list -deps -f '{{with .Module}}{{.Path}}{{end}}' golang.org/x/tools/gopls | sort -u | grep -v golang.org) +mods=$(go list -deps -f '{{with .Module}}{{.Path}}{{end}}' github.com/gnolang/gno/contribs/gnopls | sort -u | grep -v golang.org) for mod in $mods; do # Find the license file, either LICENSE or COPYING, and add it to the result. dir=$(go list -m -f {{.Dir}} $mod) diff --git a/contribs/gnopls/internal/licenses/licenses_test.go b/contribs/gnopls/internal/licenses/licenses_test.go index 00b6b6c94f7..e258b6d547f 100644 --- a/contribs/gnopls/internal/licenses/licenses_test.go +++ b/contribs/gnopls/internal/licenses/licenses_test.go @@ -11,7 +11,7 @@ import ( "runtime" "testing" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestLicenses(t *testing.T) { diff --git a/contribs/gnopls/internal/lsprpc/binder_test.go b/contribs/gnopls/internal/lsprpc/binder_test.go index 042056e7777..6942128801f 100644 --- a/contribs/gnopls/internal/lsprpc/binder_test.go +++ b/contribs/gnopls/internal/lsprpc/binder_test.go @@ -11,10 +11,10 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/protocol" - jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + jsonrpc2_v2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" - . "golang.org/x/tools/gopls/internal/lsprpc" + . "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" ) // ServerBinder binds incoming connections to a new server. diff --git a/contribs/gnopls/internal/lsprpc/commandinterceptor_test.go b/contribs/gnopls/internal/lsprpc/commandinterceptor_test.go index 7c83ef993f0..bb96b492c83 100644 --- a/contribs/gnopls/internal/lsprpc/commandinterceptor_test.go +++ b/contribs/gnopls/internal/lsprpc/commandinterceptor_test.go @@ -9,10 +9,10 @@ import ( "encoding/json" "testing" - "golang.org/x/tools/gopls/internal/protocol" - jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + jsonrpc2_v2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" - . "golang.org/x/tools/gopls/internal/lsprpc" + . "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" ) func CommandInterceptor(command string, run func(*protocol.ExecuteCommandParams) (interface{}, error)) Middleware { diff --git a/contribs/gnopls/internal/lsprpc/dialer.go b/contribs/gnopls/internal/lsprpc/dialer.go index a5f038df9f1..0682ce2d0ba 100644 --- a/contribs/gnopls/internal/lsprpc/dialer.go +++ b/contribs/gnopls/internal/lsprpc/dialer.go @@ -13,7 +13,7 @@ import ( "os/exec" "time" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // autoNetwork is the pseudo network type used to signal that gopls should use diff --git a/contribs/gnopls/internal/lsprpc/export_test.go b/contribs/gnopls/internal/lsprpc/export_test.go index 509129870dc..ef9412a5cef 100644 --- a/contribs/gnopls/internal/lsprpc/export_test.go +++ b/contribs/gnopls/internal/lsprpc/export_test.go @@ -11,10 +11,10 @@ import ( "encoding/json" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" - jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + jsonrpc2_v2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) const HandshakeMethod = handshakeMethod diff --git a/contribs/gnopls/internal/lsprpc/goenv.go b/contribs/gnopls/internal/lsprpc/goenv.go index 52ec08ff7eb..3fc2a7da6b4 100644 --- a/contribs/gnopls/internal/lsprpc/goenv.go +++ b/contribs/gnopls/internal/lsprpc/goenv.go @@ -9,7 +9,7 @@ import ( "encoding/json" "fmt" - "golang.org/x/tools/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" ) func getGoEnv(ctx context.Context, env map[string]interface{}) (map[string]string, error) { diff --git a/contribs/gnopls/internal/lsprpc/goenv_test.go b/contribs/gnopls/internal/lsprpc/goenv_test.go index 6c41540fafb..cf34da003e5 100644 --- a/contribs/gnopls/internal/lsprpc/goenv_test.go +++ b/contribs/gnopls/internal/lsprpc/goenv_test.go @@ -11,12 +11,12 @@ import ( "os" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" - jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + jsonrpc2_v2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" - . "golang.org/x/tools/gopls/internal/lsprpc" + . "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" ) func GoEnvMiddleware() (Middleware, error) { diff --git a/contribs/gnopls/internal/lsprpc/lsprpc.go b/contribs/gnopls/internal/lsprpc/lsprpc.go index b77557c9a4b..20f365614ec 100644 --- a/contribs/gnopls/internal/lsprpc/lsprpc.go +++ b/contribs/gnopls/internal/lsprpc/lsprpc.go @@ -19,15 +19,15 @@ import ( "sync/atomic" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/server" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) // Unique identifiers for client/server. diff --git a/contribs/gnopls/internal/lsprpc/lsprpc_test.go b/contribs/gnopls/internal/lsprpc/lsprpc_test.go index c4ccab71a3e..a2f5a6bd3ab 100644 --- a/contribs/gnopls/internal/lsprpc/lsprpc_test.go +++ b/contribs/gnopls/internal/lsprpc/lsprpc_test.go @@ -13,14 +13,14 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/jsonrpc2/servertest" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2/servertest" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) type FakeClient struct { diff --git a/contribs/gnopls/internal/lsprpc/middleware_test.go b/contribs/gnopls/internal/lsprpc/middleware_test.go index 526c7343b78..ced1dd5f90a 100644 --- a/contribs/gnopls/internal/lsprpc/middleware_test.go +++ b/contribs/gnopls/internal/lsprpc/middleware_test.go @@ -13,9 +13,9 @@ import ( "testing" "time" - . "golang.org/x/tools/gopls/internal/lsprpc" - "golang.org/x/tools/internal/event" - jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" + . "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + jsonrpc2_v2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" ) var noopBinder = BinderFunc(func(context.Context, *jsonrpc2_v2.Connection) jsonrpc2_v2.ConnectionOptions { diff --git a/contribs/gnopls/internal/memoize/memoize.go b/contribs/gnopls/internal/memoize/memoize.go new file mode 100644 index 00000000000..f0598e2400e --- /dev/null +++ b/contribs/gnopls/internal/memoize/memoize.go @@ -0,0 +1,335 @@ +// 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. + +// Package memoize defines a "promise" abstraction that enables +// memoization of the result of calling an expensive but idempotent +// function. +// +// Call p = NewPromise(f) to obtain a promise for the future result of +// calling f(), and call p.Get() to obtain that result. All calls to +// p.Get return the result of a single call of f(). +// Get blocks if the function has not finished (or started). +// +// A Store is a map of arbitrary keys to promises. Use Store.Promise +// to create a promise in the store. All calls to Handle(k) return the +// same promise as long as it is in the store. These promises are +// reference-counted and must be explicitly released. Once the last +// reference is released, the promise is removed from the store. +package memoize + +import ( + "context" + "fmt" + "reflect" + "runtime/trace" + "sync" + "sync/atomic" + + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" +) + +// Function is the type of a function that can be memoized. +// +// If the arg is a RefCounted, its Acquire/Release operations are called. +// +// The argument must not materially affect the result of the function +// in ways that are not captured by the promise's key, since if +// Promise.Get is called twice concurrently, with the same (implicit) +// key but different arguments, the Function is called only once but +// its result must be suitable for both callers. +// +// The main purpose of the argument is to avoid the Function closure +// needing to retain large objects (in practice: the snapshot) in +// memory that can be supplied at call time by any caller. +type Function func(ctx context.Context, arg interface{}) interface{} + +// A RefCounted is a value whose functional lifetime is determined by +// reference counting. +// +// Its Acquire method is called before the Function is invoked, and +// the corresponding release is called when the Function returns. +// Usually both events happen within a single call to Get, so Get +// would be fine with a "borrowed" reference, but if the context is +// cancelled, Get may return before the Function is complete, causing +// the argument to escape, and potential premature destruction of the +// value. For a reference-counted type, this requires a pair of +// increment/decrement operations to extend its life. +type RefCounted interface { + // Acquire prevents the value from being destroyed until the + // returned function is called. + Acquire() func() +} + +// A Promise represents the future result of a call to a function. +type Promise struct { + debug string // for observability + + // refcount is the reference count in the containing Store, used by + // Store.Promise. It is guarded by Store.promisesMu on the containing Store. + refcount int32 + + mu sync.Mutex + + // A Promise starts out IDLE, waiting for something to demand + // its evaluation. It then transitions into RUNNING state. + // + // While RUNNING, waiters tracks the number of Get calls + // waiting for a result, and the done channel is used to + // notify waiters of the next state transition. Once + // evaluation finishes, value is set, state changes to + // COMPLETED, and done is closed, unblocking waiters. + // + // Alternatively, as Get calls are cancelled, they decrement + // waiters. If it drops to zero, the inner context is + // cancelled, computation is abandoned, and state resets to + // IDLE to start the process over again. + state state + // done is set in running state, and closed when exiting it. + done chan struct{} + // cancel is set in running state. It cancels computation. + cancel context.CancelFunc + // waiters is the number of Gets outstanding. + waiters uint + // the function that will be used to populate the value + function Function + // value is set in completed state. + value interface{} +} + +// NewPromise returns a promise for the future result of calling the +// specified function. +// +// The debug string is used to classify promises in logs and metrics. +// It should be drawn from a small set. +func NewPromise(debug string, function Function) *Promise { + if function == nil { + panic("nil function") + } + return &Promise{ + debug: debug, + function: function, + } +} + +type state int + +const ( + stateIdle = iota // newly constructed, or last waiter was cancelled + stateRunning // start was called and not cancelled + stateCompleted // function call ran to completion +) + +// Cached returns the value associated with a promise. +// +// It will never cause the value to be generated. +// It will return the cached value, if present. +func (p *Promise) Cached() interface{} { + p.mu.Lock() + defer p.mu.Unlock() + if p.state == stateCompleted { + return p.value + } + return nil +} + +// Get returns the value associated with a promise. +// +// All calls to Promise.Get on a given promise return the +// same result but the function is called (to completion) at most once. +// +// If the value is not yet ready, the underlying function will be invoked. +// +// If ctx is cancelled, Get returns (nil, Canceled). +// If all concurrent calls to Get are cancelled, the context provided +// to the function is cancelled. A later call to Get may attempt to +// call the function again. +func (p *Promise) Get(ctx context.Context, arg interface{}) (interface{}, error) { + if ctx.Err() != nil { + return nil, ctx.Err() + } + p.mu.Lock() + switch p.state { + case stateIdle: + return p.run(ctx, arg) + case stateRunning: + return p.wait(ctx) + case stateCompleted: + defer p.mu.Unlock() + return p.value, nil + default: + panic("unknown state") + } +} + +// run starts p.function and returns the result. p.mu must be locked. +func (p *Promise) run(ctx context.Context, arg interface{}) (interface{}, error) { + childCtx, cancel := context.WithCancel(xcontext.Detach(ctx)) + p.cancel = cancel + p.state = stateRunning + p.done = make(chan struct{}) + function := p.function // Read under the lock + + // Make sure that the argument isn't destroyed while we're running in it. + release := func() {} + if rc, ok := arg.(RefCounted); ok { + release = rc.Acquire() + } + + go func() { + trace.WithRegion(childCtx, fmt.Sprintf("Promise.run %s", p.debug), func() { + defer release() + // Just in case the function does something expensive without checking + // the context, double-check we're still alive. + if childCtx.Err() != nil { + return + } + v := function(childCtx, arg) + if childCtx.Err() != nil { + return + } + + p.mu.Lock() + defer p.mu.Unlock() + // It's theoretically possible that the promise has been cancelled out + // of the run that started us, and then started running again since we + // checked childCtx above. Even so, that should be harmless, since each + // run should produce the same results. + if p.state != stateRunning { + return + } + + p.value = v + p.function = nil // aid GC + p.state = stateCompleted + close(p.done) + }) + }() + + return p.wait(ctx) +} + +// wait waits for the value to be computed, or ctx to be cancelled. p.mu must be locked. +func (p *Promise) wait(ctx context.Context) (interface{}, error) { + p.waiters++ + done := p.done + p.mu.Unlock() + + select { + case <-done: + p.mu.Lock() + defer p.mu.Unlock() + if p.state == stateCompleted { + return p.value, nil + } + return nil, nil + case <-ctx.Done(): + p.mu.Lock() + defer p.mu.Unlock() + p.waiters-- + if p.waiters == 0 && p.state == stateRunning { + p.cancel() + close(p.done) + p.state = stateIdle + p.done = nil + p.cancel = nil + } + return nil, ctx.Err() + } +} + +// An EvictionPolicy controls the eviction behavior of keys in a Store when +// they no longer have any references. +type EvictionPolicy int + +const ( + // ImmediatelyEvict evicts keys as soon as they no longer have references. + ImmediatelyEvict EvictionPolicy = iota + + // NeverEvict does not evict keys. + NeverEvict +) + +// A Store maps arbitrary keys to reference-counted promises. +// +// The zero value is a valid Store, though a store may also be created via +// NewStore if a custom EvictionPolicy is required. +type Store struct { + evictionPolicy EvictionPolicy + + promisesMu sync.Mutex + promises map[interface{}]*Promise +} + +// NewStore creates a new store with the given eviction policy. +func NewStore(policy EvictionPolicy) *Store { + return &Store{evictionPolicy: policy} +} + +// Promise returns a reference-counted promise for the future result of +// calling the specified function. +// +// Calls to Promise with the same key return the same promise, incrementing its +// reference count. The caller must call the returned function to decrement +// the promise's reference count when it is no longer needed. The returned +// function must not be called more than once. +// +// Once the last reference has been released, the promise is removed from the +// store. +func (store *Store) Promise(key interface{}, function Function) (*Promise, func()) { + store.promisesMu.Lock() + p, ok := store.promises[key] + if !ok { + p = NewPromise(reflect.TypeOf(key).String(), function) + if store.promises == nil { + store.promises = map[interface{}]*Promise{} + } + store.promises[key] = p + } + p.refcount++ + store.promisesMu.Unlock() + + var released int32 + release := func() { + if !atomic.CompareAndSwapInt32(&released, 0, 1) { + panic("release called more than once") + } + store.promisesMu.Lock() + + p.refcount-- + if p.refcount == 0 && store.evictionPolicy != NeverEvict { + // Inv: if p.refcount > 0, then store.promises[key] == p. + delete(store.promises, key) + } + store.promisesMu.Unlock() + } + + return p, release +} + +// Stats returns the number of each type of key in the store. +func (s *Store) Stats() map[reflect.Type]int { + result := map[reflect.Type]int{} + + s.promisesMu.Lock() + defer s.promisesMu.Unlock() + + for k := range s.promises { + result[reflect.TypeOf(k)]++ + } + return result +} + +// DebugOnlyIterate iterates through the store and, for each completed +// promise, calls f(k, v) for the map key k and function result v. It +// should only be used for debugging purposes. +func (s *Store) DebugOnlyIterate(f func(k, v interface{})) { + s.promisesMu.Lock() + defer s.promisesMu.Unlock() + + for k, p := range s.promises { + if v := p.Cached(); v != nil { + f(k, v) + } + } +} diff --git a/contribs/gnopls/internal/memoize/memoize_test.go b/contribs/gnopls/internal/memoize/memoize_test.go new file mode 100644 index 00000000000..bd842e0e4b7 --- /dev/null +++ b/contribs/gnopls/internal/memoize/memoize_test.go @@ -0,0 +1,166 @@ +// 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. + +package memoize_test + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" +) + +func TestGet(t *testing.T) { + var store memoize.Store + + evaled := 0 + + h, release := store.Promise("key", func(context.Context, interface{}) interface{} { + evaled++ + return "res" + }) + defer release() + expectGet(t, h, "res") + expectGet(t, h, "res") + if evaled != 1 { + t.Errorf("got %v calls to function, wanted 1", evaled) + } +} + +func expectGet(t *testing.T, h *memoize.Promise, wantV interface{}) { + t.Helper() + gotV, gotErr := h.Get(context.Background(), nil) + if gotV != wantV || gotErr != nil { + t.Fatalf("Get() = %v, %v, wanted %v, nil", gotV, gotErr, wantV) + } +} + +func TestNewPromise(t *testing.T) { + calls := 0 + f := func(context.Context, interface{}) interface{} { + calls++ + return calls + } + + // All calls to Get on the same promise return the same result. + p1 := memoize.NewPromise("debug", f) + expectGet(t, p1, 1) + expectGet(t, p1, 1) + + // A new promise calls the function again. + p2 := memoize.NewPromise("debug", f) + expectGet(t, p2, 2) + expectGet(t, p2, 2) + + // The original promise is unchanged. + expectGet(t, p1, 1) +} + +func TestStoredPromiseRefCounting(t *testing.T) { + var store memoize.Store + v1 := false + v2 := false + p1, release1 := store.Promise("key1", func(context.Context, interface{}) interface{} { + return &v1 + }) + p2, release2 := store.Promise("key2", func(context.Context, interface{}) interface{} { + return &v2 + }) + expectGet(t, p1, &v1) + expectGet(t, p2, &v2) + + expectGet(t, p1, &v1) + expectGet(t, p2, &v2) + + p2Copy, release2Copy := store.Promise("key2", func(context.Context, interface{}) interface{} { + return &v1 + }) + if p2 != p2Copy { + t.Error("Promise returned a new value while old is not destroyed yet") + } + expectGet(t, p2Copy, &v2) + + release2() + if got, want := v2, false; got != want { + t.Errorf("after destroying first v2 ref, got %v, want %v", got, want) + } + release2Copy() + if got, want := v1, false; got != want { + t.Errorf("after destroying v2, got %v, want %v", got, want) + } + release1() + + p2Copy, release2Copy = store.Promise("key2", func(context.Context, interface{}) interface{} { + return &v2 + }) + if p2 == p2Copy { + t.Error("Promise returned previously destroyed value") + } + release2Copy() +} + +func TestPromiseDestroyedWhileRunning(t *testing.T) { + // Test that calls to Promise.Get return even if the promise is destroyed while running. + + var store memoize.Store + c := make(chan int) + + var v int + h, release := store.Promise("key", func(ctx context.Context, _ interface{}) interface{} { + <-c + <-c + if err := ctx.Err(); err != nil { + t.Errorf("ctx.Err() = %v, want nil", err) + } + return &v + }) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // arbitrary timeout; may be removed if it causes flakes + defer cancel() + + var wg sync.WaitGroup + wg.Add(1) + var got interface{} + var err error + go func() { + got, err = h.Get(ctx, nil) + wg.Done() + }() + + c <- 0 // send once to enter the promise function + release() // release before the promise function returns + c <- 0 // let the promise function proceed + + wg.Wait() + + if err != nil { + t.Errorf("Get() failed: %v", err) + } + if got != &v { + t.Errorf("Get() = %v, want %v", got, v) + } +} + +func TestDoubleReleasePanics(t *testing.T) { + var store memoize.Store + _, release := store.Promise("key", func(ctx context.Context, _ interface{}) interface{} { return 0 }) + + panicked := false + + func() { + defer func() { + if recover() != nil { + panicked = true + } + }() + release() + release() + }() + + if !panicked { + t.Errorf("calling release() twice did not panic") + } +} diff --git a/contribs/gnopls/internal/mod/code_lens.go b/contribs/gnopls/internal/mod/code_lens.go index f80063625ff..b30c98ad77f 100644 --- a/contribs/gnopls/internal/mod/code_lens.go +++ b/contribs/gnopls/internal/mod/code_lens.go @@ -11,11 +11,11 @@ import ( "path/filepath" "golang.org/x/mod/modfile" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" ) // CodeLensSources returns the sources of code lenses for go.mod files. diff --git a/contribs/gnopls/internal/mod/diagnostics.go b/contribs/gnopls/internal/mod/diagnostics.go index 8da69313e49..5313b5d5350 100644 --- a/contribs/gnopls/internal/mod/diagnostics.go +++ b/contribs/gnopls/internal/mod/diagnostics.go @@ -17,13 +17,13 @@ import ( "golang.org/x/mod/modfile" "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/govulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // ParseDiagnostics returns diagnostics from parsing the go.mod files in the workspace. diff --git a/contribs/gnopls/internal/mod/format.go b/contribs/gnopls/internal/mod/format.go index 14408393969..da867c18674 100644 --- a/contribs/gnopls/internal/mod/format.go +++ b/contribs/gnopls/internal/mod/format.go @@ -7,11 +7,11 @@ package mod import ( "context" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func Format(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.TextEdit, error) { diff --git a/contribs/gnopls/internal/mod/hover.go b/contribs/gnopls/internal/mod/hover.go index 458c5ce67d5..4a4c5c05813 100644 --- a/contribs/gnopls/internal/mod/hover.go +++ b/contribs/gnopls/internal/mod/hover.go @@ -13,14 +13,14 @@ import ( "golang.org/x/mod/modfile" "golang.org/x/mod/semver" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/vulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/osv" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/govulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) { diff --git a/contribs/gnopls/internal/mod/inlayhint.go b/contribs/gnopls/internal/mod/inlayhint.go index 73286be4be6..389303e0d39 100644 --- a/contribs/gnopls/internal/mod/inlayhint.go +++ b/contribs/gnopls/internal/mod/inlayhint.go @@ -8,9 +8,9 @@ import ( "fmt" "golang.org/x/mod/modfile" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func InlayHint(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, _ protocol.Range) ([]protocol.InlayHint, error) { diff --git a/contribs/gnopls/internal/packagesinternal/packages.go b/contribs/gnopls/internal/packagesinternal/packages.go new file mode 100644 index 00000000000..44719de173b --- /dev/null +++ b/contribs/gnopls/internal/packagesinternal/packages.go @@ -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 packagesinternal exposes internal-only fields from go/packages. +package packagesinternal + +var GetForTest = func(p interface{}) string { return "" } +var GetDepsErrors = func(p interface{}) []*PackageError { return nil } + +type PackageError struct { + ImportStack []string // shortest path from package named on command line to this one + Pos string // position of error (if present, file:line:col) + Err string // the error itself +} + +var TypecheckCgo int +var DepsErrors int // must be set as a LoadMode to call GetDepsErrors +var ForTest int // must be set as a LoadMode to call GetForTest + +var SetModFlag = func(config interface{}, value string) {} +var SetModFile = func(config interface{}, value string) {} diff --git a/contribs/gnopls/internal/pkgbits/codes.go b/contribs/gnopls/internal/pkgbits/codes.go new file mode 100644 index 00000000000..f0cabde96eb --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/codes.go @@ -0,0 +1,77 @@ +// 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 pkgbits + +// A Code is an enum value that can be encoded into bitstreams. +// +// Code types are preferable for enum types, because they allow +// Decoder to detect desyncs. +type Code interface { + // Marker returns the SyncMarker for the Code's dynamic type. + Marker() SyncMarker + + // Value returns the Code's ordinal value. + Value() int +} + +// A CodeVal distinguishes among go/constant.Value encodings. +type CodeVal int + +func (c CodeVal) Marker() SyncMarker { return SyncVal } +func (c CodeVal) Value() int { return int(c) } + +// Note: These values are public and cannot be changed without +// updating the go/types importers. + +const ( + ValBool CodeVal = iota + ValString + ValInt64 + ValBigInt + ValBigRat + ValBigFloat +) + +// A CodeType distinguishes among go/types.Type encodings. +type CodeType int + +func (c CodeType) Marker() SyncMarker { return SyncType } +func (c CodeType) Value() int { return int(c) } + +// Note: These values are public and cannot be changed without +// updating the go/types importers. + +const ( + TypeBasic CodeType = iota + TypeNamed + TypePointer + TypeSlice + TypeArray + TypeChan + TypeMap + TypeSignature + TypeStruct + TypeInterface + TypeUnion + TypeTypeParam +) + +// A CodeObj distinguishes among go/types.Object encodings. +type CodeObj int + +func (c CodeObj) Marker() SyncMarker { return SyncCodeObj } +func (c CodeObj) Value() int { return int(c) } + +// Note: These values are public and cannot be changed without +// updating the go/types importers. + +const ( + ObjAlias CodeObj = iota + ObjConst + ObjType + ObjFunc + ObjVar + ObjStub +) diff --git a/contribs/gnopls/internal/pkgbits/decoder.go b/contribs/gnopls/internal/pkgbits/decoder.go new file mode 100644 index 00000000000..f6cb37c5c3d --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/decoder.go @@ -0,0 +1,519 @@ +// 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 pkgbits + +import ( + "encoding/binary" + "errors" + "fmt" + "go/constant" + "go/token" + "io" + "math/big" + "os" + "runtime" + "strings" +) + +// A PkgDecoder provides methods for decoding a package's Unified IR +// export data. +type PkgDecoder struct { + // version is the file format version. + version Version + + // sync indicates whether the file uses sync markers. + sync bool + + // pkgPath is the package path for the package to be decoded. + // + // TODO(mdempsky): Remove; unneeded since CL 391014. + pkgPath string + + // elemData is the full data payload of the encoded package. + // Elements are densely and contiguously packed together. + // + // The last 8 bytes of elemData are the package fingerprint. + elemData string + + // elemEnds stores the byte-offset end positions of element + // bitstreams within elemData. + // + // For example, element I's bitstream data starts at elemEnds[I-1] + // (or 0, if I==0) and ends at elemEnds[I]. + // + // Note: elemEnds is indexed by absolute indices, not + // section-relative indices. + elemEnds []uint32 + + // elemEndsEnds stores the index-offset end positions of relocation + // sections within elemEnds. + // + // For example, section K's end positions start at elemEndsEnds[K-1] + // (or 0, if K==0) and end at elemEndsEnds[K]. + elemEndsEnds [numRelocs]uint32 + + scratchRelocEnt []RelocEnt +} + +// PkgPath returns the package path for the package +// +// TODO(mdempsky): Remove; unneeded since CL 391014. +func (pr *PkgDecoder) PkgPath() string { return pr.pkgPath } + +// SyncMarkers reports whether pr uses sync markers. +func (pr *PkgDecoder) SyncMarkers() bool { return pr.sync } + +// NewPkgDecoder returns a PkgDecoder initialized to read the Unified +// IR export data from input. pkgPath is the package path for the +// compilation unit that produced the export data. +func NewPkgDecoder(pkgPath, input string) PkgDecoder { + pr := PkgDecoder{ + pkgPath: pkgPath, + } + + // TODO(mdempsky): Implement direct indexing of input string to + // avoid copying the position information. + + r := strings.NewReader(input) + + var ver uint32 + assert(binary.Read(r, binary.LittleEndian, &ver) == nil) + pr.version = Version(ver) + + if pr.version >= numVersions { + panic(fmt.Errorf("cannot decode %q, export data version %d is greater than maximum supported version %d", pkgPath, pr.version, numVersions-1)) + } + + if pr.version.Has(Flags) { + var flags uint32 + assert(binary.Read(r, binary.LittleEndian, &flags) == nil) + pr.sync = flags&flagSyncMarkers != 0 + } + + assert(binary.Read(r, binary.LittleEndian, pr.elemEndsEnds[:]) == nil) + + pr.elemEnds = make([]uint32, pr.elemEndsEnds[len(pr.elemEndsEnds)-1]) + assert(binary.Read(r, binary.LittleEndian, pr.elemEnds[:]) == nil) + + pos, err := r.Seek(0, io.SeekCurrent) + assert(err == nil) + + pr.elemData = input[pos:] + + const fingerprintSize = 8 + assert(len(pr.elemData)-fingerprintSize == int(pr.elemEnds[len(pr.elemEnds)-1])) + + return pr +} + +// NumElems returns the number of elements in section k. +func (pr *PkgDecoder) NumElems(k RelocKind) int { + count := int(pr.elemEndsEnds[k]) + if k > 0 { + count -= int(pr.elemEndsEnds[k-1]) + } + return count +} + +// TotalElems returns the total number of elements across all sections. +func (pr *PkgDecoder) TotalElems() int { + return len(pr.elemEnds) +} + +// Fingerprint returns the package fingerprint. +func (pr *PkgDecoder) Fingerprint() [8]byte { + var fp [8]byte + copy(fp[:], pr.elemData[len(pr.elemData)-8:]) + return fp +} + +// AbsIdx returns the absolute index for the given (section, index) +// pair. +func (pr *PkgDecoder) AbsIdx(k RelocKind, idx Index) int { + absIdx := int(idx) + if k > 0 { + absIdx += int(pr.elemEndsEnds[k-1]) + } + if absIdx >= int(pr.elemEndsEnds[k]) { + panicf("%v:%v is out of bounds; %v", k, idx, pr.elemEndsEnds) + } + return absIdx +} + +// DataIdx returns the raw element bitstream for the given (section, +// index) pair. +func (pr *PkgDecoder) DataIdx(k RelocKind, idx Index) string { + absIdx := pr.AbsIdx(k, idx) + + var start uint32 + if absIdx > 0 { + start = pr.elemEnds[absIdx-1] + } + end := pr.elemEnds[absIdx] + + return pr.elemData[start:end] +} + +// StringIdx returns the string value for the given string index. +func (pr *PkgDecoder) StringIdx(idx Index) string { + return pr.DataIdx(RelocString, idx) +} + +// NewDecoder returns a Decoder for the given (section, index) pair, +// and decodes the given SyncMarker from the element bitstream. +func (pr *PkgDecoder) NewDecoder(k RelocKind, idx Index, marker SyncMarker) Decoder { + r := pr.NewDecoderRaw(k, idx) + r.Sync(marker) + return r +} + +// TempDecoder returns a Decoder for the given (section, index) pair, +// and decodes the given SyncMarker from the element bitstream. +// If possible the Decoder should be RetireDecoder'd when it is no longer +// needed, this will avoid heap allocations. +func (pr *PkgDecoder) TempDecoder(k RelocKind, idx Index, marker SyncMarker) Decoder { + r := pr.TempDecoderRaw(k, idx) + r.Sync(marker) + return r +} + +func (pr *PkgDecoder) RetireDecoder(d *Decoder) { + pr.scratchRelocEnt = d.Relocs + d.Relocs = nil +} + +// NewDecoderRaw returns a Decoder for the given (section, index) pair. +// +// Most callers should use NewDecoder instead. +func (pr *PkgDecoder) NewDecoderRaw(k RelocKind, idx Index) Decoder { + r := Decoder{ + common: pr, + k: k, + Idx: idx, + } + + r.Data.Reset(pr.DataIdx(k, idx)) + r.Sync(SyncRelocs) + r.Relocs = make([]RelocEnt, r.Len()) + for i := range r.Relocs { + r.Sync(SyncReloc) + r.Relocs[i] = RelocEnt{RelocKind(r.Len()), Index(r.Len())} + } + + return r +} + +func (pr *PkgDecoder) TempDecoderRaw(k RelocKind, idx Index) Decoder { + r := Decoder{ + common: pr, + k: k, + Idx: idx, + } + + r.Data.Reset(pr.DataIdx(k, idx)) + r.Sync(SyncRelocs) + l := r.Len() + if cap(pr.scratchRelocEnt) >= l { + r.Relocs = pr.scratchRelocEnt[:l] + pr.scratchRelocEnt = nil + } else { + r.Relocs = make([]RelocEnt, l) + } + for i := range r.Relocs { + r.Sync(SyncReloc) + r.Relocs[i] = RelocEnt{RelocKind(r.Len()), Index(r.Len())} + } + + return r +} + +// A Decoder provides methods for decoding an individual element's +// bitstream data. +type Decoder struct { + common *PkgDecoder + + Relocs []RelocEnt + Data strings.Reader + + k RelocKind + Idx Index +} + +func (r *Decoder) checkErr(err error) { + if err != nil { + panicf("unexpected decoding error: %w", err) + } +} + +func (r *Decoder) rawUvarint() uint64 { + x, err := readUvarint(&r.Data) + r.checkErr(err) + return x +} + +// readUvarint is a type-specialized copy of encoding/binary.ReadUvarint. +// This avoids the interface conversion and thus has better escape properties, +// which flows up the stack. +func readUvarint(r *strings.Reader) (uint64, error) { + var x uint64 + var s uint + for i := 0; i < binary.MaxVarintLen64; i++ { + b, err := r.ReadByte() + if err != nil { + if i > 0 && err == io.EOF { + err = io.ErrUnexpectedEOF + } + return x, err + } + if b < 0x80 { + if i == binary.MaxVarintLen64-1 && b > 1 { + return x, overflow + } + return x | uint64(b)<<s, nil + } + x |= uint64(b&0x7f) << s + s += 7 + } + return x, overflow +} + +var overflow = errors.New("pkgbits: readUvarint overflows a 64-bit integer") + +func (r *Decoder) rawVarint() int64 { + ux := r.rawUvarint() + + // Zig-zag decode. + x := int64(ux >> 1) + if ux&1 != 0 { + x = ^x + } + return x +} + +func (r *Decoder) rawReloc(k RelocKind, idx int) Index { + e := r.Relocs[idx] + assert(e.Kind == k) + return e.Idx +} + +// Sync decodes a sync marker from the element bitstream and asserts +// that it matches the expected marker. +// +// If r.common.sync is false, then Sync is a no-op. +func (r *Decoder) Sync(mWant SyncMarker) { + if !r.common.sync { + return + } + + pos, _ := r.Data.Seek(0, io.SeekCurrent) + mHave := SyncMarker(r.rawUvarint()) + writerPCs := make([]int, r.rawUvarint()) + for i := range writerPCs { + writerPCs[i] = int(r.rawUvarint()) + } + + if mHave == mWant { + return + } + + // There's some tension here between printing: + // + // (1) full file paths that tools can recognize (e.g., so emacs + // hyperlinks the "file:line" text for easy navigation), or + // + // (2) short file paths that are easier for humans to read (e.g., by + // omitting redundant or irrelevant details, so it's easier to + // focus on the useful bits that remain). + // + // The current formatting favors the former, as it seems more + // helpful in practice. But perhaps the formatting could be improved + // to better address both concerns. For example, use relative file + // paths if they would be shorter, or rewrite file paths to contain + // "$GOROOT" (like objabi.AbsFile does) if tools can be taught how + // to reliably expand that again. + + fmt.Printf("export data desync: package %q, section %v, index %v, offset %v\n", r.common.pkgPath, r.k, r.Idx, pos) + + fmt.Printf("\nfound %v, written at:\n", mHave) + if len(writerPCs) == 0 { + fmt.Printf("\t[stack trace unavailable; recompile package %q with -d=syncframes]\n", r.common.pkgPath) + } + for _, pc := range writerPCs { + fmt.Printf("\t%s\n", r.common.StringIdx(r.rawReloc(RelocString, pc))) + } + + fmt.Printf("\nexpected %v, reading at:\n", mWant) + var readerPCs [32]uintptr // TODO(mdempsky): Dynamically size? + n := runtime.Callers(2, readerPCs[:]) + for _, pc := range fmtFrames(readerPCs[:n]...) { + fmt.Printf("\t%s\n", pc) + } + + // We already printed a stack trace for the reader, so now we can + // simply exit. Printing a second one with panic or base.Fatalf + // would just be noise. + os.Exit(1) +} + +// Bool decodes and returns a bool value from the element bitstream. +func (r *Decoder) Bool() bool { + r.Sync(SyncBool) + x, err := r.Data.ReadByte() + r.checkErr(err) + assert(x < 2) + return x != 0 +} + +// Int64 decodes and returns an int64 value from the element bitstream. +func (r *Decoder) Int64() int64 { + r.Sync(SyncInt64) + return r.rawVarint() +} + +// Uint64 decodes and returns a uint64 value from the element bitstream. +func (r *Decoder) Uint64() uint64 { + r.Sync(SyncUint64) + return r.rawUvarint() +} + +// Len decodes and returns a non-negative int value from the element bitstream. +func (r *Decoder) Len() int { x := r.Uint64(); v := int(x); assert(uint64(v) == x); return v } + +// Int decodes and returns an int value from the element bitstream. +func (r *Decoder) Int() int { x := r.Int64(); v := int(x); assert(int64(v) == x); return v } + +// Uint decodes and returns a uint value from the element bitstream. +func (r *Decoder) Uint() uint { x := r.Uint64(); v := uint(x); assert(uint64(v) == x); return v } + +// Code decodes a Code value from the element bitstream and returns +// its ordinal value. It's the caller's responsibility to convert the +// result to an appropriate Code type. +// +// TODO(mdempsky): Ideally this method would have signature "Code[T +// Code] T" instead, but we don't allow generic methods and the +// compiler can't depend on generics yet anyway. +func (r *Decoder) Code(mark SyncMarker) int { + r.Sync(mark) + return r.Len() +} + +// Reloc decodes a relocation of expected section k from the element +// bitstream and returns an index to the referenced element. +func (r *Decoder) Reloc(k RelocKind) Index { + r.Sync(SyncUseReloc) + return r.rawReloc(k, r.Len()) +} + +// String decodes and returns a string value from the element +// bitstream. +func (r *Decoder) String() string { + r.Sync(SyncString) + return r.common.StringIdx(r.Reloc(RelocString)) +} + +// Strings decodes and returns a variable-length slice of strings from +// the element bitstream. +func (r *Decoder) Strings() []string { + res := make([]string, r.Len()) + for i := range res { + res[i] = r.String() + } + return res +} + +// Value decodes and returns a constant.Value from the element +// bitstream. +func (r *Decoder) Value() constant.Value { + r.Sync(SyncValue) + isComplex := r.Bool() + val := r.scalar() + if isComplex { + val = constant.BinaryOp(val, token.ADD, constant.MakeImag(r.scalar())) + } + return val +} + +func (r *Decoder) scalar() constant.Value { + switch tag := CodeVal(r.Code(SyncVal)); tag { + default: + panic(fmt.Errorf("unexpected scalar tag: %v", tag)) + + case ValBool: + return constant.MakeBool(r.Bool()) + case ValString: + return constant.MakeString(r.String()) + case ValInt64: + return constant.MakeInt64(r.Int64()) + case ValBigInt: + return constant.Make(r.bigInt()) + case ValBigRat: + num := r.bigInt() + denom := r.bigInt() + return constant.Make(new(big.Rat).SetFrac(num, denom)) + case ValBigFloat: + return constant.Make(r.bigFloat()) + } +} + +func (r *Decoder) bigInt() *big.Int { + v := new(big.Int).SetBytes([]byte(r.String())) + if r.Bool() { + v.Neg(v) + } + return v +} + +func (r *Decoder) bigFloat() *big.Float { + v := new(big.Float).SetPrec(512) + assert(v.UnmarshalText([]byte(r.String())) == nil) + return v +} + +// @@@ Helpers + +// TODO(mdempsky): These should probably be removed. I think they're a +// smell that the export data format is not yet quite right. + +// PeekPkgPath returns the package path for the specified package +// index. +func (pr *PkgDecoder) PeekPkgPath(idx Index) string { + var path string + { + r := pr.TempDecoder(RelocPkg, idx, SyncPkgDef) + path = r.String() + pr.RetireDecoder(&r) + } + if path == "" { + path = pr.pkgPath + } + return path +} + +// PeekObj returns the package path, object name, and CodeObj for the +// specified object index. +func (pr *PkgDecoder) PeekObj(idx Index) (string, string, CodeObj) { + var ridx Index + var name string + var rcode int + { + r := pr.TempDecoder(RelocName, idx, SyncObject1) + r.Sync(SyncSym) + r.Sync(SyncPkg) + ridx = r.Reloc(RelocPkg) + name = r.String() + rcode = r.Code(SyncCodeObj) + pr.RetireDecoder(&r) + } + + path := pr.PeekPkgPath(ridx) + assert(name != "") + + tag := CodeObj(rcode) + + return path, name, tag +} + +// Version reports the version of the bitstream. +func (w *Decoder) Version() Version { return w.common.version } diff --git a/contribs/gnopls/internal/pkgbits/doc.go b/contribs/gnopls/internal/pkgbits/doc.go new file mode 100644 index 00000000000..c8a2796b5e4 --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/doc.go @@ -0,0 +1,32 @@ +// 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 pkgbits implements low-level coding abstractions for +// Unified IR's export data format. +// +// At a low-level, a package is a collection of bitstream elements. +// Each element has a "kind" and a dense, non-negative index. +// Elements can be randomly accessed given their kind and index. +// +// Individual elements are sequences of variable-length values (e.g., +// integers, booleans, strings, go/constant values, cross-references +// to other elements). Package pkgbits provides APIs for encoding and +// decoding these low-level values, but the details of mapping +// higher-level Go constructs into elements is left to higher-level +// abstractions. +// +// Elements may cross-reference each other with "relocations." For +// example, an element representing a pointer type has a relocation +// referring to the element type. +// +// Go constructs may be composed as a constellation of multiple +// elements. For example, a declared function may have one element to +// describe the object (e.g., its name, type, position), and a +// separate element to describe its function body. This allows readers +// some flexibility in efficiently seeking or re-reading data (e.g., +// inlining requires re-reading the function body for each inlined +// call, without needing to re-read the object-level details). +// +// This is a copy of internal/pkgbits in the Go implementation. +package pkgbits diff --git a/contribs/gnopls/internal/pkgbits/encoder.go b/contribs/gnopls/internal/pkgbits/encoder.go new file mode 100644 index 00000000000..c17a12399d0 --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/encoder.go @@ -0,0 +1,392 @@ +// 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 pkgbits + +import ( + "bytes" + "crypto/md5" + "encoding/binary" + "go/constant" + "io" + "math/big" + "runtime" + "strings" +) + +// A PkgEncoder provides methods for encoding a package's Unified IR +// export data. +type PkgEncoder struct { + // version of the bitstream. + version Version + + // elems holds the bitstream for previously encoded elements. + elems [numRelocs][]string + + // stringsIdx maps previously encoded strings to their index within + // the RelocString section, to allow deduplication. That is, + // elems[RelocString][stringsIdx[s]] == s (if present). + stringsIdx map[string]Index + + // syncFrames is the number of frames to write at each sync + // marker. A negative value means sync markers are omitted. + syncFrames int +} + +// SyncMarkers reports whether pw uses sync markers. +func (pw *PkgEncoder) SyncMarkers() bool { return pw.syncFrames >= 0 } + +// NewPkgEncoder returns an initialized PkgEncoder. +// +// syncFrames is the number of caller frames that should be serialized +// at Sync points. Serializing additional frames results in larger +// export data files, but can help diagnosing desync errors in +// higher-level Unified IR reader/writer code. If syncFrames is +// negative, then sync markers are omitted entirely. +func NewPkgEncoder(version Version, syncFrames int) PkgEncoder { + return PkgEncoder{ + version: version, + stringsIdx: make(map[string]Index), + syncFrames: syncFrames, + } +} + +// DumpTo writes the package's encoded data to out0 and returns the +// package fingerprint. +func (pw *PkgEncoder) DumpTo(out0 io.Writer) (fingerprint [8]byte) { + h := md5.New() + out := io.MultiWriter(out0, h) + + writeUint32 := func(x uint32) { + assert(binary.Write(out, binary.LittleEndian, x) == nil) + } + + writeUint32(uint32(pw.version)) + + if pw.version.Has(Flags) { + var flags uint32 + if pw.SyncMarkers() { + flags |= flagSyncMarkers + } + writeUint32(flags) + } + + // Write elemEndsEnds. + var sum uint32 + for _, elems := range &pw.elems { + sum += uint32(len(elems)) + writeUint32(sum) + } + + // Write elemEnds. + sum = 0 + for _, elems := range &pw.elems { + for _, elem := range elems { + sum += uint32(len(elem)) + writeUint32(sum) + } + } + + // Write elemData. + for _, elems := range &pw.elems { + for _, elem := range elems { + _, err := io.WriteString(out, elem) + assert(err == nil) + } + } + + // Write fingerprint. + copy(fingerprint[:], h.Sum(nil)) + _, err := out0.Write(fingerprint[:]) + assert(err == nil) + + return +} + +// StringIdx adds a string value to the strings section, if not +// already present, and returns its index. +func (pw *PkgEncoder) StringIdx(s string) Index { + if idx, ok := pw.stringsIdx[s]; ok { + assert(pw.elems[RelocString][idx] == s) + return idx + } + + idx := Index(len(pw.elems[RelocString])) + pw.elems[RelocString] = append(pw.elems[RelocString], s) + pw.stringsIdx[s] = idx + return idx +} + +// NewEncoder returns an Encoder for a new element within the given +// section, and encodes the given SyncMarker as the start of the +// element bitstream. +func (pw *PkgEncoder) NewEncoder(k RelocKind, marker SyncMarker) Encoder { + e := pw.NewEncoderRaw(k) + e.Sync(marker) + return e +} + +// NewEncoderRaw returns an Encoder for a new element within the given +// section. +// +// Most callers should use NewEncoder instead. +func (pw *PkgEncoder) NewEncoderRaw(k RelocKind) Encoder { + idx := Index(len(pw.elems[k])) + pw.elems[k] = append(pw.elems[k], "") // placeholder + + return Encoder{ + p: pw, + k: k, + Idx: idx, + } +} + +// An Encoder provides methods for encoding an individual element's +// bitstream data. +type Encoder struct { + p *PkgEncoder + + Relocs []RelocEnt + RelocMap map[RelocEnt]uint32 + Data bytes.Buffer // accumulated element bitstream data + + encodingRelocHeader bool + + k RelocKind + Idx Index // index within relocation section +} + +// Flush finalizes the element's bitstream and returns its Index. +func (w *Encoder) Flush() Index { + var sb strings.Builder + + // Backup the data so we write the relocations at the front. + var tmp bytes.Buffer + io.Copy(&tmp, &w.Data) + + // TODO(mdempsky): Consider writing these out separately so they're + // easier to strip, along with function bodies, so that we can prune + // down to just the data that's relevant to go/types. + if w.encodingRelocHeader { + panic("encodingRelocHeader already true; recursive flush?") + } + w.encodingRelocHeader = true + w.Sync(SyncRelocs) + w.Len(len(w.Relocs)) + for _, rEnt := range w.Relocs { + w.Sync(SyncReloc) + w.Len(int(rEnt.Kind)) + w.Len(int(rEnt.Idx)) + } + + io.Copy(&sb, &w.Data) + io.Copy(&sb, &tmp) + w.p.elems[w.k][w.Idx] = sb.String() + + return w.Idx +} + +func (w *Encoder) checkErr(err error) { + if err != nil { + panicf("unexpected encoding error: %v", err) + } +} + +func (w *Encoder) rawUvarint(x uint64) { + var buf [binary.MaxVarintLen64]byte + n := binary.PutUvarint(buf[:], x) + _, err := w.Data.Write(buf[:n]) + w.checkErr(err) +} + +func (w *Encoder) rawVarint(x int64) { + // Zig-zag encode. + ux := uint64(x) << 1 + if x < 0 { + ux = ^ux + } + + w.rawUvarint(ux) +} + +func (w *Encoder) rawReloc(r RelocKind, idx Index) int { + e := RelocEnt{r, idx} + if w.RelocMap != nil { + if i, ok := w.RelocMap[e]; ok { + return int(i) + } + } else { + w.RelocMap = make(map[RelocEnt]uint32) + } + + i := len(w.Relocs) + w.RelocMap[e] = uint32(i) + w.Relocs = append(w.Relocs, e) + return i +} + +func (w *Encoder) Sync(m SyncMarker) { + if !w.p.SyncMarkers() { + return + } + + // Writing out stack frame string references requires working + // relocations, but writing out the relocations themselves involves + // sync markers. To prevent infinite recursion, we simply trim the + // stack frame for sync markers within the relocation header. + var frames []string + if !w.encodingRelocHeader && w.p.syncFrames > 0 { + pcs := make([]uintptr, w.p.syncFrames) + n := runtime.Callers(2, pcs) + frames = fmtFrames(pcs[:n]...) + } + + // TODO(mdempsky): Save space by writing out stack frames as a + // linked list so we can share common stack frames. + w.rawUvarint(uint64(m)) + w.rawUvarint(uint64(len(frames))) + for _, frame := range frames { + w.rawUvarint(uint64(w.rawReloc(RelocString, w.p.StringIdx(frame)))) + } +} + +// Bool encodes and writes a bool value into the element bitstream, +// and then returns the bool value. +// +// For simple, 2-alternative encodings, the idiomatic way to call Bool +// is something like: +// +// if w.Bool(x != 0) { +// // alternative #1 +// } else { +// // alternative #2 +// } +// +// For multi-alternative encodings, use Code instead. +func (w *Encoder) Bool(b bool) bool { + w.Sync(SyncBool) + var x byte + if b { + x = 1 + } + err := w.Data.WriteByte(x) + w.checkErr(err) + return b +} + +// Int64 encodes and writes an int64 value into the element bitstream. +func (w *Encoder) Int64(x int64) { + w.Sync(SyncInt64) + w.rawVarint(x) +} + +// Uint64 encodes and writes a uint64 value into the element bitstream. +func (w *Encoder) Uint64(x uint64) { + w.Sync(SyncUint64) + w.rawUvarint(x) +} + +// Len encodes and writes a non-negative int value into the element bitstream. +func (w *Encoder) Len(x int) { assert(x >= 0); w.Uint64(uint64(x)) } + +// Int encodes and writes an int value into the element bitstream. +func (w *Encoder) Int(x int) { w.Int64(int64(x)) } + +// Uint encodes and writes a uint value into the element bitstream. +func (w *Encoder) Uint(x uint) { w.Uint64(uint64(x)) } + +// Reloc encodes and writes a relocation for the given (section, +// index) pair into the element bitstream. +// +// Note: Only the index is formally written into the element +// bitstream, so bitstream decoders must know from context which +// section an encoded relocation refers to. +func (w *Encoder) Reloc(r RelocKind, idx Index) { + w.Sync(SyncUseReloc) + w.Len(w.rawReloc(r, idx)) +} + +// Code encodes and writes a Code value into the element bitstream. +func (w *Encoder) Code(c Code) { + w.Sync(c.Marker()) + w.Len(c.Value()) +} + +// String encodes and writes a string value into the element +// bitstream. +// +// Internally, strings are deduplicated by adding them to the strings +// section (if not already present), and then writing a relocation +// into the element bitstream. +func (w *Encoder) String(s string) { + w.StringRef(w.p.StringIdx(s)) +} + +// StringRef writes a reference to the given index, which must be a +// previously encoded string value. +func (w *Encoder) StringRef(idx Index) { + w.Sync(SyncString) + w.Reloc(RelocString, idx) +} + +// Strings encodes and writes a variable-length slice of strings into +// the element bitstream. +func (w *Encoder) Strings(ss []string) { + w.Len(len(ss)) + for _, s := range ss { + w.String(s) + } +} + +// Value encodes and writes a constant.Value into the element +// bitstream. +func (w *Encoder) Value(val constant.Value) { + w.Sync(SyncValue) + if w.Bool(val.Kind() == constant.Complex) { + w.scalar(constant.Real(val)) + w.scalar(constant.Imag(val)) + } else { + w.scalar(val) + } +} + +func (w *Encoder) scalar(val constant.Value) { + switch v := constant.Val(val).(type) { + default: + panicf("unhandled %v (%v)", val, val.Kind()) + case bool: + w.Code(ValBool) + w.Bool(v) + case string: + w.Code(ValString) + w.String(v) + case int64: + w.Code(ValInt64) + w.Int64(v) + case *big.Int: + w.Code(ValBigInt) + w.bigInt(v) + case *big.Rat: + w.Code(ValBigRat) + w.bigInt(v.Num()) + w.bigInt(v.Denom()) + case *big.Float: + w.Code(ValBigFloat) + w.bigFloat(v) + } +} + +func (w *Encoder) bigInt(v *big.Int) { + b := v.Bytes() + w.String(string(b)) // TODO: More efficient encoding. + w.Bool(v.Sign() < 0) +} + +func (w *Encoder) bigFloat(v *big.Float) { + b := v.Append(nil, 'p', -1) + w.String(string(b)) // TODO: More efficient encoding. +} + +// Version reports the version of the bitstream. +func (w *Encoder) Version() Version { return w.p.version } diff --git a/contribs/gnopls/internal/pkgbits/flags.go b/contribs/gnopls/internal/pkgbits/flags.go new file mode 100644 index 00000000000..654222745fa --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/flags.go @@ -0,0 +1,9 @@ +// 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 pkgbits + +const ( + flagSyncMarkers = 1 << iota // file format contains sync markers +) diff --git a/contribs/gnopls/internal/pkgbits/pkgbits_test.go b/contribs/gnopls/internal/pkgbits/pkgbits_test.go new file mode 100644 index 00000000000..b8f946a0a4f --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/pkgbits_test.go @@ -0,0 +1,77 @@ +// 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 pkgbits_test + +import ( + "strings" + "testing" + + "golang.org/x/tools/internal/pkgbits" +) + +func TestRoundTrip(t *testing.T) { + for _, version := range []pkgbits.Version{ + pkgbits.V0, + pkgbits.V1, + pkgbits.V2, + } { + pw := pkgbits.NewPkgEncoder(version, -1) + w := pw.NewEncoder(pkgbits.RelocMeta, pkgbits.SyncPublic) + w.Flush() + + var b strings.Builder + _ = pw.DumpTo(&b) + input := b.String() + + pr := pkgbits.NewPkgDecoder("package_id", input) + r := pr.NewDecoder(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic) + + if r.Version() != w.Version() { + t.Errorf("Expected reader version %q to be the writer version %q", r.Version(), w.Version()) + } + } +} + +// Type checker to enforce that know V* have the constant values they must have. +var _ [0]bool = [pkgbits.V0]bool{} +var _ [1]bool = [pkgbits.V1]bool{} + +func TestVersions(t *testing.T) { + type vfpair struct { + v pkgbits.Version + f pkgbits.Field + } + + // has field tests + for _, c := range []vfpair{ + {pkgbits.V1, pkgbits.Flags}, + {pkgbits.V2, pkgbits.Flags}, + {pkgbits.V0, pkgbits.HasInit}, + {pkgbits.V1, pkgbits.HasInit}, + {pkgbits.V0, pkgbits.DerivedFuncInstance}, + {pkgbits.V1, pkgbits.DerivedFuncInstance}, + {pkgbits.V0, pkgbits.DerivedInfoNeeded}, + {pkgbits.V1, pkgbits.DerivedInfoNeeded}, + {pkgbits.V2, pkgbits.AliasTypeParamNames}, + } { + if !c.v.Has(c.f) { + t.Errorf("Expected version %v to have field %v", c.v, c.f) + } + } + + // does not have field tests + for _, c := range []vfpair{ + {pkgbits.V0, pkgbits.Flags}, + {pkgbits.V2, pkgbits.HasInit}, + {pkgbits.V2, pkgbits.DerivedFuncInstance}, + {pkgbits.V2, pkgbits.DerivedInfoNeeded}, + {pkgbits.V0, pkgbits.AliasTypeParamNames}, + {pkgbits.V1, pkgbits.AliasTypeParamNames}, + } { + if c.v.Has(c.f) { + t.Errorf("Expected version %v to not have field %v", c.v, c.f) + } + } +} diff --git a/contribs/gnopls/internal/pkgbits/reloc.go b/contribs/gnopls/internal/pkgbits/reloc.go new file mode 100644 index 00000000000..fcdfb97ca99 --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/reloc.go @@ -0,0 +1,42 @@ +// 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 pkgbits + +// A RelocKind indicates a particular section within a unified IR export. +type RelocKind int32 + +// An Index represents a bitstream element index within a particular +// section. +type Index int32 + +// A relocEnt (relocation entry) is an entry in an element's local +// reference table. +// +// TODO(mdempsky): Rename this too. +type RelocEnt struct { + Kind RelocKind + Idx Index +} + +// Reserved indices within the meta relocation section. +const ( + PublicRootIdx Index = 0 + PrivateRootIdx Index = 1 +) + +const ( + RelocString RelocKind = iota + RelocMeta + RelocPosBase + RelocPkg + RelocName + RelocType + RelocObj + RelocObjExt + RelocObjDict + RelocBody + + numRelocs = iota +) diff --git a/contribs/gnopls/internal/pkgbits/support.go b/contribs/gnopls/internal/pkgbits/support.go new file mode 100644 index 00000000000..50534a29553 --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/support.go @@ -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 pkgbits + +import "fmt" + +func assert(b bool) { + if !b { + panic("assertion failed") + } +} + +func panicf(format string, args ...any) { + panic(fmt.Errorf(format, args...)) +} diff --git a/contribs/gnopls/internal/pkgbits/sync.go b/contribs/gnopls/internal/pkgbits/sync.go new file mode 100644 index 00000000000..1520b73afb9 --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/sync.go @@ -0,0 +1,136 @@ +// 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 pkgbits + +import ( + "fmt" + "runtime" + "strings" +) + +// fmtFrames formats a backtrace for reporting reader/writer desyncs. +func fmtFrames(pcs ...uintptr) []string { + res := make([]string, 0, len(pcs)) + walkFrames(pcs, func(file string, line int, name string, offset uintptr) { + // Trim package from function name. It's just redundant noise. + name = strings.TrimPrefix(name, "cmd/compile/internal/noder.") + + res = append(res, fmt.Sprintf("%s:%v: %s +0x%v", file, line, name, offset)) + }) + return res +} + +type frameVisitor func(file string, line int, name string, offset uintptr) + +// walkFrames calls visit for each call frame represented by pcs. +// +// pcs should be a slice of PCs, as returned by runtime.Callers. +func walkFrames(pcs []uintptr, visit frameVisitor) { + if len(pcs) == 0 { + return + } + + frames := runtime.CallersFrames(pcs) + for { + frame, more := frames.Next() + visit(frame.File, frame.Line, frame.Function, frame.PC-frame.Entry) + if !more { + return + } + } +} + +// SyncMarker is an enum type that represents markers that may be +// written to export data to ensure the reader and writer stay +// synchronized. +type SyncMarker int + +//go:generate stringer -type=SyncMarker -trimprefix=Sync + +const ( + _ SyncMarker = iota + + // Public markers (known to go/types importers). + + // Low-level coding markers. + SyncEOF + SyncBool + SyncInt64 + SyncUint64 + SyncString + SyncValue + SyncVal + SyncRelocs + SyncReloc + SyncUseReloc + + // Higher-level object and type markers. + SyncPublic + SyncPos + SyncPosBase + SyncObject + SyncObject1 + SyncPkg + SyncPkgDef + SyncMethod + SyncType + SyncTypeIdx + SyncTypeParamNames + SyncSignature + SyncParams + SyncParam + SyncCodeObj + SyncSym + SyncLocalIdent + SyncSelector + + // Private markers (only known to cmd/compile). + SyncPrivate + + SyncFuncExt + SyncVarExt + SyncTypeExt + SyncPragma + + SyncExprList + SyncExprs + SyncExpr + SyncExprType + SyncAssign + SyncOp + SyncFuncLit + SyncCompLit + + SyncDecl + SyncFuncBody + SyncOpenScope + SyncCloseScope + SyncCloseAnotherScope + SyncDeclNames + SyncDeclName + + SyncStmts + SyncBlockStmt + SyncIfStmt + SyncForStmt + SyncSwitchStmt + SyncRangeStmt + SyncCaseClause + SyncCommClause + SyncSelectStmt + SyncDecls + SyncLabeledStmt + SyncUseObjLocal + SyncAddLocal + SyncLinkname + SyncStmt1 + SyncStmtsEnd + SyncLabel + SyncOptLabel + + SyncMultiExpr + SyncRType + SyncConvRTTI +) diff --git a/contribs/gnopls/internal/pkgbits/syncmarker_string.go b/contribs/gnopls/internal/pkgbits/syncmarker_string.go new file mode 100644 index 00000000000..582ad56d3e0 --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/syncmarker_string.go @@ -0,0 +1,92 @@ +// Code generated by "stringer -type=SyncMarker -trimprefix=Sync"; DO NOT EDIT. + +package pkgbits + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[SyncEOF-1] + _ = x[SyncBool-2] + _ = x[SyncInt64-3] + _ = x[SyncUint64-4] + _ = x[SyncString-5] + _ = x[SyncValue-6] + _ = x[SyncVal-7] + _ = x[SyncRelocs-8] + _ = x[SyncReloc-9] + _ = x[SyncUseReloc-10] + _ = x[SyncPublic-11] + _ = x[SyncPos-12] + _ = x[SyncPosBase-13] + _ = x[SyncObject-14] + _ = x[SyncObject1-15] + _ = x[SyncPkg-16] + _ = x[SyncPkgDef-17] + _ = x[SyncMethod-18] + _ = x[SyncType-19] + _ = x[SyncTypeIdx-20] + _ = x[SyncTypeParamNames-21] + _ = x[SyncSignature-22] + _ = x[SyncParams-23] + _ = x[SyncParam-24] + _ = x[SyncCodeObj-25] + _ = x[SyncSym-26] + _ = x[SyncLocalIdent-27] + _ = x[SyncSelector-28] + _ = x[SyncPrivate-29] + _ = x[SyncFuncExt-30] + _ = x[SyncVarExt-31] + _ = x[SyncTypeExt-32] + _ = x[SyncPragma-33] + _ = x[SyncExprList-34] + _ = x[SyncExprs-35] + _ = x[SyncExpr-36] + _ = x[SyncExprType-37] + _ = x[SyncAssign-38] + _ = x[SyncOp-39] + _ = x[SyncFuncLit-40] + _ = x[SyncCompLit-41] + _ = x[SyncDecl-42] + _ = x[SyncFuncBody-43] + _ = x[SyncOpenScope-44] + _ = x[SyncCloseScope-45] + _ = x[SyncCloseAnotherScope-46] + _ = x[SyncDeclNames-47] + _ = x[SyncDeclName-48] + _ = x[SyncStmts-49] + _ = x[SyncBlockStmt-50] + _ = x[SyncIfStmt-51] + _ = x[SyncForStmt-52] + _ = x[SyncSwitchStmt-53] + _ = x[SyncRangeStmt-54] + _ = x[SyncCaseClause-55] + _ = x[SyncCommClause-56] + _ = x[SyncSelectStmt-57] + _ = x[SyncDecls-58] + _ = x[SyncLabeledStmt-59] + _ = x[SyncUseObjLocal-60] + _ = x[SyncAddLocal-61] + _ = x[SyncLinkname-62] + _ = x[SyncStmt1-63] + _ = x[SyncStmtsEnd-64] + _ = x[SyncLabel-65] + _ = x[SyncOptLabel-66] + _ = x[SyncMultiExpr-67] + _ = x[SyncRType-68] + _ = x[SyncConvRTTI-69] +} + +const _SyncMarker_name = "EOFBoolInt64Uint64StringValueValRelocsRelocUseRelocPublicPosPosBaseObjectObject1PkgPkgDefMethodTypeTypeIdxTypeParamNamesSignatureParamsParamCodeObjSymLocalIdentSelectorPrivateFuncExtVarExtTypeExtPragmaExprListExprsExprExprTypeAssignOpFuncLitCompLitDeclFuncBodyOpenScopeCloseScopeCloseAnotherScopeDeclNamesDeclNameStmtsBlockStmtIfStmtForStmtSwitchStmtRangeStmtCaseClauseCommClauseSelectStmtDeclsLabeledStmtUseObjLocalAddLocalLinknameStmt1StmtsEndLabelOptLabelMultiExprRTypeConvRTTI" + +var _SyncMarker_index = [...]uint16{0, 3, 7, 12, 18, 24, 29, 32, 38, 43, 51, 57, 60, 67, 73, 80, 83, 89, 95, 99, 106, 120, 129, 135, 140, 147, 150, 160, 168, 175, 182, 188, 195, 201, 209, 214, 218, 226, 232, 234, 241, 248, 252, 260, 269, 279, 296, 305, 313, 318, 327, 333, 340, 350, 359, 369, 379, 389, 394, 405, 416, 424, 432, 437, 445, 450, 458, 467, 472, 480} + +func (i SyncMarker) String() string { + i -= 1 + if i < 0 || i >= SyncMarker(len(_SyncMarker_index)-1) { + return "SyncMarker(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _SyncMarker_name[_SyncMarker_index[i]:_SyncMarker_index[i+1]] +} diff --git a/contribs/gnopls/internal/pkgbits/version.go b/contribs/gnopls/internal/pkgbits/version.go new file mode 100644 index 00000000000..53af9df22b3 --- /dev/null +++ b/contribs/gnopls/internal/pkgbits/version.go @@ -0,0 +1,85 @@ +// 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 pkgbits + +// Version indicates a version of a unified IR bitstream. +// Each Version indicates the addition, removal, or change of +// new data in the bitstream. +// +// These are serialized to disk and the interpretation remains fixed. +type Version uint32 + +const ( + // V0: initial prototype. + // + // All data that is not assigned a Field is in version V0 + // and has not been deprecated. + V0 Version = iota + + // V1: adds the Flags uint32 word + V1 + + // V2: removes unused legacy fields and supports type parameters for aliases. + // - remove the legacy "has init" bool from the public root + // - remove obj's "derived func instance" bool + // - add a TypeParamNames field to ObjAlias + // - remove derived info "needed" bool + V2 + + numVersions = iota +) + +// Field denotes a unit of data in the serialized unified IR bitstream. +// It is conceptually a like field in a structure. +// +// We only really need Fields when the data may or may not be present +// in a stream based on the Version of the bitstream. +// +// Unlike much of pkgbits, Fields are not serialized and +// can change values as needed. +type Field int + +const ( + // Flags in a uint32 in the header of a bitstream + // that is used to indicate whether optional features are enabled. + Flags Field = iota + + // Deprecated: HasInit was a bool indicating whether a package + // has any init functions. + HasInit + + // Deprecated: DerivedFuncInstance was a bool indicating + // whether an object was a function instance. + DerivedFuncInstance + + // ObjAlias has a list of TypeParamNames. + AliasTypeParamNames + + // Deprecated: DerivedInfoNeeded was a bool indicating + // whether a type was a derived type. + DerivedInfoNeeded + + numFields = iota +) + +// introduced is the version a field was added. +var introduced = [numFields]Version{ + Flags: V1, + AliasTypeParamNames: V2, +} + +// removed is the version a field was removed in or 0 for fields +// that have not yet been deprecated. +// (So removed[f]-1 is the last version it is included in.) +var removed = [numFields]Version{ + HasInit: V2, + DerivedFuncInstance: V2, + DerivedInfoNeeded: V2, +} + +// Has reports whether field f is present in a bitstream at version v. +func (v Version) Has(f Field) bool { + return introduced[f] <= v && (v < removed[f] || removed[f] == V0) +} diff --git a/contribs/gnopls/internal/pprof/main.go b/contribs/gnopls/internal/pprof/main.go new file mode 100644 index 00000000000..48ebd325e85 --- /dev/null +++ b/contribs/gnopls/internal/pprof/main.go @@ -0,0 +1,36 @@ +// 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. + +//go:build ignore +// +build ignore + +// The pprof command prints the total time in a pprof profile provided +// through the standard input. +package main + +import ( + "compress/gzip" + "fmt" + "io" + "log" + "os" + + "github.com/gnolang/gno/contribs/gnopls/internal/pprof" +) + +func main() { + rd, err := gzip.NewReader(os.Stdin) + if err != nil { + log.Fatal(err) + } + payload, err := io.ReadAll(rd) + if err != nil { + log.Fatal(err) + } + total, err := pprof.TotalTime(payload) + if err != nil { + log.Fatal(err) + } + fmt.Println(total) +} diff --git a/contribs/gnopls/internal/pprof/pprof.go b/contribs/gnopls/internal/pprof/pprof.go new file mode 100644 index 00000000000..f3edcc67c40 --- /dev/null +++ b/contribs/gnopls/internal/pprof/pprof.go @@ -0,0 +1,89 @@ +// 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 pprof provides minimalistic routines for extracting +// information from profiles. +package pprof + +import ( + "fmt" + "time" +) + +// TotalTime parses the profile data and returns the accumulated time. +// The input should not be gzipped. +func TotalTime(data []byte) (total time.Duration, err error) { + defer func() { + if x := recover(); x != nil { + err = fmt.Errorf("error parsing pprof profile: %v", x) + } + }() + decode(&total, data, msgProfile) + return +} + +// All errors are handled by panicking. +// Constants are copied below to avoid dependency on protobufs or pprof. + +// protobuf wire types, from https://developers.google.com/protocol-buffers/docs/encoding +const ( + wireVarint = 0 + wireBytes = 2 +) + +// pprof field numbers, from https://github.com/google/pprof/blob/master/proto/profile.proto +const ( + fldProfileSample = 2 // repeated Sample + fldSampleValue = 2 // repeated int64 +) + +// arbitrary numbering of message types +const ( + msgProfile = 0 + msgSample = 1 +) + +func decode(total *time.Duration, data []byte, msg int) { + for len(data) > 0 { + // Read tag (wire type and field number). + tag := varint(&data) + + // Read wire value (int or bytes). + wire := tag & 7 + var ival uint64 + var sval []byte + switch wire { + case wireVarint: + ival = varint(&data) + + case wireBytes: + n := varint(&data) + sval, data = data[:n], data[n:] + + default: + panic(fmt.Sprintf("unexpected wire type: %d", wire)) + } + + // Process field of msg. + fld := tag >> 3 + switch { + case msg == msgProfile && fld == fldProfileSample: + decode(total, sval, msgSample) // recursively decode Sample message + + case msg == msgSample, fld == fldSampleValue: + *total += time.Duration(ival) // accumulate time + } + } +} + +func varint(data *[]byte) (v uint64) { + for i := 0; ; i++ { + b := uint64((*data)[i]) + v += (b & 0x7f) << (7 * i) + if b < 0x80 { + *data = (*data)[i+1:] + return v + } + } +} diff --git a/contribs/gnopls/internal/pprof/pprof_test.go b/contribs/gnopls/internal/pprof/pprof_test.go new file mode 100644 index 00000000000..d95e0a165b9 --- /dev/null +++ b/contribs/gnopls/internal/pprof/pprof_test.go @@ -0,0 +1,46 @@ +// 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 pprof_test + +import ( + "bytes" + "compress/gzip" + "io" + "log" + "os" + "testing" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/pprof" +) + +func TestTotalTime(t *testing.T) { + // $ go tool pprof testdata/sample.pprof <&- 2>&1 | grep Total + // Duration: 11.10s, Total samples = 27.59s (248.65%) + const ( + filename = "testdata/sample.pprof" + want = time.Duration(27590003550) + ) + + profGz, err := os.ReadFile(filename) + if err != nil { + t.Fatal(err) + } + rd, err := gzip.NewReader(bytes.NewReader(profGz)) + if err != nil { + t.Fatal(err) + } + payload, err := io.ReadAll(rd) + if err != nil { + t.Fatal(err) + } + got, err := pprof.TotalTime(payload) + if err != nil { + log.Fatal(err) + } + if got != want { + t.Fatalf("TotalTime(%q): got %v (%d), want %v (%d)", filename, got, got, want, want) + } +} diff --git a/contribs/gnopls/internal/pprof/testdata/sample.pprof b/contribs/gnopls/internal/pprof/testdata/sample.pprof new file mode 100644 index 0000000000000000000000000000000000000000..a132b4d5109e670ee0b5fc358ab41a2a7dd4abef GIT binary patch literal 50325 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 O0RR8u)QgrT!~p;w6y`ht literal 0 HcmV?d00001 diff --git a/contribs/gnopls/internal/progress/progress.go b/contribs/gnopls/internal/progress/progress.go index e35c0fe19dc..131fbb04c69 100644 --- a/contribs/gnopls/internal/progress/progress.go +++ b/contribs/gnopls/internal/progress/progress.go @@ -16,10 +16,10 @@ import ( "strings" "sync" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) // NewTracker returns a new Tracker that reports progress to the diff --git a/contribs/gnopls/internal/progress/progress_test.go b/contribs/gnopls/internal/progress/progress_test.go index 642103ae025..80bf85c3fe2 100644 --- a/contribs/gnopls/internal/progress/progress_test.go +++ b/contribs/gnopls/internal/progress/progress_test.go @@ -10,7 +10,7 @@ import ( "sync" "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) type fakeClient struct { diff --git a/contribs/gnopls/internal/protocol/command/command_gen.go b/contribs/gnopls/internal/protocol/command/command_gen.go index 73e793c087e..df4a8a1c562 100644 --- a/contribs/gnopls/internal/protocol/command/command_gen.go +++ b/contribs/gnopls/internal/protocol/command/command_gen.go @@ -15,7 +15,7 @@ import ( "context" "fmt" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // Symbolic names for gopls commands, corresponding to methods of [Interface]. diff --git a/contribs/gnopls/internal/protocol/command/commandmeta/meta.go b/contribs/gnopls/internal/protocol/command/commandmeta/meta.go index 7cc4786f549..aa0dce2ec72 100644 --- a/contribs/gnopls/internal/protocol/command/commandmeta/meta.go +++ b/contribs/gnopls/internal/protocol/command/commandmeta/meta.go @@ -20,7 +20,7 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/packages" - "golang.org/x/tools/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" // (does not depend on gopls itself) ) @@ -53,7 +53,7 @@ func Load() ([]*Command, error) { Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedImports | packages.NeedDeps, BuildFlags: []string{"-tags=generate"}, }, - "golang.org/x/tools/gopls/internal/protocol/command", + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command", ) if err != nil { return nil, fmt.Errorf("packages.Load: %v", err) diff --git a/contribs/gnopls/internal/protocol/command/gen/gen.go b/contribs/gnopls/internal/protocol/command/gen/gen.go index d9722902ca9..29b18892897 100644 --- a/contribs/gnopls/internal/protocol/command/gen/gen.go +++ b/contribs/gnopls/internal/protocol/command/gen/gen.go @@ -13,8 +13,8 @@ import ( "log" "text/template" - "golang.org/x/tools/gopls/internal/protocol/command/commandmeta" - "golang.org/x/tools/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command/commandmeta" + "github.com/gnolang/gno/contribs/gnopls/internal/imports" ) const src = `// Copyright 2024 The Go Authors. All rights reserved. @@ -109,7 +109,7 @@ func Generate() ([]byte, error) { if err != nil { return nil, fmt.Errorf("loading command data: %v", err) } - const thispkg = "golang.org/x/tools/gopls/internal/protocol/command" + const thispkg = "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" qf := func(p *types.Package) string { if p.Path() == thispkg { return "" @@ -156,7 +156,7 @@ func Generate() ([]byte, error) { Imports: map[string]bool{ "context": true, "fmt": true, - "golang.org/x/tools/gopls/internal/protocol": true, + "github.com/gnolang/gno/contribs/gnopls/internal/protocol": true, }, } for _, c := range d.Commands { diff --git a/contribs/gnopls/internal/protocol/command/generate.go b/contribs/gnopls/internal/protocol/command/generate.go index 324bc51ccab..6479344662b 100644 --- a/contribs/gnopls/internal/protocol/command/generate.go +++ b/contribs/gnopls/internal/protocol/command/generate.go @@ -13,7 +13,7 @@ import ( "log" "os" - "golang.org/x/tools/gopls/internal/protocol/command/gen" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command/gen" ) func main() { diff --git a/contribs/gnopls/internal/protocol/command/interface.go b/contribs/gnopls/internal/protocol/command/interface.go index 4ea9157d7c1..361855414b2 100644 --- a/contribs/gnopls/internal/protocol/command/interface.go +++ b/contribs/gnopls/internal/protocol/command/interface.go @@ -17,8 +17,8 @@ package command import ( "context" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/vulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" ) // Interface defines the interface gopls exposes for the diff --git a/contribs/gnopls/internal/protocol/command/interface_test.go b/contribs/gnopls/internal/protocol/command/interface_test.go index ca880619f0e..b3d9fcf2942 100644 --- a/contribs/gnopls/internal/protocol/command/interface_test.go +++ b/contribs/gnopls/internal/protocol/command/interface_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol/command/gen" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command/gen" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) // TestGenerated ensures that we haven't forgotten to update command_gen.go. diff --git a/contribs/gnopls/internal/protocol/context.go b/contribs/gnopls/internal/protocol/context.go index 5f3151cda97..60378b561d1 100644 --- a/contribs/gnopls/internal/protocol/context.go +++ b/contribs/gnopls/internal/protocol/context.go @@ -9,11 +9,11 @@ import ( "context" "sync" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/core" - "golang.org/x/tools/internal/event/export" - "golang.org/x/tools/internal/event/label" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/core" + "github.com/gnolang/gno/contribs/gnopls/internal/event/export" + "github.com/gnolang/gno/contribs/gnopls/internal/event/label" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) type contextKey int diff --git a/contribs/gnopls/internal/protocol/edits.go b/contribs/gnopls/internal/protocol/edits.go index 5f70c4efdb5..0ffdee82db9 100644 --- a/contribs/gnopls/internal/protocol/edits.go +++ b/contribs/gnopls/internal/protocol/edits.go @@ -7,7 +7,7 @@ package protocol import ( "fmt" - "golang.org/x/tools/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" ) // EditsFromDiffEdits converts diff.Edits to a non-nil slice of LSP TextEdits. diff --git a/contribs/gnopls/internal/protocol/generate/main.go b/contribs/gnopls/internal/protocol/generate/main.go index de42540a054..29b81b631a1 100644 --- a/contribs/gnopls/internal/protocol/generate/main.go +++ b/contribs/gnopls/internal/protocol/generate/main.go @@ -98,7 +98,7 @@ func writeclient() { `import ( "context" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) `) out.WriteString("type Client interface {\n") @@ -127,7 +127,7 @@ func writeserver() { `import ( "context" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) `) out.WriteString("type Server interface {\n") diff --git a/contribs/gnopls/internal/protocol/json_test.go b/contribs/gnopls/internal/protocol/json_test.go index 9aac110fa3b..9d788944399 100644 --- a/contribs/gnopls/internal/protocol/json_test.go +++ b/contribs/gnopls/internal/protocol/json_test.go @@ -12,7 +12,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // verify that type errors in Initialize lsp messages don't cause diff --git a/contribs/gnopls/internal/protocol/log.go b/contribs/gnopls/internal/protocol/log.go index fdcbb7a8d8b..ebdf7a26385 100644 --- a/contribs/gnopls/internal/protocol/log.go +++ b/contribs/gnopls/internal/protocol/log.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) type loggingStream struct { diff --git a/contribs/gnopls/internal/protocol/mapper.go b/contribs/gnopls/internal/protocol/mapper.go index 85997c24dc4..1a67794a14c 100644 --- a/contribs/gnopls/internal/protocol/mapper.go +++ b/contribs/gnopls/internal/protocol/mapper.go @@ -71,8 +71,8 @@ import ( "sync" "unicode/utf8" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // A Mapper wraps the content of a file and provides mapping diff --git a/contribs/gnopls/internal/protocol/mapper_test.go b/contribs/gnopls/internal/protocol/mapper_test.go index 8ba611a99f9..6d48a2c1090 100644 --- a/contribs/gnopls/internal/protocol/mapper_test.go +++ b/contribs/gnopls/internal/protocol/mapper_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // This file tests Mapper's logic for converting between offsets, diff --git a/contribs/gnopls/internal/protocol/protocol.go b/contribs/gnopls/internal/protocol/protocol.go index 7cc5589aa0b..04c1a48f36b 100644 --- a/contribs/gnopls/internal/protocol/protocol.go +++ b/contribs/gnopls/internal/protocol/protocol.go @@ -12,11 +12,11 @@ import ( "io" "golang.org/x/telemetry/crashmonitor" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/jsonrpc2" - jsonrpc2_v2 "golang.org/x/tools/internal/jsonrpc2_v2" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + jsonrpc2_v2 "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2_v2" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) var ( diff --git a/contribs/gnopls/internal/protocol/tsclient.go b/contribs/gnopls/internal/protocol/tsclient.go index 3f860d5351a..454f29d0517 100644 --- a/contribs/gnopls/internal/protocol/tsclient.go +++ b/contribs/gnopls/internal/protocol/tsclient.go @@ -13,7 +13,7 @@ package protocol import ( "context" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) type Client interface { diff --git a/contribs/gnopls/internal/protocol/tsserver.go b/contribs/gnopls/internal/protocol/tsserver.go index b405aae1b89..ca7dd4c1aa7 100644 --- a/contribs/gnopls/internal/protocol/tsserver.go +++ b/contribs/gnopls/internal/protocol/tsserver.go @@ -13,7 +13,7 @@ package protocol import ( "context" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) type Server interface { diff --git a/contribs/gnopls/internal/protocol/uri.go b/contribs/gnopls/internal/protocol/uri.go index 86775b065f5..dc0e2f1090a 100644 --- a/contribs/gnopls/internal/protocol/uri.go +++ b/contribs/gnopls/internal/protocol/uri.go @@ -16,7 +16,7 @@ import ( "strings" "unicode" - "golang.org/x/tools/gopls/internal/util/pathutil" + "github.com/gnolang/gno/contribs/gnopls/internal/util/pathutil" ) // A DocumentURI is the URI of a client editor document. diff --git a/contribs/gnopls/internal/protocol/uri_test.go b/contribs/gnopls/internal/protocol/uri_test.go index cad71ddc13c..99039472e9d 100644 --- a/contribs/gnopls/internal/protocol/uri_test.go +++ b/contribs/gnopls/internal/protocol/uri_test.go @@ -10,7 +10,7 @@ package protocol_test import ( "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // TestURIFromPath tests the conversion between URIs and filenames. The test cases diff --git a/contribs/gnopls/internal/protocol/uri_windows_test.go b/contribs/gnopls/internal/protocol/uri_windows_test.go index 08471167a22..38abf33cc0e 100644 --- a/contribs/gnopls/internal/protocol/uri_windows_test.go +++ b/contribs/gnopls/internal/protocol/uri_windows_test.go @@ -11,7 +11,7 @@ import ( "path/filepath" "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // TestURIFromPath tests the conversion between URIs and filenames. The test cases diff --git a/contribs/gnopls/internal/proxydir/proxydir.go b/contribs/gnopls/internal/proxydir/proxydir.go new file mode 100644 index 00000000000..63af654b005 --- /dev/null +++ b/contribs/gnopls/internal/proxydir/proxydir.go @@ -0,0 +1,99 @@ +// 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 proxydir provides functions for writing module data to a directory +// in proxy format, so that it can be used as a module proxy by setting +// GOPROXY="file://<dir>". +package proxydir + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +// WriteModuleVersion creates a directory in the proxy dir for a module. +func WriteModuleVersion(rootDir, module, ver string, files map[string][]byte) (rerr error) { + dir := filepath.Join(rootDir, module, "@v") + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + // The go command checks for versions by looking at the "list" file. Since + // we are supporting multiple versions, create this file if it does not exist + // or append the version number to the preexisting file. + f, err := os.OpenFile(filepath.Join(dir, "list"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer checkClose("list file", f, &rerr) + if _, err := f.WriteString(ver + "\n"); err != nil { + return err + } + + // Serve the go.mod file on the <version>.mod url, if it exists. Otherwise, + // serve a stub. + modContents, ok := files["go.mod"] + if !ok { + modContents = []byte("module " + module) + } + if err := os.WriteFile(filepath.Join(dir, ver+".mod"), modContents, 0644); err != nil { + return err + } + + // info file, just the bare bones. + infoContents := []byte(fmt.Sprintf(`{"Version": "%v", "Time":"2017-12-14T13:08:43Z"}`, ver)) + if err := os.WriteFile(filepath.Join(dir, ver+".info"), infoContents, 0644); err != nil { + return err + } + + // zip of all the source files. + f, err = os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer checkClose("zip file", f, &rerr) + z := zip.NewWriter(f) + defer checkClose("zip writer", z, &rerr) + for name, contents := range files { + zf, err := z.Create(module + "@" + ver + "/" + name) + if err != nil { + return err + } + if _, err := zf.Write(contents); err != nil { + return err + } + } + + return nil +} + +func checkClose(name string, closer io.Closer, err *error) { + if cerr := closer.Close(); cerr != nil && *err == nil { + *err = fmt.Errorf("closing %s: %v", name, cerr) + } +} + +// ToURL returns the file uri for a proxy directory. +func ToURL(dir string) string { + if testenv.Go1Point() >= 13 { + // file URLs on Windows must start with file:///. See golang.org/issue/6027. + path := filepath.ToSlash(dir) + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + return "file://" + path + } else { + // Prior to go1.13, the Go command on Windows only accepted GOPROXY file URLs + // of the form file://C:/path/to/proxy. This was incorrect: when parsed, "C:" + // is interpreted as the host. See golang.org/issue/6027. This has been + // fixed in go1.13, but we emit the old format for old releases. + return "file://" + filepath.ToSlash(dir) + } +} diff --git a/contribs/gnopls/internal/proxydir/proxydir_test.go b/contribs/gnopls/internal/proxydir/proxydir_test.go new file mode 100644 index 00000000000..c8137229b04 --- /dev/null +++ b/contribs/gnopls/internal/proxydir/proxydir_test.go @@ -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 proxydir + +import ( + "archive/zip" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestWriteModuleVersion(t *testing.T) { + tests := []struct { + modulePath, version string + files map[string][]byte + }{ + { + modulePath: "mod.test/module", + version: "v1.2.3", + files: map[string][]byte{ + "go.mod": []byte("module mod.com\n\ngo 1.12"), + "const.go": []byte("package module\n\nconst Answer = 42"), + }, + }, + { + modulePath: "mod.test/module", + version: "v1.2.4", + files: map[string][]byte{ + "go.mod": []byte("module mod.com\n\ngo 1.12"), + "const.go": []byte("package module\n\nconst Answer = 43"), + }, + }, + { + modulePath: "mod.test/nogomod", + version: "v0.9.0", + files: map[string][]byte{ + "const.go": []byte("package module\n\nconst Other = \"Other\""), + }, + }, + } + dir, err := os.MkdirTemp("", "proxydirtest-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + for _, test := range tests { + // Since we later assert on the contents of /list, don't use subtests. + if err := WriteModuleVersion(dir, test.modulePath, test.version, test.files); err != nil { + t.Fatal(err) + } + rootDir := filepath.Join(dir, filepath.FromSlash(test.modulePath), "@v") + gomod, err := os.ReadFile(filepath.Join(rootDir, test.version+".mod")) + if err != nil { + t.Fatal(err) + } + wantMod, ok := test.files["go.mod"] + if !ok { + wantMod = []byte("module " + test.modulePath) + } + if got, want := string(gomod), string(wantMod); got != want { + t.Errorf("reading %s/@v/%s.mod: got %q, want %q", test.modulePath, test.version, got, want) + } + zr, err := zip.OpenReader(filepath.Join(rootDir, test.version+".zip")) + if err != nil { + t.Fatal(err) + } + defer zr.Close() + + for _, zf := range zr.File { + r, err := zf.Open() + if err != nil { + t.Fatal(err) + } + defer r.Close() + content, err := io.ReadAll(r) + if err != nil { + t.Fatal(err) + } + name := strings.TrimPrefix(zf.Name, fmt.Sprintf("%s@%s/", test.modulePath, test.version)) + if got, want := string(content), string(test.files[name]); got != want { + t.Errorf("unzipping %q: got %q, want %q", zf.Name, got, want) + } + delete(test.files, name) + } + for name := range test.files { + t.Errorf("file %q not present in the module zip", name) + } + } + + lists := []struct { + modulePath, want string + }{ + {"mod.test/module", "v1.2.3\nv1.2.4\n"}, + {"mod.test/nogomod", "v0.9.0\n"}, + } + + for _, test := range lists { + fp := filepath.Join(dir, filepath.FromSlash(test.modulePath), "@v", "list") + list, err := os.ReadFile(fp) + if err != nil { + t.Fatal(err) + } + if got := string(list); got != test.want { + t.Errorf("%q/@v/list: got %q, want %q", test.modulePath, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/refactor/inline/analyzer/analyzer.go b/contribs/gnopls/internal/refactor/inline/analyzer/analyzer.go new file mode 100644 index 00000000000..0c632552f6e --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/analyzer/analyzer.go @@ -0,0 +1,166 @@ +// 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. + +//go:build go1.20 + +package analyzer + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "os" + "strings" + + "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/go/types/typeutil" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline" +) + +const Doc = `inline calls to functions with "inlineme" doc comment` + +var Analyzer = &analysis.Analyzer{ + Name: "inline", + Doc: Doc, + URL: "https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline/analyzer", + Run: run, + FactTypes: []analysis.Fact{new(inlineMeFact)}, + Requires: []*analysis.Analyzer{inspect.Analyzer}, +} + +func run(pass *analysis.Pass) (interface{}, error) { + // Memoize repeated calls for same file. + // TODO(adonovan): the analysis.Pass should abstract this (#62292) + // as the driver may not be reading directly from the file system. + fileContent := make(map[string][]byte) + readFile := func(node ast.Node) ([]byte, error) { + filename := pass.Fset.File(node.Pos()).Name() + content, ok := fileContent[filename] + if !ok { + var err error + content, err = os.ReadFile(filename) + if err != nil { + return nil, err + } + fileContent[filename] = content + } + return content, nil + } + + // Pass 1: find functions annotated with an "inlineme" + // comment, and export a fact for each one. + inlinable := make(map[*types.Func]*inline.Callee) // memoization of fact import (nil => no fact) + for _, file := range pass.Files { + for _, decl := range file.Decls { + if decl, ok := decl.(*ast.FuncDecl); ok { + // TODO(adonovan): this is just a placeholder. + // Use the precise go:fix syntax in the proposal. + // Beware that //go: comments are treated specially + // by (*ast.CommentGroup).Text(). + // TODO(adonovan): alternatively, consider using + // the universal annotation mechanism sketched in + // https://go.dev/cl/489835 (which doesn't yet have + // a proper proposal). + if strings.Contains(decl.Doc.Text(), "inlineme") { + content, err := readFile(file) + if err != nil { + pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: cannot read source file: %v", err) + continue + } + callee, err := inline.AnalyzeCallee(discard, pass.Fset, pass.Pkg, pass.TypesInfo, decl, content) + if err != nil { + pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: %v", err) + continue + } + fn := pass.TypesInfo.Defs[decl.Name].(*types.Func) + pass.ExportObjectFact(fn, &inlineMeFact{callee}) + inlinable[fn] = callee + } + } + } + } + + // Pass 2. Inline each static call to an inlinable function. + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.File)(nil), + (*ast.CallExpr)(nil), + } + var currentFile *ast.File + inspect.Preorder(nodeFilter, func(n ast.Node) { + if file, ok := n.(*ast.File); ok { + currentFile = file + return + } + call := n.(*ast.CallExpr) + if fn := typeutil.StaticCallee(pass.TypesInfo, call); fn != nil { + // Inlinable? + callee, ok := inlinable[fn] + if !ok { + var fact inlineMeFact + if pass.ImportObjectFact(fn, &fact) { + callee = fact.Callee + inlinable[fn] = callee + } + } + if callee == nil { + return // nope + } + + // Inline the call. + content, err := readFile(call) + if err != nil { + pass.Reportf(call.Lparen, "invalid inlining candidate: cannot read source file: %v", err) + return + } + caller := &inline.Caller{ + Fset: pass.Fset, + Types: pass.Pkg, + Info: pass.TypesInfo, + File: currentFile, + Call: call, + Content: content, + } + res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard}) + if err != nil { + pass.Reportf(call.Lparen, "%v", err) + return + } + got := res.Content + + // Suggest the "fix". + var textEdits []analysis.TextEdit + for _, edit := range diff.Bytes(content, got) { + textEdits = append(textEdits, analysis.TextEdit{ + Pos: currentFile.FileStart + token.Pos(edit.Start), + End: currentFile.FileStart + token.Pos(edit.End), + NewText: []byte(edit.New), + }) + } + msg := fmt.Sprintf("inline call of %v", callee) + pass.Report(analysis.Diagnostic{ + Pos: call.Pos(), + End: call.End(), + Message: msg, + SuggestedFixes: []analysis.SuggestedFix{{ + Message: msg, + TextEdits: textEdits, + }}, + }) + } + }) + + return nil, nil +} + +type inlineMeFact struct{ Callee *inline.Callee } + +func (f *inlineMeFact) String() string { return "inlineme " + f.Callee.String() } +func (*inlineMeFact) AFact() {} + +func discard(string, ...any) {} diff --git a/contribs/gnopls/internal/refactor/inline/analyzer/analyzer_test.go b/contribs/gnopls/internal/refactor/inline/analyzer/analyzer_test.go new file mode 100644 index 00000000000..2348555d282 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/analyzer/analyzer_test.go @@ -0,0 +1,18 @@ +// Copyright 2018 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 + +package analyzer_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + inlineanalyzer "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline/analyzer" +) + +func TestAnalyzer(t *testing.T) { + analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), inlineanalyzer.Analyzer, "a", "b") +} diff --git a/contribs/gnopls/internal/refactor/inline/analyzer/main.go b/contribs/gnopls/internal/refactor/inline/analyzer/main.go new file mode 100644 index 00000000000..a26a02cf33c --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/analyzer/main.go @@ -0,0 +1,19 @@ +// 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. + +//go:build ignore +// +build ignore + +// The inline command applies the inliner to the specified packages of +// Go source code. Run with: +// +// $ go run ./internal/refactor/inline/analyzer/main.go -fix packages... +package main + +import ( + "golang.org/x/tools/go/analysis/singlechecker" + inlineanalyzer "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline/analyzer" +) + +func main() { singlechecker.Main(inlineanalyzer.Analyzer) } diff --git a/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/a/a.go b/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/a/a.go new file mode 100644 index 00000000000..294278670f2 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/a/a.go @@ -0,0 +1,17 @@ +package a + +func f() { + One() // want `inline call of a.One` + + new(T).Two() // want `inline call of \(a.T\).Two` +} + +type T struct{} + +// inlineme +func One() int { return one } // want One:`inlineme a.One` + +const one = 1 + +// inlineme +func (T) Two() int { return 2 } // want Two:`inlineme \(a.T\).Two` diff --git a/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/a/a.go.golden b/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/a/a.go.golden new file mode 100644 index 00000000000..1a214fc9148 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/a/a.go.golden @@ -0,0 +1,17 @@ +package a + +func f() { + _ = one // want `inline call of a.One` + + _ = 2 // want `inline call of \(a.T\).Two` +} + +type T struct{} + +// inlineme +func One() int { return one } // want One:`inlineme a.One` + +const one = 1 + +// inlineme +func (T) Two() int { return 2 } // want Two:`inlineme \(a.T\).Two` diff --git a/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/b/b.go b/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/b/b.go new file mode 100644 index 00000000000..069e670d51e --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/b/b.go @@ -0,0 +1,9 @@ +package b + +import "a" + +func f() { + a.One() // want `cannot inline call to a.One because body refers to non-exported one` + + new(a.T).Two() // want `inline call of \(a.T\).Two` +} diff --git a/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/b/b.go.golden b/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/b/b.go.golden new file mode 100644 index 00000000000..b871b4b5100 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/analyzer/testdata/src/b/b.go.golden @@ -0,0 +1,9 @@ +package b + +import "a" + +func f() { + a.One() // want `cannot inline call to a.One because body refers to non-exported one` + + _ = 2 // want `inline call of \(a.T\).Two` +} diff --git a/contribs/gnopls/internal/refactor/inline/callee.go b/contribs/gnopls/internal/refactor/inline/callee.go new file mode 100644 index 00000000000..33a62774a42 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/callee.go @@ -0,0 +1,538 @@ +// 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 inline + +// This file defines the analysis of the callee function. + +import ( + "bytes" + "encoding/gob" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/go/types/typeutil" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" +) + +// A Callee holds information about an inlinable function. Gob-serializable. +type Callee struct { + impl gobCallee +} + +func (callee *Callee) String() string { return callee.impl.Name } + +type gobCallee struct { + Content []byte // file content, compacted to a single func decl + + // results of type analysis (does not reach go/types data structures) + PkgPath string // package path of declaring package + Name string // user-friendly name for error messages + Unexported []string // names of free objects that are unexported + FreeRefs []freeRef // locations of references to free objects + FreeObjs []object // descriptions of free objects + ValidForCallStmt bool // function body is "return expr" where expr is f() or <-ch + NumResults int // number of results (according to type, not ast.FieldList) + Params []*paramInfo // information about parameters (incl. receiver) + Results []*paramInfo // information about result variables + Effects []int // order in which parameters are evaluated (see calleefx) + HasDefer bool // uses defer + HasBareReturn bool // uses bare return in non-void function + Returns [][]returnOperandFlags // metadata about result expressions for each return + Labels []string // names of all control labels + Falcon falconResult // falcon constraint system +} + +// returnOperandFlags records metadata about a single result expression in a return +// statement. +type returnOperandFlags int + +const ( + nonTrivialResult returnOperandFlags = 1 << iota // return operand has non-trivial conversion to result type + untypedNilResult // return operand is nil literal +) + +// A freeRef records a reference to a free object. Gob-serializable. +// (This means free relative to the FuncDecl as a whole, i.e. excluding parameters.) +type freeRef struct { + Offset int // byte offset of the reference relative to the FuncDecl + Object int // index into Callee.freeObjs +} + +// An object abstracts a free types.Object referenced by the callee. Gob-serializable. +type object struct { + Name string // Object.Name() + Kind string // one of {var,func,const,type,pkgname,nil,builtin} + PkgPath string // path of object's package (or imported package if kind="pkgname") + PkgName string // name of object's package (or imported package if kind="pkgname") + // TODO(rfindley): should we also track LocalPkgName here? Do we want to + // preserve the local package name? + ValidPos bool // Object.Pos().IsValid() + Shadow map[string]bool // names shadowed at one of the object's refs +} + +// AnalyzeCallee analyzes a function that is a candidate for inlining +// and returns a Callee that describes it. The Callee object, which is +// serializable, can be passed to one or more subsequent calls to +// Inline, each with a different Caller. +// +// This design allows separate analysis of callers and callees in the +// golang.org/x/tools/go/analysis framework: the inlining information +// about a callee can be recorded as a "fact". +// +// The content should be the actual input to the compiler, not the +// apparent source file according to any //line directives that +// may be present within it. +func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Package, info *types.Info, decl *ast.FuncDecl, content []byte) (*Callee, error) { + checkInfoFields(info) + + // The client is expected to have determined that the callee + // is a function with a declaration (not a built-in or var). + fn := info.Defs[decl.Name].(*types.Func) + sig := fn.Type().(*types.Signature) + + logf("analyzeCallee %v @ %v", fn, fset.PositionFor(decl.Pos(), false)) + + // Create user-friendly name ("pkg.Func" or "(pkg.T).Method") + var name string + if sig.Recv() == nil { + name = fmt.Sprintf("%s.%s", fn.Pkg().Name(), fn.Name()) + } else { + name = fmt.Sprintf("(%s).%s", types.TypeString(sig.Recv().Type(), (*types.Package).Name), fn.Name()) + } + + if decl.Body == nil { + return nil, fmt.Errorf("cannot inline function %s as it has no body", name) + } + + // TODO(adonovan): support inlining of instantiated generic + // functions by replacing each occurrence of a type parameter + // T by its instantiating type argument (e.g. int). We'll need + // to wrap the instantiating type in parens when it's not an + // ident or qualified ident to prevent "if x == struct{}" + // parsing ambiguity, or "T(x)" where T = "*int" or "func()" + // from misparsing. + if funcHasTypeParams(decl) { + return nil, fmt.Errorf("cannot inline generic function %s: type parameters are not yet supported", name) + } + + // Record the location of all free references in the FuncDecl. + // (Parameters are not free by this definition.) + var ( + freeObjIndex = make(map[types.Object]int) + freeObjs []object + freeRefs []freeRef // free refs that may need renaming + unexported []string // free refs to unexported objects, for later error checks + ) + var f func(n ast.Node) bool + visit := func(n ast.Node) { ast.Inspect(n, f) } + var stack []ast.Node + stack = append(stack, decl.Type) // for scope of function itself + f = func(n ast.Node) bool { + if n != nil { + stack = append(stack, n) // push + } else { + stack = stack[:len(stack)-1] // pop + } + switch n := n.(type) { + case *ast.SelectorExpr: + // Check selections of free fields/methods. + if sel, ok := info.Selections[n]; ok && + !within(sel.Obj().Pos(), decl) && + !n.Sel.IsExported() { + sym := fmt.Sprintf("(%s).%s", info.TypeOf(n.X), n.Sel.Name) + unexported = append(unexported, sym) + } + + // Don't recur into SelectorExpr.Sel. + visit(n.X) + return false + + case *ast.CompositeLit: + // Check for struct literals that refer to unexported fields, + // whether keyed or unkeyed. (Logic assumes well-typedness.) + litType := typeparams.Deref(info.TypeOf(n)) + if s, ok := typeparams.CoreType(litType).(*types.Struct); ok { + if n.Type != nil { + visit(n.Type) + } + for i, elt := range n.Elts { + var field *types.Var + var value ast.Expr + if kv, ok := elt.(*ast.KeyValueExpr); ok { + field = info.Uses[kv.Key.(*ast.Ident)].(*types.Var) + value = kv.Value + } else { + field = s.Field(i) + value = elt + } + if !within(field.Pos(), decl) && !field.Exported() { + sym := fmt.Sprintf("(%s).%s", litType, field.Name()) + unexported = append(unexported, sym) + } + + // Don't recur into KeyValueExpr.Key. + visit(value) + } + return false + } + + case *ast.Ident: + if obj, ok := info.Uses[n]; ok { + // Methods and fields are handled by SelectorExpr and CompositeLit. + if isField(obj) || isMethod(obj) { + panic(obj) + } + // Inv: id is a lexical reference. + + // A reference to an unexported package-level declaration + // cannot be inlined into another package. + if !n.IsExported() && + obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope() { + unexported = append(unexported, n.Name) + } + + // Record free reference (incl. self-reference). + if obj == fn || !within(obj.Pos(), decl) { + objidx, ok := freeObjIndex[obj] + if !ok { + objidx = len(freeObjIndex) + var pkgpath, pkgname string + if pn, ok := obj.(*types.PkgName); ok { + pkgpath = pn.Imported().Path() + pkgname = pn.Imported().Name() + } else if obj.Pkg() != nil { + pkgpath = obj.Pkg().Path() + pkgname = obj.Pkg().Name() + } + freeObjs = append(freeObjs, object{ + Name: obj.Name(), + Kind: objectKind(obj), + PkgName: pkgname, + PkgPath: pkgpath, + ValidPos: obj.Pos().IsValid(), + }) + freeObjIndex[obj] = objidx + } + + freeObjs[objidx].Shadow = addShadows(freeObjs[objidx].Shadow, info, obj.Name(), stack) + + freeRefs = append(freeRefs, freeRef{ + Offset: int(n.Pos() - decl.Pos()), + Object: objidx, + }) + } + } + } + return true + } + visit(decl) + + // Analyze callee body for "return expr" form, + // where expr is f() or <-ch. These forms are + // safe to inline as a standalone statement. + validForCallStmt := false + if len(decl.Body.List) != 1 { + // not just a return statement + } else if ret, ok := decl.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 { + validForCallStmt = func() bool { + switch expr := ast.Unparen(ret.Results[0]).(type) { + case *ast.CallExpr: // f(x) + callee := typeutil.Callee(info, expr) + if callee == nil { + return false // conversion T(x) + } + + // The only non-void built-in functions that may be + // called as a statement are copy and recover + // (though arguably a call to recover should never + // be inlined as that changes its behavior). + if builtin, ok := callee.(*types.Builtin); ok { + return builtin.Name() == "copy" || + builtin.Name() == "recover" + } + + return true // ordinary call f() + + case *ast.UnaryExpr: // <-x + return expr.Op == token.ARROW // channel receive <-ch + } + + // No other expressions are valid statements. + return false + }() + } + + // Record information about control flow in the callee + // (but not any nested functions). + var ( + hasDefer = false + hasBareReturn = false + returnInfo [][]returnOperandFlags + labels []string + ) + ast.Inspect(decl.Body, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.FuncLit: + return false // prune traversal + case *ast.DeferStmt: + hasDefer = true + case *ast.LabeledStmt: + labels = append(labels, n.Label.Name) + case *ast.ReturnStmt: + + // Are implicit assignment conversions + // to result variables all trivial? + var resultInfo []returnOperandFlags + if len(n.Results) > 0 { + argInfo := func(i int) (ast.Expr, types.Type) { + expr := n.Results[i] + return expr, info.TypeOf(expr) + } + if len(n.Results) == 1 && sig.Results().Len() > 1 { + // Spread return: return f() where f.Results > 1. + tuple := info.TypeOf(n.Results[0]).(*types.Tuple) + argInfo = func(i int) (ast.Expr, types.Type) { + return nil, tuple.At(i).Type() + } + } + for i := 0; i < sig.Results().Len(); i++ { + expr, typ := argInfo(i) + var flags returnOperandFlags + if typ == types.Typ[types.UntypedNil] { // untyped nil is preserved by go/types + flags |= untypedNilResult + } + if !trivialConversion(info.Types[expr].Value, typ, sig.Results().At(i).Type()) { + flags |= nonTrivialResult + } + resultInfo = append(resultInfo, flags) + } + } else if sig.Results().Len() > 0 { + hasBareReturn = true + } + returnInfo = append(returnInfo, resultInfo) + } + return true + }) + + // Reject attempts to inline cgo-generated functions. + for _, obj := range freeObjs { + // There are others (iconst fconst sconst fpvar macro) + // but this is probably sufficient. + if strings.HasPrefix(obj.Name, "_Cfunc_") || + strings.HasPrefix(obj.Name, "_Ctype_") || + strings.HasPrefix(obj.Name, "_Cvar_") { + return nil, fmt.Errorf("cannot inline cgo-generated functions") + } + } + + // Compact content to just the FuncDecl. + // + // As a space optimization, we don't retain the complete + // callee file content; all we need is "package _; func f() { ... }". + // This reduces the size of analysis facts. + // + // Offsets in the callee information are "relocatable" + // since they are all relative to the FuncDecl. + + content = append([]byte("package _\n"), + content[offsetOf(fset, decl.Pos()):offsetOf(fset, decl.End())]...) + // Sanity check: re-parse the compacted content. + if _, _, err := parseCompact(content); err != nil { + return nil, err + } + + params, results, effects, falcon := analyzeParams(logf, fset, info, decl) + return &Callee{gobCallee{ + Content: content, + PkgPath: pkg.Path(), + Name: name, + Unexported: unexported, + FreeObjs: freeObjs, + FreeRefs: freeRefs, + ValidForCallStmt: validForCallStmt, + NumResults: sig.Results().Len(), + Params: params, + Results: results, + Effects: effects, + HasDefer: hasDefer, + HasBareReturn: hasBareReturn, + Returns: returnInfo, + Labels: labels, + Falcon: falcon, + }}, nil +} + +// parseCompact parses a Go source file of the form "package _\n func f() { ... }" +// and returns the sole function declaration. +func parseCompact(content []byte) (*token.FileSet, *ast.FuncDecl, error) { + fset := token.NewFileSet() + const mode = parser.ParseComments | parser.SkipObjectResolution | parser.AllErrors + f, err := parser.ParseFile(fset, "callee.go", content, mode) + if err != nil { + return nil, nil, fmt.Errorf("internal error: cannot compact file: %v", err) + } + return fset, f.Decls[0].(*ast.FuncDecl), nil +} + +// A paramInfo records information about a callee receiver, parameter, or result variable. +type paramInfo struct { + Name string // parameter name (may be blank, or even "") + Index int // index within signature + IsResult bool // false for receiver or parameter, true for result variable + Assigned bool // parameter appears on left side of an assignment statement + Escapes bool // parameter has its address taken + Refs []int // FuncDecl-relative byte offset of parameter ref within body + Shadow map[string]bool // names shadowed at one of the above refs + FalconType string // name of this parameter's type (if basic) in the falcon system +} + +// analyzeParams computes information about parameters of function fn, +// including a simple "address taken" escape analysis. +// +// It returns two new arrays, one of the receiver and parameters, and +// the other of the result variables of function fn. +// +// The input must be well-typed. +func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.Info, decl *ast.FuncDecl) (params, results []*paramInfo, effects []int, _ falconResult) { + fnobj, ok := info.Defs[decl.Name] + if !ok { + panic(fmt.Sprintf("%s: no func object for %q", + fset.PositionFor(decl.Name.Pos(), false), decl.Name)) // ill-typed? + } + + paramInfos := make(map[*types.Var]*paramInfo) + { + sig := fnobj.Type().(*types.Signature) + newParamInfo := func(param *types.Var, isResult bool) *paramInfo { + info := ¶mInfo{ + Name: param.Name(), + IsResult: isResult, + Index: len(paramInfos), + } + paramInfos[param] = info + return info + } + if sig.Recv() != nil { + params = append(params, newParamInfo(sig.Recv(), false)) + } + for i := 0; i < sig.Params().Len(); i++ { + params = append(params, newParamInfo(sig.Params().At(i), false)) + } + for i := 0; i < sig.Results().Len(); i++ { + results = append(results, newParamInfo(sig.Results().At(i), true)) + } + } + + // Search function body for operations &x, x.f(), and x = y + // where x is a parameter, and record it. + escape(info, decl, func(v *types.Var, escapes bool) { + if info := paramInfos[v]; info != nil { + if escapes { + info.Escapes = true + } else { + info.Assigned = true + } + } + }) + + // Record locations of all references to parameters. + // And record the set of intervening definitions for each parameter. + // + // TODO(adonovan): combine this traversal with the one that computes + // FreeRefs. The tricky part is that calleefx needs this one first. + var stack []ast.Node + stack = append(stack, decl.Type) // for scope of function itself + ast.Inspect(decl.Body, func(n ast.Node) bool { + if n != nil { + stack = append(stack, n) // push + } else { + stack = stack[:len(stack)-1] // pop + } + + if id, ok := n.(*ast.Ident); ok { + if v, ok := info.Uses[id].(*types.Var); ok { + if pinfo, ok := paramInfos[v]; ok { + // Record location of ref to parameter/result + // and any intervening (shadowing) names. + offset := int(n.Pos() - decl.Pos()) + pinfo.Refs = append(pinfo.Refs, offset) + pinfo.Shadow = addShadows(pinfo.Shadow, info, pinfo.Name, stack) + } + } + } + return true + }) + + // Compute subset and order of parameters that are strictly evaluated. + // (Depends on Refs computed above.) + effects = calleefx(info, decl.Body, paramInfos) + logf("effects list = %v", effects) + + falcon := falcon(logf, fset, paramInfos, info, decl) + + return params, results, effects, falcon +} + +// -- callee helpers -- + +// addShadows returns the shadows set augmented by the set of names +// locally shadowed at the location of the reference in the callee +// (identified by the stack). The name of the reference itself is +// excluded. +// +// These shadowed names may not be used in a replacement expression +// for the reference. +func addShadows(shadows map[string]bool, info *types.Info, exclude string, stack []ast.Node) map[string]bool { + for _, n := range stack { + if scope := scopeFor(info, n); scope != nil { + for _, name := range scope.Names() { + if name != exclude { + if shadows == nil { + shadows = make(map[string]bool) + } + shadows[name] = true + } + } + } + } + return shadows +} + +func isField(obj types.Object) bool { + if v, ok := obj.(*types.Var); ok && v.IsField() { + return true + } + return false +} + +func isMethod(obj types.Object) bool { + if f, ok := obj.(*types.Func); ok && f.Type().(*types.Signature).Recv() != nil { + return true + } + return false +} + +// -- serialization -- + +var ( + _ gob.GobEncoder = (*Callee)(nil) + _ gob.GobDecoder = (*Callee)(nil) +) + +func (callee *Callee) GobEncode() ([]byte, error) { + var out bytes.Buffer + if err := gob.NewEncoder(&out).Encode(callee.impl); err != nil { + return nil, err + } + return out.Bytes(), nil +} + +func (callee *Callee) GobDecode(data []byte) error { + return gob.NewDecoder(bytes.NewReader(data)).Decode(&callee.impl) +} diff --git a/contribs/gnopls/internal/refactor/inline/calleefx.go b/contribs/gnopls/internal/refactor/inline/calleefx.go new file mode 100644 index 00000000000..11246e5b969 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/calleefx.go @@ -0,0 +1,347 @@ +// 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 inline + +// This file defines the analysis of callee effects. + +import ( + "go/ast" + "go/token" + "go/types" +) + +const ( + rinf = -1 // R∞: arbitrary read from memory + winf = -2 // W∞: arbitrary write to memory (or unknown control) +) + +// calleefx returns a list of parameter indices indicating the order +// in which parameters are first referenced during evaluation of the +// callee, relative both to each other and to other effects of the +// callee (if any), such as arbitrary reads (rinf) and arbitrary +// effects (winf), including unknown control flow. Each parameter +// that is referenced appears once in the list. +// +// For example, the effects list of this function: +// +// func f(x, y, z int) int { +// return y + x + g() + z +// } +// +// is [1 0 -2 2], indicating reads of y and x, followed by the unknown +// effects of the g() call. and finally the read of parameter z. This +// information is used during inlining to ascertain when it is safe +// for parameter references to be replaced by their corresponding +// argument expressions. Such substitutions are permitted only when +// they do not cause "write" operations (those with effects) to +// commute with "read" operations (those that have no effect but are +// not pure). Impure operations may be reordered with other impure +// operations, and pure operations may be reordered arbitrarily. +// +// The analysis ignores the effects of runtime panics, on the +// assumption that well-behaved programs shouldn't encounter them. +func calleefx(info *types.Info, body *ast.BlockStmt, paramInfos map[*types.Var]*paramInfo) []int { + // This traversal analyzes the callee's statements (in syntax + // form, though one could do better with SSA) to compute the + // sequence of events of the following kinds: + // + // 1 read of a parameter variable. + // 2. reads from other memory. + // 3. writes to memory + + var effects []int // indices of parameters, or rinf/winf (-ve) + seen := make(map[int]bool) + effect := func(i int) { + if !seen[i] { + seen[i] = true + effects = append(effects, i) + } + } + + // unknown is called for statements of unknown effects (or control). + unknown := func() { + effect(winf) + + // Ensure that all remaining parameters are "seen" + // after we go into the unknown (unless they are + // unreferenced by the function body). This lets us + // not bother implementing the complete traversal into + // control structures. + // + // TODO(adonovan): add them in a deterministic order. + // (This is not a bug but determinism is good.) + for _, pinfo := range paramInfos { + if !pinfo.IsResult && len(pinfo.Refs) > 0 { + effect(pinfo.Index) + } + } + } + + var visitExpr func(n ast.Expr) + var visitStmt func(n ast.Stmt) bool + visitExpr = func(n ast.Expr) { + switch n := n.(type) { + case *ast.Ident: + if v, ok := info.Uses[n].(*types.Var); ok && !v.IsField() { + // Use of global? + if v.Parent() == v.Pkg().Scope() { + effect(rinf) // read global var + } + + // Use of parameter? + if pinfo, ok := paramInfos[v]; ok && !pinfo.IsResult { + effect(pinfo.Index) // read parameter var + } + + // Use of local variables is ok. + } + + case *ast.BasicLit: + // no effect + + case *ast.FuncLit: + // A func literal has no read or write effect + // until called, and (most) function calls are + // considered to have arbitrary effects. + // So, no effect. + + case *ast.CompositeLit: + for _, elt := range n.Elts { + visitExpr(elt) // note: visits KeyValueExpr + } + + case *ast.ParenExpr: + visitExpr(n.X) + + case *ast.SelectorExpr: + if seln, ok := info.Selections[n]; ok { + visitExpr(n.X) + + // See types.SelectionKind for background. + switch seln.Kind() { + case types.MethodExpr: + // A method expression T.f acts like a + // reference to a func decl, + // so it doesn't read x until called. + + case types.MethodVal, types.FieldVal: + // A field or method value selection x.f + // reads x if the selection indirects a pointer. + + if indirectSelection(seln) { + effect(rinf) + } + } + } else { + // qualified identifier: treat like unqualified + visitExpr(n.Sel) + } + + case *ast.IndexExpr: + if tv := info.Types[n.Index]; tv.IsType() { + // no effect (G[T] instantiation) + } else { + visitExpr(n.X) + visitExpr(n.Index) + switch tv.Type.Underlying().(type) { + case *types.Slice, *types.Pointer: // []T, *[n]T (not string, [n]T) + effect(rinf) // indirect read of slice/array element + } + } + + case *ast.IndexListExpr: + // no effect (M[K,V] instantiation) + + case *ast.SliceExpr: + visitExpr(n.X) + visitExpr(n.Low) + visitExpr(n.High) + visitExpr(n.Max) + + case *ast.TypeAssertExpr: + visitExpr(n.X) + + case *ast.CallExpr: + if info.Types[n.Fun].IsType() { + // conversion T(x) + visitExpr(n.Args[0]) + } else { + // call f(args) + visitExpr(n.Fun) + for i, arg := range n.Args { + if i == 0 && info.Types[arg].IsType() { + continue // new(T), make(T, n) + } + visitExpr(arg) + } + + // The pure built-ins have no effects beyond + // those of their operands (not even memory reads). + // All other calls have unknown effects. + if !callsPureBuiltin(info, n) { + unknown() // arbitrary effects + } + } + + case *ast.StarExpr: + visitExpr(n.X) + effect(rinf) // *ptr load or store depends on state of heap + + case *ast.UnaryExpr: // + - ! ^ & ~ <- + visitExpr(n.X) + if n.Op == token.ARROW { + unknown() // effect: channel receive + } + + case *ast.BinaryExpr: + visitExpr(n.X) + visitExpr(n.Y) + + case *ast.KeyValueExpr: + visitExpr(n.Key) // may be a struct field + visitExpr(n.Value) + + case *ast.BadExpr: + // no effect + + case nil: + // optional subtree + + default: + // type syntax: unreachable given traversal + panic(n) + } + } + + // visitStmt's result indicates the continuation: + // false for return, true for the next statement. + // + // We could treat return as an unknown, but this way + // yields definite effects for simple sequences like + // {S1; S2; return}, so unreferenced parameters are + // not spuriously added to the effects list, and thus + // not spuriously disqualified from elimination. + visitStmt = func(n ast.Stmt) bool { + switch n := n.(type) { + case *ast.DeclStmt: + decl := n.Decl.(*ast.GenDecl) + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: + for _, v := range spec.Values { + visitExpr(v) + } + + case *ast.TypeSpec: + // no effect + } + } + + case *ast.LabeledStmt: + return visitStmt(n.Stmt) + + case *ast.ExprStmt: + visitExpr(n.X) + + case *ast.SendStmt: + visitExpr(n.Chan) + visitExpr(n.Value) + unknown() // effect: channel send + + case *ast.IncDecStmt: + visitExpr(n.X) + unknown() // effect: variable increment + + case *ast.AssignStmt: + for _, lhs := range n.Lhs { + visitExpr(lhs) + } + for _, rhs := range n.Rhs { + visitExpr(rhs) + } + for _, lhs := range n.Lhs { + id, _ := lhs.(*ast.Ident) + if id != nil && id.Name == "_" { + continue // blank assign has no effect + } + if n.Tok == token.DEFINE && id != nil && info.Defs[id] != nil { + continue // new var declared by := has no effect + } + unknown() // assignment to existing var + break + } + + case *ast.GoStmt: + visitExpr(n.Call.Fun) + for _, arg := range n.Call.Args { + visitExpr(arg) + } + unknown() // effect: create goroutine + + case *ast.DeferStmt: + visitExpr(n.Call.Fun) + for _, arg := range n.Call.Args { + visitExpr(arg) + } + unknown() // effect: push defer + + case *ast.ReturnStmt: + for _, res := range n.Results { + visitExpr(res) + } + return false + + case *ast.BlockStmt: + for _, stmt := range n.List { + if !visitStmt(stmt) { + return false + } + } + + case *ast.BranchStmt: + unknown() // control flow + + case *ast.IfStmt: + visitStmt(n.Init) + visitExpr(n.Cond) + unknown() // control flow + + case *ast.SwitchStmt: + visitStmt(n.Init) + visitExpr(n.Tag) + unknown() // control flow + + case *ast.TypeSwitchStmt: + visitStmt(n.Init) + visitStmt(n.Assign) + unknown() // control flow + + case *ast.SelectStmt: + unknown() // control flow + + case *ast.ForStmt: + visitStmt(n.Init) + visitExpr(n.Cond) + unknown() // control flow + + case *ast.RangeStmt: + visitExpr(n.X) + unknown() // control flow + + case *ast.EmptyStmt, *ast.BadStmt: + // no effect + + case nil: + // optional subtree + + default: + panic(n) + } + return true + } + visitStmt(body) + + return effects +} diff --git a/contribs/gnopls/internal/refactor/inline/calleefx_test.go b/contribs/gnopls/internal/refactor/inline/calleefx_test.go new file mode 100644 index 00000000000..46e56be7afd --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/calleefx_test.go @@ -0,0 +1,159 @@ +// 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 inline_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline" +) + +// TestCalleeEffects is a unit test of the calleefx analysis. +func TestCalleeEffects(t *testing.T) { + // Each callee must declare a function or method named f. + const funcName = "f" + + var tests = []struct { + descr string + callee string // Go source file (sans package decl) containing callee decl + want string // expected effects string (-1=R∞ -2=W∞) + }{ + { + "Assignments have unknown effects.", + `func f(x, y int) { x = y }`, + `[0 1 -2]`, + }, + { + "Reads from globals are impure.", + `func f() { _ = g }; var g int`, + `[-1]`, + }, + { + "Writes to globals have effects.", + `func f() { g = 0 }; var g int`, + `[-1 -2]`, // the -1 is spurious but benign + }, + { + "Blank assign has no effect.", + `func f(x int) { _ = x }`, + `[0]`, + }, + { + "Short decl of new var has has no effect.", + `func f(x int) { y := x; _ = y }`, + `[0]`, + }, + { + "Short decl of existing var (y) is an assignment.", + `func f(x int) { y := x; y, z := 1, 2; _, _ = y, z }`, + `[0 -2]`, + }, + { + "Unreferenced parameters are excluded.", + `func f(x, y, z int) { _ = z + x }`, + `[2 0]`, + }, + { + "Built-in len has no effect.", + `func f(x, y string) { _ = len(y) + len(x) }`, + `[1 0]`, + }, + { + "Built-in println has effects.", + `func f(x, y int) { println(y, x) }`, + `[1 0 -2]`, + }, + { + "Return has no effect, and no control successor.", + `func f(x, y int) int { return x + y; panic(1) }`, + `[0 1]`, + }, + { + "Loops (etc) have unknown effects.", + `func f(x, y bool) { for x { _ = y } }`, + `[0 -2 1]`, + }, + { + "Calls have unknown effects.", + `func f(x, y int) { _, _, _ = x, g(), y }; func g() int`, + `[0 -2 1]`, + }, + { + "Calls to some built-ins are pure.", + `func f(x, y int) { _, _, _ = x, len("hi"), y }`, + `[0 1]`, + }, + { + "Calls to some built-ins are pure (variant).", + `func f(x, y int) { s := "hi"; _, _, _ = x, len(s), y; s = "bye" }`, + `[0 1 -2]`, + }, + { + "Calls to some built-ins are pure (another variants).", + `func f(x, y int) { s := "hi"; _, _, _ = x, len(s), y }`, + `[0 1]`, + }, + { + "Reading a local var is impure but does not have effects.", + `func f(x, y bool) { for x { _ = y } }`, + `[0 -2 1]`, + }, + } + for _, test := range tests { + test := test + t.Run(test.descr, func(t *testing.T) { + fset := token.NewFileSet() + mustParse := func(filename string, content any) *ast.File { + f, err := parser.ParseFile(fset, filename, content, parser.ParseComments|parser.SkipObjectResolution) + if err != nil { + t.Fatalf("ParseFile: %v", err) + } + return f + } + + // Parse callee file and find first func decl named f. + calleeContent := "package p\n" + test.callee + calleeFile := mustParse("callee.go", calleeContent) + var decl *ast.FuncDecl + for _, d := range calleeFile.Decls { + if d, ok := d.(*ast.FuncDecl); ok && d.Name.Name == funcName { + decl = d + break + } + } + if decl == nil { + t.Fatalf("declaration of func %s not found: %s", funcName, test.callee) + } + + info := &types.Info{ + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Types: make(map[ast.Expr]types.TypeAndValue), + Implicits: make(map[ast.Node]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + } + conf := &types.Config{Error: func(err error) { t.Error(err) }} + pkg, err := conf.Check("p", fset, []*ast.File{calleeFile}, info) + if err != nil { + t.Fatal(err) + } + + callee, err := inline.AnalyzeCallee(t.Logf, fset, pkg, info, decl, []byte(calleeContent)) + if err != nil { + t.Fatal(err) + } + if got := fmt.Sprint(callee.Effects()); got != test.want { + t.Errorf("for effects of %s, got %s want %s", + test.callee, got, test.want) + } + }) + } +} diff --git a/contribs/gnopls/internal/refactor/inline/doc.go b/contribs/gnopls/internal/refactor/inline/doc.go new file mode 100644 index 00000000000..6bb4cef055d --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/doc.go @@ -0,0 +1,288 @@ +// 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 inline implements inlining of Go function calls. + +The client provides information about the caller and callee, +including the source text, syntax tree, and type information, and +the inliner returns the modified source file for the caller, or an +error if the inlining operation is invalid (for example because the +function body refers to names that are inaccessible to the caller). + +Although this interface demands more information from the client +than might seem necessary, it enables smoother integration with +existing batch and interactive tools that have their own ways of +managing the processes of reading, parsing, and type-checking +packages. In particular, this package does not assume that the +caller and callee belong to the same token.FileSet or +types.Importer realms. + +There are many aspects to a function call. It is the only construct +that can simultaneously bind multiple variables of different +explicit types, with implicit assignment conversions. (Neither var +nor := declarations can do that.) It defines the scope of control +labels, of return statements, and of defer statements. Arguments +and results of function calls may be tuples even though tuples are +not first-class values in Go, and a tuple-valued call expression +may be "spread" across the argument list of a call or the operands +of a return statement. All these unique features mean that in the +general case, not everything that can be expressed by a function +call can be expressed without one. + +So, in general, inlining consists of modifying a function or method +call expression f(a1, ..., an) so that the name of the function f +is replaced ("literalized") by a literal copy of the function +declaration, with free identifiers suitably modified to use the +locally appropriate identifiers or perhaps constant argument +values. + +Inlining must not change the semantics of the call. Semantics +preservation is crucial for clients such as codebase maintenance +tools that automatically inline all calls to designated functions +on a large scale. Such tools must not introduce subtle behavior +changes. (Fully inlining a call is dynamically observable using +reflection over the call stack, but this exception to the rule is +explicitly allowed.) + +In many cases it is possible to entirely replace ("reduce") the +call by a copy of the function's body in which parameters have been +replaced by arguments. The inliner supports a number of reduction +strategies, and we expect this set to grow. Nonetheless, sound +reduction is surprisingly tricky. + +The inliner is in some ways 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. +When a case is found in which it emits suboptimal code, the +compiler is improved to recognize more cases, or more rules, and +more exceptions to rules; this process has no end. Inlining is +similar except that "better" code means tidier code. The baseline +translation (literalization) is correct, but there are endless +rules--and exceptions to rules--by which the output can be +improved. + +The following section lists some of the challenges, and ways in +which they can be addressed. + + - All effects of the call argument expressions must be preserved, + both in their number (they must not be eliminated or repeated), + and in their order (both with respect to other arguments, and any + effects in the callee function). + + This must be the case even if the corresponding parameters are + never referenced, are referenced multiple times, referenced in + a different order from the arguments, or referenced within a + nested function that may be executed an arbitrary number of + times. + + Currently, parameter replacement is not applied to arguments + with effects, but with further analysis of the sequence of + strict effects within the callee we could relax this constraint. + + - When not all parameters can be substituted by their arguments + (e.g. due to possible effects), if the call appears in a + statement context, the inliner may introduce a var declaration + that declares the parameter variables (with the correct types) + and assigns them to their corresponding argument values. + The rest of the function body may then follow. + For example, the call + + f(1, 2) + + to the function + + func f(x, y int32) { stmts } + + may be reduced to + + { var x, y int32 = 1, 2; stmts }. + + There are many reasons why this is not always possible. For + example, true parameters are statically resolved in the same + scope, and are dynamically assigned their arguments in + parallel; but each spec in a var declaration is statically + resolved in sequence and dynamically executed in sequence, so + earlier parameters may shadow references in later ones. + + - Even an argument expression as simple as ptr.x may not be + referentially transparent, because another argument may have the + effect of changing the value of ptr. + + This constraint could be relaxed by some kind of alias or + escape analysis that proves that ptr cannot be mutated during + the call. + + - Although constants are referentially transparent, as a matter of + style we do not wish to duplicate literals that are referenced + multiple times in the body because this undoes proper factoring. + Also, string literals may be arbitrarily large. + + - If the function body consists of statements other than just + "return expr", in some contexts it may be syntactically + impossible to reduce the call. Consider: + + if x := f(); cond { ... } + + Go has no equivalent to Lisp's progn or Rust's blocks, + nor ML's let expressions (let param = arg in body); + its closest equivalent is func(param){body}(arg). + Reduction strategies must therefore consider the syntactic + context of the call. + + In such situations we could work harder to extract a statement + context for the call, by transforming it to: + + { x := f(); if cond { ... } } + + - Similarly, without the equivalent of Rust-style blocks and + first-class tuples, there is no general way to reduce a call + to a function such as + + func(params)(args)(results) { stmts; return expr } + + to an expression such as + + { var params = args; stmts; expr } + + or even a statement such as + + results = { var params = args; stmts; expr } + + Consequently the declaration and scope of the result variables, + and the assignment and control-flow implications of the return + statement, must be dealt with by cases. + + - A standalone call statement that calls a function whose body is + "return expr" cannot be simply replaced by the body expression + if it is not itself a call or channel receive expression; it is + necessary to explicitly discard the result using "_ = expr". + + Similarly, if the body is a call expression, only calls to some + built-in functions with no result (such as copy or panic) are + permitted as statements, whereas others (such as append) return + a result that must be used, even if just by discarding. + + - If a parameter or result variable is updated by an assignment + within the function body, it cannot always be safely replaced + by a variable in the caller. For example, given + + func f(a int) int { a++; return a } + + The call y = f(x) cannot be replaced by { x++; y = x } because + this would change the value of the caller's variable x. + Only if the caller is finished with x is this safe. + + A similar argument applies to parameter or result variables + that escape: by eliminating a variable, inlining would change + the identity of the variable that escapes. + + - If the function body uses 'defer' and the inlined call is not a + tail-call, inlining may delay the deferred effects. + + - Because the scope of a control label is the entire function, a + call cannot be reduced if the caller and callee have intersecting + sets of control labels. (It is possible to α-rename any + conflicting ones, but our colleagues building C++ refactoring + tools report that, when tools must choose new identifiers, they + generally do a poor job.) + + - Given + + func f() uint8 { return 0 } + + var x any = f() + + reducing the call to var x any = 0 is unsound because it + discards the implicit conversion to uint8. We may need to make + each argument-to-parameter conversion explicit if the types + differ. Assignments to variadic parameters may need to + explicitly construct a slice. + + An analogous problem applies to the implicit assignments in + return statements: + + func g() any { return f() } + + Replacing the call f() with 0 would silently lose a + conversion to uint8 and change the behavior of the program. + + - When inlining a call f(1, x, g()) where those parameters are + unreferenced, we should be able to avoid evaluating 1 and x + since they are pure and thus have no effect. But x may be the + last reference to a local variable in the caller, so removing + it would cause a compilation error. Parameter substitution must + avoid making the caller's local variables unreferenced (or must + be prepared to eliminate the declaration too---this is where an + iterative framework for simplification would really help). + + - An expression such as s[i] may be valid if s and i are + variables but invalid if either or both of them are constants. + For example, a negative constant index s[-1] is always out of + bounds, and even a non-negative constant index may be out of + bounds depending on the particular string constant (e.g. + "abc"[4]). + + So, if a parameter participates in any expression that is + subject to additional compile-time checks when its operands are + constant, it may be unsafe to substitute that parameter by a + constant argument value (#62664). + +More complex callee functions are inlinable with more elaborate and +invasive changes to the statements surrounding the call expression. + +TODO(adonovan): future work: + + - Handle more of the above special cases by careful analysis, + thoughtful factoring of the large design space, and thorough + test coverage. + + - Compute precisely (not conservatively) when parameter + substitution would remove the last reference to a caller local + variable, and blank out the local instead of retreating from + the substitution. + + - Afford the client more control such as a limit on the total + increase in line count, or a refusal to inline using the + general approach (replacing name by function literal). This + could be achieved by returning metadata alongside the result + and having the client conditionally discard the change. + + - Support inlining of generic functions, replacing type parameters + by their instantiations. + + - Support inlining of calls to function literals ("closures"). + But note that the existing algorithm makes widespread assumptions + that the callee is a package-level function or method. + + - Eliminate explicit conversions of "untyped" literals inserted + conservatively when they are redundant. For example, the + conversion int32(1) is redundant when this value is used only as a + slice index; but it may be crucial if it is used in x := int32(1) + as it changes the type of x, which may have further implications. + The conversions may also be important to the falcon analysis. + + - Allow non-'go' build systems such as Bazel/Blaze a chance to + decide whether an import is accessible using logic other than + "/internal/" path segments. This could be achieved by returning + the list of added import paths instead of a text diff. + + - Inlining a function from another module may change the + effective version of the Go language spec that governs it. We + should probably make the client responsible for rejecting + attempts to inline from newer callees to older callers, since + there's no way for this package to access module versions. + + - Use an alternative implementation of the import-organizing + operation that doesn't require operating on a complete file + (and reformatting). Then return the results in a higher-level + form as a set of import additions and deletions plus a single + diff that encloses the call expression. This interface could + perhaps be implemented atop imports.Process by post-processing + its result to obtain the abstract import changes and discarding + its formatted output. +*/ +package inline diff --git a/contribs/gnopls/internal/refactor/inline/escape.go b/contribs/gnopls/internal/refactor/inline/escape.go new file mode 100644 index 00000000000..a3f5e555e9f --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/escape.go @@ -0,0 +1,99 @@ +// 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 inline + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" +) + +// escape implements a simple "address-taken" escape analysis. It +// calls f for each local variable that appears on the left side of an +// assignment (escapes=false) or has its address taken (escapes=true). +// The initialization of a variable by its declaration does not count +// as an assignment. +func escape(info *types.Info, root ast.Node, f func(v *types.Var, escapes bool)) { + + // lvalue is called for each address-taken expression or LHS of assignment. + // Supported forms are: x, (x), x[i], x.f, *x, T{}. + var lvalue func(e ast.Expr, escapes bool) + lvalue = func(e ast.Expr, escapes bool) { + switch e := e.(type) { + case *ast.Ident: + if v, ok := info.Uses[e].(*types.Var); ok { + if !isPkgLevel(v) { + f(v, escapes) + } + } + case *ast.ParenExpr: + lvalue(e.X, escapes) + case *ast.IndexExpr: + // TODO(adonovan): support generics without assuming e.X has a core type. + // Consider: + // + // func Index[T interface{ [3]int | []int }](t T, i int) *int { + // return &t[i] + // } + // + // We must traverse the normal terms and check + // whether any of them is an array. + if _, ok := info.TypeOf(e.X).Underlying().(*types.Array); ok { + lvalue(e.X, escapes) // &a[i] on array + } + case *ast.SelectorExpr: + if _, ok := info.TypeOf(e.X).Underlying().(*types.Struct); ok { + lvalue(e.X, escapes) // &s.f on struct + } + case *ast.StarExpr: + // *ptr indirects an existing pointer + case *ast.CompositeLit: + // &T{...} creates a new variable + default: + panic(fmt.Sprintf("&x on %T", e)) // unreachable in well-typed code + } + } + + // Search function body for operations &x, x.f(), x++, and x = y + // where x is a parameter. Each of these treats x as an address. + ast.Inspect(root, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.UnaryExpr: + if n.Op == token.AND { + lvalue(n.X, true) // &x + } + + case *ast.CallExpr: + // implicit &x in method call x.f(), + // where x has type T and method is (*T).f + if sel, ok := n.Fun.(*ast.SelectorExpr); ok { + if seln, ok := info.Selections[sel]; ok && + seln.Kind() == types.MethodVal && + isPointer(seln.Obj().Type().Underlying().(*types.Signature).Recv().Type()) { + tArg, indirect := effectiveReceiver(seln) + if !indirect && !isPointer(tArg) { + lvalue(sel.X, true) // &x.f + } + } + } + + case *ast.AssignStmt: + for _, lhs := range n.Lhs { + if id, ok := lhs.(*ast.Ident); ok && + info.Defs[id] != nil && + n.Tok == token.DEFINE { + // declaration: doesn't count + } else { + lvalue(lhs, false) + } + } + + case *ast.IncDecStmt: + lvalue(n.X, false) + } + return true + }) +} diff --git a/contribs/gnopls/internal/refactor/inline/everything_test.go b/contribs/gnopls/internal/refactor/inline/everything_test.go new file mode 100644 index 00000000000..4ef4bf39c43 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/everything_test.go @@ -0,0 +1,240 @@ +// 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 inline_test + +import ( + "flag" + "fmt" + "go/ast" + "go/parser" + "go/types" + "log" + "os" + "path/filepath" + "strings" + "testing" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/types/typeutil" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" +) + +var packagesFlag = flag.String("packages", "", "set of packages for TestEverything") + +// TestEverything invokes the inliner on every single call site in a +// given package. and checks that it produces either a reasonable +// error, or output that parses and type-checks. +// +// It does nothing during ordinary testing, but may be used to find +// inlining bugs in large corpora. +// +// Use this command to inline everything in golang.org/x/tools: +// +// $ go test ./internal/refactor/inline/ -run=Everything -packages=../../../ +// +// And these commands to inline everything in the kubernetes repository: +// +// $ go test -c -o /tmp/everything ./internal/refactor/inline/ +// $ (cd kubernetes && /tmp/everything -test.run=Everything -packages=./...) +// +// TODO(adonovan): +// - report counters (number of attempts, failed AnalyzeCallee, failed +// Inline, etc.) +// - Make a pretty log of the entire output so that we can peruse it +// for opportunities for systematic improvement. +func TestEverything(t *testing.T) { + testenv.NeedsGoPackages(t) + if testing.Short() { + t.Skipf("skipping slow test in -short mode") + } + if *packagesFlag == "" { + return + } + + // Load this package plus dependencies from typed syntax. + cfg := &packages.Config{ + Mode: packages.LoadAllSyntax, + Env: append(os.Environ(), + "GO111MODULES=on", + "GOPATH=", + "GOWORK=off", + "GOPROXY=off"), + } + pkgs, err := packages.Load(cfg, *packagesFlag) + if err != nil { + t.Errorf("Load: %v", err) + } + // Report parse/type errors. + // Also, build transitive dependency mapping. + deps := make(map[string]*packages.Package) // key is PkgPath + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + deps[pkg.Types.Path()] = pkg + for _, err := range pkg.Errors { + t.Fatal(err) + } + }) + + // Memoize repeated calls for same file. + fileContent := make(map[string][]byte) + readFile := func(filename string) ([]byte, error) { + content, ok := fileContent[filename] + if !ok { + var err error + content, err = os.ReadFile(filename) + if err != nil { + return nil, err + } + fileContent[filename] = content + } + return content, nil + } + + for _, callerPkg := range pkgs { + // Find all static function calls in the package. + for _, callerFile := range callerPkg.Syntax { + noMutCheck := checkNoMutation(callerFile) + ast.Inspect(callerFile, func(n ast.Node) bool { + call, ok := n.(*ast.CallExpr) + if !ok { + return true + } + fn := typeutil.StaticCallee(callerPkg.TypesInfo, call) + if fn == nil { + return true + } + + // Prepare caller info. + callPosn := callerPkg.Fset.PositionFor(call.Lparen, false) + callerContent, err := readFile(callPosn.Filename) + if err != nil { + t.Fatal(err) + } + caller := &inline.Caller{ + Fset: callerPkg.Fset, + Types: callerPkg.Types, + Info: callerPkg.TypesInfo, + File: callerFile, + Call: call, + Content: callerContent, + } + + // Analyze callee. + calleePkg, ok := deps[fn.Pkg().Path()] + if !ok { + t.Fatalf("missing package for callee %v", fn) + } + calleePosn := callerPkg.Fset.PositionFor(fn.Pos(), false) + calleeDecl, err := findFuncByPosition(calleePkg, calleePosn) + if err != nil { + t.Fatal(err) + } + calleeContent, err := readFile(calleePosn.Filename) + if err != nil { + t.Fatal(err) + } + + // Create a subtest for each inlining operation. + name := fmt.Sprintf("%s@%v", fn.Name(), filepath.Base(callPosn.String())) + t.Run(name, func(t *testing.T) { + // TODO(adonovan): add a panic handler. + + t.Logf("callee declared at %v", + filepath.Base(calleePosn.String())) + + t.Logf("run this command to reproduce locally:\n$ gopls codeaction -kind=refactor.inline -exec -diff %s:#%d", + callPosn.Filename, callPosn.Offset) + + callee, err := inline.AnalyzeCallee( + t.Logf, + calleePkg.Fset, + calleePkg.Types, + calleePkg.TypesInfo, + calleeDecl, + calleeContent) + if err != nil { + // Ignore the expected kinds of errors. + for _, ignore := range []string{ + "has no body", + "type parameters are not yet", + "line directives", + "cgo-generated", + } { + if strings.Contains(err.Error(), ignore) { + return + } + } + t.Fatalf("AnalyzeCallee: %v", err) + } + if err := checkTranscode(callee); err != nil { + t.Fatal(err) + } + + res, err := inline.Inline(caller, callee, &inline.Options{ + Logf: t.Logf, + }) + if err != nil { + // Write error to a log, but this ok. + t.Log(err) + return + } + got := res.Content + + // Print the diff. + t.Logf("Got diff:\n%s", + diff.Unified("old", "new", string(callerContent), string(res.Content))) + + // Parse and type-check the transformed source. + f, err := parser.ParseFile(caller.Fset, callPosn.Filename, got, parser.SkipObjectResolution) + if err != nil { + t.Fatalf("transformed source does not parse: %v", err) + } + // Splice into original file list. + syntax := append([]*ast.File(nil), callerPkg.Syntax...) + for i := range callerPkg.Syntax { + if syntax[i] == callerFile { + syntax[i] = f + break + } + } + + var typeErrors []string + conf := &types.Config{ + Error: func(err error) { + typeErrors = append(typeErrors, err.Error()) + }, + Importer: importerFunc(func(importPath string) (*types.Package, error) { + // Note: deps is properly keyed by package path, + // not import path, but we can't assume + // Package.Imports[importPath] exists in the + // case of newly added imports of indirect + // dependencies. Seems not to matter to this test. + dep, ok := deps[importPath] + if ok { + return dep.Types, nil + } + return nil, fmt.Errorf("missing package: %q", importPath) + }), + } + if _, err := conf.Check("p", caller.Fset, syntax, nil); err != nil { + t.Fatalf("transformed package has type errors:\n\n%s\n\nTransformed file:\n\n%s", + strings.Join(typeErrors, "\n"), + got) + } + }) + return true + }) + noMutCheck() + } + } + log.Printf("Analyzed %d packages", len(pkgs)) +} + +type importerFunc func(path string) (*types.Package, error) + +func (f importerFunc) Import(path string) (*types.Package, error) { + return f(path) +} diff --git a/contribs/gnopls/internal/refactor/inline/export_test.go b/contribs/gnopls/internal/refactor/inline/export_test.go new file mode 100644 index 00000000000..7b2cec7f19d --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/export_test.go @@ -0,0 +1,9 @@ +// 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 inline + +// This file opens back doors for testing. + +func (callee *Callee) Effects() []int { return callee.impl.Effects } diff --git a/contribs/gnopls/internal/refactor/inline/falcon.go b/contribs/gnopls/internal/refactor/inline/falcon.go new file mode 100644 index 00000000000..457d43e875e --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/falcon.go @@ -0,0 +1,893 @@ +// 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 inline + +// This file defines the callee side of the "fallible constant" analysis. + +import ( + "fmt" + "go/ast" + "go/constant" + "go/format" + "go/token" + "go/types" + "strconv" + "strings" + + "golang.org/x/tools/go/types/typeutil" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" +) + +// falconResult is the result of the analysis of the callee. +type falconResult struct { + Types []falconType // types for falcon constraint environment + Constraints []string // constraints (Go expressions) on values of fallible constants +} + +// A falconType specifies the name and underlying type of a synthetic +// defined type for use in falcon constraints. +// +// Unique types from callee code are bijectively mapped onto falcon +// types so that constraints are independent of callee type +// information but preserve type equivalence classes. +// +// Fresh names are deliberately obscure to avoid shadowing even if a +// callee parameter has a nanme like "int" or "any". +type falconType struct { + Name string + Kind types.BasicKind // string/number/bool +} + +// falcon identifies "fallible constant" expressions, which are +// expressions that may fail to compile if one or more of their +// operands is changed from non-constant to constant. +// +// Consider: +// +// func sub(s string, i, j int) string { return s[i:j] } +// +// If parameters are replaced by constants, the compiler is +// required to perform these additional checks: +// +// - if i is constant, 0 <= i. +// - if s and i are constant, i <= len(s). +// - ditto for j. +// - if i and j are constant, i <= j. +// +// s[i:j] is thus a "fallible constant" expression dependent on {s, i, +// j}. Each falcon creates a set of conditional constraints across one +// or more parameter variables. +// +// - When inlining a call such as sub("abc", -1, 2), the parameter i +// cannot be eliminated by substitution as its argument value is +// negative. +// +// - When inlining sub("", 2, 1), all three parameters cannot be +// simultaneously eliminated by substitution without violating i +// <= len(s) and j <= len(s), but the parameters i and j could be +// safely eliminated without s. +// +// Parameters that cannot be eliminated must remain non-constant, +// either in the form of a binding declaration: +// +// { var i int = -1; return "abc"[i:2] } +// +// or a parameter of a literalization: +// +// func (i int) string { return "abc"[i:2] }(-1) +// +// These example expressions are obviously doomed to fail at run +// time, but in realistic cases such expressions are dominated by +// appropriate conditions that make them reachable only when safe: +// +// if 0 <= i && i <= j && j <= len(s) { _ = s[i:j] } +// +// (In principle a more sophisticated inliner could entirely eliminate +// such unreachable blocks based on the condition being always-false +// for the given parameter substitution, but this is tricky to do safely +// because the type-checker considers only a single configuration. +// Consider: if runtime.GOOS == "linux" { ... }.) +// +// We believe this is an exhaustive list of "fallible constant" operations: +// +// - switch z { case x: case y } // duplicate case values +// - s[i], s[i:j], s[i:j:k] // index out of bounds (0 <= i <= j <= k <= len(s)) +// - T{x: 0} // index out of bounds, duplicate index +// - x/y, x%y, x/=y, x%=y // integer division by zero; minint/-1 overflow +// - x+y, x-y, x*y // arithmetic overflow +// - x<<y // shift out of range +// - -x // negation of minint +// - T(x) // value out of range +// +// The fundamental reason for this elaborate algorithm is that the +// "separate analysis" of callee and caller, as required when running +// in an environment such as unitchecker, means that there is no way +// for us to simply invoke the type checker on the combination of +// caller and callee code, as by the time we analyze the caller, we no +// longer have access to type information for the callee (and, in +// particular, any of its direct dependencies that are not direct +// dependencies of the caller). So, in effect, we are forced to map +// the problem in a neutral (callee-type-independent) constraint +// system that can be verified later. +func falcon(logf func(string, ...any), fset *token.FileSet, params map[*types.Var]*paramInfo, info *types.Info, decl *ast.FuncDecl) falconResult { + + st := &falconState{ + logf: logf, + fset: fset, + params: params, + info: info, + decl: decl, + } + + // type mapping + st.int = st.typename(types.Typ[types.Int]) + st.any = "interface{}" // don't use "any" as it may be shadowed + for obj, info := range st.params { + if isBasic(obj.Type(), types.IsConstType) { + info.FalconType = st.typename(obj.Type()) + } + } + + st.stmt(st.decl.Body) + + return st.result +} + +type falconState struct { + // inputs + logf func(string, ...any) + fset *token.FileSet + params map[*types.Var]*paramInfo + info *types.Info + decl *ast.FuncDecl + + // working state + int string + any string + typenames typeutil.Map + + result falconResult +} + +// typename returns the name in the falcon constraint system +// of a given string/number/bool type t. Falcon types are +// specified directly in go/types data structures rather than +// by name, avoiding potential shadowing conflicts with +// confusing parameter names such as "int". +// +// Also, each distinct type (as determined by types.Identical) +// is mapped to a fresh type in the falcon system so that we +// can map the types in the callee code into a neutral form +// that does not depend on imports, allowing us to detect +// potential conflicts such as +// +// map[any]{T1(1): 0, T2(1): 0} +// +// where T1=T2. +func (st *falconState) typename(t types.Type) string { + name, ok := st.typenames.At(t).(string) + if !ok { + basic := t.Underlying().(*types.Basic) + + // That dot ۰ is an Arabic zero numeral U+06F0. + // It is very unlikely to appear in a real program. + // TODO(adonovan): use a non-heuristic solution. + name = fmt.Sprintf("%s۰%d", basic, st.typenames.Len()) + st.typenames.Set(t, name) + st.logf("falcon: emit type %s %s // %q", name, basic, t) + st.result.Types = append(st.result.Types, falconType{ + Name: name, + Kind: basic.Kind(), + }) + } + return name +} + +// -- constraint emission -- + +// emit emits a Go expression that must have a legal type. +// In effect, we let the go/types constant folding algorithm +// do most of the heavy lifting (though it may be hard to +// believe from the complexity of this algorithm!). +func (st *falconState) emit(constraint ast.Expr) { + var out strings.Builder + if err := format.Node(&out, st.fset, constraint); err != nil { + panic(err) // can't happen + } + syntax := out.String() + st.logf("falcon: emit constraint %s", syntax) + st.result.Constraints = append(st.result.Constraints, syntax) +} + +// emitNonNegative emits an []T{}[index] constraint, +// which ensures index is non-negative if constant. +func (st *falconState) emitNonNegative(index ast.Expr) { + st.emit(&ast.IndexExpr{ + X: &ast.CompositeLit{ + Type: &ast.ArrayType{ + Elt: makeIdent(st.int), + }, + }, + Index: index, + }) +} + +// emitMonotonic emits an []T{}[i:j] constraint, +// which ensures i <= j if both are constant. +func (st *falconState) emitMonotonic(i, j ast.Expr) { + st.emit(&ast.SliceExpr{ + X: &ast.CompositeLit{ + Type: &ast.ArrayType{ + Elt: makeIdent(st.int), + }, + }, + Low: i, + High: j, + }) +} + +// emitUnique emits a T{elem1: 0, ... elemN: 0} constraint, +// which ensures that all constant elems are unique. +// T may be a map, slice, or array depending +// on the desired check semantics. +func (st *falconState) emitUnique(typ ast.Expr, elems []ast.Expr) { + if len(elems) > 1 { + var elts []ast.Expr + for _, elem := range elems { + elts = append(elts, &ast.KeyValueExpr{ + Key: elem, + Value: makeIntLit(0), + }) + } + st.emit(&ast.CompositeLit{ + Type: typ, + Elts: elts, + }) + } +} + +// -- traversal -- + +// The traversal functions scan the callee body for expressions that +// are not constant but would become constant if the parameter vars +// were redeclared as constants, and emits for each one a constraint +// (a Go expression) with the property that it will not type-check +// (using types.CheckExpr) if the particular argument values are +// unsuitable. +// +// These constraints are checked by Inline with the actual +// constant argument values. Violations cause it to reject +// parameters as candidates for substitution. + +func (st *falconState) stmt(s ast.Stmt) { + ast.Inspect(s, func(n ast.Node) bool { + switch n := n.(type) { + case ast.Expr: + _ = st.expr(n) + return false // skip usual traversal + + case *ast.AssignStmt: + switch n.Tok { + case token.QUO_ASSIGN, token.REM_ASSIGN: + // x /= y + // Possible "integer division by zero" + // Emit constraint: 1/y. + _ = st.expr(n.Lhs[0]) + kY := st.expr(n.Rhs[0]) + if kY, ok := kY.(ast.Expr); ok { + op := token.QUO + if n.Tok == token.REM_ASSIGN { + op = token.REM + } + st.emit(&ast.BinaryExpr{ + Op: op, + X: makeIntLit(1), + Y: kY, + }) + } + return false // skip usual traversal + } + + case *ast.SwitchStmt: + if n.Init != nil { + st.stmt(n.Init) + } + tBool := types.Type(types.Typ[types.Bool]) + tagType := tBool // default: true + if n.Tag != nil { + st.expr(n.Tag) + tagType = st.info.TypeOf(n.Tag) + } + + // Possible "duplicate case value". + // Emit constraint map[T]int{v1: 0, ..., vN:0} + // to ensure all maybe-constant case values are unique + // (unless switch tag is boolean, which is relaxed). + var unique []ast.Expr + for _, clause := range n.Body.List { + clause := clause.(*ast.CaseClause) + for _, caseval := range clause.List { + if k := st.expr(caseval); k != nil { + unique = append(unique, st.toExpr(k)) + } + } + for _, stmt := range clause.Body { + st.stmt(stmt) + } + } + if unique != nil && !types.Identical(tagType.Underlying(), tBool) { + tname := st.any + if !types.IsInterface(tagType) { + tname = st.typename(tagType) + } + t := &ast.MapType{ + Key: makeIdent(tname), + Value: makeIdent(st.int), + } + st.emitUnique(t, unique) + } + } + return true + }) +} + +// fieldTypes visits the .Type of each field in the list. +func (st *falconState) fieldTypes(fields *ast.FieldList) { + if fields != nil { + for _, field := range fields.List { + _ = st.expr(field.Type) + } + } +} + +// expr visits the expression (or type) and returns a +// non-nil result if the expression is constant or would +// become constant if all suitable function parameters were +// redeclared as constants. +// +// If the expression is constant, st.expr returns its type +// and value (types.TypeAndValue). If the expression would +// become constant, st.expr returns an ast.Expr tree whose +// leaves are literals and parameter references, and whose +// interior nodes are operations that may become constant, +// such as -x, x+y, f(x), and T(x). We call these would-be +// constant expressions "fallible constants", since they may +// fail to type-check for some values of x, i, and j. (We +// refer to the non-nil cases collectively as "maybe +// constant", and the nil case as "definitely non-constant".) +// +// As a side effect, st.expr emits constraints for each +// fallible constant expression; this is its main purpose. +// +// Consequently, st.expr must visit the entire subtree so +// that all necessary constraints are emitted. It may not +// short-circuit the traversal when it encounters a constant +// subexpression as constants may contain arbitrary other +// syntax that may impose constraints. Consider (as always) +// this contrived but legal example of a type parameter (!) +// that contains statement syntax: +// +// func f[T [unsafe.Sizeof(func() { stmts })]int]() +// +// There is no need to emit constraints for (e.g.) s[i] when s +// and i are already constants, because we know the expression +// is sound, but it is sometimes easier to emit these +// redundant constraints than to avoid them. +func (st *falconState) expr(e ast.Expr) (res any) { // = types.TypeAndValue | ast.Expr + tv := st.info.Types[e] + if tv.Value != nil { + // A constant value overrides any other result. + defer func() { res = tv }() + } + + switch e := e.(type) { + case *ast.Ident: + if v, ok := st.info.Uses[e].(*types.Var); ok { + if _, ok := st.params[v]; ok && isBasic(v.Type(), types.IsConstType) { + return e // reference to constable parameter + } + } + // (References to *types.Const are handled by the defer.) + + case *ast.BasicLit: + // constant + + case *ast.ParenExpr: + return st.expr(e.X) + + case *ast.FuncLit: + _ = st.expr(e.Type) + st.stmt(e.Body) + // definitely non-constant + + case *ast.CompositeLit: + // T{k: v, ...}, where T ∈ {array,*array,slice,map}, + // imposes a constraint that all constant k are + // distinct and, for arrays [n]T, within range 0-n. + // + // Types matter, not just values. For example, + // an interface-keyed map may contain keys + // that are numerically equal so long as they + // are of distinct types. For example: + // + // type myint int + // map[any]bool{1: true, 1: true} // error: duplicate key + // map[any]bool{1: true, int16(1): true} // ok + // map[any]bool{1: true, myint(1): true} // ok + // + // This can be asserted by emitting a + // constraint of the form T{k1: 0, ..., kN: 0}. + if e.Type != nil { + _ = st.expr(e.Type) + } + t := aliases.Unalias(typeparams.Deref(tv.Type)) + var uniques []ast.Expr + for _, elt := range e.Elts { + if kv, ok := elt.(*ast.KeyValueExpr); ok { + if !is[*types.Struct](t) { + if k := st.expr(kv.Key); k != nil { + uniques = append(uniques, st.toExpr(k)) + } + } + _ = st.expr(kv.Value) + } else { + _ = st.expr(elt) + } + } + if uniques != nil { + // Inv: not a struct. + + // The type T in constraint T{...} depends on the CompLit: + // - for a basic-keyed map, use map[K]int; + // - for an interface-keyed map, use map[any]int; + // - for a slice, use []int; + // - for an array or *array, use [n]int. + // The last two entail progressively stronger index checks. + var ct ast.Expr // type syntax for constraint + switch t := t.(type) { + case *types.Map: + if types.IsInterface(t.Key()) { + ct = &ast.MapType{ + Key: makeIdent(st.any), + Value: makeIdent(st.int), + } + } else { + ct = &ast.MapType{ + Key: makeIdent(st.typename(t.Key())), + Value: makeIdent(st.int), + } + } + case *types.Array: // or *array + ct = &ast.ArrayType{ + Len: makeIntLit(t.Len()), + Elt: makeIdent(st.int), + } + default: + panic(t) + } + st.emitUnique(ct, uniques) + } + // definitely non-constant + + case *ast.SelectorExpr: + _ = st.expr(e.X) + _ = st.expr(e.Sel) + // The defer is sufficient to handle + // qualified identifiers (pkg.Const). + // All other cases are definitely non-constant. + + case *ast.IndexExpr: + if tv.IsType() { + // type C[T] + _ = st.expr(e.X) + _ = st.expr(e.Index) + } else { + // term x[i] + // + // Constraints (if x is slice/string/array/*array, not map): + // - i >= 0 + // if i is a fallible constant + // - i < len(x) + // if x is array/*array and + // i is a fallible constant; + // or if s is a string and both i, + // s are maybe-constants, + // but not both are constants. + kX := st.expr(e.X) + kI := st.expr(e.Index) + if kI != nil && !is[*types.Map](st.info.TypeOf(e.X).Underlying()) { + if kI, ok := kI.(ast.Expr); ok { + st.emitNonNegative(kI) + } + // Emit constraint to check indices against known length. + // TODO(adonovan): factor with SliceExpr logic. + var x ast.Expr + if kX != nil { + // string + x = st.toExpr(kX) + } else if arr, ok := typeparams.CoreType(typeparams.Deref(st.info.TypeOf(e.X))).(*types.Array); ok { + // array, *array + x = &ast.CompositeLit{ + Type: &ast.ArrayType{ + Len: makeIntLit(arr.Len()), + Elt: makeIdent(st.int), + }, + } + } + if x != nil { + st.emit(&ast.IndexExpr{ + X: x, + Index: st.toExpr(kI), + }) + } + } + } + // definitely non-constant + + case *ast.SliceExpr: + // x[low:high:max] + // + // Emit non-negative constraints for each index, + // plus low <= high <= max <= len(x) + // for each pair that are maybe-constant + // but not definitely constant. + + kX := st.expr(e.X) + var kLow, kHigh, kMax any + if e.Low != nil { + kLow = st.expr(e.Low) + if kLow != nil { + if kLow, ok := kLow.(ast.Expr); ok { + st.emitNonNegative(kLow) + } + } + } + if e.High != nil { + kHigh = st.expr(e.High) + if kHigh != nil { + if kHigh, ok := kHigh.(ast.Expr); ok { + st.emitNonNegative(kHigh) + } + if kLow != nil { + st.emitMonotonic(st.toExpr(kLow), st.toExpr(kHigh)) + } + } + } + if e.Max != nil { + kMax = st.expr(e.Max) + if kMax != nil { + if kMax, ok := kMax.(ast.Expr); ok { + st.emitNonNegative(kMax) + } + if kHigh != nil { + st.emitMonotonic(st.toExpr(kHigh), st.toExpr(kMax)) + } + } + } + + // Emit constraint to check indices against known length. + var x ast.Expr + if kX != nil { + // string + x = st.toExpr(kX) + } else if arr, ok := typeparams.CoreType(typeparams.Deref(st.info.TypeOf(e.X))).(*types.Array); ok { + // array, *array + x = &ast.CompositeLit{ + Type: &ast.ArrayType{ + Len: makeIntLit(arr.Len()), + Elt: makeIdent(st.int), + }, + } + } + if x != nil { + // Avoid slice[::max] if kHigh is nonconstant (nil). + high, max := st.toExpr(kHigh), st.toExpr(kMax) + if high == nil { + high = max // => slice[:max:max] + } + st.emit(&ast.SliceExpr{ + X: x, + Low: st.toExpr(kLow), + High: high, + Max: max, + }) + } + // definitely non-constant + + case *ast.TypeAssertExpr: + _ = st.expr(e.X) + if e.Type != nil { + _ = st.expr(e.Type) + } + + case *ast.CallExpr: + _ = st.expr(e.Fun) + if tv, ok := st.info.Types[e.Fun]; ok && tv.IsType() { + // conversion T(x) + // + // Possible "value out of range". + kX := st.expr(e.Args[0]) + if kX != nil && isBasic(tv.Type, types.IsConstType) { + conv := convert(makeIdent(st.typename(tv.Type)), st.toExpr(kX)) + if is[ast.Expr](kX) { + st.emit(conv) + } + return conv + } + return nil // definitely non-constant + } + + // call f(x) + + all := true // all args are possibly-constant + kArgs := make([]ast.Expr, len(e.Args)) + for i, arg := range e.Args { + if kArg := st.expr(arg); kArg != nil { + kArgs[i] = st.toExpr(kArg) + } else { + all = false + } + } + + // Calls to built-ins with fallibly constant arguments + // may become constant. All other calls are either + // constant or non-constant + if id, ok := e.Fun.(*ast.Ident); ok && all && tv.Value == nil { + if builtin, ok := st.info.Uses[id].(*types.Builtin); ok { + switch builtin.Name() { + case "len", "imag", "real", "complex", "min", "max": + return &ast.CallExpr{ + Fun: id, + Args: kArgs, + Ellipsis: e.Ellipsis, + } + } + } + } + + case *ast.StarExpr: // *T, *ptr + _ = st.expr(e.X) + + case *ast.UnaryExpr: + // + - ! ^ & <- ~ + // + // Possible "negation of minint". + // Emit constraint: -x + kX := st.expr(e.X) + if kX != nil && !is[types.TypeAndValue](kX) { + if e.Op == token.SUB { + st.emit(&ast.UnaryExpr{ + Op: e.Op, + X: st.toExpr(kX), + }) + } + + return &ast.UnaryExpr{ + Op: e.Op, + X: st.toExpr(kX), + } + } + + case *ast.BinaryExpr: + kX := st.expr(e.X) + kY := st.expr(e.Y) + switch e.Op { + case token.QUO, token.REM: + // x/y, x%y + // + // Possible "integer division by zero" or + // "minint / -1" overflow. + // Emit constraint: x/y or 1/y + if kY != nil { + if kX == nil { + kX = makeIntLit(1) + } + st.emit(&ast.BinaryExpr{ + Op: e.Op, + X: st.toExpr(kX), + Y: st.toExpr(kY), + }) + } + + case token.ADD, token.SUB, token.MUL: + // x+y, x-y, x*y + // + // Possible "arithmetic overflow". + // Emit constraint: x+y + if kX != nil && kY != nil { + st.emit(&ast.BinaryExpr{ + Op: e.Op, + X: st.toExpr(kX), + Y: st.toExpr(kY), + }) + } + + case token.SHL, token.SHR: + // x << y, x >> y + // + // Possible "constant shift too large". + // Either operand may be too large individually, + // and they may be too large together. + // Emit constraint: + // x << y (if both maybe-constant) + // x << 0 (if y is non-constant) + // 1 << y (if x is non-constant) + if kX != nil || kY != nil { + x := st.toExpr(kX) + if x == nil { + x = makeIntLit(1) + } + y := st.toExpr(kY) + if y == nil { + y = makeIntLit(0) + } + st.emit(&ast.BinaryExpr{ + Op: e.Op, + X: x, + Y: y, + }) + } + + case token.LSS, token.GTR, token.EQL, token.NEQ, token.LEQ, token.GEQ: + // < > == != <= <= + // + // A "x cmp y" expression with constant operands x, y is + // itself constant, but I can't see how a constant bool + // could be fallible: the compiler doesn't reject duplicate + // boolean cases in a switch, presumably because boolean + // switches are less like n-way branches and more like + // sequential if-else chains with possibly overlapping + // conditions; and there is (sadly) no way to convert a + // boolean constant to an int constant. + } + if kX != nil && kY != nil { + return &ast.BinaryExpr{ + Op: e.Op, + X: st.toExpr(kX), + Y: st.toExpr(kY), + } + } + + // types + // + // We need to visit types (and even type parameters) + // in order to reach all the places where things could go wrong: + // + // const ( + // s = "" + // i = 0 + // ) + // type C[T [unsafe.Sizeof(func() { _ = s[i] })]int] bool + + case *ast.IndexListExpr: + _ = st.expr(e.X) + for _, expr := range e.Indices { + _ = st.expr(expr) + } + + case *ast.Ellipsis: + if e.Elt != nil { + _ = st.expr(e.Elt) + } + + case *ast.ArrayType: + if e.Len != nil { + _ = st.expr(e.Len) + } + _ = st.expr(e.Elt) + + case *ast.StructType: + st.fieldTypes(e.Fields) + + case *ast.FuncType: + st.fieldTypes(e.TypeParams) + st.fieldTypes(e.Params) + st.fieldTypes(e.Results) + + case *ast.InterfaceType: + st.fieldTypes(e.Methods) + + case *ast.MapType: + _ = st.expr(e.Key) + _ = st.expr(e.Value) + + case *ast.ChanType: + _ = st.expr(e.Value) + } + return +} + +// toExpr converts the result of visitExpr to a falcon expression. +// (We don't do this in visitExpr as we first need to discriminate +// constants from maybe-constants.) +func (st *falconState) toExpr(x any) ast.Expr { + switch x := x.(type) { + case nil: + return nil + + case types.TypeAndValue: + lit := makeLiteral(x.Value) + if !isBasic(x.Type, types.IsUntyped) { + // convert to "typed" type + lit = &ast.CallExpr{ + Fun: makeIdent(st.typename(x.Type)), + Args: []ast.Expr{lit}, + } + } + return lit + + case ast.Expr: + return x + + default: + panic(x) + } +} + +func makeLiteral(v constant.Value) ast.Expr { + switch v.Kind() { + case constant.Bool: + // Rather than refer to the true or false built-ins, + // which could be shadowed by poorly chosen parameter + // names, we use 0 == 0 for true and 0 != 0 for false. + op := token.EQL + if !constant.BoolVal(v) { + op = token.NEQ + } + return &ast.BinaryExpr{ + Op: op, + X: makeIntLit(0), + Y: makeIntLit(0), + } + + case constant.String: + return &ast.BasicLit{ + Kind: token.STRING, + Value: v.ExactString(), + } + + case constant.Int: + return &ast.BasicLit{ + Kind: token.INT, + Value: v.ExactString(), + } + + case constant.Float: + return &ast.BasicLit{ + Kind: token.FLOAT, + Value: v.ExactString(), + } + + case constant.Complex: + // The components could be float or int. + y := makeLiteral(constant.Imag(v)) + y.(*ast.BasicLit).Value += "i" // ugh + if re := constant.Real(v); !consteq(re, kZeroInt) { + // complex: x + yi + y = &ast.BinaryExpr{ + Op: token.ADD, + X: makeLiteral(re), + Y: y, + } + } + return y + + default: + panic(v.Kind()) + } +} + +func makeIntLit(x int64) *ast.BasicLit { + return &ast.BasicLit{ + Kind: token.INT, + Value: strconv.FormatInt(x, 10), + } +} + +func isBasic(t types.Type, info types.BasicInfo) bool { + basic, ok := t.Underlying().(*types.Basic) + return ok && basic.Info()&info != 0 +} diff --git a/contribs/gnopls/internal/refactor/inline/falcon_test.go b/contribs/gnopls/internal/refactor/inline/falcon_test.go new file mode 100644 index 00000000000..a16a88d836b --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/falcon_test.go @@ -0,0 +1,381 @@ +// 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 inline_test + +import "testing" + +// Testcases mostly come in pairs, of a success and a failure +// to substitute based on specific constant argument values. + +func TestFalconStringIndex(t *testing.T) { + runTests(t, []testcase{ + { + "Non-negative string index.", + `func f(i int) byte { return s[i] }; var s string`, + `func _() { f(0) }`, + `func _() { _ = s[0] }`, + }, + { + "Negative string index.", + `func f(i int) byte { return s[i] }; var s string`, + `func _() { f(-1) }`, + `func _() { + var i int = -1 + _ = s[i] +}`, + }, + { + "String index in range.", + `func f(s string, i int) byte { return s[i] }`, + `func _() { f("-", 0) }`, + `func _() { _ = "-"[0] }`, + }, + { + "String index out of range.", + `func f(s string, i int) byte { return s[i] }`, + `func _() { f("-", 1) }`, + `func _() { + var ( + s string = "-" + i int = 1 + ) + _ = s[i] +}`, + }, + { + "Remove known prefix (OK)", + `func f(s, prefix string) string { return s[:len(prefix)] }`, + `func _() { f("", "") }`, + `func _() { _ = ""[:len("")] }`, + }, + { + "Remove not-a-prefix (out of range)", + `func f(s, prefix string) string { return s[:len(prefix)] }`, + `func _() { f("", "pre") }`, + `func _() { + var s, prefix string = "", "pre" + _ = s[:len(prefix)] +}`, + }, + }) +} + +func TestFalconSliceIndices(t *testing.T) { + runTests(t, []testcase{ + { + "Monotonic (0<=i<=j) slice indices (len unknown).", + `func f(i, j int) []int { return s[i:j] }; var s []int`, + `func _() { f(0, 1) }`, + `func _() { _ = s[0:1] }`, + }, + { + "Non-monotonic slice indices (len unknown).", + `func f(i, j int) []int { return s[i:j] }; var s []int`, + `func _() { f(1, 0) }`, + `func _() { + var i, j int = 1, 0 + _ = s[i:j] +}`, + }, + { + "Negative slice index.", + `func f(i, j int) []int { return s[i:j] }; var s []int`, + `func _() { f(-1, 1) }`, + `func _() { + var i, j int = -1, 1 + _ = s[i:j] +}`, + }, + }) +} + +func TestFalconMapKeys(t *testing.T) { + runTests(t, []testcase{ + { + "Unique map keys (int)", + `func f(x int) { _ = map[int]bool{1: true, x: true} }`, + `func _() { f(2) }`, + `func _() { _ = map[int]bool{1: true, 2: true} }`, + }, + { + "Duplicate map keys (int)", + `func f(x int) { _ = map[int]bool{1: true, x: true} }`, + `func _() { f(1) }`, + `func _() { + var x int = 1 + _ = map[int]bool{1: true, x: true} +}`, + }, + { + "Unique map keys (varied built-in types)", + `func f(x int16) { _ = map[any]bool{1: true, x: true} }`, + `func _() { f(2) }`, + `func _() { _ = map[any]bool{1: true, int16(2): true} }`, + }, + { + "Duplicate map keys (varied built-in types)", + `func f(x int16) { _ = map[any]bool{1: true, x: true} }`, + `func _() { f(1) }`, + `func _() { _ = map[any]bool{1: true, int16(1): true} }`, + }, + { + "Unique map keys (varied user-defined types)", + `func f(x myint) { _ = map[any]bool{1: true, x: true} }; type myint int`, + `func _() { f(2) }`, + `func _() { _ = map[any]bool{1: true, myint(2): true} }`, + }, + { + "Duplicate map keys (varied user-defined types)", + `func f(x myint, y myint2) { _ = map[any]bool{x: true, y: true} }; type (myint int; myint2 int)`, + `func _() { f(1, 1) }`, + `func _() { + var ( + x myint = 1 + y myint2 = 1 + ) + _ = map[any]bool{x: true, y: true} +}`, + }, + { + "Duplicate map keys (user-defined alias to built-in)", + `func f(x myint, y int) { _ = map[any]bool{x: true, y: true} }; type myint = int`, + `func _() { f(1, 1) }`, + `func _() { + var ( + x myint = 1 + y int = 1 + ) + _ = map[any]bool{x: true, y: true} +}`, + }, + }) +} + +func TestFalconSwitchCases(t *testing.T) { + runTests(t, []testcase{ + { + "Unique switch cases (int).", + `func f(x int) { switch 0 { case x: case 1: } }`, + `func _() { f(2) }`, + `func _() { + switch 0 { + case 2: + case 1: + } +}`, + }, + { + "Duplicate switch cases (int).", + `func f(x int) { switch 0 { case x: case 1: } }`, + `func _() { f(1) }`, + `func _() { + var x int = 1 + switch 0 { + case x: + case 1: + } +}`, + }, + { + "Unique switch cases (varied built-in types).", + `func f(x int) { switch any(nil) { case x: case int16(1): } }`, + `func _() { f(2) }`, + `func _() { + switch any(nil) { + case 2: + case int16(1): + } +}`, + }, + { + "Duplicate switch cases (varied built-in types).", + `func f(x int) { switch any(nil) { case x: case int16(1): } }`, + `func _() { f(1) }`, + `func _() { + switch any(nil) { + case 1: + case int16(1): + } +}`, + }, + }) +} + +func TestFalconDivision(t *testing.T) { + runTests(t, []testcase{ + { + "Division by two.", + `func f(x, y int) int { return x / y }`, + `func _() { f(1, 2) }`, + `func _() { _ = 1 / 2 }`, + }, + { + "Division by zero.", + `func f(x, y int) int { return x / y }`, + `func _() { f(1, 0) }`, + `func _() { + var x, y int = 1, 0 + _ = x / y +}`, + }, + { + "Division by two (statement).", + `func f(x, y int) { x /= y }`, + `func _() { f(1, 2) }`, + `func _() { + var x int = 1 + x /= 2 +}`, + }, + { + "Division by zero (statement).", + `func f(x, y int) { x /= y }`, + `func _() { f(1, 0) }`, + `func _() { + var x, y int = 1, 0 + x /= y +}`, + }, + { + "Division of minint by two (ok).", + `func f(x, y int32) { _ = x / y }`, + `func _() { f(-0x80000000, 2) }`, + `func _() { _ = int32(-0x80000000) / int32(2) }`, + }, + { + "Division of minint by -1 (overflow).", + `func f(x, y int32) { _ = x / y }`, + `func _() { f(-0x80000000, -1) }`, + `func _() { + var x, y int32 = -0x80000000, -1 + _ = x / y +}`, + }, + }) +} + +func TestFalconMinusMinInt(t *testing.T) { + runTests(t, []testcase{ + { + "Negation of maxint.", + `func f(x int32) int32 { return -x }`, + `func _() { f(0x7fffffff) }`, + `func _() { _ = -int32(0x7fffffff) }`, + }, + { + "Negation of minint.", + `func f(x int32) int32 { return -x }`, + `func _() { f(-0x80000000) }`, + `func _() { + var x int32 = -0x80000000 + _ = -x +}`, + }, + }) +} + +func TestFalconArithmeticOverflow(t *testing.T) { + runTests(t, []testcase{ + { + "Addition without overflow.", + `func f(x, y int32) int32 { return x + y }`, + `func _() { f(100, 200) }`, + `func _() { _ = int32(100) + int32(200) }`, + }, + { + "Addition with overflow.", + `func f(x, y int32) int32 { return x + y }`, + `func _() { f(1<<30, 1<<30) }`, + `func _() { + var x, y int32 = 1 << 30, 1 << 30 + _ = x + y +}`, + }, + { + "Conversion in range.", + `func f(x int) int8 { return int8(x) }`, + `func _() { f(123) }`, + `func _() { _ = int8(123) }`, + }, + { + "Conversion out of range.", + `func f(x int) int8 { return int8(x) }`, + `func _() { f(456) }`, + `func _() { + var x int = 456 + _ = int8(x) +}`, + }, + }) +} + +func TestFalconComplex(t *testing.T) { + runTests(t, []testcase{ + { + "Complex arithmetic (good).", + `func f(re, im float64, z complex128) byte { return "x"[int(real(complex(re, im)*complex(re, -im)-z))] }`, + `func _() { f(1, 2, 5+0i) }`, + `func _() { _ = "x"[int(real(complex(1, 2)*complex(1, -2)-(5+0i)))] }`, + }, + { + "Complex arithmetic (bad).", + `func f(re, im float64, z complex128) byte { return "x"[int(real(complex(re, im)*complex(re, -im)-z))] }`, + `func _() { f(1, 3, 5+0i) }`, + `func _() { + var ( + re, im float64 = 1, 3 + z complex128 = 5 + 0i + ) + _ = "x"[int(real(complex(re, im)*complex(re, -im)-z))] +}`, + }, + }) +} +func TestFalconMisc(t *testing.T) { + runTests(t, []testcase{ + { + "Compound constant expression (good).", + `func f(x, y string, i, j int) byte { return x[i*len(y)+j] }`, + `func _() { f("abc", "xy", 2, -3) }`, + `func _() { _ = "abc"[2*len("xy")+-3] }`, + }, + { + "Compound constant expression (index out of range).", + `func f(x, y string, i, j int) byte { return x[i*len(y)+j] }`, + `func _() { f("abc", "xy", 4, -3) }`, + `func _() { + var ( + x, y string = "abc", "xy" + i, j int = 4, -3 + ) + _ = x[i*len(y)+j] +}`, + }, + { + "Constraints within nested functions (good).", + `func f(x int) { _ = func() { _ = [1]int{}[x] } }`, + `func _() { f(0) }`, + `func _() { _ = func() { _ = [1]int{}[0] } }`, + }, + { + "Constraints within nested functions (bad).", + `func f(x int) { _ = func() { _ = [1]int{}[x] } }`, + `func _() { f(1) }`, + `func _() { + var x int = 1 + _ = func() { _ = [1]int{}[x] } +}`, + }, + { + "Falcon violation rejects only the constant arguments (x, z).", + `func f(x, y, z string) string { return x[:2] + y + z[:2] }; var b string`, + `func _() { f("a", b, "c") }`, + `func _() { + var x, z string = "a", "c" + _ = x[:2] + b + z[:2] +}`, + }, + }) +} diff --git a/contribs/gnopls/internal/refactor/inline/inline.go b/contribs/gnopls/internal/refactor/inline/inline.go new file mode 100644 index 00000000000..b5892ecd1df --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/inline.go @@ -0,0 +1,3289 @@ +// 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 inline + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/format" + "go/parser" + "go/token" + "go/types" + pathpkg "path" + "reflect" + "strconv" + "strings" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/imports" + internalastutil "github.com/gnolang/gno/contribs/gnopls/internal/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" +) + +// A Caller describes the function call and its enclosing context. +// +// The client is responsible for populating this struct and passing it to Inline. +type Caller struct { + Fset *token.FileSet + Types *types.Package + Info *types.Info + File *ast.File + Call *ast.CallExpr + Content []byte // source of file containing + + path []ast.Node // path from call to root of file syntax tree + enclosingFunc *ast.FuncDecl // top-level function/method enclosing the call, if any +} + +// Options specifies parameters affecting the inliner algorithm. +// All fields are optional. +type Options struct { + Logf func(string, ...any) // log output function, records decision-making process + IgnoreEffects bool // ignore potential side effects of arguments (unsound) +} + +// Result holds the result of code transformation. +type Result struct { + Content []byte // formatted, transformed content of caller file + Literalized bool // chosen strategy replaced callee() with func(){...}() + + // TODO(adonovan): provide an API for clients that want structured + // output: a list of import additions and deletions plus one or more + // localized diffs (or even AST transformations, though ownership and + // mutation are tricky) near the call site. +} + +// Inline inlines the called function (callee) into the function call (caller) +// and returns the updated, formatted content of the caller source file. +// +// Inline does not mutate any public fields of Caller or Callee. +func Inline(caller *Caller, callee *Callee, opts *Options) (*Result, error) { + copy := *opts // shallow copy + opts = © + // Set default options. + if opts.Logf == nil { + opts.Logf = func(string, ...any) {} + } + + st := &state{ + caller: caller, + callee: callee, + opts: opts, + } + return st.inline() +} + +// state holds the working state of the inliner. +type state struct { + caller *Caller + callee *Callee + opts *Options +} + +func (st *state) inline() (*Result, error) { + logf, caller, callee := st.opts.Logf, st.caller, st.callee + + logf("inline %s @ %v", + debugFormatNode(caller.Fset, caller.Call), + caller.Fset.PositionFor(caller.Call.Lparen, false)) + + if !consistentOffsets(caller) { + return nil, fmt.Errorf("internal error: caller syntax positions are inconsistent with file content (did you forget to use FileSet.PositionFor when computing the file name?)") + } + + // TODO(adonovan): use go1.21's ast.IsGenerated. + // Break the string literal so we can use inlining in this file. :) + if bytes.Contains(caller.Content, []byte("// Code generated by "+"cmd/cgo; DO NOT EDIT.")) { + return nil, fmt.Errorf("cannot inline calls from files that import \"C\"") + } + + res, err := st.inlineCall() + if err != nil { + return nil, err + } + + // Replace the call (or some node that encloses it) by new syntax. + assert(res.old != nil, "old is nil") + assert(res.new != nil, "new is nil") + + // A single return operand inlined to a unary + // expression context may need parens. Otherwise: + // func two() int { return 1+1 } + // print(-two()) => print(-1+1) // oops! + // + // Usually it is not necessary to insert ParenExprs + // as the formatter is smart enough to insert them as + // needed by the context. But the res.{old,new} + // substitution is done by formatting res.new in isolation + // and then splicing its text over res.old, so the + // formatter doesn't see the parent node and cannot do + // the right thing. (One solution would be to always + // format the enclosing node of old, but that requires + // non-lossy comment handling, #20744.) + // + // So, we must analyze the call's context + // to see whether ambiguity is possible. + // For example, if the context is x[y:z], then + // the x subtree is subject to precedence ambiguity + // (replacing x by p+q would give p+q[y:z] which is wrong) + // but the y and z subtrees are safe. + if needsParens(caller.path, res.old, res.new) { + res.new = &ast.ParenExpr{X: res.new.(ast.Expr)} + } + + // Some reduction strategies return a new block holding the + // callee's statements. The block's braces may be elided when + // there is no conflict between names declared in the block + // with those declared by the parent block, and no risk of + // a caller's goto jumping forward across a declaration. + // + // This elision is only safe when the ExprStmt is beneath a + // BlockStmt, CaseClause.Body, or CommClause.Body; + // (see "statement theory"). + // + // The inlining analysis may have already determined that eliding braces is + // safe. Otherwise, we analyze its safety here. + elideBraces := res.elideBraces + if !elideBraces { + if newBlock, ok := res.new.(*ast.BlockStmt); ok { + i := nodeIndex(caller.path, res.old) + parent := caller.path[i+1] + var body []ast.Stmt + switch parent := parent.(type) { + case *ast.BlockStmt: + body = parent.List + case *ast.CommClause: + body = parent.Body + case *ast.CaseClause: + body = parent.Body + } + if body != nil { + callerNames := declares(body) + + // If BlockStmt is a function body, + // include its receiver, params, and results. + addFieldNames := func(fields *ast.FieldList) { + if fields != nil { + for _, field := range fields.List { + for _, id := range field.Names { + callerNames[id.Name] = true + } + } + } + } + switch f := caller.path[i+2].(type) { + case *ast.FuncDecl: + addFieldNames(f.Recv) + addFieldNames(f.Type.Params) + addFieldNames(f.Type.Results) + case *ast.FuncLit: + addFieldNames(f.Type.Params) + addFieldNames(f.Type.Results) + } + + if len(callerLabels(caller.path)) > 0 { + // TODO(adonovan): be more precise and reject + // only forward gotos across the inlined block. + logf("keeping block braces: caller uses control labels") + } else if intersects(declares(newBlock.List), callerNames) { + logf("keeping block braces: avoids name conflict") + } else { + elideBraces = true + } + } + } + } + + // Don't call replaceNode(caller.File, res.old, res.new) + // as it mutates the caller's syntax tree. + // Instead, splice the file, replacing the extent of the "old" + // node by a formatting of the "new" node, and re-parse. + // We'll fix up the imports on this new tree, and format again. + var f *ast.File + { + start := offsetOf(caller.Fset, res.old.Pos()) + end := offsetOf(caller.Fset, res.old.End()) + var out bytes.Buffer + out.Write(caller.Content[:start]) + // TODO(adonovan): might it make more sense to use + // callee.Fset when formatting res.new? + // The new tree is a mix of (cloned) caller nodes for + // the argument expressions and callee nodes for the + // function body. In essence the question is: which + // is more likely to have comments? + // Usually the callee body will be larger and more + // statement-heavy than the arguments, but a + // strategy may widen the scope of the replacement + // (res.old) from CallExpr to, say, its enclosing + // block, so the caller nodes dominate. + // Precise comment handling would make this a + // non-issue. Formatting wouldn't really need a + // FileSet at all. + if elideBraces { + for i, stmt := range res.new.(*ast.BlockStmt).List { + if i > 0 { + out.WriteByte('\n') + } + if err := format.Node(&out, caller.Fset, stmt); err != nil { + return nil, err + } + } + } else { + if err := format.Node(&out, caller.Fset, res.new); err != nil { + return nil, err + } + } + out.Write(caller.Content[end:]) + const mode = parser.ParseComments | parser.SkipObjectResolution | parser.AllErrors + f, err = parser.ParseFile(caller.Fset, "callee.go", &out, mode) + if err != nil { + // Something has gone very wrong. + logf("failed to parse <<%s>>", &out) // debugging + return nil, err + } + } + + // Add new imports. + // + // Insert new imports after last existing import, + // to avoid migration of pre-import comments. + // The imports will be organized below. + if len(res.newImports) > 0 { + var importDecl *ast.GenDecl + if len(f.Imports) > 0 { + // Append specs to existing import decl + importDecl = f.Decls[0].(*ast.GenDecl) + } else { + // Insert new import decl. + importDecl = &ast.GenDecl{Tok: token.IMPORT} + f.Decls = prepend[ast.Decl](importDecl, f.Decls...) + } + for _, imp := range res.newImports { + // Check that the new imports are accessible. + path, _ := strconv.Unquote(imp.spec.Path.Value) + if !canImport(caller.Types.Path(), path) { + return nil, fmt.Errorf("can't inline function %v as its body refers to inaccessible package %q", callee, path) + } + importDecl.Specs = append(importDecl.Specs, imp.spec) + } + } + + // Delete imports referenced only by caller.Call.Fun. + // + // (We can't let imports.Process take care of it as it may + // mistake obsolete imports for missing new imports when the + // names are similar, as is common during a package migration.) + for _, specToDelete := range res.oldImports { + for _, decl := range f.Decls { + if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { + decl.Specs = slicesDeleteFunc(decl.Specs, func(spec ast.Spec) bool { + imp := spec.(*ast.ImportSpec) + // Since we re-parsed the file, we can't match by identity; + // instead look for syntactic equivalence. + return imp.Path.Value == specToDelete.Path.Value && + (imp.Name != nil) == (specToDelete.Name != nil) && + (imp.Name == nil || imp.Name.Name == specToDelete.Name.Name) + }) + + // Edge case: import "foo" => import (). + if !decl.Lparen.IsValid() { + decl.Lparen = decl.TokPos + token.Pos(len("import")) + decl.Rparen = decl.Lparen + 1 + } + } + } + } + + var out bytes.Buffer + if err := format.Node(&out, caller.Fset, f); err != nil { + return nil, err + } + newSrc := out.Bytes() + + // Remove imports that are no longer referenced. + // + // It ought to be possible to compute the set of PkgNames used + // by the "old" code, compute the free identifiers of the + // "new" code using a syntax-only (no go/types) algorithm, and + // see if the reduction in the number of uses of any PkgName + // equals the number of times it appears in caller.Info.Uses, + // indicating that it is no longer referenced by res.new. + // + // However, the notorious ambiguity of resolving T{F: 0} makes this + // unreliable: without types, we can't tell whether F refers to + // a field of struct T, or a package-level const/var of a + // dot-imported (!) package. + // + // So, for now, we run imports.Process, which is + // unsatisfactory as it has to run the go command, and it + // looks at the user's module cache state--unnecessarily, + // since this step cannot add new imports. + // + // TODO(adonovan): replace with a simpler implementation since + // all the necessary imports are present but merely untidy. + // That will be faster, and also less prone to nondeterminism + // if there are bugs in our logic for import maintenance. + // + // However, github.com/gnolang/gno/contribs/gnopls/internal/imports.ApplyFixes is + // too simple as it requires the caller to have figured out + // all the logical edits. In our case, we know all the new + // imports that are needed (see newImports), each of which can + // be specified as: + // + // &imports.ImportFix{ + // StmtInfo: imports.ImportInfo{path, name, + // IdentName: name, + // FixType: imports.AddImport, + // } + // + // but we don't know which imports are made redundant by the + // inlining itself. For example, inlining a call to + // fmt.Println may make the "fmt" import redundant. + // + // Also, both imports.Process and internal/imports.ApplyFixes + // reformat the entire file, which is not ideal for clients + // such as gopls. (That said, the point of a canonical format + // is arguably that any tool can reformat as needed without + // this being inconvenient.) + // + // We could invoke imports.Process and parse its result, + // compare against the original AST, compute a list of import + // fixes, and return that too. + + // Recompute imports only if there were existing ones. + if len(f.Imports) > 0 { + formatted, err := imports.Process("output", newSrc, nil) + if err != nil { + logf("cannot reformat: %v <<%s>>", err, &out) + return nil, err // cannot reformat (a bug?) + } + newSrc = formatted + } + + literalized := false + if call, ok := res.new.(*ast.CallExpr); ok && is[*ast.FuncLit](call.Fun) { + literalized = true + } + + return &Result{ + Content: newSrc, + Literalized: literalized, + }, nil + +} + +type newImport struct { + pkgName string + spec *ast.ImportSpec +} + +type inlineCallResult struct { + newImports []newImport // to add + oldImports []*ast.ImportSpec // to remove + + // If elideBraces is set, old is an ast.Stmt and new is an ast.BlockStmt to + // be spliced in. This allows the inlining analysis to assert that inlining + // the block is OK; if elideBraces is unset and old is an ast.Stmt and new is + // an ast.BlockStmt, braces may still be elided if the post-processing + // analysis determines that it is safe to do so. + // + // Ideally, it would not be necessary for the inlining analysis to "reach + // through" to the post-processing pass in this way. Instead, inlining could + // just set old to be an ast.BlockStmt and rewrite the entire BlockStmt, but + // unfortunately in order to preserve comments, it is important that inlining + // replace as little syntax as possible. + elideBraces bool + old, new ast.Node // e.g. replace call expr by callee function body expression +} + +// inlineCall returns a pair of an old node (the call, or something +// enclosing it) and a new node (its replacement, which may be a +// combination of caller, callee, and new nodes), along with the set +// of new imports needed. +// +// TODO(adonovan): rethink the 'result' interface. The assumption of a +// one-to-one replacement seems fragile. One can easily imagine the +// transformation replacing the call and adding new variable +// declarations, for example, or replacing a call statement by zero or +// many statements.) +// +// TODO(adonovan): in earlier drafts, the transformation was expressed +// by splicing substrings of the two source files because syntax +// trees don't preserve comments faithfully (see #20744), but such +// transformations don't compose. The current implementation is +// tree-based but is very lossy wrt comments. It would make a good +// candidate for evaluating an alternative fully self-contained tree +// representation, such as any proposed solution to #20744, or even +// dst or some private fork of go/ast.) +func (st *state) inlineCall() (*inlineCallResult, error) { + logf, caller, callee := st.opts.Logf, st.caller, &st.callee.impl + + checkInfoFields(caller.Info) + + // Inlining of dynamic calls is not currently supported, + // even for local closure calls. (This would be a lot of work.) + calleeSymbol := typeutil.StaticCallee(caller.Info, caller.Call) + if calleeSymbol == nil { + // e.g. interface method + return nil, fmt.Errorf("cannot inline: not a static function call") + } + + // Reject cross-package inlining if callee has + // free references to unexported symbols. + samePkg := caller.Types.Path() == callee.PkgPath + if !samePkg && len(callee.Unexported) > 0 { + return nil, fmt.Errorf("cannot inline call to %s because body refers to non-exported %s", + callee.Name, callee.Unexported[0]) + } + + // -- analyze callee's free references in caller context -- + + // Compute syntax path enclosing Call, innermost first (Path[0]=Call), + // and outermost enclosing function, if any. + caller.path, _ = astutil.PathEnclosingInterval(caller.File, caller.Call.Pos(), caller.Call.End()) + for _, n := range caller.path { + if decl, ok := n.(*ast.FuncDecl); ok { + caller.enclosingFunc = decl + break + } + } + + // If call is within a function, analyze all its + // local vars for the "single assignment" property. + // (Taking the address &v counts as a potential assignment.) + var assign1 func(v *types.Var) bool // reports whether v a single-assignment local var + { + updatedLocals := make(map[*types.Var]bool) + if caller.enclosingFunc != nil { + escape(caller.Info, caller.enclosingFunc, func(v *types.Var, _ bool) { + updatedLocals[v] = true + }) + logf("multiple-assignment vars: %v", updatedLocals) + } + assign1 = func(v *types.Var) bool { return !updatedLocals[v] } + } + + // import map, initially populated with caller imports. + // + // For simplicity we ignore existing dot imports, so that a + // qualified identifier (QI) in the callee is always + // represented by a QI in the caller, allowing us to treat a + // QI like a selection on a package name. + importMap := make(map[string][]string) // maps package path to local name(s) + for _, imp := range caller.File.Imports { + if pkgname, ok := importedPkgName(caller.Info, imp); ok && + pkgname.Name() != "." && + pkgname.Name() != "_" { + path := pkgname.Imported().Path() + importMap[path] = append(importMap[path], pkgname.Name()) + } + } + + var oldImports []*ast.ImportSpec // imports referenced only caller.Call.Fun + + // localImportName returns the local name for a given imported package path. + var newImports []newImport + localImportName := func(obj *object) string { + // Does an import exist? + for _, name := range importMap[obj.PkgPath] { + // Check that either the import preexisted, + // or that it was newly added (no PkgName) but is not shadowed, + // either in the callee (shadows) or caller (caller.lookup). + if !obj.Shadow[name] { + found := caller.lookup(name) + if is[*types.PkgName](found) || found == nil { + return name + } + } + } + + newlyAdded := func(name string) bool { + for _, new := range newImports { + if new.pkgName == name { + return true + } + } + return false + } + + // shadowedInCaller reports whether a candidate package name + // already refers to a declaration in the caller. + shadowedInCaller := func(name string) bool { + existing := caller.lookup(name) + + // If the candidate refers to a PkgName p whose sole use is + // in caller.Call.Fun of the form p.F(...), where p.F is a + // qualified identifier, the p import will be deleted, + // so it's safe (and better) to recycle the name. + // + // Only the qualified identifier case matters, as other + // references to imported package names in the Call.Fun + // expression (e.g. x.after(3*time.Second).f() + // or time.Second.String()) will remain after + // inlining, as arguments. + if pkgName, ok := existing.(*types.PkgName); ok { + if sel, ok := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr); ok { + if sole := soleUse(caller.Info, pkgName); sole == sel.X { + for _, spec := range caller.File.Imports { + pkgName2, ok := importedPkgName(caller.Info, spec) + if ok && pkgName2 == pkgName { + oldImports = append(oldImports, spec) + return false + } + } + } + } + } + + return existing != nil + } + + // import added by callee + // + // Choose local PkgName based on last segment of + // package path plus, if needed, a numeric suffix to + // ensure uniqueness. + // + // "init" is not a legal PkgName. + // + // TODO(rfindley): is it worth preserving local package names for callee + // imports? Are they likely to be better or worse than the name we choose + // here? + base := obj.PkgName + name := base + for n := 0; obj.Shadow[name] || shadowedInCaller(name) || newlyAdded(name) || name == "init"; n++ { + name = fmt.Sprintf("%s%d", base, n) + } + + logf("adding import %s %q", name, obj.PkgPath) + spec := &ast.ImportSpec{ + Path: &ast.BasicLit{ + Kind: token.STRING, + Value: strconv.Quote(obj.PkgPath), + }, + } + // Use explicit pkgname (out of necessity) when it differs from the declared name, + // or (for good style) when it differs from base(pkgpath). + if name != obj.PkgName || name != pathpkg.Base(obj.PkgPath) { + spec.Name = makeIdent(name) + } + newImports = append(newImports, newImport{ + pkgName: name, + spec: spec, + }) + importMap[obj.PkgPath] = append(importMap[obj.PkgPath], name) + return name + } + + // Compute the renaming of the callee's free identifiers. + objRenames := make([]ast.Expr, len(callee.FreeObjs)) // nil => no change + for i, obj := range callee.FreeObjs { + // obj is a free object of the callee. + // + // Possible cases are: + // - builtin function, type, or value (e.g. nil, zero) + // => check not shadowed in caller. + // - package-level var/func/const/types + // => same package: check not shadowed in caller. + // => otherwise: import other package, form a qualified identifier. + // (Unexported cross-package references were rejected already.) + // - type parameter + // => not yet supported + // - pkgname + // => import other package and use its local name. + // + // There can be no free references to labels, fields, or methods. + + // Note that we must consider potential shadowing both + // at the caller side (caller.lookup) and, when + // choosing new PkgNames, within the callee (obj.shadow). + + var newName ast.Expr + if obj.Kind == "pkgname" { + // Use locally appropriate import, creating as needed. + newName = makeIdent(localImportName(&obj)) // imported package + } else if !obj.ValidPos { + // Built-in function, type, or value (e.g. nil, zero): + // check not shadowed at caller. + found := caller.lookup(obj.Name) // always finds something + if found.Pos().IsValid() { + return nil, fmt.Errorf("cannot inline, because the callee refers to built-in %q, which in the caller is shadowed by a %s (declared at line %d)", + obj.Name, objectKind(found), + caller.Fset.PositionFor(found.Pos(), false).Line) + } + + } else { + // Must be reference to package-level var/func/const/type, + // since type parameters are not yet supported. + qualify := false + if obj.PkgPath == callee.PkgPath { + // reference within callee package + if samePkg { + // Caller and callee are in same package. + // Check caller has not shadowed the decl. + // + // This may fail if the callee is "fake", such as for signature + // refactoring where the callee is modified to be a trivial wrapper + // around the refactored signature. + found := caller.lookup(obj.Name) + if found != nil && !isPkgLevel(found) { + return nil, fmt.Errorf("cannot inline, because the callee refers to %s %q, which in the caller is shadowed by a %s (declared at line %d)", + obj.Kind, obj.Name, + objectKind(found), + caller.Fset.PositionFor(found.Pos(), false).Line) + } + } else { + // Cross-package reference. + qualify = true + } + } else { + // Reference to a package-level declaration + // in another package, without a qualified identifier: + // it must be a dot import. + qualify = true + } + + // Form a qualified identifier, pkg.Name. + if qualify { + pkgName := localImportName(&obj) + newName = &ast.SelectorExpr{ + X: makeIdent(pkgName), + Sel: makeIdent(obj.Name), + } + } + } + objRenames[i] = newName + } + + res := &inlineCallResult{ + newImports: newImports, + oldImports: oldImports, + } + + // Parse callee function declaration. + calleeFset, calleeDecl, err := parseCompact(callee.Content) + if err != nil { + return nil, err // "can't happen" + } + + // replaceCalleeID replaces an identifier in the callee. + // The replacement tree must not belong to the caller; use cloneNode as needed. + replaceCalleeID := func(offset int, repl ast.Expr) { + id := findIdent(calleeDecl, calleeDecl.Pos()+token.Pos(offset)) + logf("- replace id %q @ #%d to %q", id.Name, offset, debugFormatNode(calleeFset, repl)) + replaceNode(calleeDecl, id, repl) + } + + // Generate replacements for each free identifier. + // (The same tree may be spliced in multiple times, resulting in a DAG.) + for _, ref := range callee.FreeRefs { + if repl := objRenames[ref.Object]; repl != nil { + replaceCalleeID(ref.Offset, repl) + } + } + + // Gather the effective call arguments, including the receiver. + // Later, elements will be eliminated (=> nil) by parameter substitution. + args, err := st.arguments(caller, calleeDecl, assign1) + if err != nil { + return nil, err // e.g. implicit field selection cannot be made explicit + } + + // Gather effective parameter tuple, including the receiver if any. + // Simplify variadic parameters to slices (in all cases but one). + var params []*parameter // including receiver; nil => parameter substituted + { + sig := calleeSymbol.Type().(*types.Signature) + if sig.Recv() != nil { + params = append(params, ¶meter{ + obj: sig.Recv(), + fieldType: calleeDecl.Recv.List[0].Type, + info: callee.Params[0], + }) + } + + // Flatten the list of syntactic types. + var types []ast.Expr + for _, field := range calleeDecl.Type.Params.List { + if field.Names == nil { + types = append(types, field.Type) + } else { + for range field.Names { + types = append(types, field.Type) + } + } + } + + for i := 0; i < sig.Params().Len(); i++ { + params = append(params, ¶meter{ + obj: sig.Params().At(i), + fieldType: types[i], + info: callee.Params[len(params)], + }) + } + + // Variadic function? + // + // There are three possible types of call: + // - ordinary f(a1, ..., aN) + // - ellipsis f(a1, ..., slice...) + // - spread f(recv?, g()) where g() is a tuple. + // The first two are desugared to non-variadic calls + // with an ordinary slice parameter; + // the third is tricky and cannot be reduced, and (if + // a receiver is present) cannot even be literalized. + // Fortunately it is vanishingly rare. + // + // TODO(adonovan): extract this to a function. + if sig.Variadic() { + lastParam := last(params) + if len(args) > 0 && last(args).spread { + // spread call to variadic: tricky + lastParam.variadic = true + } else { + // ordinary/ellipsis call to variadic + + // simplify decl: func(T...) -> func([]T) + lastParamField := last(calleeDecl.Type.Params.List) + lastParamField.Type = &ast.ArrayType{ + Elt: lastParamField.Type.(*ast.Ellipsis).Elt, + } + + if caller.Call.Ellipsis.IsValid() { + // ellipsis call: f(slice...) -> f(slice) + // nop + } else { + // ordinary call: f(a1, ... aN) -> f([]T{a1, ..., aN}) + n := len(params) - 1 + ordinary, extra := args[:n], args[n:] + var elts []ast.Expr + pure, effects := true, false + for _, arg := range extra { + elts = append(elts, arg.expr) + pure = pure && arg.pure + effects = effects || arg.effects + } + args = append(ordinary, &argument{ + expr: &ast.CompositeLit{ + Type: lastParamField.Type, + Elts: elts, + }, + typ: lastParam.obj.Type(), + constant: nil, + pure: pure, + effects: effects, + duplicable: false, + freevars: nil, // not needed + }) + } + } + } + } + + // Log effective arguments. + for i, arg := range args { + logf("arg #%d: %s pure=%t effects=%t duplicable=%t free=%v type=%v", + i, debugFormatNode(caller.Fset, arg.expr), + arg.pure, arg.effects, arg.duplicable, arg.freevars, arg.typ) + } + + // Note: computation below should be expressed in terms of + // the args and params slices, not the raw material. + + // Perform parameter substitution. + // May eliminate some elements of params/args. + substitute(logf, caller, params, args, callee.Effects, callee.Falcon, replaceCalleeID) + + // Update the callee's signature syntax. + updateCalleeParams(calleeDecl, params) + + // Create a var (param = arg; ...) decl for use by some strategies. + bindingDecl := createBindingDecl(logf, caller, args, calleeDecl, callee.Results) + + var remainingArgs []ast.Expr + for _, arg := range args { + if arg != nil { + remainingArgs = append(remainingArgs, arg.expr) + } + } + + // -- let the inlining strategies begin -- + // + // When we commit to a strategy, we log a message of the form: + // + // "strategy: reduce expr-context call to { return expr }" + // + // This is a terse way of saying: + // + // we plan to reduce a call + // that appears in expression context + // to a function whose body is of the form { return expr } + + // TODO(adonovan): split this huge function into a sequence of + // function calls with an error sentinel that means "try the + // next strategy", and make sure each strategy writes to the + // log the reason it didn't match. + + // Special case: eliminate a call to a function whose body is empty. + // (=> callee has no results and caller is a statement.) + // + // func f(params) {} + // f(args) + // => _, _ = args + // + if len(calleeDecl.Body.List) == 0 { + logf("strategy: reduce call to empty body") + + // Evaluate the arguments for effects and delete the call entirely. + stmt := callStmt(caller.path, false) // cannot fail + res.old = stmt + if nargs := len(remainingArgs); nargs > 0 { + // Emit "_, _ = args" to discard results. + + // TODO(adonovan): if args is the []T{a1, ..., an} + // literal synthesized during variadic simplification, + // consider unwrapping it to its (pure) elements. + // Perhaps there's no harm doing this for any slice literal. + + // Make correction for spread calls + // f(g()) or recv.f(g()) where g() is a tuple. + if last := last(args); last != nil && last.spread { + nspread := last.typ.(*types.Tuple).Len() + if len(args) > 1 { // [recv, g()] + // A single AssignStmt cannot discard both, so use a 2-spec var decl. + res.new = &ast.GenDecl{ + Tok: token.VAR, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{makeIdent("_")}, + Values: []ast.Expr{args[0].expr}, + }, + &ast.ValueSpec{ + Names: blanks[*ast.Ident](nspread), + Values: []ast.Expr{args[1].expr}, + }, + }, + } + return res, nil + } + + // Sole argument is spread call. + nargs = nspread + } + + res.new = &ast.AssignStmt{ + Lhs: blanks[ast.Expr](nargs), + Tok: token.ASSIGN, + Rhs: remainingArgs, + } + + } else { + // No remaining arguments: delete call statement entirely + res.new = &ast.EmptyStmt{} + } + return res, nil + } + + // If all parameters have been substituted and no result + // variable is referenced, we don't need a binding decl. + // This may enable better reduction strategies. + allResultsUnreferenced := forall(callee.Results, func(i int, r *paramInfo) bool { return len(r.Refs) == 0 }) + needBindingDecl := !allResultsUnreferenced || + exists(params, func(i int, p *parameter) bool { return p != nil }) + + // The two strategies below overlap for a tail call of {return exprs}: + // The expr-context reduction is nice because it keeps the + // caller's return stmt and merely switches its operand, + // without introducing a new block, but it doesn't work with + // implicit return conversions. + // + // TODO(adonovan): unify these cases more cleanly, allowing return- + // operand replacement and implicit conversions, by adding + // conversions around each return operand (if not a spread return). + + // Special case: call to { return exprs }. + // + // Reduces to: + // { var (bindings); _, _ = exprs } + // or _, _ = exprs + // or expr + // + // If: + // - the body is just "return expr" with trivial implicit conversions, + // or the caller's return type matches the callee's, + // - all parameters and result vars can be eliminated + // or replaced by a binding decl, + // then the call expression can be replaced by the + // callee's body expression, suitably substituted. + if len(calleeDecl.Body.List) == 1 && + is[*ast.ReturnStmt](calleeDecl.Body.List[0]) && + len(calleeDecl.Body.List[0].(*ast.ReturnStmt).Results) > 0 { // not a bare return + results := calleeDecl.Body.List[0].(*ast.ReturnStmt).Results + + parent, grandparent := callContext(caller.path) + + // statement context + if stmt, ok := parent.(*ast.ExprStmt); ok && + (!needBindingDecl || bindingDecl != nil) { + logf("strategy: reduce stmt-context call to { return exprs }") + clearPositions(calleeDecl.Body) + + if callee.ValidForCallStmt { + logf("callee body is valid as statement") + // Inv: len(results) == 1 + if !needBindingDecl { + // Reduces to: expr + res.old = caller.Call + res.new = results[0] + } else { + // Reduces to: { var (bindings); expr } + res.old = stmt + res.new = &ast.BlockStmt{ + List: []ast.Stmt{ + bindingDecl.stmt, + &ast.ExprStmt{X: results[0]}, + }, + } + } + } else { + logf("callee body is not valid as statement") + // The call is a standalone statement, but the + // callee body is not suitable as a standalone statement + // (f() or <-ch), explicitly discard the results: + // Reduces to: _, _ = exprs + discard := &ast.AssignStmt{ + Lhs: blanks[ast.Expr](callee.NumResults), + Tok: token.ASSIGN, + Rhs: results, + } + res.old = stmt + if !needBindingDecl { + // Reduces to: _, _ = exprs + res.new = discard + } else { + // Reduces to: { var (bindings); _, _ = exprs } + res.new = &ast.BlockStmt{ + List: []ast.Stmt{ + bindingDecl.stmt, + discard, + }, + } + } + } + return res, nil + } + + // Assignment context. + // + // If there is no binding decl, or if the binding decl declares no names, + // an assignment a, b := f() can be reduced to a, b := x, y. + if stmt, ok := parent.(*ast.AssignStmt); ok && + is[*ast.BlockStmt](grandparent) && + (!needBindingDecl || (bindingDecl != nil && len(bindingDecl.names) == 0)) { + + // Reduces to: { var (bindings); lhs... := rhs... } + if newStmts, ok := st.assignStmts(stmt, results); ok { + logf("strategy: reduce assign-context call to { return exprs }") + clearPositions(calleeDecl.Body) + + block := &ast.BlockStmt{ + List: newStmts, + } + if needBindingDecl { + block.List = prepend(bindingDecl.stmt, block.List...) + } + + // assignStmts does not introduce new bindings, and replacing an + // assignment only works if the replacement occurs in the same scope. + // Therefore, we must ensure that braces are elided. + res.elideBraces = true + res.old = stmt + res.new = block + return res, nil + } + } + + // expression context + if !needBindingDecl { + clearPositions(calleeDecl.Body) + + anyNonTrivialReturns := hasNonTrivialReturn(callee.Returns) + + if callee.NumResults == 1 { + logf("strategy: reduce expr-context call to { return expr }") + // (includes some simple tail-calls) + + // Make implicit return conversion explicit. + if anyNonTrivialReturns { + results[0] = convert(calleeDecl.Type.Results.List[0].Type, results[0]) + } + + res.old = caller.Call + res.new = results[0] + return res, nil + + } else if !anyNonTrivialReturns { + logf("strategy: reduce spread-context call to { return expr }") + // There is no general way to reify conversions in a spread + // return, hence the requirement above. + // + // TODO(adonovan): allow this reduction when no + // conversion is required by the context. + + // The call returns multiple results but is + // not a standalone call statement. It must + // be the RHS of a spread assignment: + // var x, y = f() + // x, y := f() + // x, y = f() + // or the sole argument to a spread call: + // printf(f()) + // or spread return statement: + // return f() + res.old = parent + switch context := parent.(type) { + case *ast.AssignStmt: + // Inv: the call must be in Rhs[0], not Lhs. + assign := shallowCopy(context) + assign.Rhs = results + res.new = assign + case *ast.ValueSpec: + // Inv: the call must be in Values[0], not Names. + spec := shallowCopy(context) + spec.Values = results + res.new = spec + case *ast.CallExpr: + // Inv: the call must be in Args[0], not Fun. + call := shallowCopy(context) + call.Args = results + res.new = call + case *ast.ReturnStmt: + // Inv: the call must be Results[0]. + ret := shallowCopy(context) + ret.Results = results + res.new = ret + default: + return nil, fmt.Errorf("internal error: unexpected context %T for spread call", context) + } + return res, nil + } + } + } + + // Special case: tail-call. + // + // Inlining: + // return f(args) + // where: + // func f(params) (results) { body } + // reduces to: + // { var (bindings); body } + // { body } + // so long as: + // - all parameters can be eliminated or replaced by a binding decl, + // - call is a tail-call; + // - all returns in body have trivial result conversions, + // or the caller's return type matches the callee's, + // - there is no label conflict; + // - no result variable is referenced by name, + // or implicitly by a bare return. + // + // The body may use defer, arbitrary control flow, and + // multiple returns. + // + // TODO(adonovan): add a strategy for a 'void tail + // call', i.e. a call statement prior to an (explicit + // or implicit) return. + parent, _ := callContext(caller.path) + if ret, ok := parent.(*ast.ReturnStmt); ok && + len(ret.Results) == 1 && + tailCallSafeReturn(caller, calleeSymbol, callee) && + !callee.HasBareReturn && + (!needBindingDecl || bindingDecl != nil) && + !hasLabelConflict(caller.path, callee.Labels) && + allResultsUnreferenced { + logf("strategy: reduce tail-call") + body := calleeDecl.Body + clearPositions(body) + if needBindingDecl { + body.List = prepend(bindingDecl.stmt, body.List...) + } + res.old = ret + res.new = body + return res, nil + } + + // Special case: call to void function + // + // Inlining: + // f(args) + // where: + // func f(params) { stmts } + // reduces to: + // { var (bindings); stmts } + // { stmts } + // so long as: + // - callee is a void function (no returns) + // - callee does not use defer + // - there is no label conflict between caller and callee + // - all parameters and result vars can be eliminated + // or replaced by a binding decl, + // - caller ExprStmt is in unrestricted statement context. + if stmt := callStmt(caller.path, true); stmt != nil && + (!needBindingDecl || bindingDecl != nil) && + !callee.HasDefer && + !hasLabelConflict(caller.path, callee.Labels) && + len(callee.Returns) == 0 { + logf("strategy: reduce stmt-context call to { stmts }") + body := calleeDecl.Body + var repl ast.Stmt = body + clearPositions(repl) + if needBindingDecl { + body.List = prepend(bindingDecl.stmt, body.List...) + } + res.old = stmt + res.new = repl + return res, nil + } + + // TODO(adonovan): parameterless call to { stmts; return expr } + // from one of these contexts: + // x, y = f() + // x, y := f() + // var x, y = f() + // => + // var (x T1, y T2); { stmts; x, y = expr } + // + // Because the params are no longer declared simultaneously + // we need to check that (for example) x ∉ freevars(T2), + // in addition to the usual checks for arg/result conversions, + // complex control, etc. + // Also test cases where expr is an n-ary call (spread returns). + + // Literalization isn't quite infallible. + // Consider a spread call to a method in which + // no parameters are eliminated, e.g. + // new(T).f(g()) + // where + // func (recv *T) f(x, y int) { body } + // func g() (int, int) + // This would be literalized to: + // func (recv *T, x, y int) { body }(new(T), g()), + // which is not a valid argument list because g() must appear alone. + // Reject this case for now. + if len(args) == 2 && args[0] != nil && args[1] != nil && is[*types.Tuple](args[1].typ) { + return nil, fmt.Errorf("can't yet inline spread call to method") + } + + // Infallible general case: literalization. + // + // func(params) { body }(args) + // + logf("strategy: literalization") + funcLit := &ast.FuncLit{ + Type: calleeDecl.Type, + Body: calleeDecl.Body, + } + + // Literalization can still make use of a binding + // decl as it gives a more natural reading order: + // + // func() { var params = args; body }() + // + // TODO(adonovan): relax the allResultsUnreferenced requirement + // by adding a parameter-only (no named results) binding decl. + if bindingDecl != nil && allResultsUnreferenced { + funcLit.Type.Params.List = nil + remainingArgs = nil + funcLit.Body.List = prepend(bindingDecl.stmt, funcLit.Body.List...) + } + + // Emit a new call to a function literal in place of + // the callee name, with appropriate replacements. + newCall := &ast.CallExpr{ + Fun: funcLit, + Ellipsis: token.NoPos, // f(slice...) is always simplified + Args: remainingArgs, + } + clearPositions(newCall.Fun) + res.old = caller.Call + res.new = newCall + return res, nil +} + +type argument struct { + expr ast.Expr + typ types.Type // may be tuple for sole non-receiver arg in spread call + constant constant.Value // value of argument if constant + spread bool // final arg is call() assigned to multiple params + pure bool // expr is pure (doesn't read variables) + effects bool // expr has effects (updates variables) + duplicable bool // expr may be duplicated + freevars map[string]bool // free names of expr + substitutable bool // is candidate for substitution +} + +// arguments returns the effective arguments of the call. +// +// If the receiver argument and parameter have +// different pointerness, make the "&" or "*" explicit. +// +// Also, if x.f() is shorthand for promoted method x.y.f(), +// make the .y explicit in T.f(x.y, ...). +// +// Beware that: +// +// - a method can only be called through a selection, but only +// the first of these two forms needs special treatment: +// +// expr.f(args) -> ([&*]expr, args) MethodVal +// T.f(recv, args) -> ( expr, args) MethodExpr +// +// - the presence of a value in receiver-position in the call +// is a property of the caller, not the callee. A method +// (calleeDecl.Recv != nil) may be called like an ordinary +// function. +// +// - the types.Signatures seen by the caller (from +// StaticCallee) and by the callee (from decl type) +// differ in this case. +// +// In a spread call f(g()), the sole ordinary argument g(), +// always last in args, has a tuple type. +// +// We compute type-based predicates like pure, duplicable, +// freevars, etc, now, before we start modifying syntax. +func (st *state) arguments(caller *Caller, calleeDecl *ast.FuncDecl, assign1 func(*types.Var) bool) ([]*argument, error) { + var args []*argument + + callArgs := caller.Call.Args + if calleeDecl.Recv != nil { + sel := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr) + seln := caller.Info.Selections[sel] + var recvArg ast.Expr + switch seln.Kind() { + case types.MethodVal: // recv.f(callArgs) + recvArg = sel.X + case types.MethodExpr: // T.f(recv, callArgs) + recvArg = callArgs[0] + callArgs = callArgs[1:] + } + if recvArg != nil { + // Compute all the type-based predicates now, + // before we start meddling with the syntax; + // the meddling will update them. + arg := &argument{ + expr: recvArg, + typ: caller.Info.TypeOf(recvArg), + constant: caller.Info.Types[recvArg].Value, + pure: pure(caller.Info, assign1, recvArg), + effects: st.effects(caller.Info, recvArg), + duplicable: duplicable(caller.Info, recvArg), + freevars: freeVars(caller.Info, recvArg), + } + recvArg = nil // prevent accidental use + + // Move receiver argument recv.f(args) to argument list f(&recv, args). + args = append(args, arg) + + // Make field selections explicit (recv.f -> recv.y.f), + // updating arg.{expr,typ}. + indices := seln.Index() + for _, index := range indices[:len(indices)-1] { + fld := typeparams.CoreType(typeparams.Deref(arg.typ)).(*types.Struct).Field(index) + if fld.Pkg() != caller.Types && !fld.Exported() { + return nil, fmt.Errorf("in %s, implicit reference to unexported field .%s cannot be made explicit", + debugFormatNode(caller.Fset, caller.Call.Fun), + fld.Name()) + } + if isPointer(arg.typ) { + arg.pure = false // implicit *ptr operation => impure + } + arg.expr = &ast.SelectorExpr{ + X: arg.expr, + Sel: makeIdent(fld.Name()), + } + arg.typ = fld.Type() + arg.duplicable = false + } + + // Make * or & explicit. + argIsPtr := isPointer(arg.typ) + paramIsPtr := isPointer(seln.Obj().Type().Underlying().(*types.Signature).Recv().Type()) + if !argIsPtr && paramIsPtr { + // &recv + arg.expr = &ast.UnaryExpr{Op: token.AND, X: arg.expr} + arg.typ = types.NewPointer(arg.typ) + } else if argIsPtr && !paramIsPtr { + // *recv + arg.expr = &ast.StarExpr{X: arg.expr} + arg.typ = typeparams.Deref(arg.typ) + arg.duplicable = false + arg.pure = false + } + } + } + for _, expr := range callArgs { + tv := caller.Info.Types[expr] + args = append(args, &argument{ + expr: expr, + typ: tv.Type, + constant: tv.Value, + spread: is[*types.Tuple](tv.Type), // => last + pure: pure(caller.Info, assign1, expr), + effects: st.effects(caller.Info, expr), + duplicable: duplicable(caller.Info, expr), + freevars: freeVars(caller.Info, expr), + }) + } + + // Re-typecheck each constant argument expression in a neutral context. + // + // In a call such as func(int16){}(1), the type checker infers + // the type "int16", not "untyped int", for the argument 1, + // because it has incorporated information from the left-hand + // side of the assignment implicit in parameter passing, but + // of course in a different context, the expression 1 may have + // a different type. + // + // So, we must use CheckExpr to recompute the type of the + // argument in a neutral context to find its inherent type. + // (This is arguably a bug in go/types, but I'm pretty certain + // I requested it be this way long ago... -adonovan) + // + // This is only needed for constants. Other implicit + // assignment conversions, such as unnamed-to-named struct or + // chan to <-chan, do not result in the type-checker imposing + // the LHS type on the RHS value. + for _, arg := range args { + if arg.constant == nil { + continue + } + info := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)} + if err := types.CheckExpr(caller.Fset, caller.Types, caller.Call.Pos(), arg.expr, info); err != nil { + return nil, err + } + arg.typ = info.TypeOf(arg.expr) + } + + return args, nil +} + +type parameter struct { + obj *types.Var // parameter var from caller's signature + fieldType ast.Expr // syntax of type, from calleeDecl.Type.{Recv,Params} + info *paramInfo // information from AnalyzeCallee + variadic bool // (final) parameter is unsimplified ...T +} + +// substitute implements parameter elimination by substitution. +// +// It considers each parameter and its corresponding argument in turn +// and evaluate these conditions: +// +// - the parameter is neither address-taken nor assigned; +// - the argument is pure; +// - if the parameter refcount is zero, the argument must +// not contain the last use of a local var; +// - if the parameter refcount is > 1, the argument must be duplicable; +// - the argument (or types.Default(argument) if it's untyped) has +// the same type as the parameter. +// +// If all conditions are met then the parameter can be substituted and +// each reference to it replaced by the argument. In that case, the +// replaceCalleeID function is called for each reference to the +// parameter, and is provided with its relative offset and replacement +// expression (argument), and the corresponding elements of params and +// args are replaced by nil. +func substitute(logf func(string, ...any), caller *Caller, params []*parameter, args []*argument, effects []int, falcon falconResult, replaceCalleeID func(offset int, repl ast.Expr)) { + // Inv: + // in calls to variadic, len(args) >= len(params)-1 + // in spread calls to non-variadic, len(args) < len(params) + // in spread calls to variadic, len(args) <= len(params) + // (In spread calls len(args) = 1, or 2 if call has receiver.) + // Non-spread variadics have been simplified away already, + // so the args[i] lookup is safe if we stop after the spread arg. +next: + for i, param := range params { + arg := args[i] + // Check argument against parameter. + // + // Beware: don't use types.Info on arg since + // the syntax may be synthetic (not created by parser) + // and thus lacking positions and types; + // do it earlier (see pure/duplicable/freevars). + + if arg.spread { + // spread => last argument, but not always last parameter + logf("keeping param %q and following ones: argument %s is spread", + param.info.Name, debugFormatNode(caller.Fset, arg.expr)) + return // give up + } + assert(!param.variadic, "unsimplified variadic parameter") + if param.info.Escapes { + logf("keeping param %q: escapes from callee", param.info.Name) + continue + } + if param.info.Assigned { + logf("keeping param %q: assigned by callee", param.info.Name) + continue // callee needs the parameter variable + } + if len(param.info.Refs) > 1 && !arg.duplicable { + logf("keeping param %q: argument is not duplicable", param.info.Name) + continue // incorrect or poor style to duplicate an expression + } + if len(param.info.Refs) == 0 { + if arg.effects { + logf("keeping param %q: though unreferenced, it has effects", param.info.Name) + continue + } + + // If the caller is within a function body, + // eliminating an unreferenced parameter might + // remove the last reference to a caller local var. + if caller.enclosingFunc != nil { + for free := range arg.freevars { + // TODO(rfindley): we can get this 100% right by looking for + // references among other arguments which have non-zero references + // within the callee. + if v, ok := caller.lookup(free).(*types.Var); ok && within(v.Pos(), caller.enclosingFunc.Body) && !isUsedOutsideCall(caller, v) { + logf("keeping param %q: arg contains perhaps the last reference to caller local %v @ %v", + param.info.Name, v, caller.Fset.PositionFor(v.Pos(), false)) + continue next + } + } + } + } + + // Check for shadowing. + // + // Consider inlining a call f(z, 1) to + // func f(x, y int) int { z := y; return x + y + z }: + // we can't replace x in the body by z (or any + // expression that has z as a free identifier) + // because there's an intervening declaration of z + // that would shadow the caller's one. + for free := range arg.freevars { + if param.info.Shadow[free] { + logf("keeping param %q: cannot replace with argument as it has free ref to %s that is shadowed", param.info.Name, free) + continue next // shadowing conflict + } + } + + arg.substitutable = true // may be substituted, if effects permit + } + + // Reject constant arguments as substitution candidates + // if they cause violation of falcon constraints. + checkFalconConstraints(logf, params, args, falcon) + + // As a final step, introduce bindings to resolve any + // evaluation order hazards. This must be done last, as + // additional subsequent bindings could introduce new hazards. + resolveEffects(logf, args, effects) + + // The remaining candidates are safe to substitute. + for i, param := range params { + if arg := args[i]; arg.substitutable { + + // Wrap the argument in an explicit conversion if + // substitution might materially change its type. + // (We already did the necessary shadowing check + // on the parameter type syntax.) + // + // This is only needed for substituted arguments. All + // other arguments are given explicit types in either + // a binding decl or when using the literalization + // strategy. + + // If the types are identical, we can eliminate + // redundant type conversions such as this: + // + // Callee: + // func f(i int32) { print(i) } + // Caller: + // func g() { f(int32(1)) } + // Inlined as: + // func g() { print(int32(int32(1))) + // + // Recall that non-trivial does not imply non-identical + // for constant conversions; however, at this point state.arguments + // has already re-typechecked the constant and set arg.type to + // its (possibly "untyped") inherent type, so + // the conversion from untyped 1 to int32 is non-trivial even + // though both arg and param have identical types (int32). + if len(param.info.Refs) > 0 && + !types.Identical(arg.typ, param.obj.Type()) && + !trivialConversion(arg.constant, arg.typ, param.obj.Type()) { + arg.expr = convert(param.fieldType, arg.expr) + logf("param %q: adding explicit %s -> %s conversion around argument", + param.info.Name, arg.typ, param.obj.Type()) + } + + // It is safe to substitute param and replace it with arg. + // The formatter introduces parens as needed for precedence. + // + // Because arg.expr belongs to the caller, + // we clone it before splicing it into the callee tree. + logf("replacing parameter %q by argument %q", + param.info.Name, debugFormatNode(caller.Fset, arg.expr)) + for _, ref := range param.info.Refs { + replaceCalleeID(ref, internalastutil.CloneNode(arg.expr).(ast.Expr)) + } + params[i] = nil // substituted + args[i] = nil // substituted + } + } +} + +// isUsedOutsideCall reports whether v is used outside of caller.Call, within +// the body of caller.enclosingFunc. +func isUsedOutsideCall(caller *Caller, v *types.Var) bool { + used := false + ast.Inspect(caller.enclosingFunc.Body, func(n ast.Node) bool { + if n == caller.Call { + return false + } + switch n := n.(type) { + case *ast.Ident: + if use := caller.Info.Uses[n]; use == v { + used = true + } + case *ast.FuncType: + // All params are used. + for _, fld := range n.Params.List { + for _, n := range fld.Names { + if def := caller.Info.Defs[n]; def == v { + used = true + } + } + } + } + return !used // keep going until we find a use + }) + return used +} + +// checkFalconConstraints checks whether constant arguments +// are safe to substitute (e.g. s[i] -> ""[0] is not safe.) +// +// Any failed constraint causes us to reject all constant arguments as +// substitution candidates (by clearing args[i].substitution=false). +// +// TODO(adonovan): we could obtain a finer result rejecting only the +// freevars of each failed constraint, and processing constraints in +// order of increasing arity, but failures are quite rare. +func checkFalconConstraints(logf func(string, ...any), params []*parameter, args []*argument, falcon falconResult) { + // Create a dummy package, as this is the only + // way to create an environment for CheckExpr. + pkg := types.NewPackage("falcon", "falcon") + + // Declare types used by constraints. + for _, typ := range falcon.Types { + logf("falcon env: type %s %s", typ.Name, types.Typ[typ.Kind]) + pkg.Scope().Insert(types.NewTypeName(token.NoPos, pkg, typ.Name, types.Typ[typ.Kind])) + } + + // Declared constants and variables for for parameters. + nconst := 0 + for i, param := range params { + name := param.info.Name + if name == "" { + continue // unreferenced + } + arg := args[i] + if arg.constant != nil && arg.substitutable && param.info.FalconType != "" { + t := pkg.Scope().Lookup(param.info.FalconType).Type() + pkg.Scope().Insert(types.NewConst(token.NoPos, pkg, name, t, arg.constant)) + logf("falcon env: const %s %s = %v", name, param.info.FalconType, arg.constant) + nconst++ + } else { + pkg.Scope().Insert(types.NewVar(token.NoPos, pkg, name, arg.typ)) + logf("falcon env: var %s %s", name, arg.typ) + } + } + if nconst == 0 { + return // nothing to do + } + + // Parse and evaluate the constraints in the environment. + fset := token.NewFileSet() + for _, falcon := range falcon.Constraints { + expr, err := parser.ParseExprFrom(fset, "falcon", falcon, 0) + if err != nil { + panic(fmt.Sprintf("failed to parse falcon constraint %s: %v", falcon, err)) + } + if err := types.CheckExpr(fset, pkg, token.NoPos, expr, nil); err != nil { + logf("falcon: constraint %s violated: %v", falcon, err) + for j, arg := range args { + if arg.constant != nil && arg.substitutable { + logf("keeping param %q due falcon violation", params[j].info.Name) + arg.substitutable = false + } + } + break + } + logf("falcon: constraint %s satisfied", falcon) + } +} + +// resolveEffects marks arguments as non-substitutable to resolve +// hazards resulting from the callee evaluation order described by the +// effects list. +// +// To do this, each argument is categorized as a read (R), write (W), +// or pure. A hazard occurs when the order of evaluation of a W +// changes with respect to any R or W. Pure arguments can be +// effectively ignored, as they can be safely evaluated in any order. +// +// The callee effects list contains the index of each parameter in the +// order it is first evaluated during execution of the callee. In +// addition, the two special values R∞ and W∞ indicate the relative +// position of the callee's first non-parameter read and its first +// effects (or other unknown behavior). +// For example, the list [0 2 1 R∞ 3 W∞] for func(a, b, c, d) +// indicates that the callee referenced parameters a, c, and b, +// followed by an arbitrary read, then parameter d, and finally +// unknown behavior. +// +// When an argument is marked as not substitutable, we say that it is +// 'bound', in the sense that its evaluation occurs in a binding decl +// or literalized call. Such bindings always occur in the original +// callee parameter order. +// +// In this context, "resolving hazards" means binding arguments so +// that they are evaluated in a valid, hazard-free order. A trivial +// solution to this problem would be to bind all arguments, but of +// course that's not useful. The goal is to bind as few arguments as +// possible. +// +// The algorithm proceeds by inspecting arguments in reverse parameter +// order (right to left), preserving the invariant that every +// higher-ordered argument is either already substituted or does not +// need to be substituted. At each iteration, if there is an +// evaluation hazard in the callee effects relative to the current +// argument, the argument must be bound. Subsequently, if the argument +// is bound for any reason, each lower-ordered argument must also be +// bound if either the argument or lower-order argument is a +// W---otherwise the binding itself would introduce a hazard. +// +// Thus, after each iteration, there are no hazards relative to the +// current argument. Subsequent iterations cannot introduce hazards +// with that argument because they can result only in additional +// binding of lower-ordered arguments. +func resolveEffects(logf func(string, ...any), args []*argument, effects []int) { + effectStr := func(effects bool, idx int) string { + i := fmt.Sprint(idx) + if idx == len(args) { + i = "∞" + } + return string("RW"[btoi(effects)]) + i + } + for i := len(args) - 1; i >= 0; i-- { + argi := args[i] + if argi.substitutable && !argi.pure { + // i is not bound: check whether it must be bound due to hazards. + idx := index(effects, i) + if idx >= 0 { + for _, j := range effects[:idx] { + var ( + ji int // effective param index + jw bool // j is a write + ) + if j == winf || j == rinf { + jw = j == winf + ji = len(args) + } else { + jw = args[j].effects + ji = j + } + if ji > i && (jw || argi.effects) { // out of order evaluation + logf("binding argument %s: preceded by %s", + effectStr(argi.effects, i), effectStr(jw, ji)) + argi.substitutable = false + break + } + } + } + } + if !argi.substitutable { + for j := 0; j < i; j++ { + argj := args[j] + if argj.pure { + continue + } + if (argi.effects || argj.effects) && argj.substitutable { + logf("binding argument %s: %s is bound", + effectStr(argj.effects, j), effectStr(argi.effects, i)) + argj.substitutable = false + } + } + } + } +} + +// updateCalleeParams updates the calleeDecl syntax to remove +// substituted parameters and move the receiver (if any) to the head +// of the ordinary parameters. +func updateCalleeParams(calleeDecl *ast.FuncDecl, params []*parameter) { + // The logic is fiddly because of the three forms of ast.Field: + // + // func(int), func(x int), func(x, y int) + // + // Also, ensure that all remaining parameters are named + // to avoid a mix of named/unnamed when joining (recv, params...). + // func (T) f(int, bool) -> (_ T, _ int, _ bool) + // (Strictly, we need do this only for methods and only when + // the namednesses of Recv and Params differ; that might be tidier.) + + paramIdx := 0 // index in original parameter list (incl. receiver) + var newParams []*ast.Field + filterParams := func(field *ast.Field) { + var names []*ast.Ident + if field.Names == nil { + // Unnamed parameter field (e.g. func f(int) + if params[paramIdx] != nil { + // Give it an explicit name "_" since we will + // make the receiver (if any) a regular parameter + // and one cannot mix named and unnamed parameters. + names = append(names, makeIdent("_")) + } + paramIdx++ + } else { + // Named parameter field e.g. func f(x, y int) + // Remove substituted parameters in place. + // If all were substituted, delete field. + for _, id := range field.Names { + if pinfo := params[paramIdx]; pinfo != nil { + // Rename unreferenced parameters with "_". + // This is crucial for binding decls, since + // unlike parameters, they are subject to + // "unreferenced var" checks. + if len(pinfo.info.Refs) == 0 { + id = makeIdent("_") + } + names = append(names, id) + } + paramIdx++ + } + } + if names != nil { + newParams = append(newParams, &ast.Field{ + Names: names, + Type: field.Type, + }) + } + } + if calleeDecl.Recv != nil { + filterParams(calleeDecl.Recv.List[0]) + calleeDecl.Recv = nil + } + for _, field := range calleeDecl.Type.Params.List { + filterParams(field) + } + calleeDecl.Type.Params.List = newParams +} + +// bindingDeclInfo records information about the binding decl produced by +// createBindingDecl. +type bindingDeclInfo struct { + names map[string]bool // names bound by the binding decl; possibly empty + stmt ast.Stmt // the binding decl itself +} + +// createBindingDecl constructs a "binding decl" that implements +// parameter assignment and declares any named result variables +// referenced by the callee. It returns nil if there were no +// unsubstituted parameters. +// +// It may not always be possible to create the decl (e.g. due to +// shadowing), in which case it also returns nil; but if it succeeds, +// the declaration may be used by reduction strategies to relax the +// requirement that all parameters have been substituted. +// +// For example, a call: +// +// f(a0, a1, a2) +// +// where: +// +// func f(p0, p1 T0, p2 T1) { body } +// +// reduces to: +// +// { +// var ( +// p0, p1 T0 = a0, a1 +// p2 T1 = a2 +// ) +// body +// } +// +// so long as p0, p1 ∉ freevars(T1) or freevars(a2), and so on, +// because each spec is statically resolved in sequence and +// dynamically assigned in sequence. By contrast, all +// parameters are resolved simultaneously and assigned +// simultaneously. +// +// The pX names should already be blank ("_") if the parameter +// is unreferenced; this avoids "unreferenced local var" checks. +// +// Strategies may impose additional checks on return +// conversions, labels, defer, etc. +func createBindingDecl(logf func(string, ...any), caller *Caller, args []*argument, calleeDecl *ast.FuncDecl, results []*paramInfo) *bindingDeclInfo { + // Spread calls are tricky as they may not align with the + // parameters' field groupings nor types. + // For example, given + // func g() (int, string) + // the call + // f(g()) + // is legal with these decls of f: + // func f(int, string) + // func f(x, y any) + // func f(x, y ...any) + // TODO(adonovan): support binding decls for spread calls by + // splitting parameter groupings as needed. + if lastArg := last(args); lastArg != nil && lastArg.spread { + logf("binding decls not yet supported for spread calls") + return nil + } + + var ( + specs []ast.Spec + names = make(map[string]bool) // names defined by previous specs + ) + // shadow reports whether any name referenced by spec is + // shadowed by a name declared by a previous spec (since, + // unlike parameters, each spec of a var decl is within the + // scope of the previous specs). + shadow := func(spec *ast.ValueSpec) bool { + // Compute union of free names of type and values + // and detect shadowing. Values is the arguments + // (caller syntax), so we can use type info. + // But Type is the untyped callee syntax, + // so we have to use a syntax-only algorithm. + free := make(map[string]bool) + for _, value := range spec.Values { + for name := range freeVars(caller.Info, value) { + free[name] = true + } + } + freeishNames(free, spec.Type) + for name := range free { + if names[name] { + logf("binding decl would shadow free name %q", name) + return true + } + } + for _, id := range spec.Names { + if id.Name != "_" { + names[id.Name] = true + } + } + return false + } + + // parameters + // + // Bind parameters that were not eliminated through + // substitution. (Non-nil arguments correspond to the + // remaining parameters in calleeDecl.) + var values []ast.Expr + for _, arg := range args { + if arg != nil { + values = append(values, arg.expr) + } + } + for _, field := range calleeDecl.Type.Params.List { + // Each field (param group) becomes a ValueSpec. + spec := &ast.ValueSpec{ + Names: field.Names, + Type: field.Type, + Values: values[:len(field.Names)], + } + values = values[len(field.Names):] + if shadow(spec) { + return nil + } + specs = append(specs, spec) + } + assert(len(values) == 0, "args/params mismatch") + + // results + // + // Add specs to declare any named result + // variables that are referenced by the body. + if calleeDecl.Type.Results != nil { + resultIdx := 0 + for _, field := range calleeDecl.Type.Results.List { + if field.Names == nil { + resultIdx++ + continue // unnamed field + } + var names []*ast.Ident + for _, id := range field.Names { + if len(results[resultIdx].Refs) > 0 { + names = append(names, id) + } + resultIdx++ + } + if len(names) > 0 { + spec := &ast.ValueSpec{ + Names: names, + Type: field.Type, + } + if shadow(spec) { + return nil + } + specs = append(specs, spec) + } + } + } + + if len(specs) == 0 { + logf("binding decl not needed: all parameters substituted") + return nil + } + + stmt := &ast.DeclStmt{ + Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: specs, + }, + } + logf("binding decl: %s", debugFormatNode(caller.Fset, stmt)) + return &bindingDeclInfo{names: names, stmt: stmt} +} + +// lookup does a symbol lookup in the lexical environment of the caller. +func (caller *Caller) lookup(name string) types.Object { + pos := caller.Call.Pos() + for _, n := range caller.path { + if scope := scopeFor(caller.Info, n); scope != nil { + if _, obj := scope.LookupParent(name, pos); obj != nil { + return obj + } + } + } + return nil +} + +func scopeFor(info *types.Info, n ast.Node) *types.Scope { + // The function body scope (containing not just params) + // is associated with the function's type, not body. + switch fn := n.(type) { + case *ast.FuncDecl: + n = fn.Type + case *ast.FuncLit: + n = fn.Type + } + return info.Scopes[n] +} + +// -- predicates over expressions -- + +// freeVars returns the names of all free identifiers of e: +// those lexically referenced by it but not defined within it. +// (Fields and methods are not included.) +func freeVars(info *types.Info, e ast.Expr) map[string]bool { + free := make(map[string]bool) + ast.Inspect(e, func(n ast.Node) bool { + if id, ok := n.(*ast.Ident); ok { + // The isField check is so that we don't treat T{f: 0} as a ref to f. + if obj, ok := info.Uses[id]; ok && !within(obj.Pos(), e) && !isField(obj) { + free[obj.Name()] = true + } + } + return true + }) + return free +} + +// freeishNames computes an over-approximation to the free names +// of the type syntax t, inserting values into the map. +// +// Because we don't have go/types annotations, we can't give an exact +// result in all cases. In particular, an array type [n]T might have a +// size such as unsafe.Sizeof(func() int{stmts...}()) and now the +// precise answer depends upon all the statement syntax too. But that +// never happens in practice. +func freeishNames(free map[string]bool, t ast.Expr) { + var visit func(n ast.Node) bool + visit = func(n ast.Node) bool { + switch n := n.(type) { + case *ast.Ident: + free[n.Name] = true + + case *ast.SelectorExpr: + ast.Inspect(n.X, visit) + return false // don't visit .Sel + + case *ast.Field: + ast.Inspect(n.Type, visit) + // Don't visit .Names: + // FuncType parameters, interface methods, struct fields + return false + } + return true + } + ast.Inspect(t, visit) +} + +// effects reports whether an expression might change the state of the +// program (through function calls and channel receives) and affect +// the evaluation of subsequent expressions. +func (st *state) effects(info *types.Info, expr ast.Expr) bool { + effects := false + ast.Inspect(expr, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.FuncLit: + return false // prune descent + + case *ast.CallExpr: + if info.Types[n.Fun].IsType() { + // A conversion T(x) has only the effect of its operand. + } else if !callsPureBuiltin(info, n) { + // A handful of built-ins have no effect + // beyond those of their arguments. + // All other calls (including append, copy, recover) + // have unknown effects. + // + // As with 'pure', there is room for + // improvement by inspecting the callee. + effects = true + } + + case *ast.UnaryExpr: + if n.Op == token.ARROW { // <-ch + effects = true + } + } + return true + }) + + // Even if consideration of effects is not desired, + // we continue to compute, log, and discard them. + if st.opts.IgnoreEffects && effects { + effects = false + st.opts.Logf("ignoring potential effects of argument %s", + debugFormatNode(st.caller.Fset, expr)) + } + + return effects +} + +// pure reports whether an expression has the same result no matter +// when it is executed relative to other expressions, so it can be +// commuted with any other expression or statement without changing +// its meaning. +// +// An expression is considered impure if it reads the contents of any +// variable, with the exception of "single assignment" local variables +// (as classified by the provided callback), which are never updated +// after their initialization. +// +// Pure does not imply duplicable: for example, new(T) and T{} are +// pure expressions but both return a different value each time they +// are evaluated, so they are not safe to duplicate. +// +// Purity does not imply freedom from run-time panics. We assume that +// target programs do not encounter run-time panics nor depend on them +// for correct operation. +// +// TODO(adonovan): add unit tests of this function. +func pure(info *types.Info, assign1 func(*types.Var) bool, e ast.Expr) bool { + var pure func(e ast.Expr) bool + pure = func(e ast.Expr) bool { + switch e := e.(type) { + case *ast.ParenExpr: + return pure(e.X) + + case *ast.Ident: + if v, ok := info.Uses[e].(*types.Var); ok { + // In general variables are impure + // as they may be updated, but + // single-assignment local variables + // never change value. + // + // We assume all package-level variables + // may be updated, but for non-exported + // ones we could do better by analyzing + // the complete package. + return !isPkgLevel(v) && assign1(v) + } + + // All other kinds of reference are pure. + return true + + case *ast.FuncLit: + // A function literal may allocate a closure that + // references mutable variables, but mutation + // cannot be observed without calling the function, + // and calls are considered impure. + return true + + case *ast.BasicLit: + return true + + case *ast.UnaryExpr: // + - ! ^ & but not <- + return e.Op != token.ARROW && pure(e.X) + + case *ast.BinaryExpr: // arithmetic, shifts, comparisons, &&/|| + return pure(e.X) && pure(e.Y) + + case *ast.CallExpr: + // A conversion is as pure as its operand. + if info.Types[e.Fun].IsType() { + return pure(e.Args[0]) + } + + // Calls to some built-ins are as pure as their arguments. + if callsPureBuiltin(info, e) { + for _, arg := range e.Args { + if !pure(arg) { + return false + } + } + return true + } + + // All other calls are impure, so we can + // reject them without even looking at e.Fun. + // + // More sophisticated analysis could infer purity in + // commonly used functions such as strings.Contains; + // perhaps we could offer the client a hook so that + // go/analysis-based implementation could exploit the + // results of a purity analysis. But that would make + // the inliner's choices harder to explain. + return false + + case *ast.CompositeLit: + // T{...} is as pure as its elements. + for _, elt := range e.Elts { + if kv, ok := elt.(*ast.KeyValueExpr); ok { + if !pure(kv.Value) { + return false + } + if id, ok := kv.Key.(*ast.Ident); ok { + if v, ok := info.Uses[id].(*types.Var); ok && v.IsField() { + continue // struct {field: value} + } + } + // map/slice/array {key: value} + if !pure(kv.Key) { + return false + } + + } else if !pure(elt) { + return false + } + } + return true + + case *ast.SelectorExpr: + if seln, ok := info.Selections[e]; ok { + + // See types.SelectionKind for background. + switch seln.Kind() { + case types.MethodExpr: + // A method expression T.f acts like a + // reference to a func decl, so it is pure. + return true + + case types.MethodVal, types.FieldVal: + // A field or method selection x.f is pure + // if x is pure and the selection does + // not indirect a pointer. + return !indirectSelection(seln) && pure(e.X) + + default: + panic(seln) + } + } else { + // A qualified identifier is + // treated like an unqualified one. + return pure(e.Sel) + } + + case *ast.StarExpr: + return false // *ptr depends on the state of the heap + + default: + return false + } + } + return pure(e) +} + +// callsPureBuiltin reports whether call is a call of a built-in +// function that is a pure computation over its operands (analogous to +// a + operator). Because it does not depend on program state, it may +// be evaluated at any point--though not necessarily at multiple +// points (consider new, make). +func callsPureBuiltin(info *types.Info, call *ast.CallExpr) bool { + if id, ok := ast.Unparen(call.Fun).(*ast.Ident); ok { + if b, ok := info.ObjectOf(id).(*types.Builtin); ok { + switch b.Name() { + case "len", "cap", "complex", "imag", "real", "make", "new", "max", "min": + return true + } + // Not: append clear close copy delete panic print println recover + } + } + return false +} + +// duplicable reports whether it is appropriate for the expression to +// be freely duplicated. +// +// Given the declaration +// +// func f(x T) T { return x + g() + x } +// +// an argument y is considered duplicable if we would wish to see a +// call f(y) simplified to y+g()+y. This is true for identifiers, +// integer literals, unary negation, and selectors x.f where x is not +// a pointer. But we would not wish to duplicate expressions that: +// - have side effects (e.g. nearly all calls), +// - are not referentially transparent (e.g. &T{}, ptr.field, *ptr), or +// - are long (e.g. "huge string literal"). +func duplicable(info *types.Info, e ast.Expr) bool { + switch e := e.(type) { + case *ast.ParenExpr: + return duplicable(info, e.X) + + case *ast.Ident: + return true + + case *ast.BasicLit: + v := info.Types[e].Value + switch e.Kind { + case token.INT: + return true // any int + case token.STRING: + return consteq(v, kZeroString) // only "" + case token.FLOAT: + return consteq(v, kZeroFloat) || consteq(v, kOneFloat) // only 0.0 or 1.0 + } + + case *ast.UnaryExpr: // e.g. +1, -1 + return (e.Op == token.ADD || e.Op == token.SUB) && duplicable(info, e.X) + + case *ast.CompositeLit: + // Empty struct or array literals T{} are duplicable. + // (Non-empty literals are too verbose, and slice/map + // literals allocate indirect variables.) + if len(e.Elts) == 0 { + switch info.TypeOf(e).Underlying().(type) { + case *types.Struct, *types.Array: + return true + } + } + return false + + case *ast.CallExpr: + // Treat type conversions as duplicable if they do not observably allocate. + // The only cases of observable allocations are + // the `[]byte(string)` and `[]rune(string)` conversions. + // + // Duplicating string([]byte) conversions increases + // allocation but doesn't change behavior, but the + // reverse, []byte(string), allocates a distinct array, + // which is observable. + + if !info.Types[e.Fun].IsType() { // check whether e.Fun is a type conversion + return false + } + + fun := info.TypeOf(e.Fun) + arg := info.TypeOf(e.Args[0]) + + switch fun := fun.Underlying().(type) { + case *types.Slice: + // Do not mark []byte(string) and []rune(string) as duplicable. + elem, ok := fun.Elem().Underlying().(*types.Basic) + if ok && (elem.Kind() == types.Rune || elem.Kind() == types.Byte) { + from, ok := arg.Underlying().(*types.Basic) + isString := ok && from.Info()&types.IsString != 0 + return !isString + } + case *types.TypeParam: + return false // be conservative + } + return true + + case *ast.SelectorExpr: + if seln, ok := info.Selections[e]; ok { + // A field or method selection x.f is referentially + // transparent if it does not indirect a pointer. + return !indirectSelection(seln) + } + // A qualified identifier pkg.Name is referentially transparent. + return true + } + return false +} + +func consteq(x, y constant.Value) bool { + return constant.Compare(x, token.EQL, y) +} + +var ( + kZeroInt = constant.MakeInt64(0) + kZeroString = constant.MakeString("") + kZeroFloat = constant.MakeFloat64(0.0) + kOneFloat = constant.MakeFloat64(1.0) +) + +// -- inline helpers -- + +func assert(cond bool, msg string) { + if !cond { + panic(msg) + } +} + +// blanks returns a slice of n > 0 blank identifiers. +func blanks[E ast.Expr](n int) []E { + if n == 0 { + panic("blanks(0)") + } + res := make([]E, n) + for i := range res { + res[i] = ast.Expr(makeIdent("_")).(E) // ugh + } + return res +} + +func makeIdent(name string) *ast.Ident { + return &ast.Ident{Name: name} +} + +// importedPkgName returns the PkgName object declared by an ImportSpec. +// TODO(adonovan): make this a method of types.Info (#62037). +func importedPkgName(info *types.Info, imp *ast.ImportSpec) (*types.PkgName, bool) { + var obj types.Object + if imp.Name != nil { + obj = info.Defs[imp.Name] + } else { + obj = info.Implicits[imp] + } + pkgname, ok := obj.(*types.PkgName) + return pkgname, ok +} + +func isPkgLevel(obj types.Object) bool { + // TODO(adonovan): consider using the simpler obj.Parent() == + // obj.Pkg().Scope() instead. But be sure to test carefully + // with instantiations of generics. + return obj.Pkg().Scope().Lookup(obj.Name()) == obj +} + +// callContext returns the two nodes immediately enclosing the call +// (specified as a PathEnclosingInterval), ignoring parens. +func callContext(callPath []ast.Node) (parent, grandparent ast.Node) { + _ = callPath[0].(*ast.CallExpr) // sanity check + for _, n := range callPath[1:] { + if !is[*ast.ParenExpr](n) { + if parent == nil { + parent = n + } else { + return parent, n + } + } + } + return parent, nil +} + +// hasLabelConflict reports whether the set of labels of the function +// enclosing the call (specified as a PathEnclosingInterval) +// intersects with the set of callee labels. +func hasLabelConflict(callPath []ast.Node, calleeLabels []string) bool { + labels := callerLabels(callPath) + for _, label := range calleeLabels { + if labels[label] { + return true // conflict + } + } + return false +} + +// callerLabels returns the set of control labels in the function (if +// any) enclosing the call (specified as a PathEnclosingInterval). +func callerLabels(callPath []ast.Node) map[string]bool { + var callerBody *ast.BlockStmt + switch f := callerFunc(callPath).(type) { + case *ast.FuncDecl: + callerBody = f.Body + case *ast.FuncLit: + callerBody = f.Body + } + var labels map[string]bool + if callerBody != nil { + ast.Inspect(callerBody, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.FuncLit: + return false // prune traversal + case *ast.LabeledStmt: + if labels == nil { + labels = make(map[string]bool) + } + labels[n.Label.Name] = true + } + return true + }) + } + return labels +} + +// callerFunc returns the innermost Func{Decl,Lit} node enclosing the +// call (specified as a PathEnclosingInterval). +func callerFunc(callPath []ast.Node) ast.Node { + _ = callPath[0].(*ast.CallExpr) // sanity check + for _, n := range callPath[1:] { + if is[*ast.FuncDecl](n) || is[*ast.FuncLit](n) { + return n + } + } + return nil +} + +// callStmt reports whether the function call (specified +// as a PathEnclosingInterval) appears within an ExprStmt, +// and returns it if so. +// +// If unrestricted, callStmt returns nil if the ExprStmt f() appears +// in a restricted context (such as "if f(); cond {") where it cannot +// be replaced by an arbitrary statement. (See "statement theory".) +func callStmt(callPath []ast.Node, unrestricted bool) *ast.ExprStmt { + parent, _ := callContext(callPath) + stmt, ok := parent.(*ast.ExprStmt) + if ok && unrestricted { + switch callPath[nodeIndex(callPath, stmt)+1].(type) { + case *ast.LabeledStmt, + *ast.BlockStmt, + *ast.CaseClause, + *ast.CommClause: + // unrestricted + default: + // TODO(adonovan): handle restricted + // XYZStmt.Init contexts (but not ForStmt.Post) + // by creating a block around the if/for/switch: + // "if f(); cond {" -> "{ stmts; if cond {" + + return nil // restricted + } + } + return stmt +} + +// Statement theory +// +// These are all the places a statement may appear in the AST: +// +// LabeledStmt.Stmt Stmt -- any +// BlockStmt.List []Stmt -- any (but see switch/select) +// IfStmt.Init Stmt? -- simple +// IfStmt.Body BlockStmt +// IfStmt.Else Stmt? -- IfStmt or BlockStmt +// CaseClause.Body []Stmt -- any +// SwitchStmt.Init Stmt? -- simple +// SwitchStmt.Body BlockStmt -- CaseClauses only +// TypeSwitchStmt.Init Stmt? -- simple +// TypeSwitchStmt.Assign Stmt -- AssignStmt(TypeAssertExpr) or ExprStmt(TypeAssertExpr) +// TypeSwitchStmt.Body BlockStmt -- CaseClauses only +// CommClause.Comm Stmt? -- SendStmt or ExprStmt(UnaryExpr) or AssignStmt(UnaryExpr) +// CommClause.Body []Stmt -- any +// SelectStmt.Body BlockStmt -- CommClauses only +// ForStmt.Init Stmt? -- simple +// ForStmt.Post Stmt? -- simple +// ForStmt.Body BlockStmt +// RangeStmt.Body BlockStmt +// +// simple = AssignStmt | SendStmt | IncDecStmt | ExprStmt. +// +// A BlockStmt cannot replace an ExprStmt in +// {If,Switch,TypeSwitch}Stmt.Init or ForStmt.Post. +// That is allowed only within: +// LabeledStmt.Stmt Stmt +// BlockStmt.List []Stmt +// CaseClause.Body []Stmt +// CommClause.Body []Stmt + +// replaceNode performs a destructive update of the tree rooted at +// root, replacing each occurrence of "from" with "to". If to is nil and +// the element is within a slice, the slice element is removed. +// +// The root itself cannot be replaced; an attempt will panic. +// +// This function must not be called on the caller's syntax tree. +// +// TODO(adonovan): polish this up and move it to astutil package. +// TODO(adonovan): needs a unit test. +func replaceNode(root ast.Node, from, to ast.Node) { + if from == nil { + panic("from == nil") + } + if reflect.ValueOf(from).IsNil() { + panic(fmt.Sprintf("from == (%T)(nil)", from)) + } + if from == root { + panic("from == root") + } + found := false + var parent reflect.Value // parent variable of interface type, containing a pointer + var visit func(reflect.Value) + visit = func(v reflect.Value) { + switch v.Kind() { + case reflect.Ptr: + if v.Interface() == from { + found = true + + // If v is a struct field or array element + // (e.g. Field.Comment or Field.Names[i]) + // then it is addressable (a pointer variable). + // + // But if it was the value an interface + // (e.g. *ast.Ident within ast.Node) + // then it is non-addressable, and we need + // to set the enclosing interface (parent). + if !v.CanAddr() { + v = parent + } + + // to=nil => use zero value + var toV reflect.Value + if to != nil { + toV = reflect.ValueOf(to) + } else { + toV = reflect.Zero(v.Type()) // e.g. ast.Expr(nil) + } + v.Set(toV) + + } else if !v.IsNil() { + switch v.Interface().(type) { + case *ast.Object, *ast.Scope: + // Skip fields of types potentially involved in cycles. + default: + visit(v.Elem()) + } + } + + case reflect.Struct: + for i := 0; i < v.Type().NumField(); i++ { + visit(v.Field(i)) + } + + case reflect.Slice: + compact := false + for i := 0; i < v.Len(); i++ { + visit(v.Index(i)) + if v.Index(i).IsNil() { + compact = true + } + } + if compact { + // Elements were deleted. Eliminate nils. + // (Do this is a second pass to avoid + // unnecessary writes in the common case.) + j := 0 + for i := 0; i < v.Len(); i++ { + if !v.Index(i).IsNil() { + v.Index(j).Set(v.Index(i)) + j++ + } + } + v.SetLen(j) + } + case reflect.Interface: + parent = v + visit(v.Elem()) + + case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer: + panic(v) // unreachable in AST + default: + // bool, string, number: nop + } + parent = reflect.Value{} + } + visit(reflect.ValueOf(root)) + if !found { + panic(fmt.Sprintf("%T not found", from)) + } +} + +// clearPositions destroys token.Pos information within the tree rooted at root, +// as positions in callee trees may cause caller comments to be emitted prematurely. +// +// In general it isn't safe to clear a valid Pos because some of them +// (e.g. CallExpr.Ellipsis, TypeSpec.Assign) are significant to +// go/printer, so this function sets each non-zero Pos to 1, which +// suffices to avoid advancing the printer's comment cursor. +// +// This function mutates its argument; do not invoke on caller syntax. +// +// TODO(adonovan): remove this horrendous workaround when #20744 is finally fixed. +func clearPositions(root ast.Node) { + posType := reflect.TypeOf(token.NoPos) + ast.Inspect(root, func(n ast.Node) bool { + if n != nil { + v := reflect.ValueOf(n).Elem() // deref the pointer to struct + fields := v.Type().NumField() + for i := 0; i < fields; i++ { + f := v.Field(i) + // Clearing Pos arbitrarily is destructive, + // as its presence may be semantically significant + // (e.g. CallExpr.Ellipsis, TypeSpec.Assign) + // or affect formatting preferences (e.g. GenDecl.Lparen). + // + // Note: for proper formatting, it may be necessary to be selective + // about which positions we set to 1 vs which we set to token.NoPos. + // (e.g. we can set most to token.NoPos, save the few that are + // significant). + if f.Type() == posType { + if f.Interface() != token.NoPos { + f.Set(reflect.ValueOf(token.Pos(1))) + } + } + } + } + return true + }) +} + +// findIdent returns the Ident beneath root that has the given pos. +func findIdent(root ast.Node, pos token.Pos) *ast.Ident { + // TODO(adonovan): opt: skip subtrees that don't contain pos. + var found *ast.Ident + ast.Inspect(root, func(n ast.Node) bool { + if found != nil { + return false + } + if id, ok := n.(*ast.Ident); ok { + if id.Pos() == pos { + found = id + } + } + return true + }) + if found == nil { + panic(fmt.Sprintf("findIdent %d not found in %s", + pos, debugFormatNode(token.NewFileSet(), root))) + } + return found +} + +func prepend[T any](elem T, slice ...T) []T { + return append([]T{elem}, slice...) +} + +// debugFormatNode formats a node or returns a formatting error. +// Its sloppy treatment of errors is appropriate only for logging. +func debugFormatNode(fset *token.FileSet, n ast.Node) string { + var out strings.Builder + if err := format.Node(&out, fset, n); err != nil { + out.WriteString(err.Error()) + } + return out.String() +} + +func shallowCopy[T any](ptr *T) *T { + copy := *ptr + return © +} + +// ∀ +func forall[T any](list []T, f func(i int, x T) bool) bool { + for i, x := range list { + if !f(i, x) { + return false + } + } + return true +} + +// ∃ +func exists[T any](list []T, f func(i int, x T) bool) bool { + for i, x := range list { + if f(i, x) { + return true + } + } + return false +} + +// last returns the last element of a slice, or zero if empty. +func last[T any](slice []T) T { + n := len(slice) + if n > 0 { + return slice[n-1] + } + return *new(T) +} + +// canImport reports whether one package is allowed to import another. +// +// TODO(adonovan): allow customization of the accessibility relation +// (e.g. for Bazel). +func canImport(from, to string) bool { + // TODO(adonovan): better segment hygiene. + if strings.HasPrefix(to, "internal/") { + // Special case: only std packages may import internal/... + // We can't reliably know whether we're in std, so we + // use a heuristic on the first segment. + first, _, _ := strings.Cut(from, "/") + if strings.Contains(first, ".") { + return false // example.com/foo ∉ std + } + if first == "testdata" { + return false // testdata/foo ∉ std + } + } + if i := strings.LastIndex(to, "/internal/"); i >= 0 { + return strings.HasPrefix(from, to[:i]) + } + return true +} + +// consistentOffsets reports whether the portion of caller.Content +// that corresponds to caller.Call can be parsed as a call expression. +// If not, the client has provided inconsistent information, possibly +// because they forgot to ignore line directives when computing the +// filename enclosing the call. +// This is just a heuristic. +func consistentOffsets(caller *Caller) bool { + start := offsetOf(caller.Fset, caller.Call.Pos()) + end := offsetOf(caller.Fset, caller.Call.End()) + if !(0 < start && start < end && end <= len(caller.Content)) { + return false + } + expr, err := parser.ParseExpr(string(caller.Content[start:end])) + if err != nil { + return false + } + return is[*ast.CallExpr](expr) +} + +// needsParens reports whether parens are required to avoid ambiguity +// around the new node replacing the specified old node (which is some +// ancestor of the CallExpr identified by its PathEnclosingInterval). +func needsParens(callPath []ast.Node, old, new ast.Node) bool { + // Find enclosing old node and its parent. + i := nodeIndex(callPath, old) + if i == -1 { + panic("not found") + } + + // There is no precedence ambiguity when replacing + // (e.g.) a statement enclosing the call. + if !is[ast.Expr](old) { + return false + } + + // An expression beneath a non-expression + // has no precedence ambiguity. + parent, ok := callPath[i+1].(ast.Expr) + if !ok { + return false + } + + precedence := func(n ast.Node) int { + switch n := n.(type) { + case *ast.UnaryExpr, *ast.StarExpr: + return token.UnaryPrec + case *ast.BinaryExpr: + return n.Op.Precedence() + } + return -1 + } + + // Parens are not required if the new node + // is not unary or binary. + newprec := precedence(new) + if newprec < 0 { + return false + } + + // Parens are required if parent and child are both + // unary or binary and the parent has higher precedence. + if precedence(parent) > newprec { + return true + } + + // Was the old node the operand of a postfix operator? + // f().sel + // f()[i:j] + // f()[i] + // f().(T) + // f()(x) + switch parent := parent.(type) { + case *ast.SelectorExpr: + return parent.X == old + case *ast.IndexExpr: + return parent.X == old + case *ast.SliceExpr: + return parent.X == old + case *ast.TypeAssertExpr: + return parent.X == old + case *ast.CallExpr: + return parent.Fun == old + } + return false +} + +func nodeIndex(nodes []ast.Node, n ast.Node) int { + // TODO(adonovan): Use index[ast.Node]() in go1.20. + for i, node := range nodes { + if node == n { + return i + } + } + return -1 +} + +// declares returns the set of lexical names declared by a +// sequence of statements from the same block, excluding sub-blocks. +// (Lexical names do not include control labels.) +func declares(stmts []ast.Stmt) map[string]bool { + names := make(map[string]bool) + for _, stmt := range stmts { + switch stmt := stmt.(type) { + case *ast.DeclStmt: + for _, spec := range stmt.Decl.(*ast.GenDecl).Specs { + switch spec := spec.(type) { + case *ast.ValueSpec: + for _, id := range spec.Names { + names[id.Name] = true + } + case *ast.TypeSpec: + names[spec.Name.Name] = true + } + } + + case *ast.AssignStmt: + if stmt.Tok == token.DEFINE { + for _, lhs := range stmt.Lhs { + names[lhs.(*ast.Ident).Name] = true + } + } + } + } + delete(names, "_") + return names +} + +// assignStmts rewrites a statement assigning the results of a call into zero +// or more statements that assign its return operands, or (nil, false) if no +// such rewrite is possible. The set of bindings created by the result of +// assignStmts is the same as the set of bindings created by the callerStmt. +// +// The callee must contain exactly one return statement. +// +// This is (once again) a surprisingly complex task. For example, depending on +// types and existing bindings, the assignment +// +// a, b := f() +// +// could be rewritten as: +// +// a, b := 1, 2 +// +// but may need to be written as: +// +// a, b := int8(1), int32(2) +// +// In the case where the return statement within f is a spread call to another +// function g(), we cannot explicitly convert the return values inline, and so +// it may be necessary to split the declaration and assignment of variables +// into separate statements: +// +// a, b := g() +// +// or +// +// var a int32 +// a, b = g() +// +// or +// +// var ( +// a int8 +// b int32 +// ) +// a, b = g() +// +// Note: assignStmts may return (nil, true) if it determines that the rewritten +// assignment consists only of _ = nil assignments. +func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Expr) ([]ast.Stmt, bool) { + logf, caller, callee := st.opts.Logf, st.caller, &st.callee.impl + + assert(len(callee.Returns) == 1, "unexpected multiple returns") + resultInfo := callee.Returns[0] + + // When constructing assign statements, we need to make sure that we don't + // modify types on the left-hand side, such as would happen if the type of a + // RHS expression does not match the corresponding LHS type at the caller + // (due to untyped conversion or interface widening). + // + // This turns out to be remarkably tricky to handle correctly. + // + // Substrategies below are labeled as `Substrategy <name>:`. + + // Collect LHS information. + var ( + lhs []ast.Expr // shallow copy of the LHS slice, for mutation + defs = make([]*ast.Ident, len(callerStmt.Lhs)) // indexes in lhs of defining identifiers + blanks = make([]bool, len(callerStmt.Lhs)) // indexes in lhs of blank identifiers + byType typeutil.Map // map of distinct types -> indexes, for writing specs later + ) + for i, expr := range callerStmt.Lhs { + lhs = append(lhs, expr) + if name, ok := expr.(*ast.Ident); ok { + if name.Name == "_" { + blanks[i] = true + continue // no type + } + + if obj, isDef := caller.Info.Defs[name]; isDef { + defs[i] = name + typ := obj.Type() + idxs, _ := byType.At(typ).([]int) + idxs = append(idxs, i) + byType.Set(typ, idxs) + } + } + } + + // Collect RHS information + // + // The RHS is either a parallel assignment or spread assignment, but by + // looping over both callerStmt.Rhs and returnOperands we handle both. + var ( + rhs []ast.Expr // new RHS of assignment, owned by the inliner + callIdx = -1 // index of the call among the original RHS + nilBlankAssigns = make(map[int]unit) // indexes in rhs of _ = nil assignments, which can be deleted + freeNames = make(map[string]bool) // free(ish) names among rhs expressions + nonTrivial = make(map[int]bool) // indexes in rhs of nontrivial result conversions + ) + for i, expr := range callerStmt.Rhs { + if expr == caller.Call { + assert(callIdx == -1, "malformed (duplicative) AST") + callIdx = i + for j, returnOperand := range returnOperands { + freeishNames(freeNames, returnOperand) + rhs = append(rhs, returnOperand) + if resultInfo[j]&nonTrivialResult != 0 { + nonTrivial[i+j] = true + } + if blanks[i+j] && resultInfo[j]&untypedNilResult != 0 { + nilBlankAssigns[i+j] = unit{} + } + } + } else { + // We must clone before clearing positions, since e came from the caller. + expr = internalastutil.CloneNode(expr) + clearPositions(expr) + freeishNames(freeNames, expr) + rhs = append(rhs, expr) + } + } + assert(callIdx >= 0, "failed to find call in RHS") + + // Substrategy "splice": Check to see if we can simply splice in the result + // expressions from the callee, such as simplifying + // + // x, y := f() + // + // to + // + // x, y := e1, e2 + // + // where the types of x and y match the types of e1 and e2. + // + // This works as long as we don't need to write any additional type + // information. + if callerStmt.Tok == token.ASSIGN && // LHS types already determined before call + len(nonTrivial) == 0 { // no non-trivial conversions to worry about + + logf("substrategy: slice assignment") + return []ast.Stmt{&ast.AssignStmt{ + Lhs: lhs, + Tok: callerStmt.Tok, + TokPos: callerStmt.TokPos, + Rhs: rhs, + }}, true + } + + // Inlining techniques below will need to write type information in order to + // preserve the correct types of LHS identifiers. + // + // writeType is a simple helper to write out type expressions. + // TODO(rfindley): + // 1. handle qualified type names (potentially adding new imports) + // 2. expand this to handle more type expressions. + // 3. refactor to share logic with callee rewriting. + universeAny := types.Universe.Lookup("any") + typeExpr := func(typ types.Type, shadows ...map[string]bool) ast.Expr { + var typeName string + switch typ := typ.(type) { + case *types.Basic: + typeName = typ.Name() + case interface{ Obj() *types.TypeName }: // Named, Alias, TypeParam + typeName = typ.Obj().Name() + } + + // Special case: check for universe "any". + // TODO(golang/go#66921): this may become unnecessary if any becomes a proper alias. + if typ == universeAny.Type() { + typeName = "any" + } + + if typeName == "" { + return nil + } + + for _, shadow := range shadows { + if shadow[typeName] { + logf("cannot write shadowed type name %q", typeName) + return nil + } + } + obj, _ := caller.lookup(typeName).(*types.TypeName) + if obj != nil && types.Identical(obj.Type(), typ) { + return ast.NewIdent(typeName) + } + return nil + } + + // Substrategy "spread": in the case of a spread call (func f() (T1, T2) return + // g()), since we didn't hit the 'splice' substrategy, there must be some + // non-declaring expression on the LHS. Simplify this by pre-declaring + // variables, rewriting + // + // x, y := f() + // + // to + // + // var x int + // x, y = g() + // + // Which works as long as the predeclared variables do not overlap with free + // names on the RHS. + if len(rhs) != len(lhs) { + assert(len(rhs) == 1 && len(returnOperands) == 1, "expected spread call") + + for _, id := range defs { + if id != nil && freeNames[id.Name] { + // By predeclaring variables, we're changing them to be in scope of the + // RHS. We can't do this if their names are free on the RHS. + return nil, false + } + } + + // Write out the specs, being careful to avoid shadowing free names in + // their type expressions. + var ( + specs []ast.Spec + specIdxs []int + shadow = make(map[string]bool) + ) + failed := false + byType.Iterate(func(typ types.Type, v any) { + if failed { + return + } + idxs := v.([]int) + specIdxs = append(specIdxs, idxs[0]) + texpr := typeExpr(typ, shadow) + if texpr == nil { + failed = true + return + } + spec := &ast.ValueSpec{ + Type: texpr, + } + for _, idx := range idxs { + spec.Names = append(spec.Names, ast.NewIdent(defs[idx].Name)) + } + specs = append(specs, spec) + }) + if failed { + return nil, false + } + logf("substrategy: spread assignment") + return []ast.Stmt{ + &ast.DeclStmt{ + Decl: &ast.GenDecl{ + Tok: token.VAR, + Specs: specs, + }, + }, + &ast.AssignStmt{ + Lhs: callerStmt.Lhs, + Tok: token.ASSIGN, + Rhs: returnOperands, + }, + }, true + } + + assert(len(lhs) == len(rhs), "mismatching LHS and RHS") + + // Substrategy "convert": write out RHS expressions with explicit type conversions + // as necessary, rewriting + // + // x, y := f() + // + // to + // + // x, y := 1, int32(2) + // + // As required to preserve types. + // + // In the special case of _ = nil, which is disallowed by the type checker + // (since nil has no default type), we delete the assignment. + var origIdxs []int // maps back to original indexes after lhs and rhs are pruned + i := 0 + for j := range lhs { + if _, ok := nilBlankAssigns[j]; !ok { + lhs[i] = lhs[j] + rhs[i] = rhs[j] + origIdxs = append(origIdxs, j) + i++ + } + } + lhs = lhs[:i] + rhs = rhs[:i] + + if len(lhs) == 0 { + logf("trivial assignment after pruning nil blanks assigns") + // After pruning, we have no remaining assignments. + // Signal this by returning a non-nil slice of statements. + return nil, true + } + + // Write out explicit conversions as necessary. + // + // A conversion is necessary if the LHS is being defined, and the RHS return + // involved a nontrivial implicit conversion. + for i, expr := range rhs { + idx := origIdxs[i] + if nonTrivial[idx] && defs[idx] != nil { + typ := caller.Info.TypeOf(lhs[i]) + texpr := typeExpr(typ) + if texpr == nil { + return nil, false + } + if _, ok := texpr.(*ast.StarExpr); ok { + // TODO(rfindley): is this necessary? Doesn't the formatter add these parens? + texpr = &ast.ParenExpr{X: texpr} // *T -> (*T) so that (*T)(x) is valid + } + rhs[i] = &ast.CallExpr{ + Fun: texpr, + Args: []ast.Expr{expr}, + } + } + } + logf("substrategy: convert assignment") + return []ast.Stmt{&ast.AssignStmt{ + Lhs: lhs, + Tok: callerStmt.Tok, + Rhs: rhs, + }}, true +} + +// tailCallSafeReturn reports whether the callee's return statements may be safely +// used to return from the function enclosing the caller (which must exist). +func tailCallSafeReturn(caller *Caller, calleeSymbol *types.Func, callee *gobCallee) bool { + // It is safe if all callee returns involve only trivial conversions. + if !hasNonTrivialReturn(callee.Returns) { + return true + } + + var callerType types.Type + // Find type of innermost function enclosing call. + // (Beware: Caller.enclosingFunc is the outermost.) +loop: + for _, n := range caller.path { + switch f := n.(type) { + case *ast.FuncDecl: + callerType = caller.Info.ObjectOf(f.Name).Type() + break loop + case *ast.FuncLit: + callerType = caller.Info.TypeOf(f) + break loop + } + } + + // Non-trivial return conversions in the callee are permitted + // if the same non-trivial conversion would occur after inlining, + // i.e. if the caller and callee results tuples are identical. + callerResults := callerType.(*types.Signature).Results() + calleeResults := calleeSymbol.Type().(*types.Signature).Results() + return types.Identical(callerResults, calleeResults) +} + +// hasNonTrivialReturn reports whether any of the returns involve a nontrivial +// implicit conversion of a result expression. +func hasNonTrivialReturn(returnInfo [][]returnOperandFlags) bool { + for _, resultInfo := range returnInfo { + for _, r := range resultInfo { + if r&nonTrivialResult != 0 { + return true + } + } + } + return false +} + +// soleUse returns the ident that refers to obj, if there is exactly one. +func soleUse(info *types.Info, obj types.Object) (sole *ast.Ident) { + // This is not efficient, but it is called infrequently. + for id, obj2 := range info.Uses { + if obj2 == obj { + if sole != nil { + return nil // not unique + } + sole = id + } + } + return sole +} + +type unit struct{} // for representing sets as maps + +// slicesDeleteFunc removes any elements from s for which del returns true, +// returning the modified slice. +// slicesDeleteFunc zeroes the elements between the new length and the original length. +// TODO(adonovan): use go1.21 slices.DeleteFunc +func slicesDeleteFunc[S ~[]E, E any](s S, del func(E) bool) S { + i := slicesIndexFunc(s, del) + if i == -1 { + return s + } + // Don't start copying elements until we find one to delete. + for j := i + 1; j < len(s); j++ { + if v := s[j]; !del(v) { + s[i] = v + i++ + } + } + // clear(s[i:]) // zero/nil out the obsolete elements, for GC + return s[:i] +} + +// slicesIndexFunc returns the first index i satisfying f(s[i]), +// or -1 if none do. +func slicesIndexFunc[S ~[]E, E any](s S, f func(E) bool) int { + for i := range s { + if f(s[i]) { + return i + } + } + return -1 +} diff --git a/contribs/gnopls/internal/refactor/inline/inline_test.go b/contribs/gnopls/internal/refactor/inline/inline_test.go new file mode 100644 index 00000000000..69c6229af4d --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/inline_test.go @@ -0,0 +1,1763 @@ +// 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 inline_test + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "encoding/gob" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "os" + "path/filepath" + "reflect" + "regexp" + "strings" + "testing" + "unsafe" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/expect" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/types/typeutil" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/refactor/inline" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "golang.org/x/tools/txtar" +) + +// TestData executes test scenarios specified by files in testdata/*.txtar. +func TestData(t *testing.T) { + testenv.NeedsGoPackages(t) + + files, err := filepath.Glob("testdata/*.txtar") + if err != nil { + t.Fatal(err) + } + for _, file := range files { + file := file + t.Run(filepath.Base(file), func(t *testing.T) { + t.Parallel() + + // The few tests that use cgo should be in + // files whose name includes "cgo". + if strings.Contains(t.Name(), "cgo") { + testenv.NeedsTool(t, "cgo") + } + + // Extract archive to temporary tree. + ar, err := txtar.ParseFile(file) + if err != nil { + t.Fatal(err) + } + dir := t.TempDir() + if err := extractTxtar(ar, dir); err != nil { + t.Fatal(err) + } + + // Load packages. + cfg := &packages.Config{ + Dir: dir, + Mode: packages.LoadAllSyntax, + Env: append(os.Environ(), + "GO111MODULES=on", + "GOPATH=", + "GOWORK=off", + "GOPROXY=off"), + } + pkgs, err := packages.Load(cfg, "./...") + if err != nil { + t.Errorf("Load: %v", err) + } + // Report parse/type errors; they may be benign. + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + for _, err := range pkg.Errors { + t.Log(err) + } + }) + + // Process @inline notes in comments in initial packages. + for _, pkg := range pkgs { + for _, file := range pkg.Syntax { + // Read file content (for @inline regexp, and inliner). + content, err := os.ReadFile(pkg.Fset.File(file.Pos()).Name()) + if err != nil { + t.Error(err) + continue + } + + // Read and process @inline notes. + notes, err := expect.ExtractGo(pkg.Fset, file) + if err != nil { + t.Errorf("parsing notes in %q: %v", pkg.Fset.File(file.Pos()).Name(), err) + continue + } + for _, note := range notes { + posn := pkg.Fset.PositionFor(note.Pos, false) + if note.Name != "inline" { + t.Errorf("%s: invalid marker @%s", posn, note.Name) + continue + } + if nargs := len(note.Args); nargs != 2 { + t.Errorf("@inline: want 2 args, got %d", nargs) + continue + } + pattern, ok := note.Args[0].(*regexp.Regexp) + if !ok { + t.Errorf("%s: @inline(rx, want): want regular expression rx", posn) + continue + } + + // want is a []byte (success) or *Regexp (failure) + var want any + switch x := note.Args[1].(type) { + case string, expect.Identifier: + for _, file := range ar.Files { + if file.Name == fmt.Sprint(x) { + want = file.Data + break + } + } + if want == nil { + t.Errorf("%s: @inline(rx, want): archive entry %q not found", posn, x) + continue + } + case *regexp.Regexp: + want = x + default: + t.Errorf("%s: @inline(rx, want): want file name (to assert success) or error message regexp (to assert failure)", posn) + continue + } + if err := doInlineNote(t.Logf, pkg, file, content, pattern, posn, want); err != nil { + t.Errorf("%s: @inline(%v, %v): %v", posn, note.Args[0], note.Args[1], err) + continue + } + } + } + } + }) + } +} + +// doInlineNote executes an assertion specified by a single +// @inline(re"pattern", want) note in a comment. It finds the first +// match of regular expression 'pattern' on the same line, finds the +// innermost enclosing CallExpr, and inlines it. +// +// Finally it checks that, on success, the transformed file is equal +// to want (a []byte), or on failure that the error message matches +// want (a *Regexp). +func doInlineNote(logf func(string, ...any), pkg *packages.Package, file *ast.File, content []byte, pattern *regexp.Regexp, posn token.Position, want any) error { + // Find extent of pattern match within commented line. + var startPos, endPos token.Pos + { + tokFile := pkg.Fset.File(file.Pos()) + lineStartOffset := int(tokFile.LineStart(posn.Line)) - tokFile.Base() + line := content[lineStartOffset:] + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line = line[:i] + } + matches := pattern.FindSubmatchIndex(line) + var start, end int // offsets + switch len(matches) { + case 2: + // no subgroups: return the range of the regexp expression + start, end = matches[0], matches[1] + case 4: + // one subgroup: return its range + start, end = matches[2], matches[3] + default: + return fmt.Errorf("invalid location regexp %q: expect either 0 or 1 subgroups, got %d", + pattern, len(matches)/2-1) + } + startPos = tokFile.Pos(lineStartOffset + start) + endPos = tokFile.Pos(lineStartOffset + end) + } + + // Find innermost call enclosing the pattern match. + var caller *inline.Caller + { + path, _ := astutil.PathEnclosingInterval(file, startPos, endPos) + for _, n := range path { + if call, ok := n.(*ast.CallExpr); ok { + caller = &inline.Caller{ + Fset: pkg.Fset, + Types: pkg.Types, + Info: pkg.TypesInfo, + File: file, + Call: call, + Content: content, + } + break + } + } + if caller == nil { + return fmt.Errorf("no enclosing call") + } + } + + // Is it a static function call? + fn := typeutil.StaticCallee(caller.Info, caller.Call) + if fn == nil { + return fmt.Errorf("cannot inline: not a static call") + } + + // Find callee function. + var calleePkg *packages.Package + { + // Is the call within the package? + if fn.Pkg() == caller.Types { + calleePkg = pkg // same as caller + } else { + // Different package. Load it now. + // (The primary load loaded all dependencies, + // but we choose to load it again, with + // a distinct token.FileSet and types.Importer, + // to keep the implementation honest.) + cfg := &packages.Config{ + // TODO(adonovan): get the original module root more cleanly + Dir: filepath.Dir(filepath.Dir(pkg.GoFiles[0])), + Fset: token.NewFileSet(), + Mode: packages.LoadSyntax, + } + roots, err := packages.Load(cfg, fn.Pkg().Path()) + if err != nil { + return fmt.Errorf("loading callee package: %v", err) + } + if packages.PrintErrors(roots) > 0 { + return fmt.Errorf("callee package had errors") // (see log) + } + calleePkg = roots[0] + } + } + + calleeDecl, err := findFuncByPosition(calleePkg, caller.Fset.PositionFor(fn.Pos(), false)) + if err != nil { + return err + } + + // Do the inlining. For the purposes of the test, + // AnalyzeCallee and Inline are a single operation. + res, err := func() (*inline.Result, error) { + filename := calleePkg.Fset.File(calleeDecl.Pos()).Name() + content, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + callee, err := inline.AnalyzeCallee( + logf, + calleePkg.Fset, + calleePkg.Types, + calleePkg.TypesInfo, + calleeDecl, + content) + if err != nil { + return nil, err + } + + if err := checkTranscode(callee); err != nil { + return nil, err + } + + check := checkNoMutation(caller.File) + defer check() + return inline.Inline(caller, callee, &inline.Options{Logf: logf}) + }() + if err != nil { + if wantRE, ok := want.(*regexp.Regexp); ok { + if !wantRE.MatchString(err.Error()) { + return fmt.Errorf("Inline failed with wrong error: %v (want error matching %q)", err, want) + } + return nil // expected error + } + return fmt.Errorf("Inline failed: %v", err) // success was expected + } + + // Inline succeeded. + got := res.Content + if want, ok := want.([]byte); ok { + got = append(bytes.TrimSpace(got), '\n') + want = append(bytes.TrimSpace(want), '\n') + if diff := diff.Unified("want", "got", string(want), string(got)); diff != "" { + return fmt.Errorf("Inline returned wrong output:\n%s\nWant:\n%s\nDiff:\n%s", + got, want, diff) + } + return nil + } + return fmt.Errorf("Inline succeeded unexpectedly: want error matching %q, got <<%s>>", want, got) + +} + +// findFuncByPosition returns the FuncDecl at the specified (package-agnostic) position. +func findFuncByPosition(pkg *packages.Package, posn token.Position) (*ast.FuncDecl, error) { + same := func(decl *ast.FuncDecl) bool { + // We can't rely on columns in export data: + // some variants replace it with 1. + // We can't expect file names to have the same prefix. + // export data for go1.20 std packages have $GOROOT written in + // them, so how are we supposed to find the source? Yuck! + // Ugh. need to samefile? Nope $GOROOT just won't work + // This is highly client specific anyway. + posn2 := pkg.Fset.PositionFor(decl.Name.Pos(), false) + return posn.Filename == posn2.Filename && + posn.Line == posn2.Line + } + for _, file := range pkg.Syntax { + for _, decl := range file.Decls { + if decl, ok := decl.(*ast.FuncDecl); ok && same(decl) { + return decl, nil + } + } + } + return nil, fmt.Errorf("can't find FuncDecl at %v in package %q", posn, pkg.PkgPath) +} + +// Each callee must declare a function or method named f, +// and each caller must call it. +const funcName = "f" + +// A testcase is an item in a table-driven test. +// +// The table-driven tests are less flexible, but enable more compact +// expression of single-package test cases than is possible with the +// txtar notation. +// +// TODO(adonovan): improve coverage of the cross product of each +// strategy with the checklist of concerns enumerated in the package +// doc comment. +type testcase struct { + descr string // description; substrings enable options (e.g. "IgnoreEffects") + callee, caller string // Go source files (sans package decl) of caller, callee + want string // expected new portion of caller file, or "error: regexp" +} + +func TestErrors(t *testing.T) { + runTests(t, []testcase{ + { + "Generic functions are not yet supported.", + `func f[T any](x T) T { return x }`, + `var _ = f(0)`, + `error: type parameters are not yet supported`, + }, + { + "Methods on generic types are not yet supported.", + `type G[T any] struct{}; func (G[T]) f(x T) T { return x }`, + `var _ = G[int]{}.f(0)`, + `error: type parameters are not yet supported`, + }, + }) +} + +func TestBasics(t *testing.T) { + runTests(t, []testcase{ + { + "Basic", + `func f(x int) int { return x }`, + `var _ = f(0)`, + `var _ = 0`, + }, + { + "Empty body, no arg effects.", + `func f(x, y int) {}`, + `func _() { f(1, 2) }`, + `func _() {}`, + }, + { + "Empty body, some arg effects.", + `func f(x, y, z int) {}`, + `func _() { f(1, recover().(int), 3) }`, + `func _() { _ = recover().(int) }`, + }, + { + "Non-duplicable arguments are not substituted even if pure.", + `func f(s string, i int) { print(s, s, i, i) }`, + `func _() { f("hi", 0) }`, + `func _() { + var s string = "hi" + print(s, s, 0, 0) +}`, + }, + { + "Workaround for T(x) misformatting (#63362).", + `func f(ch <-chan int) { <-ch }`, + `func _(ch chan int) { f(ch) }`, + `func _(ch chan int) { <-(<-chan int)(ch) }`, + }, + { + // (a regression test for unnecessary braces) + "In block elision, blank decls don't count when computing name conflicts.", + `func f(x int) { var _ = x; var _ = 3 }`, + `func _() { var _ = 1; f(2) }`, + `func _() { + var _ = 1 + var _ = 2 + var _ = 3 +}`, + }, + { + // (a regression test for a missing conversion) + "Implicit return conversions are inserted in expr-context reduction.", + `func f(x int) error { return nil }`, + `func _() { if err := f(0); err != nil {} }`, + `func _() { + if err := error(nil); err != nil { + } +}`, + }, + }) +} + +func TestDuplicable(t *testing.T) { + t.Run("basic", func(t *testing.T) { + runTests(t, []testcase{ + { + "Empty strings are duplicable.", + `func f(s string) { print(s, s) }`, + `func _() { f("") }`, + `func _() { print("", "") }`, + }, + { + "Non-empty string literals are not duplicable.", + `func f(s string) { print(s, s) }`, + `func _() { f("hi") }`, + `func _() { + var s string = "hi" + print(s, s) +}`, + }, + { + "Empty array literals are duplicable.", + `func f(a [2]int) { print(a, a) }`, + `func _() { f([2]int{}) }`, + `func _() { print([2]int{}, [2]int{}) }`, + }, + { + "Non-empty array literals are not duplicable.", + `func f(a [2]int) { print(a, a) }`, + `func _() { f([2]int{1, 2}) }`, + `func _() { + var a [2]int = [2]int{1, 2} + print(a, a) +}`, + }, + { + "Empty struct literals are duplicable.", + `func f(s S) { print(s, s) }; type S struct { x int }`, + `func _() { f(S{}) }`, + `func _() { print(S{}, S{}) }`, + }, + { + "Non-empty struct literals are not duplicable.", + `func f(s S) { print(s, s) }; type S struct { x int }`, + `func _() { f(S{x: 1}) }`, + `func _() { + var s S = S{x: 1} + print(s, s) +}`, + }, + }) + }) + + t.Run("conversions", func(t *testing.T) { + runTests(t, []testcase{ + { + "Conversions to integer are duplicable.", + `func f(i int) { print(i, i) }`, + `func _() { var i int8 = 1; f(int(i)) }`, + `func _() { var i int8 = 1; print(int(i), int(i)) }`, + }, + { + "Implicit conversions from underlying types are duplicable.", + `func f(i I) { print(i, i) }; type I int`, + `func _() { f(1) }`, + `func _() { print(I(1), I(1)) }`, + }, + { + "Conversions to array are duplicable.", + `func f(a [2]int) { print(a, a) }; type A [2]int`, + `func _() { var a A; f([2]int(a)) }`, + `func _() { var a A; print([2]int(a), [2]int(a)) }`, + }, + { + "Conversions from array are duplicable.", + `func f(a A) { print(a, a) }; type A [2]int`, + `func _() { var a [2]int; f(A(a)) }`, + `func _() { var a [2]int; print(A(a), A(a)) }`, + }, + { + "Conversions from byte slice to string are duplicable.", + `func f(s string) { print(s, s) }`, + `func _() { var b []byte; f(string(b)) }`, + `func _() { var b []byte; print(string(b), string(b)) }`, + }, + { + "Conversions from string to byte slice are not duplicable.", + `func f(b []byte) { print(b, b) }`, + `func _() { var s string; f([]byte(s)) }`, + `func _() { + var s string + var b []byte = []byte(s) + print(b, b) +}`, + }, + { + "Conversions from string to uint8 slice are not duplicable.", + `func f(b []uint8) { print(b, b) }`, + `func _() { var s string; f([]uint8(s)) }`, + `func _() { + var s string + var b []uint8 = []uint8(s) + print(b, b) +}`, + }, + { + "Conversions from string to rune slice are not duplicable.", + `func f(r []rune) { print(r, r) }`, + `func _() { var s string; f([]rune(s)) }`, + `func _() { + var s string + var r []rune = []rune(s) + print(r, r) +}`, + }, + { + "Conversions from string to named type with underlying byte slice are not duplicable.", + `func f(b B) { print(b, b) }; type B []byte`, + `func _() { var s string; f(B(s)) }`, + `func _() { + var s string + var b B = B(s) + print(b, b) +}`, + }, + { + "Conversions from string to named type of string are duplicable.", + `func f(s S) { print(s, s) }; type S string`, + `func _() { var s string; f(S(s)) }`, + `func _() { var s string; print(S(s), S(s)) }`, + }, + { + "Built-in function calls are not duplicable.", + `func f(i int) { print(i, i) }`, + `func _() { f(len("")) }`, + `func _() { + var i int = len("") + print(i, i) +}`, + }, + { + "Built-in function calls are not duplicable.", + `func f(c complex128) { print(c, c) }`, + `func _() { f(complex(1.0, 2.0)) }`, + `func _() { + var c complex128 = complex(1.0, 2.0) + print(c, c) +}`, + }, + { + "Non built-in function calls are not duplicable.", + `func f(i int) { print(i, i) } +//go:noinline +func f1(i int) int { return i + 1 }`, + `func _() { f(f1(1)) }`, + `func _() { + var i int = f1(1) + print(i, i) +}`, + }, + { + "Conversions between function types are duplicable.", + `func f(f F) { print(f, f) }; type F func(); func f1() {}`, + `func _() { f(F(f1)) }`, + `func _() { print(F(f1), F(f1)) }`, + }, + }) + + }) +} + +func TestExprStmtReduction(t *testing.T) { + runTests(t, []testcase{ + { + "A call in an unrestricted ExprStmt may be replaced by the body stmts.", + `func f() { var _ = len("") }`, + `func _() { f() }`, + `func _() { var _ = len("") }`, + }, + { + "ExprStmts in the body of a switch case are unrestricted.", + `func f() { x := 1; print(x) }`, + `func _() { switch { case true: f() } }`, + `func _() { + switch { + case true: + x := 1 + print(x) + } +}`, + }, + { + "ExprStmts in the body of a select case are unrestricted.", + `func f() { x := 1; print(x) }`, + `func _() { select { default: f() } }`, + `func _() { + select { + default: + x := 1 + print(x) + } +}`, + }, + { + "Some ExprStmt contexts are restricted to simple statements.", + `func f() { var _ = len("") }`, + `func _(cond bool) { if f(); cond {} }`, + `func _(cond bool) { + if func() { var _ = len("") }(); cond { + } +}`, + }, + { + "Braces must be preserved to avoid a name conflict (decl before).", + `func f() { x := 1; print(x) }`, + `func _() { x := 2; print(x); f() }`, + `func _() { + x := 2 + print(x) + { + x := 1 + print(x) + } +}`, + }, + { + "Braces must be preserved to avoid a name conflict (decl after).", + `func f() { x := 1; print(x) }`, + `func _() { f(); x := 2; print(x) }`, + `func _() { + { + x := 1 + print(x) + } + x := 2 + print(x) +}`, + }, + { + "Braces must be preserved to avoid a forward jump across a decl.", + `func f() { x := 1; print(x) }`, + `func _() { goto label; f(); label: }`, + `func _() { + goto label + { + x := 1 + print(x) + } +label: +}`, + }, + }) +} + +func TestPrecedenceParens(t *testing.T) { + // Ensure that parens are inserted when (and only when) necessary + // around the replacement for the call expression. (This is a special + // case in the way the inliner uses a combination of AST formatting + // for the call and text splicing for the rest of the file.) + runTests(t, []testcase{ + { + "Multiplication in addition context (no parens).", + `func f(x, y int) int { return x * y }`, + `func _() { _ = 1 + f(2, 3) }`, + `func _() { _ = 1 + 2*3 }`, + }, + { + "Addition in multiplication context (parens).", + `func f(x, y int) int { return x + y }`, + `func _() { _ = 1 * f(2, 3) }`, + `func _() { _ = 1 * (2 + 3) }`, + }, + { + "Addition in negation context (parens).", + `func f(x, y int) int { return x + y }`, + `func _() { _ = -f(1, 2) }`, + `func _() { _ = -(1 + 2) }`, + }, + { + "Addition in call context (no parens).", + `func f(x, y int) int { return x + y }`, + `func _() { println(f(1, 2)) }`, + `func _() { println(1 + 2) }`, + }, + { + "Addition in slice operand context (parens).", + `func f(x, y string) string { return x + y }`, + `func _() { _ = f("x", "y")[1:2] }`, + `func _() { _ = ("x" + "y")[1:2] }`, + }, + { + "String literal in slice operand context (no parens).", + `func f(x string) string { return x }`, + `func _() { _ = f("xy")[1:2] }`, + `func _() { _ = "xy"[1:2] }`, + }, + }) +} + +func TestSubstitution(t *testing.T) { + runTests(t, []testcase{ + { + "Arg to unref'd param can be eliminated if has no effects.", + `func f(x, y int) {}; var global int`, + `func _() { f(0, global) }`, + `func _() {}`, + }, + { + "But not if it may contain last reference to a caller local var.", + `func f(int) {}`, + `func _() { var local int; f(local) }`, + `func _() { var local int; _ = local }`, + }, + { + "Arguments that are used are detected", + `func f(int) {}`, + `func _() { var local int; _ = local; f(local) }`, + `func _() { var local int; _ = local }`, + }, + { + "Arguments that are used are detected", + `func f(x, y int) { print(x) }`, + `func _() { var z int; f(z, z) }`, + `func _() { + var z int + var _ int = z + print(z) +}`, + }, + { + "Function parameters are always used", + `func f(int) {}`, + `func _() { + func(local int) { + f(local) + }(1) +}`, + `func _() { + func(local int) { + + }(1) +}`, + }, + { + "Regression test for detection of shadowing in nested functions.", + `func f(x int) { _ = func() { y := 1; print(y); print(x) } }`, + `func _(y int) { f(y) } `, + `func _(y int) { + var x int = y + _ = func() { y := 1; print(y); print(x) } +}`, + }, + }) +} + +func TestTailCallStrategy(t *testing.T) { + runTests(t, []testcase{ + { + "simple", + `func f() int { return 1 }`, + `func _() int { return f() }`, + `func _() int { return 1 }`, + }, + { + "void", + `func f() { println() }`, + `func _() { f() }`, + `func _() { println() }`, + }, + { + "void with defer", // => literalized + `func f() { defer f(); println() }`, + `func _() { f() }`, + `func _() { func() { defer f(); println() }() }`, + }, + // Tests for issue #63336: + { + "non-trivial return conversion (caller.sig = callee.sig)", + `func f() error { if true { return nil } else { return e } }; var e struct{error}`, + `func _() error { return f() }`, + `func _() error { + if true { + return nil + } else { + return e + } +}`, + }, + { + "non-trivial return conversion (caller.sig != callee.sig)", + `func f() error { return E{} }; type E struct{error}`, + `func _() any { return f() }`, + `func _() any { return error(E{}) }`, + }, + }) +} + +func TestSpreadCalls(t *testing.T) { + runTests(t, []testcase{ + { + "Edge case: cannot literalize spread method call.", + `type I int + func g() (I, I) + func (r I) f(x, y I) I { + defer g() // force literalization + return x + y + r + }`, + `func _() I { return recover().(I).f(g()) }`, + `error: can't yet inline spread call to method`, + }, + { + "Spread argument evaluated for effect.", + `func f(int, int) {}; func g() (int, int)`, + `func _() { f(g()) }`, + `func _() { _, _ = g() }`, + }, + { + "Edge case: receiver and spread argument, both evaluated for effect.", + `type T int; func (T) f(int, int) {}; func g() (int, int)`, + `func _() { T(0).f(g()) }`, + `func _() { + var ( + _ = T(0) + _, _ = g() + ) +}`, + }, + { + "Spread call in return (#63398).", + `func f() (int, error) { return 0, nil }`, + `func _() (int, error) { return f() }`, + `func _() (int, error) { return 0, nil }`, + }, + }) +} + +func TestAssignmentCallStrategy(t *testing.T) { + runTests(t, []testcase{ + { + "splice: basic", + `func f(x int) (int, int) { return x, 2 }`, + `func _() { x, y := f(1); _, _ = x, y }`, + `func _() { x, y := 1, 2; _, _ = x, y }`, + }, + { + "spread: basic", + `func f(x int) (any, any) { return g() }; func g() (error, error) { return nil, nil }`, + `func _() { + var x any + x, y := f(0) + _, _ = x, y +}`, + `func _() { + var x any + var y any + x, y = g() + _, _ = x, y +}`, + }, + { + "spread: free var conflict", + `func f(x int) (any, any) { return g(x) }; func g(x int) (int, int) { return x, x }`, + `func _() { + y := 2 + { + var x any + x, y := f(y) + _, _ = x, y + } +}`, + `func _() { + y := 2 + { + var x any + x, y := func() (any, any) { return g(y) }() + _, _ = x, y + } +}`, + }, + { + "convert: basic", + `func f(x int) (int32, int8) { return 1, 2 }`, + `func _() { + var x int32 + x, y := f(0) + _, _ = x, y +}`, + `func _() { + var x int32 + x, y := 1, int8(2) + _, _ = x, y +}`, + }, + { + "convert: rune and byte", + `func f(x int) (rune, byte) { return 0, 0 }`, + `func _() { + x, y := f(0) + _, _ = x, y +}`, + `func _() { + x, y := rune(0), byte(0) + _, _ = x, y +}`, + }, + { + "convert: interface conversions", + `func f(x int) (_, _ error) { return nil, nil }`, + `func _() { + x, y := f(0) + _, _ = x, y +}`, + `func _() { + x, y := error(nil), error(nil) + _, _ = x, y +}`, + }, + { + "convert: implicit nil conversions", + `func f(x int) (_, _ error) { return nil, nil }`, + `func _() { x, y := f(0); _, _ = x, y }`, + `func _() { x, y := error(nil), error(nil); _, _ = x, y }`, + }, + { + "convert: pruning nil assignments left", + `func f(x int) (_, _ error) { return nil, nil }`, + `func _() { _, y := f(0); _ = y }`, + `func _() { y := error(nil); _ = y }`, + }, + { + "convert: pruning nil assignments right", + `func f(x int) (_, _ error) { return nil, nil }`, + `func _() { x, _ := f(0); _ = x }`, + `func _() { x := error(nil); _ = x }`, + }, + { + "convert: partial assign", + `func f(x int) (_, _ error) { return nil, nil }`, + `func _() { + var x error + x, y := f(0) + _, _ = x, y +}`, + `func _() { + var x error + x, y := nil, error(nil) + _, _ = x, y +}`, + }, + { + "convert: single assignment left", + `func f() int { return 0 }`, + `func _() { + x, y := f(), "hello" + _, _ = x, y +}`, + `func _() { + x, y := 0, "hello" + _, _ = x, y +}`, + }, + { + "convert: single assignment left with conversion", + `func f() int32 { return 0 }`, + `func _() { + x, y := f(), "hello" + _, _ = x, y +}`, + `func _() { + x, y := int32(0), "hello" + _, _ = x, y +}`, + }, + { + "convert: single assignment right", + `func f() int32 { return 0 }`, + `func _() { + x, y := "hello", f() + _, _ = x, y +}`, + `func _() { + x, y := "hello", int32(0) + _, _ = x, y +}`, + }, + { + "convert: single assignment middle", + `func f() int32 { return 0 }`, + `func _() { + x, y, z := "hello", f(), 1.56 + _, _, _ = x, y, z +}`, + `func _() { + x, y, z := "hello", int32(0), 1.56 + _, _, _ = x, y, z +}`, + }, + }) +} + +func TestVariadic(t *testing.T) { + runTests(t, []testcase{ + { + "Variadic cancellation (basic).", + `func f(args ...any) { defer f(&args); println(args) }`, + `func _(slice []any) { f(slice...) }`, + `func _(slice []any) { func() { var args []any = slice; defer f(&args); println(args) }() }`, + }, + { + "Variadic cancellation (literalization with parameter elimination).", + `func f(args ...any) { defer f(); println(args) }`, + `func _(slice []any) { f(slice...) }`, + `func _(slice []any) { func() { defer f(); println(slice) }() }`, + }, + { + "Variadic cancellation (reduction).", + `func f(args ...any) { println(args) }`, + `func _(slice []any) { f(slice...) }`, + `func _(slice []any) { println(slice) }`, + }, + { + "Variadic elimination (literalization).", + `func f(x any, rest ...any) { defer println(x, rest) }`, // defer => literalization + `func _() { f(1, 2, 3) }`, + `func _() { func() { defer println(any(1), []any{2, 3}) }() }`, + }, + { + "Variadic elimination (reduction).", + `func f(x int, rest ...int) { println(x, rest) }`, + `func _() { f(1, 2, 3) }`, + `func _() { println(1, []int{2, 3}) }`, + }, + { + "Spread call to variadic (1 arg, 1 param).", + `func f(rest ...int) { println(rest) }; func g() (a, b int)`, + `func _() { f(g()) }`, + `func _() { func(rest ...int) { println(rest) }(g()) }`, + }, + { + "Spread call to variadic (1 arg, 2 params).", + `func f(x int, rest ...int) { println(x, rest) }; func g() (a, b int)`, + `func _() { f(g()) }`, + `func _() { func(x int, rest ...int) { println(x, rest) }(g()) }`, + }, + { + "Spread call to variadic (1 arg, 3 params).", + `func f(x, y int, rest ...int) { println(x, y, rest) }; func g() (a, b, c int)`, + `func _() { f(g()) }`, + `func _() { func(x, y int, rest ...int) { println(x, y, rest) }(g()) }`, + }, + }) +} + +func TestParameterBindingDecl(t *testing.T) { + runTests(t, []testcase{ + { + "IncDec counts as assignment.", + `func f(x int) { x++ }`, + `func _() { f(1) }`, + `func _() { + var x int = 1 + x++ +}`, + }, + { + "Binding declaration (x, y, z eliminated).", + `func f(w, x, y any, z int) { println(w, y, z) }; func g(int) int`, + `func _() { f(g(0), g(1), g(2), g(3)) }`, + `func _() { + var w, _ any = g(0), g(1) + println(w, any(g(2)), g(3)) +}`, + }, + { + "Reduction of stmt-context call to { return exprs }, with substitution", + `func f(ch chan int) int { return <-ch }; func g() chan int`, + `func _() { f(g()) }`, + `func _() { <-g() }`, + }, + { + // Same again, with callee effects: + "Binding decl in reduction of stmt-context call to { return exprs }", + `func f(x int) int { return <-h(g(2), x) }; func g(int) int; func h(int, int) chan int`, + `func _() { f(g(1)) }`, + `func _() { + var x int = g(1) + <-h(g(2), x) +}`, + }, + { + "No binding decl due to shadowing of int", + `func f(int, y any, z int) { defer g(0); println(int, y, z) }; func g(int) int`, + `func _() { f(g(1), g(2), g(3)) }`, + `func _() { func(int, y any, z int) { defer g(0); println(int, y, z) }(g(1), g(2), g(3)) }`, + }, + { + "An indirect method selection (*x).g acts as a read.", + `func f(x *T, y any) any { return x.g(y) }; type T struct{}; func (T) g(x any) any { return x }`, + `func _(x *T) { f(x, recover()) }`, + `func _(x *T) { + var y any = recover() + x.g(y) +}`, + }, + { + "A direct method selection x.g is pure.", + `func f(x *T, y any) any { return x.g(y) }; type T struct{}; func (*T) g(x any) any { return x }`, + `func _(x *T) { f(x, recover()) }`, + `func _(x *T) { x.g(recover()) }`, + }, + { + "Literalization can make use of a binding decl (all params).", + `func f(x, y int) int { defer println(); return y + x }; func g(int) int`, + `func _() { println(f(g(1), g(2))) }`, + `func _() { println(func() int { var x, y int = g(1), g(2); defer println(); return y + x }()) }`, + }, + { + "Literalization can make use of a binding decl (some params).", + `func f(x, y int) int { z := y + x; defer println(); return z }; func g(int) int`, + `func _() { println(f(g(1), g(2))) }`, + `func _() { println(func() int { var x int = g(1); z := g(2) + x; defer println(); return z }()) }`, + }, + { + "Literalization can't yet use of a binding decl if named results.", + `func f(x, y int) (z int) { z = y + x; defer println(); return }; func g(int) int`, + `func _() { println(f(g(1), g(2))) }`, + `func _() { println(func(x int) (z int) { z = g(2) + x; defer println(); return }(g(1))) }`, + }, + }) +} + +func TestEmbeddedFields(t *testing.T) { + runTests(t, []testcase{ + { + "Embedded fields in x.f method selection (direct).", + `type T int; func (t T) f() { print(t) }; type U struct{ T }`, + `func _(u U) { u.f() }`, + `func _(u U) { print(u.T) }`, + }, + { + "Embedded fields in x.f method selection (implicit *).", + `type ( T int; U struct{*T}; V struct {U} ); func (t T) f() { print(t) }`, + `func _(v V) { v.f() }`, + `func _(v V) { print(*v.U.T) }`, + }, + { + "Embedded fields in x.f method selection (implicit &).", + `type ( T int; U struct{T}; V struct {U} ); func (t *T) f() { print(t) }`, + `func _(v V) { v.f() }`, + `func _(v V) { print(&v.U.T) }`, + }, + // Now the same tests again with T.f(recv). + { + "Embedded fields in T.f method selection.", + `type T int; func (t T) f() { print(t) }; type U struct{ T }`, + `func _(u U) { U.f(u) }`, + `func _(u U) { print(u.T) }`, + }, + { + "Embedded fields in T.f method selection (implicit *).", + `type ( T int; U struct{*T}; V struct {U} ); func (t T) f() { print(t) }`, + `func _(v V) { V.f(v) }`, + `func _(v V) { print(*v.U.T) }`, + }, + { + "Embedded fields in (*T).f method selection.", + `type ( T int; U struct{T}; V struct {U} ); func (t *T) f() { print(t) }`, + `func _(v V) { (*V).f(&v) }`, + `func _(v V) { print(&(&v).U.T) }`, + }, + { + // x is a single-assign var, and x.f does not load through a pointer + // (despite types.Selection.Indirect=true), so x is pure. + "No binding decl is required for recv in method-to-method calls.", + `type T struct{}; func (x *T) f() { g(); print(*x) }; func g()`, + `func (x *T) _() { x.f() }`, + `func (x *T) _() { + g() + print(*x) +}`, + }, + { + "Same, with implicit &recv.", + `type T struct{}; func (x *T) f() { g(); print(*x) }; func g()`, + `func (x T) _() { x.f() }`, + `func (x T) _() { + { + var x *T = &x + g() + print(*x) + } +}`, + }, + }) +} + +func TestSubstitutionPreservesArgumentEffectOrder(t *testing.T) { + runTests(t, []testcase{ + { + "Arguments have effects, but parameters are evaluated in order.", + `func f(a, b, c int) { print(a, b, c) }; func g(int) int`, + `func _() { f(g(1), g(2), g(3)) }`, + `func _() { print(g(1), g(2), g(3)) }`, + }, + { + "Arguments have effects, and parameters are evaluated out of order.", + `func f(a, b, c int) { print(a, c, b) }; func g(int) int`, + `func _() { f(g(1), g(2), g(3)) }`, + `func _() { + var a, b int = g(1), g(2) + print(a, g(3), b) +}`, + }, + { + "Pure arguments may commute with argument that have effects.", + `func f(a, b, c int) { print(a, c, b) }; func g(int) int`, + `func _() { f(g(1), 2, g(3)) }`, + `func _() { print(g(1), g(3), 2) }`, + }, + { + "Impure arguments may commute with each other.", + `func f(a, b, c, d int) { print(a, c, b, d) }; func g(int) int; var x, y int`, + `func _() { f(g(1), x, y, g(2)) }`, + `func _() { print(g(1), y, x, g(2)) }`, + }, + { + "Impure arguments do not commute with arguments that have effects (1)", + `func f(a, b, c, d int) { print(a, c, b, d) }; func g(int) int; var x, y int`, + `func _() { f(g(1), g(2), y, g(3)) }`, + `func _() { + var a, b int = g(1), g(2) + print(a, y, b, g(3)) +}`, + }, + { + "Impure arguments do not commute with those that have effects (2).", + `func f(a, b, c, d int) { print(a, c, b, d) }; func g(int) int; var x, y int`, + `func _() { f(g(1), y, g(2), g(3)) }`, + `func _() { + var a, b int = g(1), y + print(a, g(2), b, g(3)) +}`, + }, + { + "Callee effects commute with pure arguments.", + `func f(a, b, c int) { print(a, c, recover().(int), b) }; func g(int) int`, + `func _() { f(g(1), 2, g(3)) }`, + `func _() { print(g(1), g(3), recover().(int), 2) }`, + }, + { + "Callee reads may commute with impure arguments.", + `func f(a, b int) { print(a, x, b) }; func g(int) int; var x, y int`, + `func _() { f(g(1), y) }`, + `func _() { print(g(1), x, y) }`, + }, + { + "All impure parameters preceding a read hazard must be kept.", + `func f(a, b, c int) { print(a, b, recover().(int), c) }; var x, y, z int`, + `func _() { f(x, y, z) }`, + `func _() { + var c int = z + print(x, y, recover().(int), c) +}`, + }, + { + "All parameters preceding a write hazard must be kept.", + `func f(a, b, c int) { print(a, b, recover().(int), c) }; func g(int) int; var x, y, z int`, + `func _() { f(x, y, g(0)) }`, + `func _() { + var a, b, c int = x, y, g(0) + print(a, b, recover().(int), c) +}`, + }, + { + "[W1 R0 W2 W4 R3] -- test case for second iteration of effect loop", + `func f(a, b, c, d, e int) { print(b, a, c, e, d) }; func g(int) int; var x, y int`, + `func _() { f(x, g(1), g(2), y, g(3)) }`, + `func _() { + var a, b, c, d int = x, g(1), g(2), y + print(b, a, c, g(3), d) +}`, + }, + { + // In this example, the set() call is rejected as a substitution + // candidate due to a shadowing conflict (x). This must entail that the + // selection x.y (R) is also rejected, because it is lower numbered. + // + // Incidentally this program (which panics when executed) illustrates + // that although effects occur left-to-right, read operations such + // as x.y are not ordered wrt writes, depending on the compiler. + // Changing x.y to identity(x).y forces the ordering and avoids the panic. + "Hazards with args already rejected (e.g. due to shadowing) are detected too.", + `func f(x, y int) int { return x + y }; func set[T any](ptr *T, old, new T) int { println(old); *ptr = new; return 0; }`, + `func _() { x := new(struct{ y int }); f(x.y, set(&x, x, nil)) }`, + `func _() { + x := new(struct{ y int }) + { + var x, y int = x.y, set(&x, x, nil) + _ = x + y + } +}`, + }, + { + // Rejection of a later parameter for reasons other than callee + // effects (e.g. escape) may create hazards with lower-numbered + // parameters that require them to be rejected too. + "Hazards with already eliminated parameters (variant)", + `func f(x, y int) { _ = &y }; func g(int) int`, + `func _() { f(g(1), g(2)) }`, + `func _() { + var _, y int = g(1), g(2) + _ = &y +}`, + }, + { + // In this case g(2) is rejected for substitution because it is + // unreferenced but has effects, so parameter x must also be rejected + // so that its argument v can be evaluated earlier in the binding decl. + "Hazards with already eliminated parameters (unreferenced fx variant)", + `func f(x, y int) { _ = x }; func g(int) int; var v int`, + `func _() { f(v, g(2)) }`, + `func _() { + var x, _ int = v, g(2) + _ = x +}`, + }, + { + "Defer f() evaluates f() before unknown effects", + `func f(int, y any, z int) { defer println(int, y, z) }; func g(int) int`, + `func _() { f(g(1), g(2), g(3)) }`, + `func _() { func() { defer println(any(g(1)), any(g(2)), g(3)) }() }`, + }, + { + "Effects are ignored when IgnoreEffects", + `func f(x, y int) { println(y, x) }; func g(int) int`, + `func _() { f(g(1), g(2)) }`, + `func _() { println(g(2), g(1)) }`, + }, + }) +} + +func TestNamedResultVars(t *testing.T) { + runTests(t, []testcase{ + { + "Stmt-context call to {return g()} that mentions named result.", + `func f() (x int) { return g(x) }; func g(int) int`, + `func _() { f() }`, + `func _() { + var x int + g(x) +}`, + }, + { + "Ditto, with binding decl again.", + `func f(y string) (x int) { return x+x+len(y+y) }`, + `func _() { f(".") }`, + `func _() { + var ( + y string = "." + x int + ) + _ = x + x + len(y+y) +}`, + }, + + { + "Ditto, with binding decl (due to repeated y refs).", + `func f(y string) (x string) { return x+y+y }`, + `func _() { f(".") }`, + `func _() { + var ( + y string = "." + x string + ) + _ = x + y + y +}`, + }, + { + "Stmt-context call to {return binary} that mentions named result.", + `func f() (x int) { return x+x }`, + `func _() { f() }`, + `func _() { + var x int + _ = x + x +}`, + }, + { + "Tail call to {return expr} that mentions named result.", + `func f() (x int) { return x }`, + `func _() int { return f() }`, + `func _() int { return func() (x int) { return x }() }`, + }, + { + "Tail call to {return} that implicitly reads named result.", + `func f() (x int) { return }`, + `func _() int { return f() }`, + `func _() int { return func() (x int) { return }() }`, + }, + { + "Spread-context call to {return expr} that mentions named result.", + `func f() (x, y int) { return x, y }`, + `func _() { var _, _ = f() }`, + `func _() { var _, _ = func() (x, y int) { return x, y }() }`, + }, + { + "Shadowing in binding decl for named results => literalization.", + `func f(y string) (x y) { return x+x+len(y+y) }; type y = int`, + `func _() { f(".") }`, + `func _() { func(y string) (x y) { return x + x + len(y+y) }(".") }`, + }, + }) +} + +func TestSubstitutionPreservesParameterType(t *testing.T) { + runTests(t, []testcase{ + { + "Substitution preserves argument type (#63193).", + `func f(x int16) { y := x; _ = (*int16)(&y) }`, + `func _() { f(1) }`, + `func _() { + y := int16(1) + _ = (*int16)(&y) +}`, + }, + { + "Same, with non-constant (unnamed to named struct) conversion.", + `func f(x T) { y := x; _ = (*T)(&y) }; type T struct{}`, + `func _() { f(struct{}{}) }`, + `func _() { + y := T(struct{}{}) + _ = (*T)(&y) +}`, + }, + { + "Same, with non-constant (chan to <-chan) conversion.", + `func f(x T) { y := x; _ = (*T)(&y) }; type T = <-chan int; var ch chan int`, + `func _() { f(ch) }`, + `func _() { + y := T(ch) + _ = (*T)(&y) +}`, + }, + { + "Same, with untyped nil to typed nil conversion.", + `func f(x *int) { y := x; _ = (**int)(&y) }`, + `func _() { f(nil) }`, + `func _() { + y := (*int)(nil) + _ = (**int)(&y) +}`, + }, + { + "Conversion of untyped int to named type is made explicit.", + `type T int; func (x T) f() { x.g() }; func (T) g() {}`, + `func _() { T.f(1) }`, + `func _() { T(1).g() }`, + }, + { + "Check for shadowing error on type used in the conversion.", + `func f(x T) { _ = &x == (*T)(nil) }; type T int16`, + `func _() { type T bool; f(1) }`, + `error: T.*shadowed.*by.*type`, + }, + }) +} + +func TestRedundantConversions(t *testing.T) { + runTests(t, []testcase{ + { + "Type conversion must be added if the constant is untyped.", + `func f(i int32) { print(i) }`, + `func _() { f(1) }`, + `func _() { print(int32(1)) }`, + }, + { + "Type conversion must not be added if the constant is typed.", + `func f(i int32) { print(i) }`, + `func _() { f(int32(1)) }`, + `func _() { print(int32(1)) }`, + }, + }) +} + +func runTests(t *testing.T, tests []testcase) { + for _, test := range tests { + test := test + t.Run(test.descr, func(t *testing.T) { + fset := token.NewFileSet() + mustParse := func(filename string, content any) *ast.File { + f, err := parser.ParseFile(fset, filename, content, parser.ParseComments|parser.SkipObjectResolution) + if err != nil { + t.Fatalf("ParseFile: %v", err) + } + return f + } + + // Parse callee file and find first func decl named f. + calleeContent := "package p\n" + test.callee + calleeFile := mustParse("callee.go", calleeContent) + var decl *ast.FuncDecl + for _, d := range calleeFile.Decls { + if d, ok := d.(*ast.FuncDecl); ok && d.Name.Name == funcName { + decl = d + break + } + } + if decl == nil { + t.Fatalf("declaration of func %s not found: %s", funcName, test.callee) + } + + // Parse caller file and find first call to f(). + callerContent := "package p\n" + test.caller + callerFile := mustParse("caller.go", callerContent) + var call *ast.CallExpr + ast.Inspect(callerFile, func(n ast.Node) bool { + if n, ok := n.(*ast.CallExpr); ok { + switch fun := n.Fun.(type) { + case *ast.SelectorExpr: + if fun.Sel.Name == funcName { + call = n + } + case *ast.Ident: + if fun.Name == funcName { + call = n + } + } + } + return call == nil + }) + if call == nil { + t.Fatalf("call to %s not found: %s", funcName, test.caller) + } + + // Type check both files as one package. + info := &types.Info{ + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Types: make(map[ast.Expr]types.TypeAndValue), + Implicits: make(map[ast.Node]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + } + conf := &types.Config{Error: func(err error) { t.Error(err) }} + pkg, err := conf.Check("p", fset, []*ast.File{callerFile, calleeFile}, info) + if err != nil { + t.Fatal("transformation introduced type errors") + } + + // Analyze callee and inline call. + doIt := func() (*inline.Result, error) { + callee, err := inline.AnalyzeCallee(t.Logf, fset, pkg, info, decl, []byte(calleeContent)) + if err != nil { + return nil, err + } + if err := checkTranscode(callee); err != nil { + t.Fatal(err) + } + + caller := &inline.Caller{ + Fset: fset, + Types: pkg, + Info: info, + File: callerFile, + Call: call, + Content: []byte(callerContent), + } + check := checkNoMutation(caller.File) + defer check() + return inline.Inline(caller, callee, &inline.Options{ + Logf: t.Logf, + IgnoreEffects: strings.Contains(test.descr, "IgnoreEffects"), + }) + } + res, err := doIt() + + // Want error? + if rest := strings.TrimPrefix(test.want, "error: "); rest != test.want { + if err == nil { + t.Fatalf("unexpected success: want error matching %q", rest) + } + msg := err.Error() + if ok, err := regexp.MatchString(rest, msg); err != nil { + t.Fatalf("invalid regexp: %v", err) + } else if !ok { + t.Fatalf("wrong error: %s (want match for %q)", msg, rest) + } + return + } + + // Want success. + if err != nil { + t.Fatal(err) + } + + gotContent := res.Content + + // Compute a single-hunk line-based diff. + srcLines := strings.Split(callerContent, "\n") + gotLines := strings.Split(string(gotContent), "\n") + for len(srcLines) > 0 && len(gotLines) > 0 && + srcLines[0] == gotLines[0] { + srcLines = srcLines[1:] + gotLines = gotLines[1:] + } + for len(srcLines) > 0 && len(gotLines) > 0 && + srcLines[len(srcLines)-1] == gotLines[len(gotLines)-1] { + srcLines = srcLines[:len(srcLines)-1] + gotLines = gotLines[:len(gotLines)-1] + } + got := strings.Join(gotLines, "\n") + + if strings.TrimSpace(got) != strings.TrimSpace(test.want) { + t.Fatalf("\nInlining this call:\t%s\nof this callee: \t%s\nproduced:\n%s\nWant:\n\n%s", + test.caller, + test.callee, + got, + test.want) + } + + // Check that resulting code type-checks. + newCallerFile := mustParse("newcaller.go", gotContent) + if _, err := conf.Check("p", fset, []*ast.File{newCallerFile, calleeFile}, nil); err != nil { + t.Fatalf("modified source failed to typecheck: <<%s>>", gotContent) + } + }) + } +} + +// -- helpers -- + +// checkNoMutation returns a function that, when called, +// asserts that file was not modified since the checkNoMutation call. +func checkNoMutation(file *ast.File) func() { + pre := deepHash(file) + return func() { + post := deepHash(file) + if pre != post { + panic("Inline mutated caller.File") + } + } +} + +// checkTranscode replaces *callee by the results of gob-encoding and +// then decoding it, to test that these operations are lossless. +func checkTranscode(callee *inline.Callee) error { + // Perform Gob transcoding so that it is exercised by the test. + var enc bytes.Buffer + if err := gob.NewEncoder(&enc).Encode(callee); err != nil { + return fmt.Errorf("internal error: gob encoding failed: %v", err) + } + *callee = inline.Callee{} + if err := gob.NewDecoder(&enc).Decode(callee); err != nil { + return fmt.Errorf("internal error: gob decoding failed: %v", err) + } + return nil +} + +// TODO(adonovan): publish this a helper (#61386). +func extractTxtar(ar *txtar.Archive, dir string) error { + for _, file := range ar.Files { + name := filepath.Join(dir, file.Name) + if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil { + return err + } + if err := os.WriteFile(name, file.Data, 0666); err != nil { + return err + } + } + return nil +} + +// deepHash computes a cryptographic hash of an ast.Node so that +// if the data structure is mutated, the hash changes. +// It assumes Go variables do not change address. +// +// TODO(adonovan): consider publishing this in the astutil package. +// +// TODO(adonovan): consider a variant that reports where in the tree +// the mutation occurred (obviously at a cost in space). +func deepHash(n ast.Node) any { + seen := make(map[unsafe.Pointer]bool) // to break cycles + + hasher := sha256.New() + le := binary.LittleEndian + writeUint64 := func(v uint64) { + var bs [8]byte + le.PutUint64(bs[:], v) + hasher.Write(bs[:]) + } + + var visit func(reflect.Value) + visit = func(v reflect.Value) { + switch v.Kind() { + case reflect.Ptr: + ptr := v.UnsafePointer() + writeUint64(uint64(uintptr(ptr))) + if !v.IsNil() { + if !seen[ptr] { + seen[ptr] = true + // Skip types we don't handle yet, but don't care about. + switch v.Interface().(type) { + case *ast.Scope: + return // involves a map + } + + visit(v.Elem()) + } + } + + case reflect.Struct: + for i := 0; i < v.Type().NumField(); i++ { + visit(v.Field(i)) + } + + case reflect.Slice: + ptr := v.UnsafePointer() + // We may encounter different slices at the same address, + // so don't mark ptr as "seen". + writeUint64(uint64(uintptr(ptr))) + writeUint64(uint64(v.Len())) + writeUint64(uint64(v.Cap())) + for i := 0; i < v.Len(); i++ { + visit(v.Index(i)) + } + + case reflect.Interface: + if v.IsNil() { + writeUint64(0) + } else { + rtype := reflect.ValueOf(v.Type()).UnsafePointer() + writeUint64(uint64(uintptr(rtype))) + visit(v.Elem()) + } + + case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer: + panic(v) // unreachable in AST + + default: // bool, string, number + if v.Kind() == reflect.String { // proper framing + writeUint64(uint64(v.Len())) + } + binary.Write(hasher, le, v.Interface()) + } + } + visit(reflect.ValueOf(n)) + + var hash [sha256.Size]byte + hasher.Sum(hash[:0]) + return hash +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/basic-err.txtar b/contribs/gnopls/internal/refactor/inline/testdata/basic-err.txtar new file mode 100644 index 00000000000..4868b2cbfb1 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/basic-err.txtar @@ -0,0 +1,24 @@ +Test of inlining a function that references err.Error, +which is often a special case because it has no position. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +import "io" + +var _ = getError(io.EOF) //@ inline(re"getError", getError) + +func getError(err error) string { return err.Error() } + +-- getError -- +package a + +import "io" + +var _ = io.EOF.Error() //@ inline(re"getError", getError) + +func getError(err error) string { return err.Error() } diff --git a/contribs/gnopls/internal/refactor/inline/testdata/basic-literal.txtar b/contribs/gnopls/internal/refactor/inline/testdata/basic-literal.txtar new file mode 100644 index 00000000000..7ae640aad02 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/basic-literal.txtar @@ -0,0 +1,29 @@ +Basic tests of inlining by literalization. + +The use of defer forces literalization. + +recover() is an example of a function with effects, +defeating elimination of parameter x; but parameter +y is eliminated by substitution. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a1.go -- +package a + +func _() { + add(recover().(int), 2) //@ inline(re"add", add1) +} + +func add(x, y int) int { defer print(); return x + y } + +-- add1 -- +package a + +func _() { + func() int { var x int = recover().(int); defer print(); return x + 2 }() //@ inline(re"add", add1) +} + +func add(x, y int) int { defer print(); return x + y } diff --git a/contribs/gnopls/internal/refactor/inline/testdata/basic-reduce.txtar b/contribs/gnopls/internal/refactor/inline/testdata/basic-reduce.txtar new file mode 100644 index 00000000000..10aca5284ef --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/basic-reduce.txtar @@ -0,0 +1,50 @@ +Most basic test of inlining by reduction. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a0.go -- +package a + +var _ = zero() //@ inline(re"zero", zero) + +func zero() int { return 0 } + +-- zero -- +package a + +var _ = 0 //@ inline(re"zero", zero) + +func zero() int { return 0 } + +-- a/a1.go -- +package a + +func _() { + one := 1 + add(one, 2) //@ inline(re"add", add1) +} + +func add(x, y int) int { return x + y } + +-- add1 -- +package a + +func _() { + one := 1 + _ = one + 2 //@ inline(re"add", add1) +} + +func add(x, y int) int { return x + y } + +-- a/a2.go -- +package a + +var _ = add(len(""), 2) //@ inline(re"add", add2) + +-- add2 -- +package a + +var _ = len("") + 2 //@ inline(re"add", add2) + diff --git a/contribs/gnopls/internal/refactor/inline/testdata/cgo.txtar b/contribs/gnopls/internal/refactor/inline/testdata/cgo.txtar new file mode 100644 index 00000000000..41567ed7cbb --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/cgo.txtar @@ -0,0 +1,45 @@ +Test that attempts to inline with caller or callee in a cgo-generated +file are rejected. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +/* +static void f() {} +*/ +import "C" + +func a() { + C.f() //@ inline(re"f", re"cannot inline cgo-generated functions") + g() //@ inline(re"g", re`cannot inline calls from files that import "C"`) +} + +func g() { + println() +} + +-- a/a2.go -- +package a + +func b() { + a() //@ inline(re"a", re"cannot inline cgo-generated functions") +} + +func c() { + b() //@ inline(re"b", result) +} + +-- result -- +package a + +func b() { + a() //@ inline(re"a", re"cannot inline cgo-generated functions") +} + +func c() { + a() //@ inline(re"b", result) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/comments.txtar b/contribs/gnopls/internal/refactor/inline/testdata/comments.txtar new file mode 100644 index 00000000000..76f64926b13 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/comments.txtar @@ -0,0 +1,57 @@ +Test of (lack of) comment preservation by inlining, +whether by literalization or reduction. + +Comment handling was better in an earlier implementation +based on byte-oriented file surgery; switching to AST +manipulation (though better in all other respects) was +a regression. The underlying problem of AST comment fidelity +is Go issue #20744. + +-- go.mod -- +module testdata +go 1.12 + +-- a/f.go -- +package a + +func _() { + f() //@ inline(re"f", f) +} + +func f() { + // a + /* b */ g() /* c */ + // d +} + +-- f -- +package a + +func _() { + g() //@ inline(re"f", f) +} + +func f() { + // a + /* b */ + g() /* c */ + // d +} + +-- a/g.go -- +package a + +func _() { + println(g()) //@ inline(re"g", g) +} + +func g() int { return 1 /*hello*/ + /*there*/ 1 } + +-- g -- +package a + +func _() { + println(1 + 1) //@ inline(re"g", g) +} + +func g() int { return 1 /*hello*/ + /*there*/ 1 } diff --git a/contribs/gnopls/internal/refactor/inline/testdata/crosspkg-selfref.txtar b/contribs/gnopls/internal/refactor/inline/testdata/crosspkg-selfref.txtar new file mode 100644 index 00000000000..0c45be87d92 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/crosspkg-selfref.txtar @@ -0,0 +1,32 @@ +A self-reference counts as a free reference, +so that it gets properly package-qualified as needed. +(Regression test for a bug.) + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +import "testdata/b" + +func _() { + b.F(1) //@ inline(re"F", output) +} + +-- b/b.go -- +package b + +func F(x int) { + F(x + 2) +} + +-- output -- +package a + +import "testdata/b" + +func _() { + b.F(1 + 2) //@ inline(re"F", output) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/crosspkg.txtar b/contribs/gnopls/internal/refactor/inline/testdata/crosspkg.txtar new file mode 100644 index 00000000000..e0744f99043 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/crosspkg.txtar @@ -0,0 +1,105 @@ +Test of cross-package inlining. +The first case creates a new import, +the second reuses an existing one. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +// This comment does not migrate. + +import ( + "fmt" + "testdata/b" +) + +// Nor this one. + +func A() { + fmt.Println() + b.B1() //@ inline(re"B1", b1result) + b.B2() //@ inline(re"B2", b2result) + b.B3() //@ inline(re"B3", b3result) +} + +-- b/b.go -- +package b + +import "testdata/c" +import "testdata/d" +import "fmt" + +func B1() { c.C() } +func B2() { fmt.Println() } +func B3() { e.E() } // (note that "testdata/d" points to package e) + +-- c/c.go -- +package c + +func C() {} + +-- d/d.go -- +package e // <- this package name intentionally mismatches the path + +func E() {} + +-- b1result -- +package a + +// This comment does not migrate. + +import ( + "fmt" + "testdata/b" + "testdata/c" +) + +// Nor this one. + +func A() { + fmt.Println() + c.C() //@ inline(re"B1", b1result) + b.B2() //@ inline(re"B2", b2result) + b.B3() //@ inline(re"B3", b3result) +} + +-- b2result -- +package a + +// This comment does not migrate. + +import ( + "fmt" + "testdata/b" +) + +// Nor this one. + +func A() { + fmt.Println() + b.B1() //@ inline(re"B1", b1result) + fmt.Println() //@ inline(re"B2", b2result) + b.B3() //@ inline(re"B3", b3result) +} +-- b3result -- +package a + +// This comment does not migrate. + +import ( + "fmt" + "testdata/b" + e "testdata/d" +) + +// Nor this one. + +func A() { + fmt.Println() + b.B1() //@ inline(re"B1", b1result) + b.B2() //@ inline(re"B2", b2result) + e.E() //@ inline(re"B3", b3result) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/dotimport.txtar b/contribs/gnopls/internal/refactor/inline/testdata/dotimport.txtar new file mode 100644 index 00000000000..644398b1df0 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/dotimport.txtar @@ -0,0 +1,37 @@ +Test of inlining a function that uses a dot import. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +func A() {} + +-- b/b.go -- +package b + +import . "testdata/a" + +func B() { A() } + +-- c/c.go -- +package c + +import "testdata/b" + +func _() { + b.B() //@ inline(re"B", result) +} + +-- result -- +package c + +import ( + "testdata/a" +) + +func _() { + a.A() //@ inline(re"B", result) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/embed.txtar b/contribs/gnopls/internal/refactor/inline/testdata/embed.txtar new file mode 100644 index 00000000000..ab52f5a5a00 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/embed.txtar @@ -0,0 +1,28 @@ +Test of implicit field selections in method calls. + +The two level wrapping T -> unexported -> U is required +to exercise the implicit selections exportedness check; +with only a single level, the receiver declaration in +"func (unexported) F()" would fail the earlier +unexportedness check. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +import "testdata/b" + +func _(x b.T) { + x.F() //@ inline(re"F", re"in x.F, implicit reference to unexported field .unexported cannot be made explicit") +} + +-- b/b.go -- +package b + +type T struct { unexported } +type unexported struct { U } +type U struct{} +func (U) F() {} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/empty-body.txtar b/contribs/gnopls/internal/refactor/inline/testdata/empty-body.txtar new file mode 100644 index 00000000000..8983fda8c6e --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/empty-body.txtar @@ -0,0 +1,103 @@ +Test of elimination of calls to functions with completely empty bodies. +The arguments must still be evaluated and their results discarded. +The number of discard blanks must match the type, not the syntax (see 2-ary f). +If there are no arguments, the entire call is eliminated. + +We cannot eliminate some pure argument expressions because they +may contain the last reference to a local variable. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a0.go -- +package a + +func _() { + empty() //@ inline(re"empty", empty0) +} + +func empty(...any) {} + +-- empty0 -- +package a + +func _() { + //@ inline(re"empty", empty0) +} + +func empty(...any) {} + +-- a/a1.go -- +package a + +func _(ch chan int) { + empty(f()) //@ inline(re"empty", empty1) +} + +func f() (int, int) + +-- empty1 -- +package a + +func _(ch chan int) { + _, _ = f() //@ inline(re"empty", empty1) +} + +func f() (int, int) + +-- a/a2.go -- +package a + +func _(ch chan int) { + empty(-1, ch, len(""), g(), <-ch) //@ inline(re"empty", empty2) +} + +func g() int + +-- empty2 -- +package a + +func _(ch chan int) { + _ = []any{-1, ch, len(""), g(), <-ch} //@ inline(re"empty", empty2) +} + +func g() int + +-- a/a3.go -- +package a + +func _() { + new(T).empty() //@ inline(re"empty", empty3) +} + +type T int + +func (T) empty() int {} + +-- empty3 -- +package a + +func _() { + //@ inline(re"empty", empty3) +} + +type T int + +func (T) empty() int {} + +-- a/a4.go -- +package a + +func _() { + var x T + x.empty() //@ inline(re"empty", empty4) +} + +-- empty4 -- +package a + +func _() { + var x T + _ = x //@ inline(re"empty", empty4) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/err-basic.txtar b/contribs/gnopls/internal/refactor/inline/testdata/err-basic.txtar new file mode 100644 index 00000000000..54377c70c4b --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/err-basic.txtar @@ -0,0 +1,30 @@ +Basic errors: +- Inlining of generic functions is not yet supported. + +We can't express tests for the error resulting from inlining a +conversion T(x), a call to a literal func(){}(), a call to a +func-typed var, or a call to an interface method, since all of these +cause the test driver to fail to locate the callee, so +it doesn't even reach the Indent function. + +-- go.mod -- +module testdata +go 1.12 + +-- a/generic.go -- +package a + +func _() { + f[int]() //@ inline(re"f", re"type parameters are not yet supported") +} + +func f[T any]() {} + +-- a/nobody.go -- +package a + +func _() { + g() //@ inline(re"g", re"has no body") +} + +func g() diff --git a/contribs/gnopls/internal/refactor/inline/testdata/err-shadow-builtin.txtar b/contribs/gnopls/internal/refactor/inline/testdata/err-shadow-builtin.txtar new file mode 100644 index 00000000000..34ea586ab3e --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/err-shadow-builtin.txtar @@ -0,0 +1,36 @@ +Failures to inline because callee references a builtin that +is shadowed by caller. + +-- go.mod -- +module testdata +go 1.12 + +-- a/nil.go -- +package a + +func _() { + const nil = 1 + _ = f() //@ inline(re"f", re"nil.*shadowed.*by.*const.*line 4") +} + +func f() *int { return nil } + +-- a/append.go -- +package a + +func _() { + type append int + g(nil) //@ inline(re"g", re"append.*shadowed.*by.*typename.*line 4") +} + +func g(x []int) { _ = append(x, x...) } + +-- a/type.go -- +package a + +func _() { + type int uint8 + _ = h(0) //@ inline(re"h", re"int.*shadowed.*by.*typename.*line 4") +} + +func h(x int) int { return x + 1 } diff --git a/contribs/gnopls/internal/refactor/inline/testdata/err-shadow-pkg.txtar b/contribs/gnopls/internal/refactor/inline/testdata/err-shadow-pkg.txtar new file mode 100644 index 00000000000..792418dd453 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/err-shadow-pkg.txtar @@ -0,0 +1,36 @@ +Test of failure to inline because callee references a +package-level decl that is shadowed by caller. + +Observe that the first call to f can be inlined because +the shadowing has not yet occurred; but the second call +to f is within the scope of the local constant v. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +func _() { + f() //@ inline(re"f", result) + const v = 1 + f() //@ inline(re"f", re"v.*shadowed.*by.*const.*line 5") +} + +func f() int { return v } + +var v int + +-- result -- +package a + +func _() { + _ = v //@ inline(re"f", result) + const v = 1 + f() //@ inline(re"f", re"v.*shadowed.*by.*const.*line 5") +} + +func f() int { return v } + +var v int diff --git a/contribs/gnopls/internal/refactor/inline/testdata/err-unexported.txtar b/contribs/gnopls/internal/refactor/inline/testdata/err-unexported.txtar new file mode 100644 index 00000000000..9ba91e5195d --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/err-unexported.txtar @@ -0,0 +1,31 @@ +Errors from attempting to import a function from another +package whose body refers to unexported declarations. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +func A1() { b() } +func b() {} + +func A2() { var x T; print(x.f) } +type T struct { f int } + +func A3() { _ = &T{f: 0} } + +func A4() { _ = &T{0} } + +-- b/b.go -- +package b + +import "testdata/a" + +func _() { + a.A1() //@ inline(re"A1", re`body refers to non-exported b`) + a.A2() //@ inline(re"A2", re`body refers to non-exported \(testdata/a.T\).f`) + a.A3() //@ inline(re"A3", re`body refers to non-exported \(testdata/a.T\).f`) + a.A4() //@ inline(re"A4", re`body refers to non-exported \(testdata/a.T\).f`) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/exprstmt.txtar b/contribs/gnopls/internal/refactor/inline/testdata/exprstmt.txtar new file mode 100644 index 00000000000..449ce35c454 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/exprstmt.txtar @@ -0,0 +1,99 @@ +Inlining an expression into an ExprStmt. +Call and receive expressions can be inlined directly +(though calls to only some builtins can be reduced). +All other expressions are inlined as "_ = expr". + +-- go.mod -- +module testdata +go 1.12 + +-- a/call.go -- +package a + +func _() { + call() //@ inline(re"call", call) +} + +func call() int { return recv() } + +-- call -- +package a + +func _() { + recv() //@ inline(re"call", call) +} + +func call() int { return recv() } + +-- a/recv.go -- +package a + +func _() { + recv() //@ inline(re"recv", recv) +} + +func recv() int { return <-(chan int)(nil) } + +-- recv -- +package a + +func _() { + <-(chan int)(nil) //@ inline(re"recv", recv) +} + +func recv() int { return <-(chan int)(nil) } + +-- a/constant.go -- +package a + +func _() { + constant() //@ inline(re"constant", constant) +} + +func constant() int { return 0 } + +-- constant -- +package a + +func _() { + _ = 0 //@ inline(re"constant", constant) +} + +func constant() int { return 0 } + +-- a/builtin.go -- +package a + +func _() { + builtin() //@ inline(re"builtin", builtin) +} + +func builtin() int { return len("") } + +-- builtin -- +package a + +func _() { + _ = len("") //@ inline(re"builtin", builtin) +} + +func builtin() int { return len("") } + +-- a/copy.go -- +package a + +func _() { + _copy() //@ inline(re"copy", copy) +} + +func _copy() int { return copy([]int(nil), []int(nil)) } + +-- copy -- +package a + +func _() { + copy([]int(nil), []int(nil)) //@ inline(re"copy", copy) +} + +func _copy() int { return copy([]int(nil), []int(nil)) } + diff --git a/contribs/gnopls/internal/refactor/inline/testdata/import-rename.txtar b/contribs/gnopls/internal/refactor/inline/testdata/import-rename.txtar new file mode 100644 index 00000000000..0b567f626e0 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/import-rename.txtar @@ -0,0 +1,40 @@ +Regtest for https://github.com/golang/go/issues/67281 + +-- go.mod -- +module example.com +go 1.19 + +-- main/main.go -- +package main + +import "example.com/a" + +func main() { + a.A() //@ inline(re"A", result) +} + +-- a/a.go -- +package a + +import "example.com/other/a" + +func A() { + a.A() +} + +-- other/a/a.go -- +package a + +func A() { +} + +-- result -- +package main + +import ( + "example.com/other/a" +) + +func main() { + a.A() //@ inline(re"A", result) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/import-shadow.txtar b/contribs/gnopls/internal/refactor/inline/testdata/import-shadow.txtar new file mode 100644 index 00000000000..9d1abdb9e95 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/import-shadow.txtar @@ -0,0 +1,123 @@ +Just because a package (e.g. log) is imported by the caller, +and the name log is in scope, doesn't mean the name in scope +refers to the package: it could be locally shadowed. + +In all three scenarios below, renaming import with a fresh name is +added because the usual name is locally shadowed: in cases 1, 2 an +existing import is shadowed by (respectively) a local constant, +parameter; in case 3 there is no existing import. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +import "testdata/b" +import "log" + +func A() { + const log = "shadow" + b.B() //@ inline(re"B", bresult) +} + +var _ log.Logger + +-- b/b.go -- +package b + +import "log" + +func B() { + log.Printf("") +} + +-- bresult -- +package a + +import ( + "log" + log0 "log" +) + +func A() { + const log = "shadow" + log0.Printf("") //@ inline(re"B", bresult) +} + +var _ log.Logger + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +import "testdata/b" + +var x b.T + +func A(b int) { + x.F() //@ inline(re"F", fresult) +} + +-- b/b.go -- +package b + +type T struct{} + +func (T) F() { + One() + Two() +} + +func One() {} +func Two() {} + +-- fresult -- +package a + +import ( + "testdata/b" + b0 "testdata/b" +) + +var x b.T + +func A(b int) { + b0.One() + b0.Two() //@ inline(re"F", fresult) +} + +-- d/d.go -- +package d + +import "testdata/e" + +func D() { + const log = "shadow" + e.E() //@ inline(re"E", eresult) +} + +-- e/e.go -- +package e + +import "log" + +func E() { + log.Printf("") +} + +-- eresult -- +package d + +import ( + log0 "log" +) + +func D() { + const log = "shadow" + log0.Printf("") //@ inline(re"E", eresult) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/internal.txtar b/contribs/gnopls/internal/refactor/inline/testdata/internal.txtar new file mode 100644 index 00000000000..92a0fef4c0a --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/internal.txtar @@ -0,0 +1,29 @@ +Test of inlining a function that references an +internal package that is not accessible to the caller. + +(c -> b -> b/internal/a) + +-- go.mod -- +module testdata +go 1.12 + +-- b/internal/a/a.go -- +package a + +func A() {} + +-- b/b.go -- +package b + +import "testdata/b/internal/a" + +func B() { a.A() } + +-- c/c.go -- +package c + +import "testdata/b" + +func _() { + b.B() //@ inline(re"B", re`body refers to inaccessible package "testdata/b/internal/a"`) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/issue62667.txtar b/contribs/gnopls/internal/refactor/inline/testdata/issue62667.txtar new file mode 100644 index 00000000000..b6ff83b4bce --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/issue62667.txtar @@ -0,0 +1,44 @@ +Regression test for #62667: the callee's reference to Split +was blindly qualified to path.Split even though the imported +PkgName path is shadowed by the parameter of the same name. + +The defer is to defeat reduction of the call and +substitution of the path parameter by g(). + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +import "testdata/path" + +func A() { + path.Dir(g()) //@ inline(re"Dir", result) +} + +func g() string + +-- path/path.go -- +package path + +func Dir(path string) { + defer func(){}() + Split(path) +} + +func Split(string) {} + +-- result -- +package a + +import ( + path0 "testdata/path" +) + +func A() { + func() { var path string = g(); defer func() {}(); path0.Split(path) }() //@ inline(re"Dir", result) +} + +func g() string \ No newline at end of file diff --git a/contribs/gnopls/internal/refactor/inline/testdata/issue63298.txtar b/contribs/gnopls/internal/refactor/inline/testdata/issue63298.txtar new file mode 100644 index 00000000000..cc556c90ecd --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/issue63298.txtar @@ -0,0 +1,50 @@ +Regression test for #63298: inlining a function that +depends on two packages with the same name leads +to duplicate PkgNames. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +func _() { + a2() //@ inline(re"a2", result) +} + +-- a/a2.go -- +package a + +import "testdata/b" +import anotherb "testdata/another/b" + +func a2() { + b.B() + anotherb.B() +} + +-- b/b.go -- +package b + +func B() {} + +-- b/another/b.go -- +package b + +func B() {} + +-- result -- +package a + +import ( + "testdata/b" + b0 "testdata/another/b" + + //@ inline(re"a2", result) +) + +func _() { + b.B() + b0.B() +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/line-directives.txtar b/contribs/gnopls/internal/refactor/inline/testdata/line-directives.txtar new file mode 100644 index 00000000000..66ae9ede335 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/line-directives.txtar @@ -0,0 +1,35 @@ +Test of line directives in caller and caller. +Neither should have any effect on inlining. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +import "testdata/b" + +func A() { +//line b2.go:3:3 + b.F() //@ inline(re"F", result) +} + +-- b/b.go -- +package b + +//line b2.go:1:1 +func F() { println("hi") } + +-- b/b2.go -- +package b + +func NotWhatYouWereLookingFor() {} + +-- result -- +package a + +func A() { +//line b2.go:3:3 + println("hi") //@ inline(re"F", result) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/method.txtar b/contribs/gnopls/internal/refactor/inline/testdata/method.txtar new file mode 100644 index 00000000000..92343edd840 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/method.txtar @@ -0,0 +1,127 @@ +Test of inlining a method call. + +The call to (*T).g0 implicitly takes the address &x, and +the call to T.h implictly dereferences the argument *ptr. + +The f1/g1 methods have parameters, exercising the +splicing of the receiver into the parameter list. +Notice that the unnamed parameters become named. + +-- go.mod -- +module testdata +go 1.12 + +-- a/f0.go -- +package a + +type T int + +func (recv T) f0() { println(recv) } + +func _(x T) { + x.f0() //@ inline(re"f0", f0) +} + +-- f0 -- +package a + +type T int + +func (recv T) f0() { println(recv) } + +func _(x T) { + println(x) //@ inline(re"f0", f0) +} + +-- a/g0.go -- +package a + +func (recv *T) g0() { println(recv) } + +func _(x T) { + x.g0() //@ inline(re"g0", g0) +} + +-- g0 -- +package a + +func (recv *T) g0() { println(recv) } + +func _(x T) { + println(&x) //@ inline(re"g0", g0) +} + +-- a/f1.go -- +package a + +func (recv T) f1(int, int) { println(recv) } + +func _(x T) { + x.f1(1, 2) //@ inline(re"f1", f1) +} + +-- f1 -- +package a + +func (recv T) f1(int, int) { println(recv) } + +func _(x T) { + println(x) //@ inline(re"f1", f1) +} + +-- a/g1.go -- +package a + +func (recv *T) g1(int, int) { println(recv) } + +func _(x T) { + x.g1(1, 2) //@ inline(re"g1", g1) +} + +-- g1 -- +package a + +func (recv *T) g1(int, int) { println(recv) } + +func _(x T) { + println(&x) //@ inline(re"g1", g1) +} + +-- a/h.go -- +package a + +func (T) h() int { return 1 } + +func _() { + var ptr *T + ptr.h() //@ inline(re"h", h) +} + +-- h -- +package a + +func (T) h() int { return 1 } + +func _() { + var ptr *T + var _ T = *ptr + _ = 1 //@ inline(re"h", h) +} + +-- a/i.go -- +package a + +func (T) i() int { return 1 } + +func _() { + (*T).i(nil) //@ inline(re"i", i) +} + +-- i -- +package a + +func (T) i() int { return 1 } + +func _() { + _ = 1 //@ inline(re"i", i) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/multistmt-body.txtar b/contribs/gnopls/internal/refactor/inline/testdata/multistmt-body.txtar new file mode 100644 index 00000000000..77027191bd4 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/multistmt-body.txtar @@ -0,0 +1,85 @@ +Tests of reduction of calls to multi-statement bodies. + +a1: reduced to a block with a parameter binding decl. + (Parameter x can't be substituted by z without a shadowing conflict.) + +a2: reduced with parameter substitution (no shadowing). + +a3: literalized, because of the return statement. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a1.go -- +package a + +func _() { + z := 1 + f(z, 2) //@ inline(re"f", out1) +} + +func f(x, y int) { + z := 1 + print(x + y + z) +} + +-- out1 -- +package a + +func _() { + z := 1 + { + var x int = z + z := 1 + print(x + 2 + z) + } //@ inline(re"f", out1) +} + +func f(x, y int) { + z := 1 + print(x + y + z) +} + +-- a/a2.go -- +package a + +func _() { + a := 1 + f(a, 2) //@ inline(re"f", out2) +} + +-- out2 -- +package a + +func _() { + a := 1 + z := 1 + print(a + 2 + z) //@ inline(re"f", out2) +} + +-- a/a3.go -- +package a + +func _() { + a := 1 + g(a, 2) //@ inline(re"g", out3) +} + +func g(x, y int) int { + z := 1 + return x + y + z +} + +-- out3 -- +package a + +func _() { + a := 1 + func() int { z := 1; return a + 2 + z }() //@ inline(re"g", out3) +} + +func g(x, y int) int { + z := 1 + return x + y + z +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/n-ary.txtar b/contribs/gnopls/internal/refactor/inline/testdata/n-ary.txtar new file mode 100644 index 00000000000..2de97358aed --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/n-ary.txtar @@ -0,0 +1,79 @@ +Tests of various n-ary result function cases. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +func _() { + println(f1()) //@ inline(re"f1", f1) +} + +func f1() (int, int) { return 1, 1 } + +-- f1 -- +package a + +func _() { + println(1, 1) //@ inline(re"f1", f1) +} + +func f1() (int, int) { return 1, 1 } + +-- b/b.go -- +package b + +func _() { + f2() //@ inline(re"f2", f2) +} + +func f2() (int, int) { return 2, 2 } + +-- f2 -- +package b + +func _() { + _, _ = 2, 2 //@ inline(re"f2", f2) +} + +func f2() (int, int) { return 2, 2 } + +-- c/c.go -- +package c + +func _() { + _, _ = f3() //@ inline(re"f3", f3) +} + +func f3() (int, int) { return f3A() } +func f3A() (x, y int) + +-- f3 -- +package c + +func _() { + _, _ = f3A() //@ inline(re"f3", f3) +} + +func f3() (int, int) { return f3A() } +func f3A() (x, y int) + +-- d/d.go -- +package d + +func _() { + println(-f4()) //@ inline(re"f4", f4) +} + +func f4() int { return 2 + 2 } + +-- f4 -- +package d + +func _() { + println(-(2 + 2)) //@ inline(re"f4", f4) +} + +func f4() int { return 2 + 2 } diff --git a/contribs/gnopls/internal/refactor/inline/testdata/param-subst.txtar b/contribs/gnopls/internal/refactor/inline/testdata/param-subst.txtar new file mode 100644 index 00000000000..b6e462d7e71 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/param-subst.txtar @@ -0,0 +1,19 @@ +Test of parameter substitution. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a0.go -- +package a + +var _ = add(2, 1+1) //@ inline(re"add", add) + +func add(x, y int) int { return x + 2*y } + +-- add -- +package a + +var _ = 2 + 2*(1+1) //@ inline(re"add", add) + +func add(x, y int) int { return x + 2*y } \ No newline at end of file diff --git a/contribs/gnopls/internal/refactor/inline/testdata/revdotimport.txtar b/contribs/gnopls/internal/refactor/inline/testdata/revdotimport.txtar new file mode 100644 index 00000000000..f33304f9da3 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/revdotimport.txtar @@ -0,0 +1,42 @@ +Test of inlining a function into a context that already +dot-imports the necessary additional import. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +func A() {} + +-- b/b.go -- +package b + +import "testdata/a" + +func B() { a.A() } + +-- c/c.go -- +package c + +import . "testdata/a" +import "testdata/b" + +func _() { + A() + b.B() //@ inline(re"B", result) +} + +-- result -- +package c + +import ( + "testdata/a" + . "testdata/a" +) + +func _() { + A() + a.A() //@ inline(re"B", result) +} diff --git a/contribs/gnopls/internal/refactor/inline/testdata/std-internal.txtar b/contribs/gnopls/internal/refactor/inline/testdata/std-internal.txtar new file mode 100644 index 00000000000..0077bb0aa47 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/std-internal.txtar @@ -0,0 +1,15 @@ + +std packages are a special case of the internal package check. + +This test assumes that syscall.ByteSliceFromString refers to internal/bytealg. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a.go -- +package a + +import "syscall" + +var _, _ = syscall.ByteSliceFromString("") //@ inline(re"ByteSliceFromString", re`inaccessible package "internal/bytealg"`) diff --git a/contribs/gnopls/internal/refactor/inline/testdata/tailcall.txtar b/contribs/gnopls/internal/refactor/inline/testdata/tailcall.txtar new file mode 100644 index 00000000000..ccfe9f4d866 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/testdata/tailcall.txtar @@ -0,0 +1,120 @@ +Reduction of parameterless tail-call to functions. + +1. a0 (sum) is reduced, despite the complexity of the callee. + +2. a1 (conflict) is not reduced, because the caller and callee have + intersecting sets of labels. + +3. a2 (usesResult) is not reduced, because it refers to a result variable. + +-- go.mod -- +module testdata +go 1.12 + +-- a/a0.go -- +package a + +func _() int { + return sum(1, 2) //@ inline(re"sum", sum) +} + +func sum(lo, hi int) int { + total := 0 +start: + for i := lo; i <= hi; i++ { + total += i + if i == 6 { + goto start + } else if i == 7 { + return -1 + } + } + return total +} + +-- sum -- +package a + +func _() int { + total := 0 +start: + for i := 1; i <= 2; i++ { + total += i + if i == 6 { + goto start + } else if i == 7 { + return -1 + } + } + return total //@ inline(re"sum", sum) +} + +func sum(lo, hi int) int { + total := 0 +start: + for i := lo; i <= hi; i++ { + total += i + if i == 6 { + goto start + } else if i == 7 { + return -1 + } + } + return total +} + +-- a/a1.go -- +package a + +func _() int { + hello: + return conflict(1, 2) //@ inline(re"conflict", conflict) + goto hello +} + +func conflict(lo, hi int) int { +hello: + return lo + hi +} + +-- conflict -- +package a + +func _() int { +hello: + return func() int { + hello: + return 1 + 2 + }() //@ inline(re"conflict", conflict) + goto hello +} + +func conflict(lo, hi int) int { +hello: + return lo + hi +} + +-- a/a2.go -- +package a + +func _() int { + return usesResult(1, 2) //@ inline(re"usesResult", usesResult) +} + +func usesResult(lo, hi int) (z int) { + z = y + x + return +} + +-- usesResult -- +package a + +func _() int { + return func() (z int) { z = y + x; return }() //@ inline(re"usesResult", usesResult) +} + +func usesResult(lo, hi int) (z int) { + z = y + x + return +} + diff --git a/contribs/gnopls/internal/refactor/inline/util.go b/contribs/gnopls/internal/refactor/inline/util.go new file mode 100644 index 00000000000..f36f93914e1 --- /dev/null +++ b/contribs/gnopls/internal/refactor/inline/util.go @@ -0,0 +1,197 @@ +// 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 inline + +// This file defines various common helpers. + +import ( + "go/ast" + "go/constant" + "go/token" + "go/types" + "reflect" + "strings" + + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" +) + +func is[T any](x any) bool { + _, ok := x.(T) + return ok +} + +// TODO(adonovan): use go1.21's slices.Clone. +func clone[T any](slice []T) []T { return append([]T{}, slice...) } + +// TODO(adonovan): use go1.21's slices.Index. +func index[T comparable](slice []T, x T) int { + for i, elem := range slice { + if elem == x { + return i + } + } + return -1 +} + +func btoi(b bool) int { + if b { + return 1 + } else { + return 0 + } +} + +func offsetOf(fset *token.FileSet, pos token.Pos) int { + return fset.PositionFor(pos, false).Offset +} + +// objectKind returns an object's kind (e.g. var, func, const, typename). +func objectKind(obj types.Object) string { + return strings.TrimPrefix(strings.ToLower(reflect.TypeOf(obj).String()), "*types.") +} + +// within reports whether pos is within the half-open interval [n.Pos, n.End). +func within(pos token.Pos, n ast.Node) bool { + return n.Pos() <= pos && pos < n.End() +} + +// trivialConversion reports whether it is safe to omit the implicit +// value-to-variable conversion that occurs in argument passing or +// result return. The only case currently allowed is converting from +// untyped constant to its default type (e.g. 0 to int). +// +// The reason for this check is that converting from A to B to C may +// yield a different result than converting A directly to C: consider +// 0 to int32 to any. +// +// trivialConversion under-approximates trivial conversions, as unfortunately +// go/types does not record the type of an expression *before* it is implicitly +// converted, and therefore it cannot distinguish typed constant +// expressions from untyped constant expressions. For example, in the +// expression `c + 2`, where c is a uint32 constant, trivialConversion does not +// detect that the default type of this expression is actually uint32, not untyped +// int. +// +// We could, of course, do better here by reverse engineering some of go/types' +// constant handling. That may or may not be worthwhile. +// +// Example: in func f() int32 { return 0 }, +// the type recorded for 0 is int32, not untyped int; +// although it is Identical to the result var, +// the conversion is non-trivial. +func trivialConversion(fromValue constant.Value, from, to types.Type) bool { + if fromValue != nil { + var defaultType types.Type + switch fromValue.Kind() { + case constant.Bool: + defaultType = types.Typ[types.Bool] + case constant.String: + defaultType = types.Typ[types.String] + case constant.Int: + defaultType = types.Typ[types.Int] + case constant.Float: + defaultType = types.Typ[types.Float64] + case constant.Complex: + defaultType = types.Typ[types.Complex128] + default: + return false + } + return types.Identical(defaultType, to) + } + return types.Identical(from, to) +} + +func checkInfoFields(info *types.Info) { + assert(info.Defs != nil, "types.Info.Defs is nil") + assert(info.Implicits != nil, "types.Info.Implicits is nil") + assert(info.Scopes != nil, "types.Info.Scopes is nil") + assert(info.Selections != nil, "types.Info.Selections is nil") + assert(info.Types != nil, "types.Info.Types is nil") + assert(info.Uses != nil, "types.Info.Uses is nil") +} + +func funcHasTypeParams(decl *ast.FuncDecl) bool { + // generic function? + if decl.Type.TypeParams != nil { + return true + } + // method on generic type? + if decl.Recv != nil { + t := decl.Recv.List[0].Type + if u, ok := t.(*ast.StarExpr); ok { + t = u.X + } + return is[*ast.IndexExpr](t) || is[*ast.IndexListExpr](t) + } + return false +} + +// intersects reports whether the maps' key sets intersect. +func intersects[K comparable, T1, T2 any](x map[K]T1, y map[K]T2) bool { + if len(x) > len(y) { + return intersects(y, x) + } + for k := range x { + if _, ok := y[k]; ok { + return true + } + } + return false +} + +// convert returns syntax for the conversion T(x). +func convert(T, x ast.Expr) *ast.CallExpr { + // The formatter generally adds parens as needed, + // but before go1.22 it had a bug (#63362) for + // channel types that requires this workaround. + if ch, ok := T.(*ast.ChanType); ok && ch.Dir == ast.RECV { + T = &ast.ParenExpr{X: T} + } + return &ast.CallExpr{ + Fun: T, + Args: []ast.Expr{x}, + } +} + +// isPointer reports whether t's core type is a pointer. +func isPointer(t types.Type) bool { + return is[*types.Pointer](typeparams.CoreType(t)) +} + +// indirectSelection is like seln.Indirect() without bug #8353. +func indirectSelection(seln *types.Selection) bool { + // Work around bug #8353 in Selection.Indirect when Kind=MethodVal. + if seln.Kind() == types.MethodVal { + tArg, indirect := effectiveReceiver(seln) + if indirect { + return true + } + + tParam := seln.Obj().Type().Underlying().(*types.Signature).Recv().Type() + return isPointer(tArg) && !isPointer(tParam) // implicit * + } + + return seln.Indirect() +} + +// effectiveReceiver returns the effective type of the method +// receiver after all implicit field selections (but not implicit * or +// & operations) have been applied. +// +// The boolean indicates whether any implicit field selection was indirect. +func effectiveReceiver(seln *types.Selection) (types.Type, bool) { + assert(seln.Kind() == types.MethodVal, "not MethodVal") + t := seln.Recv() + indices := seln.Index() + indirect := false + for _, index := range indices[:len(indices)-1] { + if isPointer(t) { + indirect = true + t = typeparams.MustDeref(t) + } + t = typeparams.CoreType(t).(*types.Struct).Field(index).Type() + } + return t, indirect +} diff --git a/contribs/gnopls/internal/robustio/copyfiles.go b/contribs/gnopls/internal/robustio/copyfiles.go new file mode 100644 index 00000000000..8c93fcd7163 --- /dev/null +++ b/contribs/gnopls/internal/robustio/copyfiles.go @@ -0,0 +1,117 @@ +// 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 ignore +// +build ignore + +// The copyfiles script copies the contents of the internal cmd/go robustio +// package to the current directory, with adjustments to make it build. +// +// NOTE: In retrospect this script got out of hand, as we have to perform +// various operations on the package to get it to build at old Go versions. If +// in the future it proves to be flaky, delete it and just copy code manually. +package main + +import ( + "bytes" + "go/build/constraint" + "go/scanner" + "go/token" + "log" + "os" + "path/filepath" + "runtime" + "strings" +) + +func main() { + dir := filepath.Join(runtime.GOROOT(), "src", "cmd", "go", "internal", "robustio") + + entries, err := os.ReadDir(dir) + if err != nil { + log.Fatalf("reading the robustio dir: %v", err) + } + + // Collect file content so that we can validate before copying. + fileContent := make(map[string][]byte) + windowsImport := []byte("\t\"internal/syscall/windows\"\n") + foundWindowsImport := false + for _, entry := range entries { + if strings.HasSuffix(entry.Name(), ".go") { + pth := filepath.Join(dir, entry.Name()) + content, err := os.ReadFile(pth) + if err != nil { + log.Fatalf("reading %q: %v", entry.Name(), err) + } + + // Replace the use of internal/syscall/windows.ERROR_SHARING_VIOLATION + // with a local constant. + if entry.Name() == "robustio_windows.go" && bytes.Contains(content, windowsImport) { + foundWindowsImport = true + content = bytes.Replace(content, windowsImport, nil, 1) + content = bytes.Replace(content, []byte("windows.ERROR_SHARING_VIOLATION"), []byte("ERROR_SHARING_VIOLATION"), -1) + } + + // Replace os.ReadFile with os.ReadFile (for 1.15 and older). We + // attempt to match calls (via the '('), to avoid matching mentions of + // os.ReadFile in comments. + // + // TODO(rfindley): once we (shortly!) no longer support 1.15, remove + // this and break the build. + if bytes.Contains(content, []byte("os.ReadFile(")) { + content = bytes.Replace(content, []byte("\"os\""), []byte("\"io/ioutil\"\n\t\"os\""), 1) + content = bytes.Replace(content, []byte("os.ReadFile("), []byte("os.ReadFile("), -1) + } + + // Add +build constraints, for 1.16. + content = addPlusBuildConstraints(content) + + fileContent[entry.Name()] = content + } + } + + if !foundWindowsImport { + log.Fatal("missing expected import of internal/syscall/windows in robustio_windows.go") + } + + for name, content := range fileContent { + if err := os.WriteFile(name, content, 0644); err != nil { + log.Fatalf("writing %q: %v", name, err) + } + } +} + +// addPlusBuildConstraints splices in +build constraints for go:build +// constraints encountered in the source. +// +// Gopls still builds at Go 1.16, which requires +build constraints. +func addPlusBuildConstraints(src []byte) []byte { + var s scanner.Scanner + fset := token.NewFileSet() + file := fset.AddFile("", fset.Base(), len(src)) + s.Init(file, src, nil /* no error handler */, scanner.ScanComments) + + result := make([]byte, 0, len(src)) + lastInsertion := 0 + for { + pos, tok, lit := s.Scan() + if tok == token.EOF { + break + } + if tok == token.COMMENT { + if c, err := constraint.Parse(lit); err == nil { + plusBuild, err := constraint.PlusBuildLines(c) + if err != nil { + log.Fatalf("computing +build constraint for %q: %v", lit, err) + } + insertAt := file.Offset(pos) + len(lit) + result = append(result, src[lastInsertion:insertAt]...) + result = append(result, []byte("\n"+strings.Join(plusBuild, "\n"))...) + lastInsertion = insertAt + } + } + } + result = append(result, src[lastInsertion:]...) + return result +} diff --git a/contribs/gnopls/internal/robustio/gopls_windows.go b/contribs/gnopls/internal/robustio/gopls_windows.go new file mode 100644 index 00000000000..949f2781619 --- /dev/null +++ b/contribs/gnopls/internal/robustio/gopls_windows.go @@ -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 robustio + +import "syscall" + +// The robustio package is copied from cmd/go/internal/robustio, a package used +// by the go command to retry known flaky operations on certain operating systems. + +//go:generate go run copyfiles.go + +// Since the gopls module cannot access internal/syscall/windows, copy a +// necessary constant. +const ERROR_SHARING_VIOLATION syscall.Errno = 32 diff --git a/contribs/gnopls/internal/robustio/robustio.go b/contribs/gnopls/internal/robustio/robustio.go new file mode 100644 index 00000000000..0a559fc9b80 --- /dev/null +++ b/contribs/gnopls/internal/robustio/robustio.go @@ -0,0 +1,69 @@ +// 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. + +// Package robustio wraps I/O functions that are prone to failure on Windows, +// transparently retrying errors up to an arbitrary timeout. +// +// Errors are classified heuristically and retries are bounded, so the functions +// in this package do not completely eliminate spurious errors. However, they do +// significantly reduce the rate of failure in practice. +// +// If so, the error will likely wrap one of: +// The functions in this package do not completely eliminate spurious errors, +// but substantially reduce their rate of occurrence in practice. +package robustio + +import "time" + +// Rename is like os.Rename, but on Windows retries errors that may occur if the +// file is concurrently read or overwritten. +// +// (See golang.org/issue/31247 and golang.org/issue/32188.) +func Rename(oldpath, newpath string) error { + return rename(oldpath, newpath) +} + +// ReadFile is like os.ReadFile, but on Windows retries errors that may +// occur if the file is concurrently replaced. +// +// (See golang.org/issue/31247 and golang.org/issue/32188.) +func ReadFile(filename string) ([]byte, error) { + return readFile(filename) +} + +// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur +// if an executable file in the directory has recently been executed. +// +// (See golang.org/issue/19491.) +func RemoveAll(path string) error { + return removeAll(path) +} + +// IsEphemeralError reports whether err is one of the errors that the functions +// in this package attempt to mitigate. +// +// Errors considered ephemeral include: +// - syscall.ERROR_ACCESS_DENIED +// - syscall.ERROR_FILE_NOT_FOUND +// - internal/syscall/windows.ERROR_SHARING_VIOLATION +// +// This set may be expanded in the future; programs must not rely on the +// non-ephemerality of any given error. +func IsEphemeralError(err error) bool { + return isEphemeralError(err) +} + +// A FileID uniquely identifies a file in the file system. +// +// If GetFileID(name1) returns the same ID as GetFileID(name2), the two file +// names denote the same file. +// A FileID is comparable, and thus suitable for use as a map key. +type FileID struct { + device, inode uint64 +} + +// GetFileID returns the file system's identifier for the file, and its +// modification time. +// Like os.Stat, it reads through symbolic links. +func GetFileID(filename string) (FileID, time.Time, error) { return getFileID(filename) } diff --git a/contribs/gnopls/internal/robustio/robustio_darwin.go b/contribs/gnopls/internal/robustio/robustio_darwin.go new file mode 100644 index 00000000000..99fd8ebc2ff --- /dev/null +++ b/contribs/gnopls/internal/robustio/robustio_darwin.go @@ -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. + +package robustio + +import ( + "errors" + "syscall" +) + +const errFileNotFound = syscall.ENOENT + +// isEphemeralError returns true if err may be resolved by waiting. +func isEphemeralError(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + return errno == errFileNotFound + } + return false +} diff --git a/contribs/gnopls/internal/robustio/robustio_flaky.go b/contribs/gnopls/internal/robustio/robustio_flaky.go new file mode 100644 index 00000000000..d5c241857b4 --- /dev/null +++ b/contribs/gnopls/internal/robustio/robustio_flaky.go @@ -0,0 +1,92 @@ +// 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. + +//go:build windows || darwin +// +build windows darwin + +package robustio + +import ( + "errors" + "math/rand" + "os" + "syscall" + "time" +) + +const arbitraryTimeout = 2000 * time.Millisecond + +// retry retries ephemeral errors from f up to an arbitrary timeout +// to work around filesystem flakiness on Windows and Darwin. +func retry(f func() (err error, mayRetry bool)) error { + var ( + bestErr error + lowestErrno syscall.Errno + start time.Time + nextSleep time.Duration = 1 * time.Millisecond + ) + for { + err, mayRetry := f() + if err == nil || !mayRetry { + return err + } + + var errno syscall.Errno + if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) { + bestErr = err + lowestErrno = errno + } else if bestErr == nil { + bestErr = err + } + + if start.IsZero() { + start = time.Now() + } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout { + break + } + time.Sleep(nextSleep) + nextSleep += time.Duration(rand.Int63n(int64(nextSleep))) + } + + return bestErr +} + +// rename is like os.Rename, but retries ephemeral errors. +// +// On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with +// MOVEFILE_REPLACE_EXISTING. +// +// Windows also provides a different system call, ReplaceFile, +// that provides similar semantics, but perhaps preserves more metadata. (The +// documentation on the differences between the two is very sparse.) +// +// Empirical error rates with MoveFileEx are lower under modest concurrency, so +// for now we're sticking with what the os package already provides. +func rename(oldpath, newpath string) (err error) { + return retry(func() (err error, mayRetry bool) { + err = os.Rename(oldpath, newpath) + return err, isEphemeralError(err) + }) +} + +// readFile is like os.ReadFile, but retries ephemeral errors. +func readFile(filename string) ([]byte, error) { + var b []byte + err := retry(func() (err error, mayRetry bool) { + b, err = os.ReadFile(filename) + + // Unlike in rename, we do not retry errFileNotFound here: it can occur + // as a spurious error, but the file may also genuinely not exist, so the + // increase in robustness is probably not worth the extra latency. + return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound) + }) + return b, err +} + +func removeAll(path string) error { + return retry(func() (err error, mayRetry bool) { + err = os.RemoveAll(path) + return err, isEphemeralError(err) + }) +} diff --git a/contribs/gnopls/internal/robustio/robustio_other.go b/contribs/gnopls/internal/robustio/robustio_other.go new file mode 100644 index 00000000000..3a20cac6cf8 --- /dev/null +++ b/contribs/gnopls/internal/robustio/robustio_other.go @@ -0,0 +1,28 @@ +// 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. + +//go:build !windows && !darwin +// +build !windows,!darwin + +package robustio + +import ( + "os" +) + +func rename(oldpath, newpath string) error { + return os.Rename(oldpath, newpath) +} + +func readFile(filename string) ([]byte, error) { + return os.ReadFile(filename) +} + +func removeAll(path string) error { + return os.RemoveAll(path) +} + +func isEphemeralError(err error) bool { + return false +} diff --git a/contribs/gnopls/internal/robustio/robustio_plan9.go b/contribs/gnopls/internal/robustio/robustio_plan9.go new file mode 100644 index 00000000000..9fa4cacb5a3 --- /dev/null +++ b/contribs/gnopls/internal/robustio/robustio_plan9.go @@ -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 plan9 +// +build plan9 + +package robustio + +import ( + "os" + "syscall" + "time" +) + +func getFileID(filename string) (FileID, time.Time, error) { + fi, err := os.Stat(filename) + if err != nil { + return FileID{}, time.Time{}, err + } + dir := fi.Sys().(*syscall.Dir) + return FileID{ + device: uint64(dir.Type)<<32 | uint64(dir.Dev), + inode: dir.Qid.Path, + }, fi.ModTime(), nil +} diff --git a/contribs/gnopls/internal/robustio/robustio_posix.go b/contribs/gnopls/internal/robustio/robustio_posix.go new file mode 100644 index 00000000000..cf74865d0b5 --- /dev/null +++ b/contribs/gnopls/internal/robustio/robustio_posix.go @@ -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 !windows && !plan9 +// +build !windows,!plan9 + +package robustio + +import ( + "os" + "syscall" + "time" +) + +func getFileID(filename string) (FileID, time.Time, error) { + fi, err := os.Stat(filename) + if err != nil { + return FileID{}, time.Time{}, err + } + stat := fi.Sys().(*syscall.Stat_t) + return FileID{ + device: uint64(stat.Dev), // (int32 on darwin, uint64 on linux) + inode: stat.Ino, + }, fi.ModTime(), nil +} diff --git a/contribs/gnopls/internal/robustio/robustio_test.go b/contribs/gnopls/internal/robustio/robustio_test.go new file mode 100644 index 00000000000..5fc68ccb3e0 --- /dev/null +++ b/contribs/gnopls/internal/robustio/robustio_test.go @@ -0,0 +1,101 @@ +// 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 robustio_test + +import ( + "os" + "path/filepath" + "runtime" + "testing" + "time" + + "github.com/gnolang/gno/contribs/gnopls/internal/robustio" +) + +func checkOSLink(t *testing.T, err error) { + if err == nil { + return + } + + t.Helper() + switch runtime.GOOS { + case "aix", "darwin", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "solaris": + // Non-mobile OS known to always support os.Symlink and os.Link. + t.Fatal(err) + default: + t.Skipf("skipping due to error on %v: %v", runtime.GOOS, err) + } +} + +func TestFileInfo(t *testing.T) { + // A nonexistent file has no ID. + nonexistent := filepath.Join(t.TempDir(), "nonexistent") + if _, _, err := robustio.GetFileID(nonexistent); err == nil { + t.Fatalf("GetFileID(nonexistent) succeeded unexpectedly") + } + + // A regular file has an ID. + real := filepath.Join(t.TempDir(), "real") + if err := os.WriteFile(real, nil, 0644); err != nil { + t.Fatalf("can't create regular file: %v", err) + } + realID, realMtime, err := robustio.GetFileID(real) + if err != nil { + t.Fatalf("can't get ID of regular file: %v", err) + } + + // Sleep so that we get a new mtime for subsequent writes. + time.Sleep(2 * time.Second) + + // A second regular file has a different ID. + real2 := filepath.Join(t.TempDir(), "real2") + if err := os.WriteFile(real2, nil, 0644); err != nil { + t.Fatalf("can't create second regular file: %v", err) + } + real2ID, real2Mtime, err := robustio.GetFileID(real2) + if err != nil { + t.Fatalf("can't get ID of second regular file: %v", err) + } + if realID == real2ID { + t.Errorf("realID %+v == real2ID %+v", realID, real2ID) + } + if realMtime.Equal(real2Mtime) { + t.Errorf("realMtime %v == real2Mtime %v", realMtime, real2Mtime) + } + + // A symbolic link has the same ID as its target. + t.Run("symlink", func(t *testing.T) { + symlink := filepath.Join(t.TempDir(), "symlink") + checkOSLink(t, os.Symlink(real, symlink)) + + symlinkID, symlinkMtime, err := robustio.GetFileID(symlink) + if err != nil { + t.Fatalf("can't get ID of symbolic link: %v", err) + } + if realID != symlinkID { + t.Errorf("realID %+v != symlinkID %+v", realID, symlinkID) + } + if !realMtime.Equal(symlinkMtime) { + t.Errorf("realMtime %v != symlinkMtime %v", realMtime, symlinkMtime) + } + }) + + // Two hard-linked files have the same ID. + t.Run("hardlink", func(t *testing.T) { + hardlink := filepath.Join(t.TempDir(), "hardlink") + checkOSLink(t, os.Link(real, hardlink)) + + hardlinkID, hardlinkMtime, err := robustio.GetFileID(hardlink) + if err != nil { + t.Fatalf("can't get ID of hard link: %v", err) + } + if realID != hardlinkID { + t.Errorf("realID %+v != hardlinkID %+v", realID, hardlinkID) + } + if !realMtime.Equal(hardlinkMtime) { + t.Errorf("realMtime %v != hardlinkMtime %v", realMtime, hardlinkMtime) + } + }) +} diff --git a/contribs/gnopls/internal/robustio/robustio_windows.go b/contribs/gnopls/internal/robustio/robustio_windows.go new file mode 100644 index 00000000000..616c32883d6 --- /dev/null +++ b/contribs/gnopls/internal/robustio/robustio_windows.go @@ -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. + +package robustio + +import ( + "errors" + "syscall" + "time" +) + +const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND + +// isEphemeralError returns true if err may be resolved by waiting. +func isEphemeralError(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + switch errno { + case syscall.ERROR_ACCESS_DENIED, + syscall.ERROR_FILE_NOT_FOUND, + ERROR_SHARING_VIOLATION: + return true + } + } + return false +} + +// Note: it may be convenient to have this helper return fs.FileInfo, but +// implementing this is actually quite involved on Windows. Since we only +// currently use mtime, keep it simple. +func getFileID(filename string) (FileID, time.Time, error) { + filename16, err := syscall.UTF16PtrFromString(filename) + if err != nil { + return FileID{}, time.Time{}, err + } + h, err := syscall.CreateFile(filename16, 0, 0, nil, syscall.OPEN_EXISTING, uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS), 0) + if err != nil { + return FileID{}, time.Time{}, err + } + defer syscall.CloseHandle(h) + var i syscall.ByHandleFileInformation + if err := syscall.GetFileInformationByHandle(h, &i); err != nil { + return FileID{}, time.Time{}, err + } + mtime := time.Unix(0, i.LastWriteTime.Nanoseconds()) + return FileID{ + device: uint64(i.VolumeSerialNumber), + inode: uint64(i.FileIndexHigh)<<32 | uint64(i.FileIndexLow), + }, mtime, nil +} diff --git a/contribs/gnopls/internal/server/call_hierarchy.go b/contribs/gnopls/internal/server/call_hierarchy.go index 671d4f8c81c..83070772e6b 100644 --- a/contribs/gnopls/internal/server/call_hierarchy.go +++ b/contribs/gnopls/internal/server/call_hierarchy.go @@ -7,10 +7,10 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func (s *server) PrepareCallHierarchy(ctx context.Context, params *protocol.CallHierarchyPrepareParams) ([]protocol.CallHierarchyItem, error) { @@ -22,7 +22,7 @@ func (s *server) PrepareCallHierarchy(ctx context.Context, params *protocol.Call return nil, err } defer release() - if snapshot.FileKind(fh) != file.Go { + if snapshot.FileKind(fh) != file.Gno { return nil, nil // empty result } return golang.PrepareCallHierarchy(ctx, snapshot, fh, params.Position) @@ -37,7 +37,7 @@ func (s *server) IncomingCalls(ctx context.Context, params *protocol.CallHierarc return nil, err } defer release() - if snapshot.FileKind(fh) != file.Go { + if snapshot.FileKind(fh) != file.Gno { return nil, nil // empty result } return golang.IncomingCalls(ctx, snapshot, fh, params.Item.Range.Start) @@ -52,7 +52,7 @@ func (s *server) OutgoingCalls(ctx context.Context, params *protocol.CallHierarc return nil, err } defer release() - if snapshot.FileKind(fh) != file.Go { + if snapshot.FileKind(fh) != file.Gno { return nil, nil // empty result } return golang.OutgoingCalls(ctx, snapshot, fh, params.Item.Range.Start) diff --git a/contribs/gnopls/internal/server/code_action.go b/contribs/gnopls/internal/server/code_action.go index 5fc0fb6aa21..0ba86dacb21 100644 --- a/contribs/gnopls/internal/server/code_action.go +++ b/contribs/gnopls/internal/server/code_action.go @@ -11,14 +11,14 @@ import ( "sort" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/mod" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/mod" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" ) func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { @@ -125,7 +125,7 @@ func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionPara return actions, nil - case file.Go: + case file.Gno: // diagnostic-bundled code actions // // The diagnostics already have a UI presence (e.g. squiggly underline); diff --git a/contribs/gnopls/internal/server/code_lens.go b/contribs/gnopls/internal/server/code_lens.go index 67b359e866c..2e602c70a71 100644 --- a/contribs/gnopls/internal/server/code_lens.go +++ b/contribs/gnopls/internal/server/code_lens.go @@ -9,14 +9,14 @@ import ( "fmt" "sort" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/mod" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/mod" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" ) // CodeLens reports the set of available CodeLenses @@ -35,7 +35,7 @@ func (s *server) CodeLens(ctx context.Context, params *protocol.CodeLensParams) switch snapshot.FileKind(fh) { case file.Mod: lensFuncs = mod.CodeLensSources() - case file.Go: + case file.Gno: lensFuncs = golang.CodeLensSources() default: // Unsupported file kind for a code lens. diff --git a/contribs/gnopls/internal/server/command.go b/contribs/gnopls/internal/server/command.go index 352008ac43b..4cf6bfadff4 100644 --- a/contribs/gnopls/internal/server/command.go +++ b/contribs/gnopls/internal/server/command.go @@ -22,27 +22,27 @@ import ( "strings" "sync" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/progress" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/scan" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" "golang.org/x/mod/modfile" "golang.org/x/telemetry/counter" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/progress" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/vulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/scan" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/tokeninternal" - "golang.org/x/tools/internal/xcontext" ) func (s *server) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { @@ -1577,7 +1577,7 @@ func (c *commandHandler) DiagnoseFiles(ctx context.Context, args command.Diagnos if err != nil { return err } - if snapshots[snapshot] || snapshot.FileKind(fh) != file.Go { + if snapshots[snapshot] || snapshot.FileKind(fh) != file.Gno { release() continue } diff --git a/contribs/gnopls/internal/server/completion.go b/contribs/gnopls/internal/server/completion.go index 079db865fb5..a3d5f8a8dc8 100644 --- a/contribs/gnopls/internal/server/completion.go +++ b/contribs/gnopls/internal/server/completion.go @@ -9,16 +9,16 @@ import ( "fmt" "strings" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/golang/completion" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/telemetry" - "golang.org/x/tools/gopls/internal/template" - "golang.org/x/tools/gopls/internal/work" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/golang/completion" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/telemetry" + "github.com/gnolang/gno/contribs/gnopls/internal/template" + "github.com/gnolang/gno/contribs/gnopls/internal/work" ) func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (_ *protocol.CompletionList, rerr error) { @@ -39,7 +39,7 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara var candidates []completion.CompletionItem var surrounding *completion.Selection switch snapshot.FileKind(fh) { - case file.Go: + case file.Gno: candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context) case file.Mod: candidates, surrounding = nil, nil @@ -77,7 +77,7 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara if err != nil { return nil, err } - if snapshot.FileKind(fh) == file.Go { + if snapshot.FileKind(fh) == file.Gno { s.saveLastCompletion(fh.URI(), fh.Version(), items, params.Position) } @@ -158,7 +158,7 @@ func toProtocolCompletionItems(candidates []completion.CompletionItem, surroundi // Insert and Replace ranges share the same start position and // the same text edit but the end position may differ. // See the comment for the CompletionItem's TextEdit field. - // https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol#CompletionItem + // https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/protocol#CompletionItem edits = &protocol.Or_CompletionItem_textEdit{ Value: protocol.InsertReplaceEdit{ NewText: insertText, diff --git a/contribs/gnopls/internal/server/definition.go b/contribs/gnopls/internal/server/definition.go index 7b4df3c7c07..c8bb89f6176 100644 --- a/contribs/gnopls/internal/server/definition.go +++ b/contribs/gnopls/internal/server/definition.go @@ -8,13 +8,13 @@ import ( "context" "fmt" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/telemetry" - "golang.org/x/tools/gopls/internal/template" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/telemetry" + "github.com/gnolang/gno/contribs/gnopls/internal/template" ) func (s *server) Definition(ctx context.Context, params *protocol.DefinitionParams) (_ []protocol.Location, rerr error) { @@ -35,7 +35,7 @@ func (s *server) Definition(ctx context.Context, params *protocol.DefinitionPara switch kind := snapshot.FileKind(fh); kind { case file.Tmpl: return template.Definition(snapshot, fh, params.Position) - case file.Go: + case file.Gno: return golang.Definition(ctx, snapshot, fh, params.Position) default: return nil, fmt.Errorf("can't find definitions for file type %s", kind) @@ -53,7 +53,7 @@ func (s *server) TypeDefinition(ctx context.Context, params *protocol.TypeDefini } defer release() switch kind := snapshot.FileKind(fh); kind { - case file.Go: + case file.Gno: return golang.TypeDefinition(ctx, snapshot, fh, params.Position) default: return nil, fmt.Errorf("can't find type definitions for file type %s", kind) diff --git a/contribs/gnopls/internal/server/diagnostics.go b/contribs/gnopls/internal/server/diagnostics.go index a4466e2fc76..e5692b67e54 100644 --- a/contribs/gnopls/internal/server/diagnostics.go +++ b/contribs/gnopls/internal/server/diagnostics.go @@ -17,19 +17,19 @@ import ( "sync" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/mod" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/template" - "golang.org/x/tools/gopls/internal/util/moremaps" - "golang.org/x/tools/gopls/internal/work" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/event/keys" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/mod" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/template" + "github.com/gnolang/gno/contribs/gnopls/internal/util/moremaps" + "github.com/gnolang/gno/contribs/gnopls/internal/work" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event/keys" ) // fileDiagnostics holds the current state of published diagnostics for a file. diff --git a/contribs/gnopls/internal/server/folding_range.go b/contribs/gnopls/internal/server/folding_range.go index 0ad00e54c8d..ba24a41c922 100644 --- a/contribs/gnopls/internal/server/folding_range.go +++ b/contribs/gnopls/internal/server/folding_range.go @@ -7,11 +7,11 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func (s *server) FoldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) { @@ -23,7 +23,7 @@ func (s *server) FoldingRange(ctx context.Context, params *protocol.FoldingRange return nil, err } defer release() - if snapshot.FileKind(fh) != file.Go { + if snapshot.FileKind(fh) != file.Gno { return nil, nil // empty result } ranges, err := golang.FoldingRange(ctx, snapshot, fh, snapshot.Options().LineFoldingOnly) diff --git a/contribs/gnopls/internal/server/format.go b/contribs/gnopls/internal/server/format.go index 1e6344dcff4..7c361363301 100644 --- a/contribs/gnopls/internal/server/format.go +++ b/contribs/gnopls/internal/server/format.go @@ -7,13 +7,13 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/mod" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/work" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/mod" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/work" ) func (s *server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) { @@ -29,7 +29,7 @@ func (s *server) Formatting(ctx context.Context, params *protocol.DocumentFormat switch snapshot.FileKind(fh) { case file.Mod: return mod.Format(ctx, snapshot, fh) - case file.Go: + case file.Gno: return golang.Format(ctx, snapshot, fh) case file.Work: return work.Format(ctx, snapshot, fh) diff --git a/contribs/gnopls/internal/server/general.go b/contribs/gnopls/internal/server/general.go index e330bd5bbc3..529ea2c2538 100644 --- a/contribs/gnopls/internal/server/general.go +++ b/contribs/gnopls/internal/server/general.go @@ -21,17 +21,17 @@ import ( "sync" "golang.org/x/telemetry/counter" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug" - debuglog "golang.org/x/tools/gopls/internal/debug/log" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/goversion" - "golang.org/x/tools/gopls/internal/util/moremaps" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + debuglog "github.com/gnolang/gno/contribs/gnopls/internal/debug/log" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/goversion" + "github.com/gnolang/gno/contribs/gnopls/internal/util/moremaps" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { diff --git a/contribs/gnopls/internal/server/highlight.go b/contribs/gnopls/internal/server/highlight.go index 35ffc2db2f5..b18bde87238 100644 --- a/contribs/gnopls/internal/server/highlight.go +++ b/contribs/gnopls/internal/server/highlight.go @@ -7,12 +7,12 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/template" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/template" ) func (s *server) DocumentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) { @@ -28,7 +28,7 @@ func (s *server) DocumentHighlight(ctx context.Context, params *protocol.Documen switch snapshot.FileKind(fh) { case file.Tmpl: return template.Highlight(ctx, snapshot, fh, params.Position) - case file.Go: + case file.Gno: rngs, err := golang.Highlight(ctx, snapshot, fh, params.Position) if err != nil { event.Error(ctx, "no highlight", err) diff --git a/contribs/gnopls/internal/server/hover.go b/contribs/gnopls/internal/server/hover.go index 80c35c09565..ead5b691bca 100644 --- a/contribs/gnopls/internal/server/hover.go +++ b/contribs/gnopls/internal/server/hover.go @@ -7,16 +7,16 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/mod" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/telemetry" - "golang.org/x/tools/gopls/internal/template" - "golang.org/x/tools/gopls/internal/work" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/mod" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/telemetry" + "github.com/gnolang/gno/contribs/gnopls/internal/template" + "github.com/gnolang/gno/contribs/gnopls/internal/work" ) func (s *server) Hover(ctx context.Context, params *protocol.HoverParams) (_ *protocol.Hover, rerr error) { @@ -37,7 +37,7 @@ func (s *server) Hover(ctx context.Context, params *protocol.HoverParams) (_ *pr switch snapshot.FileKind(fh) { case file.Mod: return mod.Hover(ctx, snapshot, fh, params.Position) - case file.Go: + case file.Gno: var pkgURL func(path golang.PackagePath, fragment string) protocol.URI if snapshot.Options().LinksInHover == settings.LinksInHover_Gopls { web, err := s.getWeb() diff --git a/contribs/gnopls/internal/server/implementation.go b/contribs/gnopls/internal/server/implementation.go index 9e61ebc4d88..1ac21daf60d 100644 --- a/contribs/gnopls/internal/server/implementation.go +++ b/contribs/gnopls/internal/server/implementation.go @@ -7,12 +7,12 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/telemetry" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/telemetry" ) func (s *server) Implementation(ctx context.Context, params *protocol.ImplementationParams) (_ []protocol.Location, rerr error) { @@ -29,7 +29,7 @@ func (s *server) Implementation(ctx context.Context, params *protocol.Implementa return nil, err } defer release() - if snapshot.FileKind(fh) != file.Go { + if snapshot.FileKind(fh) != file.Gno { return nil, nil // empty result } return golang.Implementation(ctx, snapshot, fh, params.Position) diff --git a/contribs/gnopls/internal/server/inlay_hint.go b/contribs/gnopls/internal/server/inlay_hint.go index fca8bcbc1c8..9be23df0b47 100644 --- a/contribs/gnopls/internal/server/inlay_hint.go +++ b/contribs/gnopls/internal/server/inlay_hint.go @@ -7,12 +7,12 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/mod" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/mod" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func (s *server) InlayHint(ctx context.Context, params *protocol.InlayHintParams) ([]protocol.InlayHint, error) { @@ -28,7 +28,7 @@ func (s *server) InlayHint(ctx context.Context, params *protocol.InlayHintParams switch snapshot.FileKind(fh) { case file.Mod: return mod.InlayHint(ctx, snapshot, fh, params.Range) - case file.Go: + case file.Gno: return golang.InlayHint(ctx, snapshot, fh, params.Range) } return nil, nil // empty result diff --git a/contribs/gnopls/internal/server/link.go b/contribs/gnopls/internal/server/link.go index 13097d89887..c797c47477d 100644 --- a/contribs/gnopls/internal/server/link.go +++ b/contribs/gnopls/internal/server/link.go @@ -15,16 +15,16 @@ import ( "strings" "sync" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" "golang.org/x/mod/modfile" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/event" "mvdan.cc/xurls/v2" ) @@ -41,7 +41,7 @@ func (s *server) DocumentLink(ctx context.Context, params *protocol.DocumentLink switch snapshot.FileKind(fh) { case file.Mod: links, err = modLinks(ctx, snapshot, fh) - case file.Go: + case file.Gno: links, err = goLinks(ctx, snapshot, fh) } // Don't return errors for document links. diff --git a/contribs/gnopls/internal/server/prompt.go b/contribs/gnopls/internal/server/prompt.go index 66329784a6f..d6e885687ac 100644 --- a/contribs/gnopls/internal/server/prompt.go +++ b/contribs/gnopls/internal/server/prompt.go @@ -15,8 +15,8 @@ import ( "golang.org/x/telemetry" "golang.org/x/telemetry/counter" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // promptTimeout is the amount of time we wait for an ongoing prompt before diff --git a/contribs/gnopls/internal/server/references.go b/contribs/gnopls/internal/server/references.go index f5019693946..95a6311c7f6 100644 --- a/contribs/gnopls/internal/server/references.go +++ b/contribs/gnopls/internal/server/references.go @@ -7,13 +7,13 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/telemetry" - "golang.org/x/tools/gopls/internal/template" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/telemetry" + "github.com/gnolang/gno/contribs/gnopls/internal/template" ) func (s *server) References(ctx context.Context, params *protocol.ReferenceParams) (_ []protocol.Location, rerr error) { @@ -33,7 +33,7 @@ func (s *server) References(ctx context.Context, params *protocol.ReferenceParam switch snapshot.FileKind(fh) { case file.Tmpl: return template.References(ctx, snapshot, fh, params) - case file.Go: + case file.Gno: return golang.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration) } return nil, nil // empty result diff --git a/contribs/gnopls/internal/server/rename.go b/contribs/gnopls/internal/server/rename.go index 93b2ac6f9c4..8109ff442e0 100644 --- a/contribs/gnopls/internal/server/rename.go +++ b/contribs/gnopls/internal/server/rename.go @@ -9,11 +9,11 @@ import ( "fmt" "path/filepath" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func (s *server) Rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) { @@ -26,7 +26,7 @@ func (s *server) Rename(ctx context.Context, params *protocol.RenameParams) (*pr } defer release() - if kind := snapshot.FileKind(fh); kind != file.Go { + if kind := snapshot.FileKind(fh); kind != file.Gno { return nil, fmt.Errorf("cannot rename in file of type %s", kind) } @@ -77,7 +77,7 @@ func (s *server) PrepareRename(ctx context.Context, params *protocol.PrepareRena } defer release() - if kind := snapshot.FileKind(fh); kind != file.Go { + if kind := snapshot.FileKind(fh); kind != file.Gno { return nil, fmt.Errorf("cannot rename in file of type %s", kind) } diff --git a/contribs/gnopls/internal/server/selection_range.go b/contribs/gnopls/internal/server/selection_range.go index 042812217f3..14cfd33dc26 100644 --- a/contribs/gnopls/internal/server/selection_range.go +++ b/contribs/gnopls/internal/server/selection_range.go @@ -8,11 +8,11 @@ import ( "context" "fmt" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/gopls/internal/cache/parsego" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" ) // selectionRange defines the textDocument/selectionRange feature, @@ -36,7 +36,7 @@ func (s *server) SelectionRange(ctx context.Context, params *protocol.SelectionR } defer release() - if kind := snapshot.FileKind(fh); kind != file.Go { + if kind := snapshot.FileKind(fh); kind != file.Gno { return nil, fmt.Errorf("SelectionRange not supported for file of type %s", kind) } diff --git a/contribs/gnopls/internal/server/semantic.go b/contribs/gnopls/internal/server/semantic.go index f746593a3dd..66c01a10846 100644 --- a/contribs/gnopls/internal/server/semantic.go +++ b/contribs/gnopls/internal/server/semantic.go @@ -7,12 +7,12 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/template" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/template" ) func (s *server) SemanticTokensFull(ctx context.Context, params *protocol.SemanticTokensParams) (*protocol.SemanticTokens, error) { @@ -37,7 +37,7 @@ func (s *server) semanticTokens(ctx context.Context, td protocol.TextDocumentIde switch snapshot.FileKind(fh) { case file.Tmpl: return template.SemanticTokens(ctx, snapshot, fh.URI()) - case file.Go: + case file.Gno: return golang.SemanticTokens(ctx, snapshot, fh, rng) } } diff --git a/contribs/gnopls/internal/server/server.go b/contribs/gnopls/internal/server/server.go index 80e64bb996c..7421c8b696c 100644 --- a/contribs/gnopls/internal/server/server.go +++ b/contribs/gnopls/internal/server/server.go @@ -22,14 +22,14 @@ import ( "strings" "sync" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cache/metadata" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/progress" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/progress" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // New creates an LSP server and binds it to handle incoming client diff --git a/contribs/gnopls/internal/server/signature_help.go b/contribs/gnopls/internal/server/signature_help.go index addcfe1e262..ee43b045cac 100644 --- a/contribs/gnopls/internal/server/signature_help.go +++ b/contribs/gnopls/internal/server/signature_help.go @@ -7,11 +7,11 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func (s *server) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { @@ -24,7 +24,7 @@ func (s *server) SignatureHelp(ctx context.Context, params *protocol.SignatureHe } defer release() - if snapshot.FileKind(fh) != file.Go { + if snapshot.FileKind(fh) != file.Gno { return nil, nil // empty result } diff --git a/contribs/gnopls/internal/server/symbols.go b/contribs/gnopls/internal/server/symbols.go index e35b2c75451..6d1d8a06597 100644 --- a/contribs/gnopls/internal/server/symbols.go +++ b/contribs/gnopls/internal/server/symbols.go @@ -7,12 +7,12 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/template" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/template" ) func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]any, error) { @@ -29,7 +29,7 @@ func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSy switch snapshot.FileKind(fh) { case file.Tmpl: docSymbols, err = template.DocumentSymbols(snapshot, fh) - case file.Go: + case file.Gno: docSymbols, err = golang.DocumentSymbols(ctx, snapshot, fh) default: return nil, nil // empty result diff --git a/contribs/gnopls/internal/server/text_synchronization.go b/contribs/gnopls/internal/server/text_synchronization.go index 257eadbbf41..2bcb43ec1a8 100644 --- a/contribs/gnopls/internal/server/text_synchronization.go +++ b/contribs/gnopls/internal/server/text_synchronization.go @@ -13,14 +13,14 @@ import ( "strings" "sync" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/label" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) // ModificationSource identifies the origin of a change. diff --git a/contribs/gnopls/internal/server/unimplemented.go b/contribs/gnopls/internal/server/unimplemented.go index c293ee167a7..4d3a87c5b05 100644 --- a/contribs/gnopls/internal/server/unimplemented.go +++ b/contribs/gnopls/internal/server/unimplemented.go @@ -10,8 +10,8 @@ import ( "context" "fmt" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" ) func (s *server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) { diff --git a/contribs/gnopls/internal/server/workspace.go b/contribs/gnopls/internal/server/workspace.go index 84e663c1049..454272bfe0c 100644 --- a/contribs/gnopls/internal/server/workspace.go +++ b/contribs/gnopls/internal/server/workspace.go @@ -11,10 +11,10 @@ import ( "strings" "sync" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func (s *server) DidChangeWorkspaceFolders(ctx context.Context, params *protocol.DidChangeWorkspaceFoldersParams) error { diff --git a/contribs/gnopls/internal/server/workspace_symbol.go b/contribs/gnopls/internal/server/workspace_symbol.go index 9eafeb015ad..861e2139737 100644 --- a/contribs/gnopls/internal/server/workspace_symbol.go +++ b/contribs/gnopls/internal/server/workspace_symbol.go @@ -7,11 +7,11 @@ package server import ( "context" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/golang" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/telemetry" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/golang" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/telemetry" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func (s *server) Symbol(ctx context.Context, params *protocol.WorkspaceSymbolParams) (_ []protocol.SymbolInformation, rerr error) { diff --git a/contribs/gnopls/internal/settings/analysis.go b/contribs/gnopls/internal/settings/analysis.go index 65ecb215c02..b202e87f6f4 100644 --- a/contribs/gnopls/internal/settings/analysis.go +++ b/contribs/gnopls/internal/settings/analysis.go @@ -45,22 +45,22 @@ import ( "golang.org/x/tools/go/analysis/passes/unsafeptr" "golang.org/x/tools/go/analysis/passes/unusedresult" "golang.org/x/tools/go/analysis/passes/unusedwrite" - "golang.org/x/tools/gopls/internal/analysis/deprecated" - "golang.org/x/tools/gopls/internal/analysis/embeddirective" - "golang.org/x/tools/gopls/internal/analysis/fillreturns" - "golang.org/x/tools/gopls/internal/analysis/infertypeargs" - "golang.org/x/tools/gopls/internal/analysis/nonewvars" - "golang.org/x/tools/gopls/internal/analysis/norangeoverfunc" - "golang.org/x/tools/gopls/internal/analysis/noresultvalues" - "golang.org/x/tools/gopls/internal/analysis/simplifycompositelit" - "golang.org/x/tools/gopls/internal/analysis/simplifyrange" - "golang.org/x/tools/gopls/internal/analysis/simplifyslice" - "golang.org/x/tools/gopls/internal/analysis/stubmethods" - "golang.org/x/tools/gopls/internal/analysis/undeclaredname" - "golang.org/x/tools/gopls/internal/analysis/unusedparams" - "golang.org/x/tools/gopls/internal/analysis/unusedvariable" - "golang.org/x/tools/gopls/internal/analysis/useany" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/deprecated" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/embeddirective" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/fillreturns" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/infertypeargs" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/nonewvars" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/norangeoverfunc" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/noresultvalues" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifycompositelit" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyrange" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/simplifyslice" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/stubmethods" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/undeclaredname" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedparams" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/unusedvariable" + "github.com/gnolang/gno/contribs/gnopls/internal/analysis/useany" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" "honnef.co/go/tools/staticcheck" ) diff --git a/contribs/gnopls/internal/settings/codeactionkind.go b/contribs/gnopls/internal/settings/codeactionkind.go index e8e29d8ddb8..30fe74cc209 100644 --- a/contribs/gnopls/internal/settings/codeactionkind.go +++ b/contribs/gnopls/internal/settings/codeactionkind.go @@ -4,7 +4,7 @@ package settings -import "golang.org/x/tools/gopls/internal/protocol" +import "github.com/gnolang/gno/contribs/gnopls/internal/protocol" // This file defines constants for non-standard CodeActions. diff --git a/contribs/gnopls/internal/settings/default.go b/contribs/gnopls/internal/settings/default.go index 25f3eae80f5..b773e0d67c4 100644 --- a/contribs/gnopls/internal/settings/default.go +++ b/contribs/gnopls/internal/settings/default.go @@ -8,9 +8,9 @@ import ( "sync" "time" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" ) var ( @@ -42,7 +42,7 @@ func DefaultOptions(overrides ...func(*Options)) *Options { }, ServerOptions: ServerOptions{ SupportedCodeActions: map[file.Kind]map[protocol.CodeActionKind]bool{ - file.Go: { + file.Gno: { // This should include specific leaves in the tree, // (e.g. refactor.inline.call) not generic branches // (e.g. refactor.inline or refactor). diff --git a/contribs/gnopls/internal/settings/settings.go b/contribs/gnopls/internal/settings/settings.go index 719d0690b5a..5df4454c360 100644 --- a/contribs/gnopls/internal/settings/settings.go +++ b/contribs/gnopls/internal/settings/settings.go @@ -11,9 +11,9 @@ import ( "strings" "time" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/util/frob" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/util/frob" ) type Annotation string diff --git a/contribs/gnopls/internal/settings/settings_test.go b/contribs/gnopls/internal/settings/settings_test.go index 6f865083a9d..9b92dea3988 100644 --- a/contribs/gnopls/internal/settings/settings_test.go +++ b/contribs/gnopls/internal/settings/settings_test.go @@ -10,8 +10,8 @@ import ( "time" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/clonetest" - . "golang.org/x/tools/gopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/clonetest" + . "github.com/gnolang/gno/contribs/gnopls/internal/settings" ) func TestDefaultsEquivalence(t *testing.T) { diff --git a/contribs/gnopls/internal/settings/staticcheck.go b/contribs/gnopls/internal/settings/staticcheck.go index fca3e55f17e..2d2163a060f 100644 --- a/contribs/gnopls/internal/settings/staticcheck.go +++ b/contribs/gnopls/internal/settings/staticcheck.go @@ -5,7 +5,7 @@ package settings import ( - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/quickfix" "honnef.co/go/tools/simple" diff --git a/contribs/gnopls/internal/settings/vet_test.go b/contribs/gnopls/internal/settings/vet_test.go index 56daf678c43..4fc19ec37a7 100644 --- a/contribs/gnopls/internal/settings/vet_test.go +++ b/contribs/gnopls/internal/settings/vet_test.go @@ -11,8 +11,8 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/doc" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/doc" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) // TestVetSuite ensures that gopls's analyser suite is a superset of vet's. diff --git a/contribs/gnopls/internal/stack/gostacks/gostacks.go b/contribs/gnopls/internal/stack/gostacks/gostacks.go new file mode 100644 index 00000000000..699471f202b --- /dev/null +++ b/contribs/gnopls/internal/stack/gostacks/gostacks.go @@ -0,0 +1,23 @@ +// 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. + +// The gostacks command processes stdin looking for things that look like +// stack traces and simplifying them to make the log more readable. +// It collates stack traces that have the same path as well as simplifying the +// individual lines of the trace. +// The processed log is printed to stdout. +package main + +import ( + "fmt" + "os" + + "golang.org/x/tools/internal/stack" +) + +func main() { + if err := stack.Process(os.Stdout, os.Stdin); err != nil { + fmt.Fprintln(os.Stderr, err) + } +} diff --git a/contribs/gnopls/internal/stack/parse.go b/contribs/gnopls/internal/stack/parse.go new file mode 100644 index 00000000000..e01da8f0eef --- /dev/null +++ b/contribs/gnopls/internal/stack/parse.go @@ -0,0 +1,175 @@ +// 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 stack + +import ( + "bufio" + "errors" + "io" + "regexp" + "strconv" +) + +var ( + reBlank = regexp.MustCompile(`^\s*$`) + reGoroutine = regexp.MustCompile(`^\s*goroutine (\d+) \[([^\]]*)\]:\s*$`) + reCall = regexp.MustCompile(`^\s*` + + `(created by )?` + //marker + `(([\w/.]+/)?[\w]+)\.` + //package + `(\(([^:.)]*)\)\.)?` + //optional type + `([\w\.]+)` + //function + `(\(.*\))?` + // args + `\s*$`) + rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`) + + errBreakParse = errors.New("break parse") +) + +// Scanner splits an input stream into lines in a way that is consumable by +// the parser. +type Scanner struct { + lines *bufio.Scanner + done bool +} + +// NewScanner creates a scanner on top of a reader. +func NewScanner(r io.Reader) *Scanner { + s := &Scanner{ + lines: bufio.NewScanner(r), + } + s.Skip() // prefill + return s +} + +// Peek returns the next line without consuming it. +func (s *Scanner) Peek() string { + if s.done { + return "" + } + return s.lines.Text() +} + +// Skip consumes the next line without looking at it. +// Normally used after it has already been looked at using Peek. +func (s *Scanner) Skip() { + if !s.lines.Scan() { + s.done = true + } +} + +// Next consumes and returns the next line. +func (s *Scanner) Next() string { + line := s.Peek() + s.Skip() + return line +} + +// Done returns true if the scanner has reached the end of the underlying +// stream. +func (s *Scanner) Done() bool { + return s.done +} + +// Err returns true if the scanner has reached the end of the underlying +// stream. +func (s *Scanner) Err() error { + return s.lines.Err() +} + +// Match returns the submatchs of the regular expression against the next line. +// If it matched the line is also consumed. +func (s *Scanner) Match(re *regexp.Regexp) []string { + if s.done { + return nil + } + match := re.FindStringSubmatch(s.Peek()) + if match != nil { + s.Skip() + } + return match +} + +// SkipBlank skips any number of pure whitespace lines. +func (s *Scanner) SkipBlank() { + for !s.done { + line := s.Peek() + if len(line) != 0 && !reBlank.MatchString(line) { + return + } + s.Skip() + } +} + +// Parse the current contiguous block of goroutine stack traces until the +// scanned content no longer matches. +func Parse(scanner *Scanner) (Dump, error) { + dump := Dump{} + for { + gr, ok := parseGoroutine(scanner) + if !ok { + return dump, nil + } + dump = append(dump, gr) + } +} + +func parseGoroutine(scanner *Scanner) (Goroutine, bool) { + match := scanner.Match(reGoroutine) + if match == nil { + return Goroutine{}, false + } + id, _ := strconv.ParseInt(match[1], 0, 32) + gr := Goroutine{ + ID: int(id), + State: match[2], + } + for { + frame, ok := parseFrame(scanner) + if !ok { + scanner.SkipBlank() + return gr, true + } + if frame.Position.Filename != "" { + gr.Stack = append(gr.Stack, frame) + } + } +} + +func parseFrame(scanner *Scanner) (Frame, bool) { + fun, ok := parseFunction(scanner) + if !ok { + return Frame{}, false + } + frame := Frame{ + Function: fun, + } + frame.Position, ok = parsePosition(scanner) + // if ok is false, then this is a broken state. + // we got the func but not the file that must follow + // the consumed line can be recovered from the frame + //TODO: push back the fun raw + return frame, ok +} + +func parseFunction(scanner *Scanner) (Function, bool) { + match := scanner.Match(reCall) + if match == nil { + return Function{}, false + } + return Function{ + Package: match[2], + Type: match[5], + Name: match[6], + }, true +} + +func parsePosition(scanner *Scanner) (Position, bool) { + match := scanner.Match(rePos) + if match == nil { + return Position{}, false + } + line, _ := strconv.ParseInt(match[2], 0, 32) + return Position{Filename: match[1], Line: int(line)}, true +} diff --git a/contribs/gnopls/internal/stack/process.go b/contribs/gnopls/internal/stack/process.go new file mode 100644 index 00000000000..8812de9521c --- /dev/null +++ b/contribs/gnopls/internal/stack/process.go @@ -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 stack + +import ( + "bytes" + "fmt" + "io" + "runtime" + "sort" +) + +// Capture get the current stack traces from the runtime. +func Capture() Dump { + buf := make([]byte, 2<<20) + buf = buf[:runtime.Stack(buf, true)] + scanner := NewScanner(bytes.NewReader(buf)) + dump, _ := Parse(scanner) + return dump +} + +// Summarize a dump for easier consumption. +// This collates goroutines with equivalent stacks. +func Summarize(dump Dump) Summary { + s := Summary{ + Total: len(dump), + } + for _, gr := range dump { + s.addGoroutine(gr) + } + return s +} + +// Process and input stream to an output stream, summarizing any stacks that +// are detected in place. +func Process(out io.Writer, in io.Reader) error { + scanner := NewScanner(in) + for { + dump, err := Parse(scanner) + summary := Summarize(dump) + switch { + case len(dump) > 0: + fmt.Fprintf(out, "%+v\n\n", summary) + case err != nil: + return err + case scanner.Done(): + return scanner.Err() + default: + // must have been a line that is not part of a dump + fmt.Fprintln(out, scanner.Next()) + } + } +} + +// Diff calculates the delta between two dumps. +func Diff(before, after Dump) Delta { + result := Delta{} + processed := make(map[int]bool) + for _, gr := range before { + processed[gr.ID] = false + } + for _, gr := range after { + if _, found := processed[gr.ID]; found { + result.Shared = append(result.Shared, gr) + } else { + result.After = append(result.After, gr) + } + processed[gr.ID] = true + } + for _, gr := range before { + if done := processed[gr.ID]; !done { + result.Before = append(result.Before, gr) + } + } + return result +} + +// TODO: do we want to allow contraction of stacks before comparison? +func (s *Summary) addGoroutine(gr Goroutine) { + index := sort.Search(len(s.Calls), func(i int) bool { + return !s.Calls[i].Stack.less(gr.Stack) + }) + if index >= len(s.Calls) || !s.Calls[index].Stack.equal(gr.Stack) { + // insert new stack, first increase the length + s.Calls = append(s.Calls, Call{}) + // move the top part upward to make space + copy(s.Calls[index+1:], s.Calls[index:]) + // insert the new call + s.Calls[index] = Call{ + Stack: gr.Stack, + } + } + // merge the goroutine into the matched call + s.Calls[index].merge(gr) +} + +// TODO: do we want other grouping strategies? +func (c *Call) merge(gr Goroutine) { + for i := range c.Groups { + canditate := &c.Groups[i] + if canditate.State == gr.State { + canditate.Goroutines = append(canditate.Goroutines, gr) + return + } + } + c.Groups = append(c.Groups, Group{ + State: gr.State, + Goroutines: []Goroutine{gr}, + }) +} diff --git a/contribs/gnopls/internal/stack/stack.go b/contribs/gnopls/internal/stack/stack.go new file mode 100644 index 00000000000..479301a78d5 --- /dev/null +++ b/contribs/gnopls/internal/stack/stack.go @@ -0,0 +1,170 @@ +// 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 stack provides support for parsing standard goroutine stack traces. +package stack + +import ( + "fmt" + "text/tabwriter" +) + +// Dump is a raw set of goroutines and their stacks. +type Dump []Goroutine + +// Goroutine is a single parsed goroutine dump. +type Goroutine struct { + State string // state that the goroutine is in. + ID int // id of the goroutine. + Stack Stack // call frames that make up the stack +} + +// Stack is a set of frames in a callstack. +type Stack []Frame + +// Frame is a point in a call stack. +type Frame struct { + Function Function + Position Position +} + +// Function is the function called at a frame. +type Function struct { + Package string // package name of function if known + Type string // if set function is a method of this type + Name string // function name of the frame +} + +// Position is the file position for a frame. +type Position struct { + Filename string // source filename + Line int // line number within file +} + +// Summary is a set of stacks processed and collated into Calls. +type Summary struct { + Total int // the total count of goroutines in the summary + Calls []Call // the collated stack traces +} + +// Call is set of goroutines that all share the same callstack. +// They will be grouped by state. +type Call struct { + Stack Stack // the shared callstack information + Groups []Group // the sets of goroutines with the same state +} + +// Group is a set of goroutines with the same stack that are in the same state. +type Group struct { + State string // the shared state of the goroutines + Goroutines []Goroutine // the set of goroutines in this group +} + +// Delta represents the difference between two stack dumps. +type Delta struct { + Before Dump // The goroutines that were only in the before set. + Shared Dump // The goroutines that were in both sets. + After Dump // The goroutines that were only in the after set. +} + +func (s Stack) equal(other Stack) bool { + if len(s) != len(other) { + return false + } + for i, frame := range s { + if !frame.equal(other[i]) { + return false + } + } + return true +} + +func (s Stack) less(other Stack) bool { + for i, frame := range s { + if i >= len(other) { + return false + } + if frame.less(other[i]) { + return true + } + if !frame.equal(other[i]) { + return false + } + } + return len(s) < len(other) +} + +func (f Frame) equal(other Frame) bool { + return f.Position.equal(other.Position) +} + +func (f Frame) less(other Frame) bool { + return f.Position.less(other.Position) +} + +func (p Position) equal(other Position) bool { + return p.Filename == other.Filename && p.Line == other.Line +} + +func (p Position) less(other Position) bool { + if p.Filename < other.Filename { + return true + } + if p.Filename > other.Filename { + return false + } + return p.Line < other.Line +} + +func (s Summary) Format(w fmt.State, r rune) { + tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) + for i, c := range s.Calls { + if i > 0 { + fmt.Fprintf(tw, "\n\n") + tw.Flush() + } + fmt.Fprint(tw, c) + } + tw.Flush() + if s.Total > 0 && w.Flag('+') { + fmt.Fprintf(w, "\n\n%d goroutines, %d unique", s.Total, len(s.Calls)) + } +} + +func (c Call) Format(w fmt.State, r rune) { + for i, g := range c.Groups { + if i > 0 { + fmt.Fprint(w, " ") + } + fmt.Fprint(w, g) + } + for _, f := range c.Stack { + fmt.Fprintf(w, "\n%v", f) + } +} + +func (g Group) Format(w fmt.State, r rune) { + fmt.Fprintf(w, "[%v]: ", g.State) + for i, gr := range g.Goroutines { + if i > 0 { + fmt.Fprint(w, ", ") + } + fmt.Fprintf(w, "$%d", gr.ID) + } +} + +func (f Frame) Format(w fmt.State, c rune) { + fmt.Fprintf(w, "%v:\t%v", f.Position, f.Function) +} + +func (f Function) Format(w fmt.State, c rune) { + if f.Type != "" { + fmt.Fprintf(w, "(%v).", f.Type) + } + fmt.Fprintf(w, "%v", f.Name) +} + +func (p Position) Format(w fmt.State, c rune) { + fmt.Fprintf(w, "%v:%v", p.Filename, p.Line) +} diff --git a/contribs/gnopls/internal/stack/stack_test.go b/contribs/gnopls/internal/stack/stack_test.go new file mode 100644 index 00000000000..371492aebea --- /dev/null +++ b/contribs/gnopls/internal/stack/stack_test.go @@ -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 stack_test + +import ( + "bytes" + "strings" + "testing" + + "golang.org/x/tools/internal/stack" +) + +func TestProcess(t *testing.T) { + for _, test := range []struct{ name, input, expect string }{{ + name: `empty`, + input: ``, + expect: ``, + }, { + name: `no_frame`, + input: `goroutine 1 [running]:`, + expect: ` +[running]: $1 + +1 goroutines, 1 unique +`, + }, { + name: `one_frame`, + input: ` +goroutine 1 [running]: +package.function(args) + file.go:10 +`, + expect: ` +[running]: $1 +file.go:10: function + +1 goroutines, 1 unique +`, + }, { + name: `one_call`, + input: ` +goroutine 1 [running]: +package1.functionA(args) + file1.go:10 +package2.functionB(args) + file2.go:20 +package3.functionC(args) + file3.go:30 +`, + expect: ` +[running]: $1 +file1.go:10: functionA +file2.go:20: functionB +file3.go:30: functionC + +1 goroutines, 1 unique +`, + }, { + name: `two_call`, + input: ` +goroutine 1 [running]: +package1.functionA(args) + file1.go:10 +goroutine 2 [running]: +package2.functionB(args) + file2.go:20 +`, + expect: ` +[running]: $1 +file1.go:10: functionA + +[running]: $2 +file2.go:20: functionB + +2 goroutines, 2 unique +`, + }, { + name: `merge_call`, + input: ` +goroutine 1 [running]: +package1.functionA(args) + file1.go:10 +goroutine 2 [running]: +package1.functionA(args) + file1.go:10 +`, + expect: ` +[running]: $1, $2 +file1.go:10: functionA + +2 goroutines, 1 unique +`, + }, { + name: `alternating_call`, + input: ` +goroutine 1 [running]: +package1.functionA(args) + file1.go:10 +goroutine 2 [running]: +package2.functionB(args) + file2.go:20 +goroutine 3 [running]: +package1.functionA(args) + file1.go:10 +goroutine 4 [running]: +package2.functionB(args) + file2.go:20 +goroutine 5 [running]: +package1.functionA(args) + file1.go:10 +goroutine 6 [running]: +package2.functionB(args) + file2.go:20 +`, + expect: ` +[running]: $1, $3, $5 +file1.go:10: functionA + +[running]: $2, $4, $6 +file2.go:20: functionB + +6 goroutines, 2 unique +`, + }, { + name: `sort_calls`, + input: ` +goroutine 1 [running]: +package3.functionC(args) + file3.go:30 +goroutine 2 [running]: +package2.functionB(args) + file2.go:20 +goroutine 3 [running]: +package1.functionA(args) + file1.go:10 +`, + expect: ` +[running]: $3 +file1.go:10: functionA + +[running]: $2 +file2.go:20: functionB + +[running]: $1 +file3.go:30: functionC + +3 goroutines, 3 unique +`, + }, { + name: `real_single`, + input: ` +panic: oops + +goroutine 53 [running]: +golang.org/x/tools/internal/jsonrpc2_test.testHandler.func1(0x1240c20, 0xc000013350, 0xc0000133b0, 0x1240ca0, 0xc00002ab00, 0x3, 0x3) + /work/tools/internal/jsonrpc2/jsonrpc2_test.go:160 +0x74c +golang.org/x/tools/internal/jsonrpc2.(*Conn).Run(0xc000204330, 0x1240c20, 0xc000204270, 0x1209570, 0xc000212120, 0x1242700) + /work/tools/internal/jsonrpc2/jsonrpc2.go:187 +0x777 +golang.org/x/tools/internal/jsonrpc2_test.run.func1(0x123ebe0, 0xc000206018, 0x123ec20, 0xc000206010, 0xc0002080a0, 0xc000204330, 0x1240c20, 0xc000204270, 0xc000212120) + /work/tools/internal/jsonrpc2/jsonrpc2_test.go:131 +0xe2 +created by golang.org/x/tools/internal/jsonrpc2_test.run + /work/tools/internal/jsonrpc2/jsonrpc2_test.go:121 +0x263 +FAIL golang.org/x/tools/internal/jsonrpc2 0.252s +FAIL +`, + expect: ` +panic: oops + +[running]: $53 +/work/tools/internal/jsonrpc2/jsonrpc2_test.go:160: testHandler.func1 +/work/tools/internal/jsonrpc2/jsonrpc2.go:187: (*Conn).Run +/work/tools/internal/jsonrpc2/jsonrpc2_test.go:131: run.func1 +/work/tools/internal/jsonrpc2/jsonrpc2_test.go:121: run + +1 goroutines, 1 unique + +FAIL golang.org/x/tools/internal/jsonrpc2 0.252s +FAIL +`, + }} { + t.Run(test.name, func(t *testing.T) { + buf := &bytes.Buffer{} + stack.Process(buf, strings.NewReader(test.input)) + expect := strings.TrimSpace(test.expect) + got := strings.TrimSpace(buf.String()) + if got != expect { + t.Errorf("got:\n%s\nexpect:\n%s", got, expect) + } + }) + } +} diff --git a/contribs/gnopls/internal/stack/stacktest/stacktest.go b/contribs/gnopls/internal/stack/stacktest/stacktest.go new file mode 100644 index 00000000000..d778d3c3322 --- /dev/null +++ b/contribs/gnopls/internal/stack/stacktest/stacktest.go @@ -0,0 +1,50 @@ +// Copyright 2018 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 stacktest + +import ( + "testing" + "time" + + "golang.org/x/tools/internal/stack" +) + +// this is only needed to support pre 1.14 when testing.TB did not have Cleanup +type withCleanup interface { + Cleanup(func()) +} + +// the maximum amount of time to wait for goroutines to clean themselves up. +const maxWait = time.Second + +// NoLeak checks that a test (or benchmark) does not leak any goroutines. +func NoLeak(t testing.TB) { + c, ok := t.(withCleanup) + if !ok { + return + } + before := stack.Capture() + c.Cleanup(func() { + var delta stack.Delta + start := time.Now() + delay := time.Millisecond + for { + after := stack.Capture() + delta = stack.Diff(before, after) + if len(delta.After) == 0 { + // no leaks + return + } + if time.Since(start) > maxWait { + break + } + time.Sleep(delay) + delay *= 2 + } + // it's been long enough, and leaks are still present + summary := stack.Summarize(delta.After) + t.Errorf("goroutine leak detected:\n%+v", summary) + }) +} diff --git a/contribs/gnopls/internal/stdlib/generate.go b/contribs/gnopls/internal/stdlib/generate.go new file mode 100644 index 00000000000..d4964f60955 --- /dev/null +++ b/contribs/gnopls/internal/stdlib/generate.go @@ -0,0 +1,226 @@ +// 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. + +//go:build ignore +// +build ignore + +// The generate command reads all the GOROOT/api/go1.*.txt files and +// generates a single combined manifest.go file containing the Go +// standard library API symbols along with versions. +package main + +import ( + "bytes" + "cmp" + "errors" + "fmt" + "go/format" + "go/types" + "io/fs" + "log" + "os" + "path/filepath" + "regexp" + "runtime" + "slices" + "strings" + + "golang.org/x/tools/go/packages" +) + +func main() { + pkgs := make(map[string]map[string]symInfo) // package -> symbol -> info + symRE := regexp.MustCompile(`^pkg (\S+).*?, (var|func|type|const|method \([^)]*\)) ([\pL\p{Nd}_]+)(.*)`) + + // parse parses symbols out of GOROOT/api/*.txt data, with the specified minor version. + // Errors are reported against filename. + parse := func(filename string, data []byte, minor int) { + for linenum, line := range strings.Split(string(data), "\n") { + if line == "" || strings.HasPrefix(line, "#") { + continue + } + m := symRE.FindStringSubmatch(line) + if m == nil { + log.Fatalf("invalid input: %s:%d: %s", filename, linenum+1, line) + } + path, kind, sym, rest := m[1], m[2], m[3], m[4] + + if _, recv, ok := strings.Cut(kind, "method "); ok { + // e.g. "method (*Func) Pos() token.Pos" + kind = "method" + + recv := removeTypeParam(recv) // (*Foo[T]) -> (*Foo) + + sym = recv + "." + sym // (*T).m + + } else if _, field, ok := strings.Cut(rest, " struct, "); ok && kind == "type" { + // e.g. "type ParenExpr struct, Lparen token.Pos" + kind = "field" + name, typ, _ := strings.Cut(field, " ") + + // The api script uses the name + // "embedded" (ambiguously) for + // the name of an anonymous field. + if name == "embedded" { + // Strip "*pkg.T" down to "T". + typ = strings.TrimPrefix(typ, "*") + if _, after, ok := strings.Cut(typ, "."); ok { + typ = after + } + typ = removeTypeParam(typ) // embedded Foo[T] -> Foo + name = typ + } + + sym += "." + name // T.f + } + + symbols, ok := pkgs[path] + if !ok { + symbols = make(map[string]symInfo) + pkgs[path] = symbols + } + + // Don't overwrite earlier entries: + // enums are redeclared in later versions + // as their encoding changes; + // deprecations count as updates too. + if _, ok := symbols[sym]; !ok { + symbols[sym] = symInfo{kind, minor} + } + } + } + + // Read and parse the GOROOT/api manifests. + for minor := 0; ; minor++ { + base := "go1.txt" + if minor > 0 { + base = fmt.Sprintf("go1.%d.txt", minor) + } + filename := filepath.Join(runtime.GOROOT(), "api", base) + data, err := os.ReadFile(filename) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // All caught up. + // Synthesize one final file from any api/next/*.txt fragments. + // (They are consolidated into a go1.%d file some time between + // the freeze and the first release candidate.) + filenames, err := filepath.Glob(filepath.Join(runtime.GOROOT(), "api/next/*.txt")) + if err != nil { + log.Fatal(err) + } + var next bytes.Buffer + for _, filename := range filenames { + data, err := os.ReadFile(filename) + if err != nil { + log.Fatal(err) + } + next.Write(data) + } + parse(filename, next.Bytes(), minor) // (filename is a lie) + break + } + log.Fatal(err) + } + parse(filename, data, minor) + } + + // The APIs of the syscall/js and unsafe packages need to be computed explicitly, + // because they're not included in the GOROOT/api/go1.*.txt files at this time. + pkgs["syscall/js"] = loadSymbols("syscall/js", "GOOS=js", "GOARCH=wasm") + pkgs["unsafe"] = exportedSymbols(types.Unsafe) // TODO(adonovan): set correct versions + + // Write the combined manifest. + var buf bytes.Buffer + buf.WriteString(`// 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. + +// Code generated by generate.go. DO NOT EDIT. + +package stdlib + +var PackageSymbols = map[string][]Symbol{ +`) + + for _, path := range sortedKeys(pkgs) { + pkg := pkgs[path] + fmt.Fprintf(&buf, "\t%q: {\n", path) + for _, name := range sortedKeys(pkg) { + info := pkg[name] + fmt.Fprintf(&buf, "\t\t{%q, %s, %d},\n", + name, strings.Title(info.kind), info.minor) + } + fmt.Fprintln(&buf, "},") + } + fmt.Fprintln(&buf, "}") + fmtbuf, err := format.Source(buf.Bytes()) + if err != nil { + log.Fatal(err) + } + if err := os.WriteFile("manifest.go", fmtbuf, 0666); err != nil { + log.Fatal(err) + } +} + +type symInfo struct { + kind string // e.g. "func" + minor int // go1.%d +} + +// loadSymbols computes the exported symbols in the specified package +// by parsing and type-checking the current source. +func loadSymbols(pkg string, extraEnv ...string) map[string]symInfo { + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedTypes, + Env: append(os.Environ(), extraEnv...), + }, pkg) + if err != nil { + log.Fatalln(err) + } else if len(pkgs) != 1 { + log.Fatalf("got %d packages, want one package %q", len(pkgs), pkg) + } + return exportedSymbols(pkgs[0].Types) +} + +func exportedSymbols(pkg *types.Package) map[string]symInfo { + symbols := make(map[string]symInfo) + for _, name := range pkg.Scope().Names() { + if obj := pkg.Scope().Lookup(name); obj.Exported() { + var kind string + switch obj.(type) { + case *types.Func, *types.Builtin: + kind = "func" + case *types.Const: + kind = "const" + case *types.Var: + kind = "var" + case *types.TypeName: + kind = "type" + // TODO(adonovan): expand fields and methods of syscall/js.* + default: + log.Fatalf("unexpected object type: %v", obj) + } + symbols[name] = symInfo{kind: kind, minor: 0} // pretend go1.0 + } + } + return symbols +} + +func sortedKeys[M ~map[K]V, K cmp.Ordered, V any](m M) []K { + r := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) + } + slices.Sort(r) + return r +} + +func removeTypeParam(s string) string { + i := strings.IndexByte(s, '[') + j := strings.LastIndexByte(s, ']') + if i > 0 && j > i { + s = s[:i] + s[j+len("["):] + } + return s +} diff --git a/contribs/gnopls/internal/stdlib/manifest.go b/contribs/gnopls/internal/stdlib/manifest.go new file mode 100644 index 00000000000..cdaac9ab34d --- /dev/null +++ b/contribs/gnopls/internal/stdlib/manifest.go @@ -0,0 +1,17431 @@ +// 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. + +// Code generated by generate.go. DO NOT EDIT. + +package stdlib + +var PackageSymbols = map[string][]Symbol{ + "archive/tar": { + {"(*Header).FileInfo", Method, 1}, + {"(*Reader).Next", Method, 0}, + {"(*Reader).Read", Method, 0}, + {"(*Writer).AddFS", Method, 22}, + {"(*Writer).Close", Method, 0}, + {"(*Writer).Flush", Method, 0}, + {"(*Writer).Write", Method, 0}, + {"(*Writer).WriteHeader", Method, 0}, + {"(Format).String", Method, 10}, + {"ErrFieldTooLong", Var, 0}, + {"ErrHeader", Var, 0}, + {"ErrInsecurePath", Var, 20}, + {"ErrWriteAfterClose", Var, 0}, + {"ErrWriteTooLong", Var, 0}, + {"FileInfoHeader", Func, 1}, + {"FileInfoNames", Type, 23}, + {"Format", Type, 10}, + {"FormatGNU", Const, 10}, + {"FormatPAX", Const, 10}, + {"FormatUSTAR", Const, 10}, + {"FormatUnknown", Const, 10}, + {"Header", Type, 0}, + {"Header.AccessTime", Field, 0}, + {"Header.ChangeTime", Field, 0}, + {"Header.Devmajor", Field, 0}, + {"Header.Devminor", Field, 0}, + {"Header.Format", Field, 10}, + {"Header.Gid", Field, 0}, + {"Header.Gname", Field, 0}, + {"Header.Linkname", Field, 0}, + {"Header.ModTime", Field, 0}, + {"Header.Mode", Field, 0}, + {"Header.Name", Field, 0}, + {"Header.PAXRecords", Field, 10}, + {"Header.Size", Field, 0}, + {"Header.Typeflag", Field, 0}, + {"Header.Uid", Field, 0}, + {"Header.Uname", Field, 0}, + {"Header.Xattrs", Field, 3}, + {"NewReader", Func, 0}, + {"NewWriter", Func, 0}, + {"Reader", Type, 0}, + {"TypeBlock", Const, 0}, + {"TypeChar", Const, 0}, + {"TypeCont", Const, 0}, + {"TypeDir", Const, 0}, + {"TypeFifo", Const, 0}, + {"TypeGNULongLink", Const, 1}, + {"TypeGNULongName", Const, 1}, + {"TypeGNUSparse", Const, 3}, + {"TypeLink", Const, 0}, + {"TypeReg", Const, 0}, + {"TypeRegA", Const, 0}, + {"TypeSymlink", Const, 0}, + {"TypeXGlobalHeader", Const, 0}, + {"TypeXHeader", Const, 0}, + {"Writer", Type, 0}, + }, + "archive/zip": { + {"(*File).DataOffset", Method, 2}, + {"(*File).FileInfo", Method, 0}, + {"(*File).ModTime", Method, 0}, + {"(*File).Mode", Method, 0}, + {"(*File).Open", Method, 0}, + {"(*File).OpenRaw", Method, 17}, + {"(*File).SetModTime", Method, 0}, + {"(*File).SetMode", Method, 0}, + {"(*FileHeader).FileInfo", Method, 0}, + {"(*FileHeader).ModTime", Method, 0}, + {"(*FileHeader).Mode", Method, 0}, + {"(*FileHeader).SetModTime", Method, 0}, + {"(*FileHeader).SetMode", Method, 0}, + {"(*ReadCloser).Close", Method, 0}, + {"(*ReadCloser).Open", Method, 16}, + {"(*ReadCloser).RegisterDecompressor", Method, 6}, + {"(*Reader).Open", Method, 16}, + {"(*Reader).RegisterDecompressor", Method, 6}, + {"(*Writer).AddFS", Method, 22}, + {"(*Writer).Close", Method, 0}, + {"(*Writer).Copy", Method, 17}, + {"(*Writer).Create", Method, 0}, + {"(*Writer).CreateHeader", Method, 0}, + {"(*Writer).CreateRaw", Method, 17}, + {"(*Writer).Flush", Method, 4}, + {"(*Writer).RegisterCompressor", Method, 6}, + {"(*Writer).SetComment", Method, 10}, + {"(*Writer).SetOffset", Method, 5}, + {"Compressor", Type, 2}, + {"Decompressor", Type, 2}, + {"Deflate", Const, 0}, + {"ErrAlgorithm", Var, 0}, + {"ErrChecksum", Var, 0}, + {"ErrFormat", Var, 0}, + {"ErrInsecurePath", Var, 20}, + {"File", Type, 0}, + {"File.FileHeader", Field, 0}, + {"FileHeader", Type, 0}, + {"FileHeader.CRC32", Field, 0}, + {"FileHeader.Comment", Field, 0}, + {"FileHeader.CompressedSize", Field, 0}, + {"FileHeader.CompressedSize64", Field, 1}, + {"FileHeader.CreatorVersion", Field, 0}, + {"FileHeader.ExternalAttrs", Field, 0}, + {"FileHeader.Extra", Field, 0}, + {"FileHeader.Flags", Field, 0}, + {"FileHeader.Method", Field, 0}, + {"FileHeader.Modified", Field, 10}, + {"FileHeader.ModifiedDate", Field, 0}, + {"FileHeader.ModifiedTime", Field, 0}, + {"FileHeader.Name", Field, 0}, + {"FileHeader.NonUTF8", Field, 10}, + {"FileHeader.ReaderVersion", Field, 0}, + {"FileHeader.UncompressedSize", Field, 0}, + {"FileHeader.UncompressedSize64", Field, 1}, + {"FileInfoHeader", Func, 0}, + {"NewReader", Func, 0}, + {"NewWriter", Func, 0}, + {"OpenReader", Func, 0}, + {"ReadCloser", Type, 0}, + {"ReadCloser.Reader", Field, 0}, + {"Reader", Type, 0}, + {"Reader.Comment", Field, 0}, + {"Reader.File", Field, 0}, + {"RegisterCompressor", Func, 2}, + {"RegisterDecompressor", Func, 2}, + {"Store", Const, 0}, + {"Writer", Type, 0}, + }, + "bufio": { + {"(*Reader).Buffered", Method, 0}, + {"(*Reader).Discard", Method, 5}, + {"(*Reader).Peek", Method, 0}, + {"(*Reader).Read", Method, 0}, + {"(*Reader).ReadByte", Method, 0}, + {"(*Reader).ReadBytes", Method, 0}, + {"(*Reader).ReadLine", Method, 0}, + {"(*Reader).ReadRune", Method, 0}, + {"(*Reader).ReadSlice", Method, 0}, + {"(*Reader).ReadString", Method, 0}, + {"(*Reader).Reset", Method, 2}, + {"(*Reader).Size", Method, 10}, + {"(*Reader).UnreadByte", Method, 0}, + {"(*Reader).UnreadRune", Method, 0}, + {"(*Reader).WriteTo", Method, 1}, + {"(*Scanner).Buffer", Method, 6}, + {"(*Scanner).Bytes", Method, 1}, + {"(*Scanner).Err", Method, 1}, + {"(*Scanner).Scan", Method, 1}, + {"(*Scanner).Split", Method, 1}, + {"(*Scanner).Text", Method, 1}, + {"(*Writer).Available", Method, 0}, + {"(*Writer).AvailableBuffer", Method, 18}, + {"(*Writer).Buffered", Method, 0}, + {"(*Writer).Flush", Method, 0}, + {"(*Writer).ReadFrom", Method, 1}, + {"(*Writer).Reset", Method, 2}, + {"(*Writer).Size", Method, 10}, + {"(*Writer).Write", Method, 0}, + {"(*Writer).WriteByte", Method, 0}, + {"(*Writer).WriteRune", Method, 0}, + {"(*Writer).WriteString", Method, 0}, + {"(ReadWriter).Available", Method, 0}, + {"(ReadWriter).AvailableBuffer", Method, 18}, + {"(ReadWriter).Discard", Method, 5}, + {"(ReadWriter).Flush", Method, 0}, + {"(ReadWriter).Peek", Method, 0}, + {"(ReadWriter).Read", Method, 0}, + {"(ReadWriter).ReadByte", Method, 0}, + {"(ReadWriter).ReadBytes", Method, 0}, + {"(ReadWriter).ReadFrom", Method, 1}, + {"(ReadWriter).ReadLine", Method, 0}, + {"(ReadWriter).ReadRune", Method, 0}, + {"(ReadWriter).ReadSlice", Method, 0}, + {"(ReadWriter).ReadString", Method, 0}, + {"(ReadWriter).UnreadByte", Method, 0}, + {"(ReadWriter).UnreadRune", Method, 0}, + {"(ReadWriter).Write", Method, 0}, + {"(ReadWriter).WriteByte", Method, 0}, + {"(ReadWriter).WriteRune", Method, 0}, + {"(ReadWriter).WriteString", Method, 0}, + {"(ReadWriter).WriteTo", Method, 1}, + {"ErrAdvanceTooFar", Var, 1}, + {"ErrBadReadCount", Var, 15}, + {"ErrBufferFull", Var, 0}, + {"ErrFinalToken", Var, 6}, + {"ErrInvalidUnreadByte", Var, 0}, + {"ErrInvalidUnreadRune", Var, 0}, + {"ErrNegativeAdvance", Var, 1}, + {"ErrNegativeCount", Var, 0}, + {"ErrTooLong", Var, 1}, + {"MaxScanTokenSize", Const, 1}, + {"NewReadWriter", Func, 0}, + {"NewReader", Func, 0}, + {"NewReaderSize", Func, 0}, + {"NewScanner", Func, 1}, + {"NewWriter", Func, 0}, + {"NewWriterSize", Func, 0}, + {"ReadWriter", Type, 0}, + {"ReadWriter.Reader", Field, 0}, + {"ReadWriter.Writer", Field, 0}, + {"Reader", Type, 0}, + {"ScanBytes", Func, 1}, + {"ScanLines", Func, 1}, + {"ScanRunes", Func, 1}, + {"ScanWords", Func, 1}, + {"Scanner", Type, 1}, + {"SplitFunc", Type, 1}, + {"Writer", Type, 0}, + }, + "bytes": { + {"(*Buffer).Available", Method, 21}, + {"(*Buffer).AvailableBuffer", Method, 21}, + {"(*Buffer).Bytes", Method, 0}, + {"(*Buffer).Cap", Method, 5}, + {"(*Buffer).Grow", Method, 1}, + {"(*Buffer).Len", Method, 0}, + {"(*Buffer).Next", Method, 0}, + {"(*Buffer).Read", Method, 0}, + {"(*Buffer).ReadByte", Method, 0}, + {"(*Buffer).ReadBytes", Method, 0}, + {"(*Buffer).ReadFrom", Method, 0}, + {"(*Buffer).ReadRune", Method, 0}, + {"(*Buffer).ReadString", Method, 0}, + {"(*Buffer).Reset", Method, 0}, + {"(*Buffer).String", Method, 0}, + {"(*Buffer).Truncate", Method, 0}, + {"(*Buffer).UnreadByte", Method, 0}, + {"(*Buffer).UnreadRune", Method, 0}, + {"(*Buffer).Write", Method, 0}, + {"(*Buffer).WriteByte", Method, 0}, + {"(*Buffer).WriteRune", Method, 0}, + {"(*Buffer).WriteString", Method, 0}, + {"(*Buffer).WriteTo", Method, 0}, + {"(*Reader).Len", Method, 0}, + {"(*Reader).Read", Method, 0}, + {"(*Reader).ReadAt", Method, 0}, + {"(*Reader).ReadByte", Method, 0}, + {"(*Reader).ReadRune", Method, 0}, + {"(*Reader).Reset", Method, 7}, + {"(*Reader).Seek", Method, 0}, + {"(*Reader).Size", Method, 5}, + {"(*Reader).UnreadByte", Method, 0}, + {"(*Reader).UnreadRune", Method, 0}, + {"(*Reader).WriteTo", Method, 1}, + {"Buffer", Type, 0}, + {"Clone", Func, 20}, + {"Compare", Func, 0}, + {"Contains", Func, 0}, + {"ContainsAny", Func, 7}, + {"ContainsFunc", Func, 21}, + {"ContainsRune", Func, 7}, + {"Count", Func, 0}, + {"Cut", Func, 18}, + {"CutPrefix", Func, 20}, + {"CutSuffix", Func, 20}, + {"Equal", Func, 0}, + {"EqualFold", Func, 0}, + {"ErrTooLarge", Var, 0}, + {"Fields", Func, 0}, + {"FieldsFunc", Func, 0}, + {"HasPrefix", Func, 0}, + {"HasSuffix", Func, 0}, + {"Index", Func, 0}, + {"IndexAny", Func, 0}, + {"IndexByte", Func, 0}, + {"IndexFunc", Func, 0}, + {"IndexRune", Func, 0}, + {"Join", Func, 0}, + {"LastIndex", Func, 0}, + {"LastIndexAny", Func, 0}, + {"LastIndexByte", Func, 5}, + {"LastIndexFunc", Func, 0}, + {"Map", Func, 0}, + {"MinRead", Const, 0}, + {"NewBuffer", Func, 0}, + {"NewBufferString", Func, 0}, + {"NewReader", Func, 0}, + {"Reader", Type, 0}, + {"Repeat", Func, 0}, + {"Replace", Func, 0}, + {"ReplaceAll", Func, 12}, + {"Runes", Func, 0}, + {"Split", Func, 0}, + {"SplitAfter", Func, 0}, + {"SplitAfterN", Func, 0}, + {"SplitN", Func, 0}, + {"Title", Func, 0}, + {"ToLower", Func, 0}, + {"ToLowerSpecial", Func, 0}, + {"ToTitle", Func, 0}, + {"ToTitleSpecial", Func, 0}, + {"ToUpper", Func, 0}, + {"ToUpperSpecial", Func, 0}, + {"ToValidUTF8", Func, 13}, + {"Trim", Func, 0}, + {"TrimFunc", Func, 0}, + {"TrimLeft", Func, 0}, + {"TrimLeftFunc", Func, 0}, + {"TrimPrefix", Func, 1}, + {"TrimRight", Func, 0}, + {"TrimRightFunc", Func, 0}, + {"TrimSpace", Func, 0}, + {"TrimSuffix", Func, 1}, + }, + "cmp": { + {"Compare", Func, 21}, + {"Less", Func, 21}, + {"Or", Func, 22}, + {"Ordered", Type, 21}, + }, + "compress/bzip2": { + {"(StructuralError).Error", Method, 0}, + {"NewReader", Func, 0}, + {"StructuralError", Type, 0}, + }, + "compress/flate": { + {"(*ReadError).Error", Method, 0}, + {"(*WriteError).Error", Method, 0}, + {"(*Writer).Close", Method, 0}, + {"(*Writer).Flush", Method, 0}, + {"(*Writer).Reset", Method, 2}, + {"(*Writer).Write", Method, 0}, + {"(CorruptInputError).Error", Method, 0}, + {"(InternalError).Error", Method, 0}, + {"BestCompression", Const, 0}, + {"BestSpeed", Const, 0}, + {"CorruptInputError", Type, 0}, + {"DefaultCompression", Const, 0}, + {"HuffmanOnly", Const, 7}, + {"InternalError", Type, 0}, + {"NewReader", Func, 0}, + {"NewReaderDict", Func, 0}, + {"NewWriter", Func, 0}, + {"NewWriterDict", Func, 0}, + {"NoCompression", Const, 0}, + {"ReadError", Type, 0}, + {"ReadError.Err", Field, 0}, + {"ReadError.Offset", Field, 0}, + {"Reader", Type, 0}, + {"Resetter", Type, 4}, + {"WriteError", Type, 0}, + {"WriteError.Err", Field, 0}, + {"WriteError.Offset", Field, 0}, + {"Writer", Type, 0}, + }, + "compress/gzip": { + {"(*Reader).Close", Method, 0}, + {"(*Reader).Multistream", Method, 4}, + {"(*Reader).Read", Method, 0}, + {"(*Reader).Reset", Method, 3}, + {"(*Writer).Close", Method, 0}, + {"(*Writer).Flush", Method, 1}, + {"(*Writer).Reset", Method, 2}, + {"(*Writer).Write", Method, 0}, + {"BestCompression", Const, 0}, + {"BestSpeed", Const, 0}, + {"DefaultCompression", Const, 0}, + {"ErrChecksum", Var, 0}, + {"ErrHeader", Var, 0}, + {"Header", Type, 0}, + {"Header.Comment", Field, 0}, + {"Header.Extra", Field, 0}, + {"Header.ModTime", Field, 0}, + {"Header.Name", Field, 0}, + {"Header.OS", Field, 0}, + {"HuffmanOnly", Const, 8}, + {"NewReader", Func, 0}, + {"NewWriter", Func, 0}, + {"NewWriterLevel", Func, 0}, + {"NoCompression", Const, 0}, + {"Reader", Type, 0}, + {"Reader.Header", Field, 0}, + {"Writer", Type, 0}, + {"Writer.Header", Field, 0}, + }, + "compress/lzw": { + {"(*Reader).Close", Method, 17}, + {"(*Reader).Read", Method, 17}, + {"(*Reader).Reset", Method, 17}, + {"(*Writer).Close", Method, 17}, + {"(*Writer).Reset", Method, 17}, + {"(*Writer).Write", Method, 17}, + {"LSB", Const, 0}, + {"MSB", Const, 0}, + {"NewReader", Func, 0}, + {"NewWriter", Func, 0}, + {"Order", Type, 0}, + {"Reader", Type, 17}, + {"Writer", Type, 17}, + }, + "compress/zlib": { + {"(*Writer).Close", Method, 0}, + {"(*Writer).Flush", Method, 0}, + {"(*Writer).Reset", Method, 2}, + {"(*Writer).Write", Method, 0}, + {"BestCompression", Const, 0}, + {"BestSpeed", Const, 0}, + {"DefaultCompression", Const, 0}, + {"ErrChecksum", Var, 0}, + {"ErrDictionary", Var, 0}, + {"ErrHeader", Var, 0}, + {"HuffmanOnly", Const, 8}, + {"NewReader", Func, 0}, + {"NewReaderDict", Func, 0}, + {"NewWriter", Func, 0}, + {"NewWriterLevel", Func, 0}, + {"NewWriterLevelDict", Func, 0}, + {"NoCompression", Const, 0}, + {"Resetter", Type, 4}, + {"Writer", Type, 0}, + }, + "container/heap": { + {"Fix", Func, 2}, + {"Init", Func, 0}, + {"Interface", Type, 0}, + {"Pop", Func, 0}, + {"Push", Func, 0}, + {"Remove", Func, 0}, + }, + "container/list": { + {"(*Element).Next", Method, 0}, + {"(*Element).Prev", Method, 0}, + {"(*List).Back", Method, 0}, + {"(*List).Front", Method, 0}, + {"(*List).Init", Method, 0}, + {"(*List).InsertAfter", Method, 0}, + {"(*List).InsertBefore", Method, 0}, + {"(*List).Len", Method, 0}, + {"(*List).MoveAfter", Method, 2}, + {"(*List).MoveBefore", Method, 2}, + {"(*List).MoveToBack", Method, 0}, + {"(*List).MoveToFront", Method, 0}, + {"(*List).PushBack", Method, 0}, + {"(*List).PushBackList", Method, 0}, + {"(*List).PushFront", Method, 0}, + {"(*List).PushFrontList", Method, 0}, + {"(*List).Remove", Method, 0}, + {"Element", Type, 0}, + {"Element.Value", Field, 0}, + {"List", Type, 0}, + {"New", Func, 0}, + }, + "container/ring": { + {"(*Ring).Do", Method, 0}, + {"(*Ring).Len", Method, 0}, + {"(*Ring).Link", Method, 0}, + {"(*Ring).Move", Method, 0}, + {"(*Ring).Next", Method, 0}, + {"(*Ring).Prev", Method, 0}, + {"(*Ring).Unlink", Method, 0}, + {"New", Func, 0}, + {"Ring", Type, 0}, + {"Ring.Value", Field, 0}, + }, + "context": { + {"AfterFunc", Func, 21}, + {"Background", Func, 7}, + {"CancelCauseFunc", Type, 20}, + {"CancelFunc", Type, 7}, + {"Canceled", Var, 7}, + {"Cause", Func, 20}, + {"Context", Type, 7}, + {"DeadlineExceeded", Var, 7}, + {"TODO", Func, 7}, + {"WithCancel", Func, 7}, + {"WithCancelCause", Func, 20}, + {"WithDeadline", Func, 7}, + {"WithDeadlineCause", Func, 21}, + {"WithTimeout", Func, 7}, + {"WithTimeoutCause", Func, 21}, + {"WithValue", Func, 7}, + {"WithoutCancel", Func, 21}, + }, + "crypto": { + {"(Hash).Available", Method, 0}, + {"(Hash).HashFunc", Method, 4}, + {"(Hash).New", Method, 0}, + {"(Hash).Size", Method, 0}, + {"(Hash).String", Method, 15}, + {"BLAKE2b_256", Const, 9}, + {"BLAKE2b_384", Const, 9}, + {"BLAKE2b_512", Const, 9}, + {"BLAKE2s_256", Const, 9}, + {"Decrypter", Type, 5}, + {"DecrypterOpts", Type, 5}, + {"Hash", Type, 0}, + {"MD4", Const, 0}, + {"MD5", Const, 0}, + {"MD5SHA1", Const, 0}, + {"PrivateKey", Type, 0}, + {"PublicKey", Type, 2}, + {"RIPEMD160", Const, 0}, + {"RegisterHash", Func, 0}, + {"SHA1", Const, 0}, + {"SHA224", Const, 0}, + {"SHA256", Const, 0}, + {"SHA384", Const, 0}, + {"SHA3_224", Const, 4}, + {"SHA3_256", Const, 4}, + {"SHA3_384", Const, 4}, + {"SHA3_512", Const, 4}, + {"SHA512", Const, 0}, + {"SHA512_224", Const, 5}, + {"SHA512_256", Const, 5}, + {"Signer", Type, 4}, + {"SignerOpts", Type, 4}, + }, + "crypto/aes": { + {"(KeySizeError).Error", Method, 0}, + {"BlockSize", Const, 0}, + {"KeySizeError", Type, 0}, + {"NewCipher", Func, 0}, + }, + "crypto/cipher": { + {"(StreamReader).Read", Method, 0}, + {"(StreamWriter).Close", Method, 0}, + {"(StreamWriter).Write", Method, 0}, + {"AEAD", Type, 2}, + {"Block", Type, 0}, + {"BlockMode", Type, 0}, + {"NewCBCDecrypter", Func, 0}, + {"NewCBCEncrypter", Func, 0}, + {"NewCFBDecrypter", Func, 0}, + {"NewCFBEncrypter", Func, 0}, + {"NewCTR", Func, 0}, + {"NewGCM", Func, 2}, + {"NewGCMWithNonceSize", Func, 5}, + {"NewGCMWithTagSize", Func, 11}, + {"NewOFB", Func, 0}, + {"Stream", Type, 0}, + {"StreamReader", Type, 0}, + {"StreamReader.R", Field, 0}, + {"StreamReader.S", Field, 0}, + {"StreamWriter", Type, 0}, + {"StreamWriter.Err", Field, 0}, + {"StreamWriter.S", Field, 0}, + {"StreamWriter.W", Field, 0}, + }, + "crypto/des": { + {"(KeySizeError).Error", Method, 0}, + {"BlockSize", Const, 0}, + {"KeySizeError", Type, 0}, + {"NewCipher", Func, 0}, + {"NewTripleDESCipher", Func, 0}, + }, + "crypto/dsa": { + {"ErrInvalidPublicKey", Var, 0}, + {"GenerateKey", Func, 0}, + {"GenerateParameters", Func, 0}, + {"L1024N160", Const, 0}, + {"L2048N224", Const, 0}, + {"L2048N256", Const, 0}, + {"L3072N256", Const, 0}, + {"ParameterSizes", Type, 0}, + {"Parameters", Type, 0}, + {"Parameters.G", Field, 0}, + {"Parameters.P", Field, 0}, + {"Parameters.Q", Field, 0}, + {"PrivateKey", Type, 0}, + {"PrivateKey.PublicKey", Field, 0}, + {"PrivateKey.X", Field, 0}, + {"PublicKey", Type, 0}, + {"PublicKey.Parameters", Field, 0}, + {"PublicKey.Y", Field, 0}, + {"Sign", Func, 0}, + {"Verify", Func, 0}, + }, + "crypto/ecdh": { + {"(*PrivateKey).Bytes", Method, 20}, + {"(*PrivateKey).Curve", Method, 20}, + {"(*PrivateKey).ECDH", Method, 20}, + {"(*PrivateKey).Equal", Method, 20}, + {"(*PrivateKey).Public", Method, 20}, + {"(*PrivateKey).PublicKey", Method, 20}, + {"(*PublicKey).Bytes", Method, 20}, + {"(*PublicKey).Curve", Method, 20}, + {"(*PublicKey).Equal", Method, 20}, + {"Curve", Type, 20}, + {"P256", Func, 20}, + {"P384", Func, 20}, + {"P521", Func, 20}, + {"PrivateKey", Type, 20}, + {"PublicKey", Type, 20}, + {"X25519", Func, 20}, + }, + "crypto/ecdsa": { + {"(*PrivateKey).ECDH", Method, 20}, + {"(*PrivateKey).Equal", Method, 15}, + {"(*PrivateKey).Public", Method, 4}, + {"(*PrivateKey).Sign", Method, 4}, + {"(*PublicKey).ECDH", Method, 20}, + {"(*PublicKey).Equal", Method, 15}, + {"(PrivateKey).Add", Method, 0}, + {"(PrivateKey).Double", Method, 0}, + {"(PrivateKey).IsOnCurve", Method, 0}, + {"(PrivateKey).Params", Method, 0}, + {"(PrivateKey).ScalarBaseMult", Method, 0}, + {"(PrivateKey).ScalarMult", Method, 0}, + {"(PublicKey).Add", Method, 0}, + {"(PublicKey).Double", Method, 0}, + {"(PublicKey).IsOnCurve", Method, 0}, + {"(PublicKey).Params", Method, 0}, + {"(PublicKey).ScalarBaseMult", Method, 0}, + {"(PublicKey).ScalarMult", Method, 0}, + {"GenerateKey", Func, 0}, + {"PrivateKey", Type, 0}, + {"PrivateKey.D", Field, 0}, + {"PrivateKey.PublicKey", Field, 0}, + {"PublicKey", Type, 0}, + {"PublicKey.Curve", Field, 0}, + {"PublicKey.X", Field, 0}, + {"PublicKey.Y", Field, 0}, + {"Sign", Func, 0}, + {"SignASN1", Func, 15}, + {"Verify", Func, 0}, + {"VerifyASN1", Func, 15}, + }, + "crypto/ed25519": { + {"(*Options).HashFunc", Method, 20}, + {"(PrivateKey).Equal", Method, 15}, + {"(PrivateKey).Public", Method, 13}, + {"(PrivateKey).Seed", Method, 13}, + {"(PrivateKey).Sign", Method, 13}, + {"(PublicKey).Equal", Method, 15}, + {"GenerateKey", Func, 13}, + {"NewKeyFromSeed", Func, 13}, + {"Options", Type, 20}, + {"Options.Context", Field, 20}, + {"Options.Hash", Field, 20}, + {"PrivateKey", Type, 13}, + {"PrivateKeySize", Const, 13}, + {"PublicKey", Type, 13}, + {"PublicKeySize", Const, 13}, + {"SeedSize", Const, 13}, + {"Sign", Func, 13}, + {"SignatureSize", Const, 13}, + {"Verify", Func, 13}, + {"VerifyWithOptions", Func, 20}, + }, + "crypto/elliptic": { + {"(*CurveParams).Add", Method, 0}, + {"(*CurveParams).Double", Method, 0}, + {"(*CurveParams).IsOnCurve", Method, 0}, + {"(*CurveParams).Params", Method, 0}, + {"(*CurveParams).ScalarBaseMult", Method, 0}, + {"(*CurveParams).ScalarMult", Method, 0}, + {"Curve", Type, 0}, + {"CurveParams", Type, 0}, + {"CurveParams.B", Field, 0}, + {"CurveParams.BitSize", Field, 0}, + {"CurveParams.Gx", Field, 0}, + {"CurveParams.Gy", Field, 0}, + {"CurveParams.N", Field, 0}, + {"CurveParams.Name", Field, 5}, + {"CurveParams.P", Field, 0}, + {"GenerateKey", Func, 0}, + {"Marshal", Func, 0}, + {"MarshalCompressed", Func, 15}, + {"P224", Func, 0}, + {"P256", Func, 0}, + {"P384", Func, 0}, + {"P521", Func, 0}, + {"Unmarshal", Func, 0}, + {"UnmarshalCompressed", Func, 15}, + }, + "crypto/hmac": { + {"Equal", Func, 1}, + {"New", Func, 0}, + }, + "crypto/md5": { + {"BlockSize", Const, 0}, + {"New", Func, 0}, + {"Size", Const, 0}, + {"Sum", Func, 2}, + }, + "crypto/rand": { + {"Int", Func, 0}, + {"Prime", Func, 0}, + {"Read", Func, 0}, + {"Reader", Var, 0}, + }, + "crypto/rc4": { + {"(*Cipher).Reset", Method, 0}, + {"(*Cipher).XORKeyStream", Method, 0}, + {"(KeySizeError).Error", Method, 0}, + {"Cipher", Type, 0}, + {"KeySizeError", Type, 0}, + {"NewCipher", Func, 0}, + }, + "crypto/rsa": { + {"(*PSSOptions).HashFunc", Method, 4}, + {"(*PrivateKey).Decrypt", Method, 5}, + {"(*PrivateKey).Equal", Method, 15}, + {"(*PrivateKey).Precompute", Method, 0}, + {"(*PrivateKey).Public", Method, 4}, + {"(*PrivateKey).Sign", Method, 4}, + {"(*PrivateKey).Size", Method, 11}, + {"(*PrivateKey).Validate", Method, 0}, + {"(*PublicKey).Equal", Method, 15}, + {"(*PublicKey).Size", Method, 11}, + {"CRTValue", Type, 0}, + {"CRTValue.Coeff", Field, 0}, + {"CRTValue.Exp", Field, 0}, + {"CRTValue.R", Field, 0}, + {"DecryptOAEP", Func, 0}, + {"DecryptPKCS1v15", Func, 0}, + {"DecryptPKCS1v15SessionKey", Func, 0}, + {"EncryptOAEP", Func, 0}, + {"EncryptPKCS1v15", Func, 0}, + {"ErrDecryption", Var, 0}, + {"ErrMessageTooLong", Var, 0}, + {"ErrVerification", Var, 0}, + {"GenerateKey", Func, 0}, + {"GenerateMultiPrimeKey", Func, 0}, + {"OAEPOptions", Type, 5}, + {"OAEPOptions.Hash", Field, 5}, + {"OAEPOptions.Label", Field, 5}, + {"OAEPOptions.MGFHash", Field, 20}, + {"PKCS1v15DecryptOptions", Type, 5}, + {"PKCS1v15DecryptOptions.SessionKeyLen", Field, 5}, + {"PSSOptions", Type, 2}, + {"PSSOptions.Hash", Field, 4}, + {"PSSOptions.SaltLength", Field, 2}, + {"PSSSaltLengthAuto", Const, 2}, + {"PSSSaltLengthEqualsHash", Const, 2}, + {"PrecomputedValues", Type, 0}, + {"PrecomputedValues.CRTValues", Field, 0}, + {"PrecomputedValues.Dp", Field, 0}, + {"PrecomputedValues.Dq", Field, 0}, + {"PrecomputedValues.Qinv", Field, 0}, + {"PrivateKey", Type, 0}, + {"PrivateKey.D", Field, 0}, + {"PrivateKey.Precomputed", Field, 0}, + {"PrivateKey.Primes", Field, 0}, + {"PrivateKey.PublicKey", Field, 0}, + {"PublicKey", Type, 0}, + {"PublicKey.E", Field, 0}, + {"PublicKey.N", Field, 0}, + {"SignPKCS1v15", Func, 0}, + {"SignPSS", Func, 2}, + {"VerifyPKCS1v15", Func, 0}, + {"VerifyPSS", Func, 2}, + }, + "crypto/sha1": { + {"BlockSize", Const, 0}, + {"New", Func, 0}, + {"Size", Const, 0}, + {"Sum", Func, 2}, + }, + "crypto/sha256": { + {"BlockSize", Const, 0}, + {"New", Func, 0}, + {"New224", Func, 0}, + {"Size", Const, 0}, + {"Size224", Const, 0}, + {"Sum224", Func, 2}, + {"Sum256", Func, 2}, + }, + "crypto/sha512": { + {"BlockSize", Const, 0}, + {"New", Func, 0}, + {"New384", Func, 0}, + {"New512_224", Func, 5}, + {"New512_256", Func, 5}, + {"Size", Const, 0}, + {"Size224", Const, 5}, + {"Size256", Const, 5}, + {"Size384", Const, 0}, + {"Sum384", Func, 2}, + {"Sum512", Func, 2}, + {"Sum512_224", Func, 5}, + {"Sum512_256", Func, 5}, + }, + "crypto/subtle": { + {"ConstantTimeByteEq", Func, 0}, + {"ConstantTimeCompare", Func, 0}, + {"ConstantTimeCopy", Func, 0}, + {"ConstantTimeEq", Func, 0}, + {"ConstantTimeLessOrEq", Func, 2}, + {"ConstantTimeSelect", Func, 0}, + {"XORBytes", Func, 20}, + }, + "crypto/tls": { + {"(*CertificateRequestInfo).Context", Method, 17}, + {"(*CertificateRequestInfo).SupportsCertificate", Method, 14}, + {"(*CertificateVerificationError).Error", Method, 20}, + {"(*CertificateVerificationError).Unwrap", Method, 20}, + {"(*ClientHelloInfo).Context", Method, 17}, + {"(*ClientHelloInfo).SupportsCertificate", Method, 14}, + {"(*ClientSessionState).ResumptionState", Method, 21}, + {"(*Config).BuildNameToCertificate", Method, 0}, + {"(*Config).Clone", Method, 8}, + {"(*Config).DecryptTicket", Method, 21}, + {"(*Config).EncryptTicket", Method, 21}, + {"(*Config).SetSessionTicketKeys", Method, 5}, + {"(*Conn).Close", Method, 0}, + {"(*Conn).CloseWrite", Method, 8}, + {"(*Conn).ConnectionState", Method, 0}, + {"(*Conn).Handshake", Method, 0}, + {"(*Conn).HandshakeContext", Method, 17}, + {"(*Conn).LocalAddr", Method, 0}, + {"(*Conn).NetConn", Method, 18}, + {"(*Conn).OCSPResponse", Method, 0}, + {"(*Conn).Read", Method, 0}, + {"(*Conn).RemoteAddr", Method, 0}, + {"(*Conn).SetDeadline", Method, 0}, + {"(*Conn).SetReadDeadline", Method, 0}, + {"(*Conn).SetWriteDeadline", Method, 0}, + {"(*Conn).VerifyHostname", Method, 0}, + {"(*Conn).Write", Method, 0}, + {"(*ConnectionState).ExportKeyingMaterial", Method, 11}, + {"(*Dialer).Dial", Method, 15}, + {"(*Dialer).DialContext", Method, 15}, + {"(*ECHRejectionError).Error", Method, 23}, + {"(*QUICConn).Close", Method, 21}, + {"(*QUICConn).ConnectionState", Method, 21}, + {"(*QUICConn).HandleData", Method, 21}, + {"(*QUICConn).NextEvent", Method, 21}, + {"(*QUICConn).SendSessionTicket", Method, 21}, + {"(*QUICConn).SetTransportParameters", Method, 21}, + {"(*QUICConn).Start", Method, 21}, + {"(*QUICConn).StoreSession", Method, 23}, + {"(*SessionState).Bytes", Method, 21}, + {"(AlertError).Error", Method, 21}, + {"(ClientAuthType).String", Method, 15}, + {"(CurveID).String", Method, 15}, + {"(QUICEncryptionLevel).String", Method, 21}, + {"(RecordHeaderError).Error", Method, 6}, + {"(SignatureScheme).String", Method, 15}, + {"AlertError", Type, 21}, + {"Certificate", Type, 0}, + {"Certificate.Certificate", Field, 0}, + {"Certificate.Leaf", Field, 0}, + {"Certificate.OCSPStaple", Field, 0}, + {"Certificate.PrivateKey", Field, 0}, + {"Certificate.SignedCertificateTimestamps", Field, 5}, + {"Certificate.SupportedSignatureAlgorithms", Field, 14}, + {"CertificateRequestInfo", Type, 8}, + {"CertificateRequestInfo.AcceptableCAs", Field, 8}, + {"CertificateRequestInfo.SignatureSchemes", Field, 8}, + {"CertificateRequestInfo.Version", Field, 14}, + {"CertificateVerificationError", Type, 20}, + {"CertificateVerificationError.Err", Field, 20}, + {"CertificateVerificationError.UnverifiedCertificates", Field, 20}, + {"CipherSuite", Type, 14}, + {"CipherSuite.ID", Field, 14}, + {"CipherSuite.Insecure", Field, 14}, + {"CipherSuite.Name", Field, 14}, + {"CipherSuite.SupportedVersions", Field, 14}, + {"CipherSuiteName", Func, 14}, + {"CipherSuites", Func, 14}, + {"Client", Func, 0}, + {"ClientAuthType", Type, 0}, + {"ClientHelloInfo", Type, 4}, + {"ClientHelloInfo.CipherSuites", Field, 4}, + {"ClientHelloInfo.Conn", Field, 8}, + {"ClientHelloInfo.ServerName", Field, 4}, + {"ClientHelloInfo.SignatureSchemes", Field, 8}, + {"ClientHelloInfo.SupportedCurves", Field, 4}, + {"ClientHelloInfo.SupportedPoints", Field, 4}, + {"ClientHelloInfo.SupportedProtos", Field, 8}, + {"ClientHelloInfo.SupportedVersions", Field, 8}, + {"ClientSessionCache", Type, 3}, + {"ClientSessionState", Type, 3}, + {"Config", Type, 0}, + {"Config.Certificates", Field, 0}, + {"Config.CipherSuites", Field, 0}, + {"Config.ClientAuth", Field, 0}, + {"Config.ClientCAs", Field, 0}, + {"Config.ClientSessionCache", Field, 3}, + {"Config.CurvePreferences", Field, 3}, + {"Config.DynamicRecordSizingDisabled", Field, 7}, + {"Config.EncryptedClientHelloConfigList", Field, 23}, + {"Config.EncryptedClientHelloRejectionVerify", Field, 23}, + {"Config.GetCertificate", Field, 4}, + {"Config.GetClientCertificate", Field, 8}, + {"Config.GetConfigForClient", Field, 8}, + {"Config.InsecureSkipVerify", Field, 0}, + {"Config.KeyLogWriter", Field, 8}, + {"Config.MaxVersion", Field, 2}, + {"Config.MinVersion", Field, 2}, + {"Config.NameToCertificate", Field, 0}, + {"Config.NextProtos", Field, 0}, + {"Config.PreferServerCipherSuites", Field, 1}, + {"Config.Rand", Field, 0}, + {"Config.Renegotiation", Field, 7}, + {"Config.RootCAs", Field, 0}, + {"Config.ServerName", Field, 0}, + {"Config.SessionTicketKey", Field, 1}, + {"Config.SessionTicketsDisabled", Field, 1}, + {"Config.Time", Field, 0}, + {"Config.UnwrapSession", Field, 21}, + {"Config.VerifyConnection", Field, 15}, + {"Config.VerifyPeerCertificate", Field, 8}, + {"Config.WrapSession", Field, 21}, + {"Conn", Type, 0}, + {"ConnectionState", Type, 0}, + {"ConnectionState.CipherSuite", Field, 0}, + {"ConnectionState.DidResume", Field, 1}, + {"ConnectionState.ECHAccepted", Field, 23}, + {"ConnectionState.HandshakeComplete", Field, 0}, + {"ConnectionState.NegotiatedProtocol", Field, 0}, + {"ConnectionState.NegotiatedProtocolIsMutual", Field, 0}, + {"ConnectionState.OCSPResponse", Field, 5}, + {"ConnectionState.PeerCertificates", Field, 0}, + {"ConnectionState.ServerName", Field, 0}, + {"ConnectionState.SignedCertificateTimestamps", Field, 5}, + {"ConnectionState.TLSUnique", Field, 4}, + {"ConnectionState.VerifiedChains", Field, 0}, + {"ConnectionState.Version", Field, 3}, + {"CurveID", Type, 3}, + {"CurveP256", Const, 3}, + {"CurveP384", Const, 3}, + {"CurveP521", Const, 3}, + {"Dial", Func, 0}, + {"DialWithDialer", Func, 3}, + {"Dialer", Type, 15}, + {"Dialer.Config", Field, 15}, + {"Dialer.NetDialer", Field, 15}, + {"ECDSAWithP256AndSHA256", Const, 8}, + {"ECDSAWithP384AndSHA384", Const, 8}, + {"ECDSAWithP521AndSHA512", Const, 8}, + {"ECDSAWithSHA1", Const, 10}, + {"ECHRejectionError", Type, 23}, + {"ECHRejectionError.RetryConfigList", Field, 23}, + {"Ed25519", Const, 13}, + {"InsecureCipherSuites", Func, 14}, + {"Listen", Func, 0}, + {"LoadX509KeyPair", Func, 0}, + {"NewLRUClientSessionCache", Func, 3}, + {"NewListener", Func, 0}, + {"NewResumptionState", Func, 21}, + {"NoClientCert", Const, 0}, + {"PKCS1WithSHA1", Const, 8}, + {"PKCS1WithSHA256", Const, 8}, + {"PKCS1WithSHA384", Const, 8}, + {"PKCS1WithSHA512", Const, 8}, + {"PSSWithSHA256", Const, 8}, + {"PSSWithSHA384", Const, 8}, + {"PSSWithSHA512", Const, 8}, + {"ParseSessionState", Func, 21}, + {"QUICClient", Func, 21}, + {"QUICConfig", Type, 21}, + {"QUICConfig.EnableSessionEvents", Field, 23}, + {"QUICConfig.TLSConfig", Field, 21}, + {"QUICConn", Type, 21}, + {"QUICEncryptionLevel", Type, 21}, + {"QUICEncryptionLevelApplication", Const, 21}, + {"QUICEncryptionLevelEarly", Const, 21}, + {"QUICEncryptionLevelHandshake", Const, 21}, + {"QUICEncryptionLevelInitial", Const, 21}, + {"QUICEvent", Type, 21}, + {"QUICEvent.Data", Field, 21}, + {"QUICEvent.Kind", Field, 21}, + {"QUICEvent.Level", Field, 21}, + {"QUICEvent.SessionState", Field, 23}, + {"QUICEvent.Suite", Field, 21}, + {"QUICEventKind", Type, 21}, + {"QUICHandshakeDone", Const, 21}, + {"QUICNoEvent", Const, 21}, + {"QUICRejectedEarlyData", Const, 21}, + {"QUICResumeSession", Const, 23}, + {"QUICServer", Func, 21}, + {"QUICSessionTicketOptions", Type, 21}, + {"QUICSessionTicketOptions.EarlyData", Field, 21}, + {"QUICSessionTicketOptions.Extra", Field, 23}, + {"QUICSetReadSecret", Const, 21}, + {"QUICSetWriteSecret", Const, 21}, + {"QUICStoreSession", Const, 23}, + {"QUICTransportParameters", Const, 21}, + {"QUICTransportParametersRequired", Const, 21}, + {"QUICWriteData", Const, 21}, + {"RecordHeaderError", Type, 6}, + {"RecordHeaderError.Conn", Field, 12}, + {"RecordHeaderError.Msg", Field, 6}, + {"RecordHeaderError.RecordHeader", Field, 6}, + {"RenegotiateFreelyAsClient", Const, 7}, + {"RenegotiateNever", Const, 7}, + {"RenegotiateOnceAsClient", Const, 7}, + {"RenegotiationSupport", Type, 7}, + {"RequestClientCert", Const, 0}, + {"RequireAndVerifyClientCert", Const, 0}, + {"RequireAnyClientCert", Const, 0}, + {"Server", Func, 0}, + {"SessionState", Type, 21}, + {"SessionState.EarlyData", Field, 21}, + {"SessionState.Extra", Field, 21}, + {"SignatureScheme", Type, 8}, + {"TLS_AES_128_GCM_SHA256", Const, 12}, + {"TLS_AES_256_GCM_SHA384", Const, 12}, + {"TLS_CHACHA20_POLY1305_SHA256", Const, 12}, + {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", Const, 2}, + {"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", Const, 8}, + {"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", Const, 2}, + {"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", Const, 2}, + {"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", Const, 5}, + {"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", Const, 8}, + {"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", Const, 14}, + {"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", Const, 2}, + {"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", Const, 0}, + {"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", Const, 0}, + {"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", Const, 8}, + {"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", Const, 2}, + {"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", Const, 1}, + {"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", Const, 5}, + {"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", Const, 8}, + {"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", Const, 14}, + {"TLS_ECDHE_RSA_WITH_RC4_128_SHA", Const, 0}, + {"TLS_FALLBACK_SCSV", Const, 4}, + {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", Const, 0}, + {"TLS_RSA_WITH_AES_128_CBC_SHA", Const, 0}, + {"TLS_RSA_WITH_AES_128_CBC_SHA256", Const, 8}, + {"TLS_RSA_WITH_AES_128_GCM_SHA256", Const, 6}, + {"TLS_RSA_WITH_AES_256_CBC_SHA", Const, 1}, + {"TLS_RSA_WITH_AES_256_GCM_SHA384", Const, 6}, + {"TLS_RSA_WITH_RC4_128_SHA", Const, 0}, + {"VerifyClientCertIfGiven", Const, 0}, + {"VersionName", Func, 21}, + {"VersionSSL30", Const, 2}, + {"VersionTLS10", Const, 2}, + {"VersionTLS11", Const, 2}, + {"VersionTLS12", Const, 2}, + {"VersionTLS13", Const, 12}, + {"X25519", Const, 8}, + {"X509KeyPair", Func, 0}, + }, + "crypto/x509": { + {"(*CertPool).AddCert", Method, 0}, + {"(*CertPool).AddCertWithConstraint", Method, 22}, + {"(*CertPool).AppendCertsFromPEM", Method, 0}, + {"(*CertPool).Clone", Method, 19}, + {"(*CertPool).Equal", Method, 19}, + {"(*CertPool).Subjects", Method, 0}, + {"(*Certificate).CheckCRLSignature", Method, 0}, + {"(*Certificate).CheckSignature", Method, 0}, + {"(*Certificate).CheckSignatureFrom", Method, 0}, + {"(*Certificate).CreateCRL", Method, 0}, + {"(*Certificate).Equal", Method, 0}, + {"(*Certificate).Verify", Method, 0}, + {"(*Certificate).VerifyHostname", Method, 0}, + {"(*CertificateRequest).CheckSignature", Method, 5}, + {"(*OID).UnmarshalBinary", Method, 23}, + {"(*OID).UnmarshalText", Method, 23}, + {"(*RevocationList).CheckSignatureFrom", Method, 19}, + {"(CertificateInvalidError).Error", Method, 0}, + {"(ConstraintViolationError).Error", Method, 0}, + {"(HostnameError).Error", Method, 0}, + {"(InsecureAlgorithmError).Error", Method, 6}, + {"(OID).Equal", Method, 22}, + {"(OID).EqualASN1OID", Method, 22}, + {"(OID).MarshalBinary", Method, 23}, + {"(OID).MarshalText", Method, 23}, + {"(OID).String", Method, 22}, + {"(PublicKeyAlgorithm).String", Method, 10}, + {"(SignatureAlgorithm).String", Method, 6}, + {"(SystemRootsError).Error", Method, 1}, + {"(SystemRootsError).Unwrap", Method, 16}, + {"(UnhandledCriticalExtension).Error", Method, 0}, + {"(UnknownAuthorityError).Error", Method, 0}, + {"CANotAuthorizedForExtKeyUsage", Const, 10}, + {"CANotAuthorizedForThisName", Const, 0}, + {"CertPool", Type, 0}, + {"Certificate", Type, 0}, + {"Certificate.AuthorityKeyId", Field, 0}, + {"Certificate.BasicConstraintsValid", Field, 0}, + {"Certificate.CRLDistributionPoints", Field, 2}, + {"Certificate.DNSNames", Field, 0}, + {"Certificate.EmailAddresses", Field, 0}, + {"Certificate.ExcludedDNSDomains", Field, 9}, + {"Certificate.ExcludedEmailAddresses", Field, 10}, + {"Certificate.ExcludedIPRanges", Field, 10}, + {"Certificate.ExcludedURIDomains", Field, 10}, + {"Certificate.ExtKeyUsage", Field, 0}, + {"Certificate.Extensions", Field, 2}, + {"Certificate.ExtraExtensions", Field, 2}, + {"Certificate.IPAddresses", Field, 1}, + {"Certificate.IsCA", Field, 0}, + {"Certificate.Issuer", Field, 0}, + {"Certificate.IssuingCertificateURL", Field, 2}, + {"Certificate.KeyUsage", Field, 0}, + {"Certificate.MaxPathLen", Field, 0}, + {"Certificate.MaxPathLenZero", Field, 4}, + {"Certificate.NotAfter", Field, 0}, + {"Certificate.NotBefore", Field, 0}, + {"Certificate.OCSPServer", Field, 2}, + {"Certificate.PermittedDNSDomains", Field, 0}, + {"Certificate.PermittedDNSDomainsCritical", Field, 0}, + {"Certificate.PermittedEmailAddresses", Field, 10}, + {"Certificate.PermittedIPRanges", Field, 10}, + {"Certificate.PermittedURIDomains", Field, 10}, + {"Certificate.Policies", Field, 22}, + {"Certificate.PolicyIdentifiers", Field, 0}, + {"Certificate.PublicKey", Field, 0}, + {"Certificate.PublicKeyAlgorithm", Field, 0}, + {"Certificate.Raw", Field, 0}, + {"Certificate.RawIssuer", Field, 0}, + {"Certificate.RawSubject", Field, 0}, + {"Certificate.RawSubjectPublicKeyInfo", Field, 0}, + {"Certificate.RawTBSCertificate", Field, 0}, + {"Certificate.SerialNumber", Field, 0}, + {"Certificate.Signature", Field, 0}, + {"Certificate.SignatureAlgorithm", Field, 0}, + {"Certificate.Subject", Field, 0}, + {"Certificate.SubjectKeyId", Field, 0}, + {"Certificate.URIs", Field, 10}, + {"Certificate.UnhandledCriticalExtensions", Field, 5}, + {"Certificate.UnknownExtKeyUsage", Field, 0}, + {"Certificate.Version", Field, 0}, + {"CertificateInvalidError", Type, 0}, + {"CertificateInvalidError.Cert", Field, 0}, + {"CertificateInvalidError.Detail", Field, 10}, + {"CertificateInvalidError.Reason", Field, 0}, + {"CertificateRequest", Type, 3}, + {"CertificateRequest.Attributes", Field, 3}, + {"CertificateRequest.DNSNames", Field, 3}, + {"CertificateRequest.EmailAddresses", Field, 3}, + {"CertificateRequest.Extensions", Field, 3}, + {"CertificateRequest.ExtraExtensions", Field, 3}, + {"CertificateRequest.IPAddresses", Field, 3}, + {"CertificateRequest.PublicKey", Field, 3}, + {"CertificateRequest.PublicKeyAlgorithm", Field, 3}, + {"CertificateRequest.Raw", Field, 3}, + {"CertificateRequest.RawSubject", Field, 3}, + {"CertificateRequest.RawSubjectPublicKeyInfo", Field, 3}, + {"CertificateRequest.RawTBSCertificateRequest", Field, 3}, + {"CertificateRequest.Signature", Field, 3}, + {"CertificateRequest.SignatureAlgorithm", Field, 3}, + {"CertificateRequest.Subject", Field, 3}, + {"CertificateRequest.URIs", Field, 10}, + {"CertificateRequest.Version", Field, 3}, + {"ConstraintViolationError", Type, 0}, + {"CreateCertificate", Func, 0}, + {"CreateCertificateRequest", Func, 3}, + {"CreateRevocationList", Func, 15}, + {"DSA", Const, 0}, + {"DSAWithSHA1", Const, 0}, + {"DSAWithSHA256", Const, 0}, + {"DecryptPEMBlock", Func, 1}, + {"ECDSA", Const, 1}, + {"ECDSAWithSHA1", Const, 1}, + {"ECDSAWithSHA256", Const, 1}, + {"ECDSAWithSHA384", Const, 1}, + {"ECDSAWithSHA512", Const, 1}, + {"Ed25519", Const, 13}, + {"EncryptPEMBlock", Func, 1}, + {"ErrUnsupportedAlgorithm", Var, 0}, + {"Expired", Const, 0}, + {"ExtKeyUsage", Type, 0}, + {"ExtKeyUsageAny", Const, 0}, + {"ExtKeyUsageClientAuth", Const, 0}, + {"ExtKeyUsageCodeSigning", Const, 0}, + {"ExtKeyUsageEmailProtection", Const, 0}, + {"ExtKeyUsageIPSECEndSystem", Const, 1}, + {"ExtKeyUsageIPSECTunnel", Const, 1}, + {"ExtKeyUsageIPSECUser", Const, 1}, + {"ExtKeyUsageMicrosoftCommercialCodeSigning", Const, 10}, + {"ExtKeyUsageMicrosoftKernelCodeSigning", Const, 10}, + {"ExtKeyUsageMicrosoftServerGatedCrypto", Const, 1}, + {"ExtKeyUsageNetscapeServerGatedCrypto", Const, 1}, + {"ExtKeyUsageOCSPSigning", Const, 0}, + {"ExtKeyUsageServerAuth", Const, 0}, + {"ExtKeyUsageTimeStamping", Const, 0}, + {"HostnameError", Type, 0}, + {"HostnameError.Certificate", Field, 0}, + {"HostnameError.Host", Field, 0}, + {"IncompatibleUsage", Const, 1}, + {"IncorrectPasswordError", Var, 1}, + {"InsecureAlgorithmError", Type, 6}, + {"InvalidReason", Type, 0}, + {"IsEncryptedPEMBlock", Func, 1}, + {"KeyUsage", Type, 0}, + {"KeyUsageCRLSign", Const, 0}, + {"KeyUsageCertSign", Const, 0}, + {"KeyUsageContentCommitment", Const, 0}, + {"KeyUsageDataEncipherment", Const, 0}, + {"KeyUsageDecipherOnly", Const, 0}, + {"KeyUsageDigitalSignature", Const, 0}, + {"KeyUsageEncipherOnly", Const, 0}, + {"KeyUsageKeyAgreement", Const, 0}, + {"KeyUsageKeyEncipherment", Const, 0}, + {"MD2WithRSA", Const, 0}, + {"MD5WithRSA", Const, 0}, + {"MarshalECPrivateKey", Func, 2}, + {"MarshalPKCS1PrivateKey", Func, 0}, + {"MarshalPKCS1PublicKey", Func, 10}, + {"MarshalPKCS8PrivateKey", Func, 10}, + {"MarshalPKIXPublicKey", Func, 0}, + {"NameConstraintsWithoutSANs", Const, 10}, + {"NameMismatch", Const, 8}, + {"NewCertPool", Func, 0}, + {"NotAuthorizedToSign", Const, 0}, + {"OID", Type, 22}, + {"OIDFromInts", Func, 22}, + {"PEMCipher", Type, 1}, + {"PEMCipher3DES", Const, 1}, + {"PEMCipherAES128", Const, 1}, + {"PEMCipherAES192", Const, 1}, + {"PEMCipherAES256", Const, 1}, + {"PEMCipherDES", Const, 1}, + {"ParseCRL", Func, 0}, + {"ParseCertificate", Func, 0}, + {"ParseCertificateRequest", Func, 3}, + {"ParseCertificates", Func, 0}, + {"ParseDERCRL", Func, 0}, + {"ParseECPrivateKey", Func, 1}, + {"ParseOID", Func, 23}, + {"ParsePKCS1PrivateKey", Func, 0}, + {"ParsePKCS1PublicKey", Func, 10}, + {"ParsePKCS8PrivateKey", Func, 0}, + {"ParsePKIXPublicKey", Func, 0}, + {"ParseRevocationList", Func, 19}, + {"PublicKeyAlgorithm", Type, 0}, + {"PureEd25519", Const, 13}, + {"RSA", Const, 0}, + {"RevocationList", Type, 15}, + {"RevocationList.AuthorityKeyId", Field, 19}, + {"RevocationList.Extensions", Field, 19}, + {"RevocationList.ExtraExtensions", Field, 15}, + {"RevocationList.Issuer", Field, 19}, + {"RevocationList.NextUpdate", Field, 15}, + {"RevocationList.Number", Field, 15}, + {"RevocationList.Raw", Field, 19}, + {"RevocationList.RawIssuer", Field, 19}, + {"RevocationList.RawTBSRevocationList", Field, 19}, + {"RevocationList.RevokedCertificateEntries", Field, 21}, + {"RevocationList.RevokedCertificates", Field, 15}, + {"RevocationList.Signature", Field, 19}, + {"RevocationList.SignatureAlgorithm", Field, 15}, + {"RevocationList.ThisUpdate", Field, 15}, + {"RevocationListEntry", Type, 21}, + {"RevocationListEntry.Extensions", Field, 21}, + {"RevocationListEntry.ExtraExtensions", Field, 21}, + {"RevocationListEntry.Raw", Field, 21}, + {"RevocationListEntry.ReasonCode", Field, 21}, + {"RevocationListEntry.RevocationTime", Field, 21}, + {"RevocationListEntry.SerialNumber", Field, 21}, + {"SHA1WithRSA", Const, 0}, + {"SHA256WithRSA", Const, 0}, + {"SHA256WithRSAPSS", Const, 8}, + {"SHA384WithRSA", Const, 0}, + {"SHA384WithRSAPSS", Const, 8}, + {"SHA512WithRSA", Const, 0}, + {"SHA512WithRSAPSS", Const, 8}, + {"SetFallbackRoots", Func, 20}, + {"SignatureAlgorithm", Type, 0}, + {"SystemCertPool", Func, 7}, + {"SystemRootsError", Type, 1}, + {"SystemRootsError.Err", Field, 7}, + {"TooManyConstraints", Const, 10}, + {"TooManyIntermediates", Const, 0}, + {"UnconstrainedName", Const, 10}, + {"UnhandledCriticalExtension", Type, 0}, + {"UnknownAuthorityError", Type, 0}, + {"UnknownAuthorityError.Cert", Field, 8}, + {"UnknownPublicKeyAlgorithm", Const, 0}, + {"UnknownSignatureAlgorithm", Const, 0}, + {"VerifyOptions", Type, 0}, + {"VerifyOptions.CurrentTime", Field, 0}, + {"VerifyOptions.DNSName", Field, 0}, + {"VerifyOptions.Intermediates", Field, 0}, + {"VerifyOptions.KeyUsages", Field, 1}, + {"VerifyOptions.MaxConstraintComparisions", Field, 10}, + {"VerifyOptions.Roots", Field, 0}, + }, + "crypto/x509/pkix": { + {"(*CertificateList).HasExpired", Method, 0}, + {"(*Name).FillFromRDNSequence", Method, 0}, + {"(Name).String", Method, 10}, + {"(Name).ToRDNSequence", Method, 0}, + {"(RDNSequence).String", Method, 10}, + {"AlgorithmIdentifier", Type, 0}, + {"AlgorithmIdentifier.Algorithm", Field, 0}, + {"AlgorithmIdentifier.Parameters", Field, 0}, + {"AttributeTypeAndValue", Type, 0}, + {"AttributeTypeAndValue.Type", Field, 0}, + {"AttributeTypeAndValue.Value", Field, 0}, + {"AttributeTypeAndValueSET", Type, 3}, + {"AttributeTypeAndValueSET.Type", Field, 3}, + {"AttributeTypeAndValueSET.Value", Field, 3}, + {"CertificateList", Type, 0}, + {"CertificateList.SignatureAlgorithm", Field, 0}, + {"CertificateList.SignatureValue", Field, 0}, + {"CertificateList.TBSCertList", Field, 0}, + {"Extension", Type, 0}, + {"Extension.Critical", Field, 0}, + {"Extension.Id", Field, 0}, + {"Extension.Value", Field, 0}, + {"Name", Type, 0}, + {"Name.CommonName", Field, 0}, + {"Name.Country", Field, 0}, + {"Name.ExtraNames", Field, 5}, + {"Name.Locality", Field, 0}, + {"Name.Names", Field, 0}, + {"Name.Organization", Field, 0}, + {"Name.OrganizationalUnit", Field, 0}, + {"Name.PostalCode", Field, 0}, + {"Name.Province", Field, 0}, + {"Name.SerialNumber", Field, 0}, + {"Name.StreetAddress", Field, 0}, + {"RDNSequence", Type, 0}, + {"RelativeDistinguishedNameSET", Type, 0}, + {"RevokedCertificate", Type, 0}, + {"RevokedCertificate.Extensions", Field, 0}, + {"RevokedCertificate.RevocationTime", Field, 0}, + {"RevokedCertificate.SerialNumber", Field, 0}, + {"TBSCertificateList", Type, 0}, + {"TBSCertificateList.Extensions", Field, 0}, + {"TBSCertificateList.Issuer", Field, 0}, + {"TBSCertificateList.NextUpdate", Field, 0}, + {"TBSCertificateList.Raw", Field, 0}, + {"TBSCertificateList.RevokedCertificates", Field, 0}, + {"TBSCertificateList.Signature", Field, 0}, + {"TBSCertificateList.ThisUpdate", Field, 0}, + {"TBSCertificateList.Version", Field, 0}, + }, + "database/sql": { + {"(*ColumnType).DatabaseTypeName", Method, 8}, + {"(*ColumnType).DecimalSize", Method, 8}, + {"(*ColumnType).Length", Method, 8}, + {"(*ColumnType).Name", Method, 8}, + {"(*ColumnType).Nullable", Method, 8}, + {"(*ColumnType).ScanType", Method, 8}, + {"(*Conn).BeginTx", Method, 9}, + {"(*Conn).Close", Method, 9}, + {"(*Conn).ExecContext", Method, 9}, + {"(*Conn).PingContext", Method, 9}, + {"(*Conn).PrepareContext", Method, 9}, + {"(*Conn).QueryContext", Method, 9}, + {"(*Conn).QueryRowContext", Method, 9}, + {"(*Conn).Raw", Method, 13}, + {"(*DB).Begin", Method, 0}, + {"(*DB).BeginTx", Method, 8}, + {"(*DB).Close", Method, 0}, + {"(*DB).Conn", Method, 9}, + {"(*DB).Driver", Method, 0}, + {"(*DB).Exec", Method, 0}, + {"(*DB).ExecContext", Method, 8}, + {"(*DB).Ping", Method, 1}, + {"(*DB).PingContext", Method, 8}, + {"(*DB).Prepare", Method, 0}, + {"(*DB).PrepareContext", Method, 8}, + {"(*DB).Query", Method, 0}, + {"(*DB).QueryContext", Method, 8}, + {"(*DB).QueryRow", Method, 0}, + {"(*DB).QueryRowContext", Method, 8}, + {"(*DB).SetConnMaxIdleTime", Method, 15}, + {"(*DB).SetConnMaxLifetime", Method, 6}, + {"(*DB).SetMaxIdleConns", Method, 1}, + {"(*DB).SetMaxOpenConns", Method, 2}, + {"(*DB).Stats", Method, 5}, + {"(*Null).Scan", Method, 22}, + {"(*NullBool).Scan", Method, 0}, + {"(*NullByte).Scan", Method, 17}, + {"(*NullFloat64).Scan", Method, 0}, + {"(*NullInt16).Scan", Method, 17}, + {"(*NullInt32).Scan", Method, 13}, + {"(*NullInt64).Scan", Method, 0}, + {"(*NullString).Scan", Method, 0}, + {"(*NullTime).Scan", Method, 13}, + {"(*Row).Err", Method, 15}, + {"(*Row).Scan", Method, 0}, + {"(*Rows).Close", Method, 0}, + {"(*Rows).ColumnTypes", Method, 8}, + {"(*Rows).Columns", Method, 0}, + {"(*Rows).Err", Method, 0}, + {"(*Rows).Next", Method, 0}, + {"(*Rows).NextResultSet", Method, 8}, + {"(*Rows).Scan", Method, 0}, + {"(*Stmt).Close", Method, 0}, + {"(*Stmt).Exec", Method, 0}, + {"(*Stmt).ExecContext", Method, 8}, + {"(*Stmt).Query", Method, 0}, + {"(*Stmt).QueryContext", Method, 8}, + {"(*Stmt).QueryRow", Method, 0}, + {"(*Stmt).QueryRowContext", Method, 8}, + {"(*Tx).Commit", Method, 0}, + {"(*Tx).Exec", Method, 0}, + {"(*Tx).ExecContext", Method, 8}, + {"(*Tx).Prepare", Method, 0}, + {"(*Tx).PrepareContext", Method, 8}, + {"(*Tx).Query", Method, 0}, + {"(*Tx).QueryContext", Method, 8}, + {"(*Tx).QueryRow", Method, 0}, + {"(*Tx).QueryRowContext", Method, 8}, + {"(*Tx).Rollback", Method, 0}, + {"(*Tx).Stmt", Method, 0}, + {"(*Tx).StmtContext", Method, 8}, + {"(IsolationLevel).String", Method, 11}, + {"(Null).Value", Method, 22}, + {"(NullBool).Value", Method, 0}, + {"(NullByte).Value", Method, 17}, + {"(NullFloat64).Value", Method, 0}, + {"(NullInt16).Value", Method, 17}, + {"(NullInt32).Value", Method, 13}, + {"(NullInt64).Value", Method, 0}, + {"(NullString).Value", Method, 0}, + {"(NullTime).Value", Method, 13}, + {"ColumnType", Type, 8}, + {"Conn", Type, 9}, + {"DB", Type, 0}, + {"DBStats", Type, 5}, + {"DBStats.Idle", Field, 11}, + {"DBStats.InUse", Field, 11}, + {"DBStats.MaxIdleClosed", Field, 11}, + {"DBStats.MaxIdleTimeClosed", Field, 15}, + {"DBStats.MaxLifetimeClosed", Field, 11}, + {"DBStats.MaxOpenConnections", Field, 11}, + {"DBStats.OpenConnections", Field, 5}, + {"DBStats.WaitCount", Field, 11}, + {"DBStats.WaitDuration", Field, 11}, + {"Drivers", Func, 4}, + {"ErrConnDone", Var, 9}, + {"ErrNoRows", Var, 0}, + {"ErrTxDone", Var, 0}, + {"IsolationLevel", Type, 8}, + {"LevelDefault", Const, 8}, + {"LevelLinearizable", Const, 8}, + {"LevelReadCommitted", Const, 8}, + {"LevelReadUncommitted", Const, 8}, + {"LevelRepeatableRead", Const, 8}, + {"LevelSerializable", Const, 8}, + {"LevelSnapshot", Const, 8}, + {"LevelWriteCommitted", Const, 8}, + {"Named", Func, 8}, + {"NamedArg", Type, 8}, + {"NamedArg.Name", Field, 8}, + {"NamedArg.Value", Field, 8}, + {"Null", Type, 22}, + {"Null.V", Field, 22}, + {"Null.Valid", Field, 22}, + {"NullBool", Type, 0}, + {"NullBool.Bool", Field, 0}, + {"NullBool.Valid", Field, 0}, + {"NullByte", Type, 17}, + {"NullByte.Byte", Field, 17}, + {"NullByte.Valid", Field, 17}, + {"NullFloat64", Type, 0}, + {"NullFloat64.Float64", Field, 0}, + {"NullFloat64.Valid", Field, 0}, + {"NullInt16", Type, 17}, + {"NullInt16.Int16", Field, 17}, + {"NullInt16.Valid", Field, 17}, + {"NullInt32", Type, 13}, + {"NullInt32.Int32", Field, 13}, + {"NullInt32.Valid", Field, 13}, + {"NullInt64", Type, 0}, + {"NullInt64.Int64", Field, 0}, + {"NullInt64.Valid", Field, 0}, + {"NullString", Type, 0}, + {"NullString.String", Field, 0}, + {"NullString.Valid", Field, 0}, + {"NullTime", Type, 13}, + {"NullTime.Time", Field, 13}, + {"NullTime.Valid", Field, 13}, + {"Open", Func, 0}, + {"OpenDB", Func, 10}, + {"Out", Type, 9}, + {"Out.Dest", Field, 9}, + {"Out.In", Field, 9}, + {"RawBytes", Type, 0}, + {"Register", Func, 0}, + {"Result", Type, 0}, + {"Row", Type, 0}, + {"Rows", Type, 0}, + {"Scanner", Type, 0}, + {"Stmt", Type, 0}, + {"Tx", Type, 0}, + {"TxOptions", Type, 8}, + {"TxOptions.Isolation", Field, 8}, + {"TxOptions.ReadOnly", Field, 8}, + }, + "database/sql/driver": { + {"(NotNull).ConvertValue", Method, 0}, + {"(Null).ConvertValue", Method, 0}, + {"(RowsAffected).LastInsertId", Method, 0}, + {"(RowsAffected).RowsAffected", Method, 0}, + {"Bool", Var, 0}, + {"ColumnConverter", Type, 0}, + {"Conn", Type, 0}, + {"ConnBeginTx", Type, 8}, + {"ConnPrepareContext", Type, 8}, + {"Connector", Type, 10}, + {"DefaultParameterConverter", Var, 0}, + {"Driver", Type, 0}, + {"DriverContext", Type, 10}, + {"ErrBadConn", Var, 0}, + {"ErrRemoveArgument", Var, 9}, + {"ErrSkip", Var, 0}, + {"Execer", Type, 0}, + {"ExecerContext", Type, 8}, + {"Int32", Var, 0}, + {"IsScanValue", Func, 0}, + {"IsValue", Func, 0}, + {"IsolationLevel", Type, 8}, + {"NamedValue", Type, 8}, + {"NamedValue.Name", Field, 8}, + {"NamedValue.Ordinal", Field, 8}, + {"NamedValue.Value", Field, 8}, + {"NamedValueChecker", Type, 9}, + {"NotNull", Type, 0}, + {"NotNull.Converter", Field, 0}, + {"Null", Type, 0}, + {"Null.Converter", Field, 0}, + {"Pinger", Type, 8}, + {"Queryer", Type, 1}, + {"QueryerContext", Type, 8}, + {"Result", Type, 0}, + {"ResultNoRows", Var, 0}, + {"Rows", Type, 0}, + {"RowsAffected", Type, 0}, + {"RowsColumnTypeDatabaseTypeName", Type, 8}, + {"RowsColumnTypeLength", Type, 8}, + {"RowsColumnTypeNullable", Type, 8}, + {"RowsColumnTypePrecisionScale", Type, 8}, + {"RowsColumnTypeScanType", Type, 8}, + {"RowsNextResultSet", Type, 8}, + {"SessionResetter", Type, 10}, + {"Stmt", Type, 0}, + {"StmtExecContext", Type, 8}, + {"StmtQueryContext", Type, 8}, + {"String", Var, 0}, + {"Tx", Type, 0}, + {"TxOptions", Type, 8}, + {"TxOptions.Isolation", Field, 8}, + {"TxOptions.ReadOnly", Field, 8}, + {"Validator", Type, 15}, + {"Value", Type, 0}, + {"ValueConverter", Type, 0}, + {"Valuer", Type, 0}, + }, + "debug/buildinfo": { + {"BuildInfo", Type, 18}, + {"Read", Func, 18}, + {"ReadFile", Func, 18}, + }, + "debug/dwarf": { + {"(*AddrType).Basic", Method, 0}, + {"(*AddrType).Common", Method, 0}, + {"(*AddrType).Size", Method, 0}, + {"(*AddrType).String", Method, 0}, + {"(*ArrayType).Common", Method, 0}, + {"(*ArrayType).Size", Method, 0}, + {"(*ArrayType).String", Method, 0}, + {"(*BasicType).Basic", Method, 0}, + {"(*BasicType).Common", Method, 0}, + {"(*BasicType).Size", Method, 0}, + {"(*BasicType).String", Method, 0}, + {"(*BoolType).Basic", Method, 0}, + {"(*BoolType).Common", Method, 0}, + {"(*BoolType).Size", Method, 0}, + {"(*BoolType).String", Method, 0}, + {"(*CharType).Basic", Method, 0}, + {"(*CharType).Common", Method, 0}, + {"(*CharType).Size", Method, 0}, + {"(*CharType).String", Method, 0}, + {"(*CommonType).Common", Method, 0}, + {"(*CommonType).Size", Method, 0}, + {"(*ComplexType).Basic", Method, 0}, + {"(*ComplexType).Common", Method, 0}, + {"(*ComplexType).Size", Method, 0}, + {"(*ComplexType).String", Method, 0}, + {"(*Data).AddSection", Method, 14}, + {"(*Data).AddTypes", Method, 3}, + {"(*Data).LineReader", Method, 5}, + {"(*Data).Ranges", Method, 7}, + {"(*Data).Reader", Method, 0}, + {"(*Data).Type", Method, 0}, + {"(*DotDotDotType).Common", Method, 0}, + {"(*DotDotDotType).Size", Method, 0}, + {"(*DotDotDotType).String", Method, 0}, + {"(*Entry).AttrField", Method, 5}, + {"(*Entry).Val", Method, 0}, + {"(*EnumType).Common", Method, 0}, + {"(*EnumType).Size", Method, 0}, + {"(*EnumType).String", Method, 0}, + {"(*FloatType).Basic", Method, 0}, + {"(*FloatType).Common", Method, 0}, + {"(*FloatType).Size", Method, 0}, + {"(*FloatType).String", Method, 0}, + {"(*FuncType).Common", Method, 0}, + {"(*FuncType).Size", Method, 0}, + {"(*FuncType).String", Method, 0}, + {"(*IntType).Basic", Method, 0}, + {"(*IntType).Common", Method, 0}, + {"(*IntType).Size", Method, 0}, + {"(*IntType).String", Method, 0}, + {"(*LineReader).Files", Method, 14}, + {"(*LineReader).Next", Method, 5}, + {"(*LineReader).Reset", Method, 5}, + {"(*LineReader).Seek", Method, 5}, + {"(*LineReader).SeekPC", Method, 5}, + {"(*LineReader).Tell", Method, 5}, + {"(*PtrType).Common", Method, 0}, + {"(*PtrType).Size", Method, 0}, + {"(*PtrType).String", Method, 0}, + {"(*QualType).Common", Method, 0}, + {"(*QualType).Size", Method, 0}, + {"(*QualType).String", Method, 0}, + {"(*Reader).AddressSize", Method, 5}, + {"(*Reader).ByteOrder", Method, 14}, + {"(*Reader).Next", Method, 0}, + {"(*Reader).Seek", Method, 0}, + {"(*Reader).SeekPC", Method, 7}, + {"(*Reader).SkipChildren", Method, 0}, + {"(*StructType).Common", Method, 0}, + {"(*StructType).Defn", Method, 0}, + {"(*StructType).Size", Method, 0}, + {"(*StructType).String", Method, 0}, + {"(*TypedefType).Common", Method, 0}, + {"(*TypedefType).Size", Method, 0}, + {"(*TypedefType).String", Method, 0}, + {"(*UcharType).Basic", Method, 0}, + {"(*UcharType).Common", Method, 0}, + {"(*UcharType).Size", Method, 0}, + {"(*UcharType).String", Method, 0}, + {"(*UintType).Basic", Method, 0}, + {"(*UintType).Common", Method, 0}, + {"(*UintType).Size", Method, 0}, + {"(*UintType).String", Method, 0}, + {"(*UnspecifiedType).Basic", Method, 4}, + {"(*UnspecifiedType).Common", Method, 4}, + {"(*UnspecifiedType).Size", Method, 4}, + {"(*UnspecifiedType).String", Method, 4}, + {"(*UnsupportedType).Common", Method, 13}, + {"(*UnsupportedType).Size", Method, 13}, + {"(*UnsupportedType).String", Method, 13}, + {"(*VoidType).Common", Method, 0}, + {"(*VoidType).Size", Method, 0}, + {"(*VoidType).String", Method, 0}, + {"(Attr).GoString", Method, 0}, + {"(Attr).String", Method, 0}, + {"(Class).GoString", Method, 5}, + {"(Class).String", Method, 5}, + {"(DecodeError).Error", Method, 0}, + {"(Tag).GoString", Method, 0}, + {"(Tag).String", Method, 0}, + {"AddrType", Type, 0}, + {"AddrType.BasicType", Field, 0}, + {"ArrayType", Type, 0}, + {"ArrayType.CommonType", Field, 0}, + {"ArrayType.Count", Field, 0}, + {"ArrayType.StrideBitSize", Field, 0}, + {"ArrayType.Type", Field, 0}, + {"Attr", Type, 0}, + {"AttrAbstractOrigin", Const, 0}, + {"AttrAccessibility", Const, 0}, + {"AttrAddrBase", Const, 14}, + {"AttrAddrClass", Const, 0}, + {"AttrAlignment", Const, 14}, + {"AttrAllocated", Const, 0}, + {"AttrArtificial", Const, 0}, + {"AttrAssociated", Const, 0}, + {"AttrBaseTypes", Const, 0}, + {"AttrBinaryScale", Const, 14}, + {"AttrBitOffset", Const, 0}, + {"AttrBitSize", Const, 0}, + {"AttrByteSize", Const, 0}, + {"AttrCallAllCalls", Const, 14}, + {"AttrCallAllSourceCalls", Const, 14}, + {"AttrCallAllTailCalls", Const, 14}, + {"AttrCallColumn", Const, 0}, + {"AttrCallDataLocation", Const, 14}, + {"AttrCallDataValue", Const, 14}, + {"AttrCallFile", Const, 0}, + {"AttrCallLine", Const, 0}, + {"AttrCallOrigin", Const, 14}, + {"AttrCallPC", Const, 14}, + {"AttrCallParameter", Const, 14}, + {"AttrCallReturnPC", Const, 14}, + {"AttrCallTailCall", Const, 14}, + {"AttrCallTarget", Const, 14}, + {"AttrCallTargetClobbered", Const, 14}, + {"AttrCallValue", Const, 14}, + {"AttrCalling", Const, 0}, + {"AttrCommonRef", Const, 0}, + {"AttrCompDir", Const, 0}, + {"AttrConstExpr", Const, 14}, + {"AttrConstValue", Const, 0}, + {"AttrContainingType", Const, 0}, + {"AttrCount", Const, 0}, + {"AttrDataBitOffset", Const, 14}, + {"AttrDataLocation", Const, 0}, + {"AttrDataMemberLoc", Const, 0}, + {"AttrDecimalScale", Const, 14}, + {"AttrDecimalSign", Const, 14}, + {"AttrDeclColumn", Const, 0}, + {"AttrDeclFile", Const, 0}, + {"AttrDeclLine", Const, 0}, + {"AttrDeclaration", Const, 0}, + {"AttrDefaultValue", Const, 0}, + {"AttrDefaulted", Const, 14}, + {"AttrDeleted", Const, 14}, + {"AttrDescription", Const, 0}, + {"AttrDigitCount", Const, 14}, + {"AttrDiscr", Const, 0}, + {"AttrDiscrList", Const, 0}, + {"AttrDiscrValue", Const, 0}, + {"AttrDwoName", Const, 14}, + {"AttrElemental", Const, 14}, + {"AttrEncoding", Const, 0}, + {"AttrEndianity", Const, 14}, + {"AttrEntrypc", Const, 0}, + {"AttrEnumClass", Const, 14}, + {"AttrExplicit", Const, 14}, + {"AttrExportSymbols", Const, 14}, + {"AttrExtension", Const, 0}, + {"AttrExternal", Const, 0}, + {"AttrFrameBase", Const, 0}, + {"AttrFriend", Const, 0}, + {"AttrHighpc", Const, 0}, + {"AttrIdentifierCase", Const, 0}, + {"AttrImport", Const, 0}, + {"AttrInline", Const, 0}, + {"AttrIsOptional", Const, 0}, + {"AttrLanguage", Const, 0}, + {"AttrLinkageName", Const, 14}, + {"AttrLocation", Const, 0}, + {"AttrLoclistsBase", Const, 14}, + {"AttrLowerBound", Const, 0}, + {"AttrLowpc", Const, 0}, + {"AttrMacroInfo", Const, 0}, + {"AttrMacros", Const, 14}, + {"AttrMainSubprogram", Const, 14}, + {"AttrMutable", Const, 14}, + {"AttrName", Const, 0}, + {"AttrNamelistItem", Const, 0}, + {"AttrNoreturn", Const, 14}, + {"AttrObjectPointer", Const, 14}, + {"AttrOrdering", Const, 0}, + {"AttrPictureString", Const, 14}, + {"AttrPriority", Const, 0}, + {"AttrProducer", Const, 0}, + {"AttrPrototyped", Const, 0}, + {"AttrPure", Const, 14}, + {"AttrRanges", Const, 0}, + {"AttrRank", Const, 14}, + {"AttrRecursive", Const, 14}, + {"AttrReference", Const, 14}, + {"AttrReturnAddr", Const, 0}, + {"AttrRnglistsBase", Const, 14}, + {"AttrRvalueReference", Const, 14}, + {"AttrSegment", Const, 0}, + {"AttrSibling", Const, 0}, + {"AttrSignature", Const, 14}, + {"AttrSmall", Const, 14}, + {"AttrSpecification", Const, 0}, + {"AttrStartScope", Const, 0}, + {"AttrStaticLink", Const, 0}, + {"AttrStmtList", Const, 0}, + {"AttrStrOffsetsBase", Const, 14}, + {"AttrStride", Const, 0}, + {"AttrStrideSize", Const, 0}, + {"AttrStringLength", Const, 0}, + {"AttrStringLengthBitSize", Const, 14}, + {"AttrStringLengthByteSize", Const, 14}, + {"AttrThreadsScaled", Const, 14}, + {"AttrTrampoline", Const, 0}, + {"AttrType", Const, 0}, + {"AttrUpperBound", Const, 0}, + {"AttrUseLocation", Const, 0}, + {"AttrUseUTF8", Const, 0}, + {"AttrVarParam", Const, 0}, + {"AttrVirtuality", Const, 0}, + {"AttrVisibility", Const, 0}, + {"AttrVtableElemLoc", Const, 0}, + {"BasicType", Type, 0}, + {"BasicType.BitOffset", Field, 0}, + {"BasicType.BitSize", Field, 0}, + {"BasicType.CommonType", Field, 0}, + {"BasicType.DataBitOffset", Field, 18}, + {"BoolType", Type, 0}, + {"BoolType.BasicType", Field, 0}, + {"CharType", Type, 0}, + {"CharType.BasicType", Field, 0}, + {"Class", Type, 5}, + {"ClassAddrPtr", Const, 14}, + {"ClassAddress", Const, 5}, + {"ClassBlock", Const, 5}, + {"ClassConstant", Const, 5}, + {"ClassExprLoc", Const, 5}, + {"ClassFlag", Const, 5}, + {"ClassLinePtr", Const, 5}, + {"ClassLocList", Const, 14}, + {"ClassLocListPtr", Const, 5}, + {"ClassMacPtr", Const, 5}, + {"ClassRangeListPtr", Const, 5}, + {"ClassReference", Const, 5}, + {"ClassReferenceAlt", Const, 5}, + {"ClassReferenceSig", Const, 5}, + {"ClassRngList", Const, 14}, + {"ClassRngListsPtr", Const, 14}, + {"ClassStrOffsetsPtr", Const, 14}, + {"ClassString", Const, 5}, + {"ClassStringAlt", Const, 5}, + {"ClassUnknown", Const, 6}, + {"CommonType", Type, 0}, + {"CommonType.ByteSize", Field, 0}, + {"CommonType.Name", Field, 0}, + {"ComplexType", Type, 0}, + {"ComplexType.BasicType", Field, 0}, + {"Data", Type, 0}, + {"DecodeError", Type, 0}, + {"DecodeError.Err", Field, 0}, + {"DecodeError.Name", Field, 0}, + {"DecodeError.Offset", Field, 0}, + {"DotDotDotType", Type, 0}, + {"DotDotDotType.CommonType", Field, 0}, + {"Entry", Type, 0}, + {"Entry.Children", Field, 0}, + {"Entry.Field", Field, 0}, + {"Entry.Offset", Field, 0}, + {"Entry.Tag", Field, 0}, + {"EnumType", Type, 0}, + {"EnumType.CommonType", Field, 0}, + {"EnumType.EnumName", Field, 0}, + {"EnumType.Val", Field, 0}, + {"EnumValue", Type, 0}, + {"EnumValue.Name", Field, 0}, + {"EnumValue.Val", Field, 0}, + {"ErrUnknownPC", Var, 5}, + {"Field", Type, 0}, + {"Field.Attr", Field, 0}, + {"Field.Class", Field, 5}, + {"Field.Val", Field, 0}, + {"FloatType", Type, 0}, + {"FloatType.BasicType", Field, 0}, + {"FuncType", Type, 0}, + {"FuncType.CommonType", Field, 0}, + {"FuncType.ParamType", Field, 0}, + {"FuncType.ReturnType", Field, 0}, + {"IntType", Type, 0}, + {"IntType.BasicType", Field, 0}, + {"LineEntry", Type, 5}, + {"LineEntry.Address", Field, 5}, + {"LineEntry.BasicBlock", Field, 5}, + {"LineEntry.Column", Field, 5}, + {"LineEntry.Discriminator", Field, 5}, + {"LineEntry.EndSequence", Field, 5}, + {"LineEntry.EpilogueBegin", Field, 5}, + {"LineEntry.File", Field, 5}, + {"LineEntry.ISA", Field, 5}, + {"LineEntry.IsStmt", Field, 5}, + {"LineEntry.Line", Field, 5}, + {"LineEntry.OpIndex", Field, 5}, + {"LineEntry.PrologueEnd", Field, 5}, + {"LineFile", Type, 5}, + {"LineFile.Length", Field, 5}, + {"LineFile.Mtime", Field, 5}, + {"LineFile.Name", Field, 5}, + {"LineReader", Type, 5}, + {"LineReaderPos", Type, 5}, + {"New", Func, 0}, + {"Offset", Type, 0}, + {"PtrType", Type, 0}, + {"PtrType.CommonType", Field, 0}, + {"PtrType.Type", Field, 0}, + {"QualType", Type, 0}, + {"QualType.CommonType", Field, 0}, + {"QualType.Qual", Field, 0}, + {"QualType.Type", Field, 0}, + {"Reader", Type, 0}, + {"StructField", Type, 0}, + {"StructField.BitOffset", Field, 0}, + {"StructField.BitSize", Field, 0}, + {"StructField.ByteOffset", Field, 0}, + {"StructField.ByteSize", Field, 0}, + {"StructField.DataBitOffset", Field, 18}, + {"StructField.Name", Field, 0}, + {"StructField.Type", Field, 0}, + {"StructType", Type, 0}, + {"StructType.CommonType", Field, 0}, + {"StructType.Field", Field, 0}, + {"StructType.Incomplete", Field, 0}, + {"StructType.Kind", Field, 0}, + {"StructType.StructName", Field, 0}, + {"Tag", Type, 0}, + {"TagAccessDeclaration", Const, 0}, + {"TagArrayType", Const, 0}, + {"TagAtomicType", Const, 14}, + {"TagBaseType", Const, 0}, + {"TagCallSite", Const, 14}, + {"TagCallSiteParameter", Const, 14}, + {"TagCatchDwarfBlock", Const, 0}, + {"TagClassType", Const, 0}, + {"TagCoarrayType", Const, 14}, + {"TagCommonDwarfBlock", Const, 0}, + {"TagCommonInclusion", Const, 0}, + {"TagCompileUnit", Const, 0}, + {"TagCondition", Const, 3}, + {"TagConstType", Const, 0}, + {"TagConstant", Const, 0}, + {"TagDwarfProcedure", Const, 0}, + {"TagDynamicType", Const, 14}, + {"TagEntryPoint", Const, 0}, + {"TagEnumerationType", Const, 0}, + {"TagEnumerator", Const, 0}, + {"TagFileType", Const, 0}, + {"TagFormalParameter", Const, 0}, + {"TagFriend", Const, 0}, + {"TagGenericSubrange", Const, 14}, + {"TagImmutableType", Const, 14}, + {"TagImportedDeclaration", Const, 0}, + {"TagImportedModule", Const, 0}, + {"TagImportedUnit", Const, 0}, + {"TagInheritance", Const, 0}, + {"TagInlinedSubroutine", Const, 0}, + {"TagInterfaceType", Const, 0}, + {"TagLabel", Const, 0}, + {"TagLexDwarfBlock", Const, 0}, + {"TagMember", Const, 0}, + {"TagModule", Const, 0}, + {"TagMutableType", Const, 0}, + {"TagNamelist", Const, 0}, + {"TagNamelistItem", Const, 0}, + {"TagNamespace", Const, 0}, + {"TagPackedType", Const, 0}, + {"TagPartialUnit", Const, 0}, + {"TagPointerType", Const, 0}, + {"TagPtrToMemberType", Const, 0}, + {"TagReferenceType", Const, 0}, + {"TagRestrictType", Const, 0}, + {"TagRvalueReferenceType", Const, 3}, + {"TagSetType", Const, 0}, + {"TagSharedType", Const, 3}, + {"TagSkeletonUnit", Const, 14}, + {"TagStringType", Const, 0}, + {"TagStructType", Const, 0}, + {"TagSubprogram", Const, 0}, + {"TagSubrangeType", Const, 0}, + {"TagSubroutineType", Const, 0}, + {"TagTemplateAlias", Const, 3}, + {"TagTemplateTypeParameter", Const, 0}, + {"TagTemplateValueParameter", Const, 0}, + {"TagThrownType", Const, 0}, + {"TagTryDwarfBlock", Const, 0}, + {"TagTypeUnit", Const, 3}, + {"TagTypedef", Const, 0}, + {"TagUnionType", Const, 0}, + {"TagUnspecifiedParameters", Const, 0}, + {"TagUnspecifiedType", Const, 0}, + {"TagVariable", Const, 0}, + {"TagVariant", Const, 0}, + {"TagVariantPart", Const, 0}, + {"TagVolatileType", Const, 0}, + {"TagWithStmt", Const, 0}, + {"Type", Type, 0}, + {"TypedefType", Type, 0}, + {"TypedefType.CommonType", Field, 0}, + {"TypedefType.Type", Field, 0}, + {"UcharType", Type, 0}, + {"UcharType.BasicType", Field, 0}, + {"UintType", Type, 0}, + {"UintType.BasicType", Field, 0}, + {"UnspecifiedType", Type, 4}, + {"UnspecifiedType.BasicType", Field, 4}, + {"UnsupportedType", Type, 13}, + {"UnsupportedType.CommonType", Field, 13}, + {"UnsupportedType.Tag", Field, 13}, + {"VoidType", Type, 0}, + {"VoidType.CommonType", Field, 0}, + }, + "debug/elf": { + {"(*File).Close", Method, 0}, + {"(*File).DWARF", Method, 0}, + {"(*File).DynString", Method, 1}, + {"(*File).DynValue", Method, 21}, + {"(*File).DynamicSymbols", Method, 4}, + {"(*File).ImportedLibraries", Method, 0}, + {"(*File).ImportedSymbols", Method, 0}, + {"(*File).Section", Method, 0}, + {"(*File).SectionByType", Method, 0}, + {"(*File).Symbols", Method, 0}, + {"(*FormatError).Error", Method, 0}, + {"(*Prog).Open", Method, 0}, + {"(*Section).Data", Method, 0}, + {"(*Section).Open", Method, 0}, + {"(Class).GoString", Method, 0}, + {"(Class).String", Method, 0}, + {"(CompressionType).GoString", Method, 6}, + {"(CompressionType).String", Method, 6}, + {"(Data).GoString", Method, 0}, + {"(Data).String", Method, 0}, + {"(DynFlag).GoString", Method, 0}, + {"(DynFlag).String", Method, 0}, + {"(DynFlag1).GoString", Method, 21}, + {"(DynFlag1).String", Method, 21}, + {"(DynTag).GoString", Method, 0}, + {"(DynTag).String", Method, 0}, + {"(Machine).GoString", Method, 0}, + {"(Machine).String", Method, 0}, + {"(NType).GoString", Method, 0}, + {"(NType).String", Method, 0}, + {"(OSABI).GoString", Method, 0}, + {"(OSABI).String", Method, 0}, + {"(Prog).ReadAt", Method, 0}, + {"(ProgFlag).GoString", Method, 0}, + {"(ProgFlag).String", Method, 0}, + {"(ProgType).GoString", Method, 0}, + {"(ProgType).String", Method, 0}, + {"(R_386).GoString", Method, 0}, + {"(R_386).String", Method, 0}, + {"(R_390).GoString", Method, 7}, + {"(R_390).String", Method, 7}, + {"(R_AARCH64).GoString", Method, 4}, + {"(R_AARCH64).String", Method, 4}, + {"(R_ALPHA).GoString", Method, 0}, + {"(R_ALPHA).String", Method, 0}, + {"(R_ARM).GoString", Method, 0}, + {"(R_ARM).String", Method, 0}, + {"(R_LARCH).GoString", Method, 19}, + {"(R_LARCH).String", Method, 19}, + {"(R_MIPS).GoString", Method, 6}, + {"(R_MIPS).String", Method, 6}, + {"(R_PPC).GoString", Method, 0}, + {"(R_PPC).String", Method, 0}, + {"(R_PPC64).GoString", Method, 5}, + {"(R_PPC64).String", Method, 5}, + {"(R_RISCV).GoString", Method, 11}, + {"(R_RISCV).String", Method, 11}, + {"(R_SPARC).GoString", Method, 0}, + {"(R_SPARC).String", Method, 0}, + {"(R_X86_64).GoString", Method, 0}, + {"(R_X86_64).String", Method, 0}, + {"(Section).ReadAt", Method, 0}, + {"(SectionFlag).GoString", Method, 0}, + {"(SectionFlag).String", Method, 0}, + {"(SectionIndex).GoString", Method, 0}, + {"(SectionIndex).String", Method, 0}, + {"(SectionType).GoString", Method, 0}, + {"(SectionType).String", Method, 0}, + {"(SymBind).GoString", Method, 0}, + {"(SymBind).String", Method, 0}, + {"(SymType).GoString", Method, 0}, + {"(SymType).String", Method, 0}, + {"(SymVis).GoString", Method, 0}, + {"(SymVis).String", Method, 0}, + {"(Type).GoString", Method, 0}, + {"(Type).String", Method, 0}, + {"(Version).GoString", Method, 0}, + {"(Version).String", Method, 0}, + {"ARM_MAGIC_TRAMP_NUMBER", Const, 0}, + {"COMPRESS_HIOS", Const, 6}, + {"COMPRESS_HIPROC", Const, 6}, + {"COMPRESS_LOOS", Const, 6}, + {"COMPRESS_LOPROC", Const, 6}, + {"COMPRESS_ZLIB", Const, 6}, + {"COMPRESS_ZSTD", Const, 21}, + {"Chdr32", Type, 6}, + {"Chdr32.Addralign", Field, 6}, + {"Chdr32.Size", Field, 6}, + {"Chdr32.Type", Field, 6}, + {"Chdr64", Type, 6}, + {"Chdr64.Addralign", Field, 6}, + {"Chdr64.Size", Field, 6}, + {"Chdr64.Type", Field, 6}, + {"Class", Type, 0}, + {"CompressionType", Type, 6}, + {"DF_1_CONFALT", Const, 21}, + {"DF_1_DIRECT", Const, 21}, + {"DF_1_DISPRELDNE", Const, 21}, + {"DF_1_DISPRELPND", Const, 21}, + {"DF_1_EDITED", Const, 21}, + {"DF_1_ENDFILTEE", Const, 21}, + {"DF_1_GLOBAL", Const, 21}, + {"DF_1_GLOBAUDIT", Const, 21}, + {"DF_1_GROUP", Const, 21}, + {"DF_1_IGNMULDEF", Const, 21}, + {"DF_1_INITFIRST", Const, 21}, + {"DF_1_INTERPOSE", Const, 21}, + {"DF_1_KMOD", Const, 21}, + {"DF_1_LOADFLTR", Const, 21}, + {"DF_1_NOCOMMON", Const, 21}, + {"DF_1_NODEFLIB", Const, 21}, + {"DF_1_NODELETE", Const, 21}, + {"DF_1_NODIRECT", Const, 21}, + {"DF_1_NODUMP", Const, 21}, + {"DF_1_NOHDR", Const, 21}, + {"DF_1_NOKSYMS", Const, 21}, + {"DF_1_NOOPEN", Const, 21}, + {"DF_1_NORELOC", Const, 21}, + {"DF_1_NOW", Const, 21}, + {"DF_1_ORIGIN", Const, 21}, + {"DF_1_PIE", Const, 21}, + {"DF_1_SINGLETON", Const, 21}, + {"DF_1_STUB", Const, 21}, + {"DF_1_SYMINTPOSE", Const, 21}, + {"DF_1_TRANS", Const, 21}, + {"DF_1_WEAKFILTER", Const, 21}, + {"DF_BIND_NOW", Const, 0}, + {"DF_ORIGIN", Const, 0}, + {"DF_STATIC_TLS", Const, 0}, + {"DF_SYMBOLIC", Const, 0}, + {"DF_TEXTREL", Const, 0}, + {"DT_ADDRRNGHI", Const, 16}, + {"DT_ADDRRNGLO", Const, 16}, + {"DT_AUDIT", Const, 16}, + {"DT_AUXILIARY", Const, 16}, + {"DT_BIND_NOW", Const, 0}, + {"DT_CHECKSUM", Const, 16}, + {"DT_CONFIG", Const, 16}, + {"DT_DEBUG", Const, 0}, + {"DT_DEPAUDIT", Const, 16}, + {"DT_ENCODING", Const, 0}, + {"DT_FEATURE", Const, 16}, + {"DT_FILTER", Const, 16}, + {"DT_FINI", Const, 0}, + {"DT_FINI_ARRAY", Const, 0}, + {"DT_FINI_ARRAYSZ", Const, 0}, + {"DT_FLAGS", Const, 0}, + {"DT_FLAGS_1", Const, 16}, + {"DT_GNU_CONFLICT", Const, 16}, + {"DT_GNU_CONFLICTSZ", Const, 16}, + {"DT_GNU_HASH", Const, 16}, + {"DT_GNU_LIBLIST", Const, 16}, + {"DT_GNU_LIBLISTSZ", Const, 16}, + {"DT_GNU_PRELINKED", Const, 16}, + {"DT_HASH", Const, 0}, + {"DT_HIOS", Const, 0}, + {"DT_HIPROC", Const, 0}, + {"DT_INIT", Const, 0}, + {"DT_INIT_ARRAY", Const, 0}, + {"DT_INIT_ARRAYSZ", Const, 0}, + {"DT_JMPREL", Const, 0}, + {"DT_LOOS", Const, 0}, + {"DT_LOPROC", Const, 0}, + {"DT_MIPS_AUX_DYNAMIC", Const, 16}, + {"DT_MIPS_BASE_ADDRESS", Const, 16}, + {"DT_MIPS_COMPACT_SIZE", Const, 16}, + {"DT_MIPS_CONFLICT", Const, 16}, + {"DT_MIPS_CONFLICTNO", Const, 16}, + {"DT_MIPS_CXX_FLAGS", Const, 16}, + {"DT_MIPS_DELTA_CLASS", Const, 16}, + {"DT_MIPS_DELTA_CLASSSYM", Const, 16}, + {"DT_MIPS_DELTA_CLASSSYM_NO", Const, 16}, + {"DT_MIPS_DELTA_CLASS_NO", Const, 16}, + {"DT_MIPS_DELTA_INSTANCE", Const, 16}, + {"DT_MIPS_DELTA_INSTANCE_NO", Const, 16}, + {"DT_MIPS_DELTA_RELOC", Const, 16}, + {"DT_MIPS_DELTA_RELOC_NO", Const, 16}, + {"DT_MIPS_DELTA_SYM", Const, 16}, + {"DT_MIPS_DELTA_SYM_NO", Const, 16}, + {"DT_MIPS_DYNSTR_ALIGN", Const, 16}, + {"DT_MIPS_FLAGS", Const, 16}, + {"DT_MIPS_GOTSYM", Const, 16}, + {"DT_MIPS_GP_VALUE", Const, 16}, + {"DT_MIPS_HIDDEN_GOTIDX", Const, 16}, + {"DT_MIPS_HIPAGENO", Const, 16}, + {"DT_MIPS_ICHECKSUM", Const, 16}, + {"DT_MIPS_INTERFACE", Const, 16}, + {"DT_MIPS_INTERFACE_SIZE", Const, 16}, + {"DT_MIPS_IVERSION", Const, 16}, + {"DT_MIPS_LIBLIST", Const, 16}, + {"DT_MIPS_LIBLISTNO", Const, 16}, + {"DT_MIPS_LOCALPAGE_GOTIDX", Const, 16}, + {"DT_MIPS_LOCAL_GOTIDX", Const, 16}, + {"DT_MIPS_LOCAL_GOTNO", Const, 16}, + {"DT_MIPS_MSYM", Const, 16}, + {"DT_MIPS_OPTIONS", Const, 16}, + {"DT_MIPS_PERF_SUFFIX", Const, 16}, + {"DT_MIPS_PIXIE_INIT", Const, 16}, + {"DT_MIPS_PLTGOT", Const, 16}, + {"DT_MIPS_PROTECTED_GOTIDX", Const, 16}, + {"DT_MIPS_RLD_MAP", Const, 16}, + {"DT_MIPS_RLD_MAP_REL", Const, 16}, + {"DT_MIPS_RLD_TEXT_RESOLVE_ADDR", Const, 16}, + {"DT_MIPS_RLD_VERSION", Const, 16}, + {"DT_MIPS_RWPLT", Const, 16}, + {"DT_MIPS_SYMBOL_LIB", Const, 16}, + {"DT_MIPS_SYMTABNO", Const, 16}, + {"DT_MIPS_TIME_STAMP", Const, 16}, + {"DT_MIPS_UNREFEXTNO", Const, 16}, + {"DT_MOVEENT", Const, 16}, + {"DT_MOVESZ", Const, 16}, + {"DT_MOVETAB", Const, 16}, + {"DT_NEEDED", Const, 0}, + {"DT_NULL", Const, 0}, + {"DT_PLTGOT", Const, 0}, + {"DT_PLTPAD", Const, 16}, + {"DT_PLTPADSZ", Const, 16}, + {"DT_PLTREL", Const, 0}, + {"DT_PLTRELSZ", Const, 0}, + {"DT_POSFLAG_1", Const, 16}, + {"DT_PPC64_GLINK", Const, 16}, + {"DT_PPC64_OPD", Const, 16}, + {"DT_PPC64_OPDSZ", Const, 16}, + {"DT_PPC64_OPT", Const, 16}, + {"DT_PPC_GOT", Const, 16}, + {"DT_PPC_OPT", Const, 16}, + {"DT_PREINIT_ARRAY", Const, 0}, + {"DT_PREINIT_ARRAYSZ", Const, 0}, + {"DT_REL", Const, 0}, + {"DT_RELA", Const, 0}, + {"DT_RELACOUNT", Const, 16}, + {"DT_RELAENT", Const, 0}, + {"DT_RELASZ", Const, 0}, + {"DT_RELCOUNT", Const, 16}, + {"DT_RELENT", Const, 0}, + {"DT_RELSZ", Const, 0}, + {"DT_RPATH", Const, 0}, + {"DT_RUNPATH", Const, 0}, + {"DT_SONAME", Const, 0}, + {"DT_SPARC_REGISTER", Const, 16}, + {"DT_STRSZ", Const, 0}, + {"DT_STRTAB", Const, 0}, + {"DT_SYMBOLIC", Const, 0}, + {"DT_SYMENT", Const, 0}, + {"DT_SYMINENT", Const, 16}, + {"DT_SYMINFO", Const, 16}, + {"DT_SYMINSZ", Const, 16}, + {"DT_SYMTAB", Const, 0}, + {"DT_SYMTAB_SHNDX", Const, 16}, + {"DT_TEXTREL", Const, 0}, + {"DT_TLSDESC_GOT", Const, 16}, + {"DT_TLSDESC_PLT", Const, 16}, + {"DT_USED", Const, 16}, + {"DT_VALRNGHI", Const, 16}, + {"DT_VALRNGLO", Const, 16}, + {"DT_VERDEF", Const, 16}, + {"DT_VERDEFNUM", Const, 16}, + {"DT_VERNEED", Const, 0}, + {"DT_VERNEEDNUM", Const, 0}, + {"DT_VERSYM", Const, 0}, + {"Data", Type, 0}, + {"Dyn32", Type, 0}, + {"Dyn32.Tag", Field, 0}, + {"Dyn32.Val", Field, 0}, + {"Dyn64", Type, 0}, + {"Dyn64.Tag", Field, 0}, + {"Dyn64.Val", Field, 0}, + {"DynFlag", Type, 0}, + {"DynFlag1", Type, 21}, + {"DynTag", Type, 0}, + {"EI_ABIVERSION", Const, 0}, + {"EI_CLASS", Const, 0}, + {"EI_DATA", Const, 0}, + {"EI_NIDENT", Const, 0}, + {"EI_OSABI", Const, 0}, + {"EI_PAD", Const, 0}, + {"EI_VERSION", Const, 0}, + {"ELFCLASS32", Const, 0}, + {"ELFCLASS64", Const, 0}, + {"ELFCLASSNONE", Const, 0}, + {"ELFDATA2LSB", Const, 0}, + {"ELFDATA2MSB", Const, 0}, + {"ELFDATANONE", Const, 0}, + {"ELFMAG", Const, 0}, + {"ELFOSABI_86OPEN", Const, 0}, + {"ELFOSABI_AIX", Const, 0}, + {"ELFOSABI_ARM", Const, 0}, + {"ELFOSABI_AROS", Const, 11}, + {"ELFOSABI_CLOUDABI", Const, 11}, + {"ELFOSABI_FENIXOS", Const, 11}, + {"ELFOSABI_FREEBSD", Const, 0}, + {"ELFOSABI_HPUX", Const, 0}, + {"ELFOSABI_HURD", Const, 0}, + {"ELFOSABI_IRIX", Const, 0}, + {"ELFOSABI_LINUX", Const, 0}, + {"ELFOSABI_MODESTO", Const, 0}, + {"ELFOSABI_NETBSD", Const, 0}, + {"ELFOSABI_NONE", Const, 0}, + {"ELFOSABI_NSK", Const, 0}, + {"ELFOSABI_OPENBSD", Const, 0}, + {"ELFOSABI_OPENVMS", Const, 0}, + {"ELFOSABI_SOLARIS", Const, 0}, + {"ELFOSABI_STANDALONE", Const, 0}, + {"ELFOSABI_TRU64", Const, 0}, + {"EM_386", Const, 0}, + {"EM_486", Const, 0}, + {"EM_56800EX", Const, 11}, + {"EM_68HC05", Const, 11}, + {"EM_68HC08", Const, 11}, + {"EM_68HC11", Const, 11}, + {"EM_68HC12", Const, 0}, + {"EM_68HC16", Const, 11}, + {"EM_68K", Const, 0}, + {"EM_78KOR", Const, 11}, + {"EM_8051", Const, 11}, + {"EM_860", Const, 0}, + {"EM_88K", Const, 0}, + {"EM_960", Const, 0}, + {"EM_AARCH64", Const, 4}, + {"EM_ALPHA", Const, 0}, + {"EM_ALPHA_STD", Const, 0}, + {"EM_ALTERA_NIOS2", Const, 11}, + {"EM_AMDGPU", Const, 11}, + {"EM_ARC", Const, 0}, + {"EM_ARCA", Const, 11}, + {"EM_ARC_COMPACT", Const, 11}, + {"EM_ARC_COMPACT2", Const, 11}, + {"EM_ARM", Const, 0}, + {"EM_AVR", Const, 11}, + {"EM_AVR32", Const, 11}, + {"EM_BA1", Const, 11}, + {"EM_BA2", Const, 11}, + {"EM_BLACKFIN", Const, 11}, + {"EM_BPF", Const, 11}, + {"EM_C166", Const, 11}, + {"EM_CDP", Const, 11}, + {"EM_CE", Const, 11}, + {"EM_CLOUDSHIELD", Const, 11}, + {"EM_COGE", Const, 11}, + {"EM_COLDFIRE", Const, 0}, + {"EM_COOL", Const, 11}, + {"EM_COREA_1ST", Const, 11}, + {"EM_COREA_2ND", Const, 11}, + {"EM_CR", Const, 11}, + {"EM_CR16", Const, 11}, + {"EM_CRAYNV2", Const, 11}, + {"EM_CRIS", Const, 11}, + {"EM_CRX", Const, 11}, + {"EM_CSR_KALIMBA", Const, 11}, + {"EM_CUDA", Const, 11}, + {"EM_CYPRESS_M8C", Const, 11}, + {"EM_D10V", Const, 11}, + {"EM_D30V", Const, 11}, + {"EM_DSP24", Const, 11}, + {"EM_DSPIC30F", Const, 11}, + {"EM_DXP", Const, 11}, + {"EM_ECOG1", Const, 11}, + {"EM_ECOG16", Const, 11}, + {"EM_ECOG1X", Const, 11}, + {"EM_ECOG2", Const, 11}, + {"EM_ETPU", Const, 11}, + {"EM_EXCESS", Const, 11}, + {"EM_F2MC16", Const, 11}, + {"EM_FIREPATH", Const, 11}, + {"EM_FR20", Const, 0}, + {"EM_FR30", Const, 11}, + {"EM_FT32", Const, 11}, + {"EM_FX66", Const, 11}, + {"EM_H8S", Const, 0}, + {"EM_H8_300", Const, 0}, + {"EM_H8_300H", Const, 0}, + {"EM_H8_500", Const, 0}, + {"EM_HUANY", Const, 11}, + {"EM_IA_64", Const, 0}, + {"EM_INTEL205", Const, 11}, + {"EM_INTEL206", Const, 11}, + {"EM_INTEL207", Const, 11}, + {"EM_INTEL208", Const, 11}, + {"EM_INTEL209", Const, 11}, + {"EM_IP2K", Const, 11}, + {"EM_JAVELIN", Const, 11}, + {"EM_K10M", Const, 11}, + {"EM_KM32", Const, 11}, + {"EM_KMX16", Const, 11}, + {"EM_KMX32", Const, 11}, + {"EM_KMX8", Const, 11}, + {"EM_KVARC", Const, 11}, + {"EM_L10M", Const, 11}, + {"EM_LANAI", Const, 11}, + {"EM_LATTICEMICO32", Const, 11}, + {"EM_LOONGARCH", Const, 19}, + {"EM_M16C", Const, 11}, + {"EM_M32", Const, 0}, + {"EM_M32C", Const, 11}, + {"EM_M32R", Const, 11}, + {"EM_MANIK", Const, 11}, + {"EM_MAX", Const, 11}, + {"EM_MAXQ30", Const, 11}, + {"EM_MCHP_PIC", Const, 11}, + {"EM_MCST_ELBRUS", Const, 11}, + {"EM_ME16", Const, 0}, + {"EM_METAG", Const, 11}, + {"EM_MICROBLAZE", Const, 11}, + {"EM_MIPS", Const, 0}, + {"EM_MIPS_RS3_LE", Const, 0}, + {"EM_MIPS_RS4_BE", Const, 0}, + {"EM_MIPS_X", Const, 0}, + {"EM_MMA", Const, 0}, + {"EM_MMDSP_PLUS", Const, 11}, + {"EM_MMIX", Const, 11}, + {"EM_MN10200", Const, 11}, + {"EM_MN10300", Const, 11}, + {"EM_MOXIE", Const, 11}, + {"EM_MSP430", Const, 11}, + {"EM_NCPU", Const, 0}, + {"EM_NDR1", Const, 0}, + {"EM_NDS32", Const, 11}, + {"EM_NONE", Const, 0}, + {"EM_NORC", Const, 11}, + {"EM_NS32K", Const, 11}, + {"EM_OPEN8", Const, 11}, + {"EM_OPENRISC", Const, 11}, + {"EM_PARISC", Const, 0}, + {"EM_PCP", Const, 0}, + {"EM_PDP10", Const, 11}, + {"EM_PDP11", Const, 11}, + {"EM_PDSP", Const, 11}, + {"EM_PJ", Const, 11}, + {"EM_PPC", Const, 0}, + {"EM_PPC64", Const, 0}, + {"EM_PRISM", Const, 11}, + {"EM_QDSP6", Const, 11}, + {"EM_R32C", Const, 11}, + {"EM_RCE", Const, 0}, + {"EM_RH32", Const, 0}, + {"EM_RISCV", Const, 11}, + {"EM_RL78", Const, 11}, + {"EM_RS08", Const, 11}, + {"EM_RX", Const, 11}, + {"EM_S370", Const, 0}, + {"EM_S390", Const, 0}, + {"EM_SCORE7", Const, 11}, + {"EM_SEP", Const, 11}, + {"EM_SE_C17", Const, 11}, + {"EM_SE_C33", Const, 11}, + {"EM_SH", Const, 0}, + {"EM_SHARC", Const, 11}, + {"EM_SLE9X", Const, 11}, + {"EM_SNP1K", Const, 11}, + {"EM_SPARC", Const, 0}, + {"EM_SPARC32PLUS", Const, 0}, + {"EM_SPARCV9", Const, 0}, + {"EM_ST100", Const, 0}, + {"EM_ST19", Const, 11}, + {"EM_ST200", Const, 11}, + {"EM_ST7", Const, 11}, + {"EM_ST9PLUS", Const, 11}, + {"EM_STARCORE", Const, 0}, + {"EM_STM8", Const, 11}, + {"EM_STXP7X", Const, 11}, + {"EM_SVX", Const, 11}, + {"EM_TILE64", Const, 11}, + {"EM_TILEGX", Const, 11}, + {"EM_TILEPRO", Const, 11}, + {"EM_TINYJ", Const, 0}, + {"EM_TI_ARP32", Const, 11}, + {"EM_TI_C2000", Const, 11}, + {"EM_TI_C5500", Const, 11}, + {"EM_TI_C6000", Const, 11}, + {"EM_TI_PRU", Const, 11}, + {"EM_TMM_GPP", Const, 11}, + {"EM_TPC", Const, 11}, + {"EM_TRICORE", Const, 0}, + {"EM_TRIMEDIA", Const, 11}, + {"EM_TSK3000", Const, 11}, + {"EM_UNICORE", Const, 11}, + {"EM_V800", Const, 0}, + {"EM_V850", Const, 11}, + {"EM_VAX", Const, 11}, + {"EM_VIDEOCORE", Const, 11}, + {"EM_VIDEOCORE3", Const, 11}, + {"EM_VIDEOCORE5", Const, 11}, + {"EM_VISIUM", Const, 11}, + {"EM_VPP500", Const, 0}, + {"EM_X86_64", Const, 0}, + {"EM_XCORE", Const, 11}, + {"EM_XGATE", Const, 11}, + {"EM_XIMO16", Const, 11}, + {"EM_XTENSA", Const, 11}, + {"EM_Z80", Const, 11}, + {"EM_ZSP", Const, 11}, + {"ET_CORE", Const, 0}, + {"ET_DYN", Const, 0}, + {"ET_EXEC", Const, 0}, + {"ET_HIOS", Const, 0}, + {"ET_HIPROC", Const, 0}, + {"ET_LOOS", Const, 0}, + {"ET_LOPROC", Const, 0}, + {"ET_NONE", Const, 0}, + {"ET_REL", Const, 0}, + {"EV_CURRENT", Const, 0}, + {"EV_NONE", Const, 0}, + {"ErrNoSymbols", Var, 4}, + {"File", Type, 0}, + {"File.FileHeader", Field, 0}, + {"File.Progs", Field, 0}, + {"File.Sections", Field, 0}, + {"FileHeader", Type, 0}, + {"FileHeader.ABIVersion", Field, 0}, + {"FileHeader.ByteOrder", Field, 0}, + {"FileHeader.Class", Field, 0}, + {"FileHeader.Data", Field, 0}, + {"FileHeader.Entry", Field, 1}, + {"FileHeader.Machine", Field, 0}, + {"FileHeader.OSABI", Field, 0}, + {"FileHeader.Type", Field, 0}, + {"FileHeader.Version", Field, 0}, + {"FormatError", Type, 0}, + {"Header32", Type, 0}, + {"Header32.Ehsize", Field, 0}, + {"Header32.Entry", Field, 0}, + {"Header32.Flags", Field, 0}, + {"Header32.Ident", Field, 0}, + {"Header32.Machine", Field, 0}, + {"Header32.Phentsize", Field, 0}, + {"Header32.Phnum", Field, 0}, + {"Header32.Phoff", Field, 0}, + {"Header32.Shentsize", Field, 0}, + {"Header32.Shnum", Field, 0}, + {"Header32.Shoff", Field, 0}, + {"Header32.Shstrndx", Field, 0}, + {"Header32.Type", Field, 0}, + {"Header32.Version", Field, 0}, + {"Header64", Type, 0}, + {"Header64.Ehsize", Field, 0}, + {"Header64.Entry", Field, 0}, + {"Header64.Flags", Field, 0}, + {"Header64.Ident", Field, 0}, + {"Header64.Machine", Field, 0}, + {"Header64.Phentsize", Field, 0}, + {"Header64.Phnum", Field, 0}, + {"Header64.Phoff", Field, 0}, + {"Header64.Shentsize", Field, 0}, + {"Header64.Shnum", Field, 0}, + {"Header64.Shoff", Field, 0}, + {"Header64.Shstrndx", Field, 0}, + {"Header64.Type", Field, 0}, + {"Header64.Version", Field, 0}, + {"ImportedSymbol", Type, 0}, + {"ImportedSymbol.Library", Field, 0}, + {"ImportedSymbol.Name", Field, 0}, + {"ImportedSymbol.Version", Field, 0}, + {"Machine", Type, 0}, + {"NT_FPREGSET", Const, 0}, + {"NT_PRPSINFO", Const, 0}, + {"NT_PRSTATUS", Const, 0}, + {"NType", Type, 0}, + {"NewFile", Func, 0}, + {"OSABI", Type, 0}, + {"Open", Func, 0}, + {"PF_MASKOS", Const, 0}, + {"PF_MASKPROC", Const, 0}, + {"PF_R", Const, 0}, + {"PF_W", Const, 0}, + {"PF_X", Const, 0}, + {"PT_AARCH64_ARCHEXT", Const, 16}, + {"PT_AARCH64_UNWIND", Const, 16}, + {"PT_ARM_ARCHEXT", Const, 16}, + {"PT_ARM_EXIDX", Const, 16}, + {"PT_DYNAMIC", Const, 0}, + {"PT_GNU_EH_FRAME", Const, 16}, + {"PT_GNU_MBIND_HI", Const, 16}, + {"PT_GNU_MBIND_LO", Const, 16}, + {"PT_GNU_PROPERTY", Const, 16}, + {"PT_GNU_RELRO", Const, 16}, + {"PT_GNU_STACK", Const, 16}, + {"PT_HIOS", Const, 0}, + {"PT_HIPROC", Const, 0}, + {"PT_INTERP", Const, 0}, + {"PT_LOAD", Const, 0}, + {"PT_LOOS", Const, 0}, + {"PT_LOPROC", Const, 0}, + {"PT_MIPS_ABIFLAGS", Const, 16}, + {"PT_MIPS_OPTIONS", Const, 16}, + {"PT_MIPS_REGINFO", Const, 16}, + {"PT_MIPS_RTPROC", Const, 16}, + {"PT_NOTE", Const, 0}, + {"PT_NULL", Const, 0}, + {"PT_OPENBSD_BOOTDATA", Const, 16}, + {"PT_OPENBSD_NOBTCFI", Const, 23}, + {"PT_OPENBSD_RANDOMIZE", Const, 16}, + {"PT_OPENBSD_WXNEEDED", Const, 16}, + {"PT_PAX_FLAGS", Const, 16}, + {"PT_PHDR", Const, 0}, + {"PT_S390_PGSTE", Const, 16}, + {"PT_SHLIB", Const, 0}, + {"PT_SUNWSTACK", Const, 16}, + {"PT_SUNW_EH_FRAME", Const, 16}, + {"PT_TLS", Const, 0}, + {"Prog", Type, 0}, + {"Prog.ProgHeader", Field, 0}, + {"Prog.ReaderAt", Field, 0}, + {"Prog32", Type, 0}, + {"Prog32.Align", Field, 0}, + {"Prog32.Filesz", Field, 0}, + {"Prog32.Flags", Field, 0}, + {"Prog32.Memsz", Field, 0}, + {"Prog32.Off", Field, 0}, + {"Prog32.Paddr", Field, 0}, + {"Prog32.Type", Field, 0}, + {"Prog32.Vaddr", Field, 0}, + {"Prog64", Type, 0}, + {"Prog64.Align", Field, 0}, + {"Prog64.Filesz", Field, 0}, + {"Prog64.Flags", Field, 0}, + {"Prog64.Memsz", Field, 0}, + {"Prog64.Off", Field, 0}, + {"Prog64.Paddr", Field, 0}, + {"Prog64.Type", Field, 0}, + {"Prog64.Vaddr", Field, 0}, + {"ProgFlag", Type, 0}, + {"ProgHeader", Type, 0}, + {"ProgHeader.Align", Field, 0}, + {"ProgHeader.Filesz", Field, 0}, + {"ProgHeader.Flags", Field, 0}, + {"ProgHeader.Memsz", Field, 0}, + {"ProgHeader.Off", Field, 0}, + {"ProgHeader.Paddr", Field, 0}, + {"ProgHeader.Type", Field, 0}, + {"ProgHeader.Vaddr", Field, 0}, + {"ProgType", Type, 0}, + {"R_386", Type, 0}, + {"R_386_16", Const, 10}, + {"R_386_32", Const, 0}, + {"R_386_32PLT", Const, 10}, + {"R_386_8", Const, 10}, + {"R_386_COPY", Const, 0}, + {"R_386_GLOB_DAT", Const, 0}, + {"R_386_GOT32", Const, 0}, + {"R_386_GOT32X", Const, 10}, + {"R_386_GOTOFF", Const, 0}, + {"R_386_GOTPC", Const, 0}, + {"R_386_IRELATIVE", Const, 10}, + {"R_386_JMP_SLOT", Const, 0}, + {"R_386_NONE", Const, 0}, + {"R_386_PC16", Const, 10}, + {"R_386_PC32", Const, 0}, + {"R_386_PC8", Const, 10}, + {"R_386_PLT32", Const, 0}, + {"R_386_RELATIVE", Const, 0}, + {"R_386_SIZE32", Const, 10}, + {"R_386_TLS_DESC", Const, 10}, + {"R_386_TLS_DESC_CALL", Const, 10}, + {"R_386_TLS_DTPMOD32", Const, 0}, + {"R_386_TLS_DTPOFF32", Const, 0}, + {"R_386_TLS_GD", Const, 0}, + {"R_386_TLS_GD_32", Const, 0}, + {"R_386_TLS_GD_CALL", Const, 0}, + {"R_386_TLS_GD_POP", Const, 0}, + {"R_386_TLS_GD_PUSH", Const, 0}, + {"R_386_TLS_GOTDESC", Const, 10}, + {"R_386_TLS_GOTIE", Const, 0}, + {"R_386_TLS_IE", Const, 0}, + {"R_386_TLS_IE_32", Const, 0}, + {"R_386_TLS_LDM", Const, 0}, + {"R_386_TLS_LDM_32", Const, 0}, + {"R_386_TLS_LDM_CALL", Const, 0}, + {"R_386_TLS_LDM_POP", Const, 0}, + {"R_386_TLS_LDM_PUSH", Const, 0}, + {"R_386_TLS_LDO_32", Const, 0}, + {"R_386_TLS_LE", Const, 0}, + {"R_386_TLS_LE_32", Const, 0}, + {"R_386_TLS_TPOFF", Const, 0}, + {"R_386_TLS_TPOFF32", Const, 0}, + {"R_390", Type, 7}, + {"R_390_12", Const, 7}, + {"R_390_16", Const, 7}, + {"R_390_20", Const, 7}, + {"R_390_32", Const, 7}, + {"R_390_64", Const, 7}, + {"R_390_8", Const, 7}, + {"R_390_COPY", Const, 7}, + {"R_390_GLOB_DAT", Const, 7}, + {"R_390_GOT12", Const, 7}, + {"R_390_GOT16", Const, 7}, + {"R_390_GOT20", Const, 7}, + {"R_390_GOT32", Const, 7}, + {"R_390_GOT64", Const, 7}, + {"R_390_GOTENT", Const, 7}, + {"R_390_GOTOFF", Const, 7}, + {"R_390_GOTOFF16", Const, 7}, + {"R_390_GOTOFF64", Const, 7}, + {"R_390_GOTPC", Const, 7}, + {"R_390_GOTPCDBL", Const, 7}, + {"R_390_GOTPLT12", Const, 7}, + {"R_390_GOTPLT16", Const, 7}, + {"R_390_GOTPLT20", Const, 7}, + {"R_390_GOTPLT32", Const, 7}, + {"R_390_GOTPLT64", Const, 7}, + {"R_390_GOTPLTENT", Const, 7}, + {"R_390_GOTPLTOFF16", Const, 7}, + {"R_390_GOTPLTOFF32", Const, 7}, + {"R_390_GOTPLTOFF64", Const, 7}, + {"R_390_JMP_SLOT", Const, 7}, + {"R_390_NONE", Const, 7}, + {"R_390_PC16", Const, 7}, + {"R_390_PC16DBL", Const, 7}, + {"R_390_PC32", Const, 7}, + {"R_390_PC32DBL", Const, 7}, + {"R_390_PC64", Const, 7}, + {"R_390_PLT16DBL", Const, 7}, + {"R_390_PLT32", Const, 7}, + {"R_390_PLT32DBL", Const, 7}, + {"R_390_PLT64", Const, 7}, + {"R_390_RELATIVE", Const, 7}, + {"R_390_TLS_DTPMOD", Const, 7}, + {"R_390_TLS_DTPOFF", Const, 7}, + {"R_390_TLS_GD32", Const, 7}, + {"R_390_TLS_GD64", Const, 7}, + {"R_390_TLS_GDCALL", Const, 7}, + {"R_390_TLS_GOTIE12", Const, 7}, + {"R_390_TLS_GOTIE20", Const, 7}, + {"R_390_TLS_GOTIE32", Const, 7}, + {"R_390_TLS_GOTIE64", Const, 7}, + {"R_390_TLS_IE32", Const, 7}, + {"R_390_TLS_IE64", Const, 7}, + {"R_390_TLS_IEENT", Const, 7}, + {"R_390_TLS_LDCALL", Const, 7}, + {"R_390_TLS_LDM32", Const, 7}, + {"R_390_TLS_LDM64", Const, 7}, + {"R_390_TLS_LDO32", Const, 7}, + {"R_390_TLS_LDO64", Const, 7}, + {"R_390_TLS_LE32", Const, 7}, + {"R_390_TLS_LE64", Const, 7}, + {"R_390_TLS_LOAD", Const, 7}, + {"R_390_TLS_TPOFF", Const, 7}, + {"R_AARCH64", Type, 4}, + {"R_AARCH64_ABS16", Const, 4}, + {"R_AARCH64_ABS32", Const, 4}, + {"R_AARCH64_ABS64", Const, 4}, + {"R_AARCH64_ADD_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_ADR_GOT_PAGE", Const, 4}, + {"R_AARCH64_ADR_PREL_LO21", Const, 4}, + {"R_AARCH64_ADR_PREL_PG_HI21", Const, 4}, + {"R_AARCH64_ADR_PREL_PG_HI21_NC", Const, 4}, + {"R_AARCH64_CALL26", Const, 4}, + {"R_AARCH64_CONDBR19", Const, 4}, + {"R_AARCH64_COPY", Const, 4}, + {"R_AARCH64_GLOB_DAT", Const, 4}, + {"R_AARCH64_GOT_LD_PREL19", Const, 4}, + {"R_AARCH64_IRELATIVE", Const, 4}, + {"R_AARCH64_JUMP26", Const, 4}, + {"R_AARCH64_JUMP_SLOT", Const, 4}, + {"R_AARCH64_LD64_GOTOFF_LO15", Const, 10}, + {"R_AARCH64_LD64_GOTPAGE_LO15", Const, 10}, + {"R_AARCH64_LD64_GOT_LO12_NC", Const, 4}, + {"R_AARCH64_LDST128_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_LDST16_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_LDST32_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_LDST64_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_LDST8_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_LD_PREL_LO19", Const, 4}, + {"R_AARCH64_MOVW_SABS_G0", Const, 4}, + {"R_AARCH64_MOVW_SABS_G1", Const, 4}, + {"R_AARCH64_MOVW_SABS_G2", Const, 4}, + {"R_AARCH64_MOVW_UABS_G0", Const, 4}, + {"R_AARCH64_MOVW_UABS_G0_NC", Const, 4}, + {"R_AARCH64_MOVW_UABS_G1", Const, 4}, + {"R_AARCH64_MOVW_UABS_G1_NC", Const, 4}, + {"R_AARCH64_MOVW_UABS_G2", Const, 4}, + {"R_AARCH64_MOVW_UABS_G2_NC", Const, 4}, + {"R_AARCH64_MOVW_UABS_G3", Const, 4}, + {"R_AARCH64_NONE", Const, 4}, + {"R_AARCH64_NULL", Const, 4}, + {"R_AARCH64_P32_ABS16", Const, 4}, + {"R_AARCH64_P32_ABS32", Const, 4}, + {"R_AARCH64_P32_ADD_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_P32_ADR_GOT_PAGE", Const, 4}, + {"R_AARCH64_P32_ADR_PREL_LO21", Const, 4}, + {"R_AARCH64_P32_ADR_PREL_PG_HI21", Const, 4}, + {"R_AARCH64_P32_CALL26", Const, 4}, + {"R_AARCH64_P32_CONDBR19", Const, 4}, + {"R_AARCH64_P32_COPY", Const, 4}, + {"R_AARCH64_P32_GLOB_DAT", Const, 4}, + {"R_AARCH64_P32_GOT_LD_PREL19", Const, 4}, + {"R_AARCH64_P32_IRELATIVE", Const, 4}, + {"R_AARCH64_P32_JUMP26", Const, 4}, + {"R_AARCH64_P32_JUMP_SLOT", Const, 4}, + {"R_AARCH64_P32_LD32_GOT_LO12_NC", Const, 4}, + {"R_AARCH64_P32_LDST128_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_P32_LDST16_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_P32_LDST32_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_P32_LDST64_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_P32_LDST8_ABS_LO12_NC", Const, 4}, + {"R_AARCH64_P32_LD_PREL_LO19", Const, 4}, + {"R_AARCH64_P32_MOVW_SABS_G0", Const, 4}, + {"R_AARCH64_P32_MOVW_UABS_G0", Const, 4}, + {"R_AARCH64_P32_MOVW_UABS_G0_NC", Const, 4}, + {"R_AARCH64_P32_MOVW_UABS_G1", Const, 4}, + {"R_AARCH64_P32_PREL16", Const, 4}, + {"R_AARCH64_P32_PREL32", Const, 4}, + {"R_AARCH64_P32_RELATIVE", Const, 4}, + {"R_AARCH64_P32_TLSDESC", Const, 4}, + {"R_AARCH64_P32_TLSDESC_ADD_LO12_NC", Const, 4}, + {"R_AARCH64_P32_TLSDESC_ADR_PAGE21", Const, 4}, + {"R_AARCH64_P32_TLSDESC_ADR_PREL21", Const, 4}, + {"R_AARCH64_P32_TLSDESC_CALL", Const, 4}, + {"R_AARCH64_P32_TLSDESC_LD32_LO12_NC", Const, 4}, + {"R_AARCH64_P32_TLSDESC_LD_PREL19", Const, 4}, + {"R_AARCH64_P32_TLSGD_ADD_LO12_NC", Const, 4}, + {"R_AARCH64_P32_TLSGD_ADR_PAGE21", Const, 4}, + {"R_AARCH64_P32_TLSIE_ADR_GOTTPREL_PAGE21", Const, 4}, + {"R_AARCH64_P32_TLSIE_LD32_GOTTPREL_LO12_NC", Const, 4}, + {"R_AARCH64_P32_TLSIE_LD_GOTTPREL_PREL19", Const, 4}, + {"R_AARCH64_P32_TLSLE_ADD_TPREL_HI12", Const, 4}, + {"R_AARCH64_P32_TLSLE_ADD_TPREL_LO12", Const, 4}, + {"R_AARCH64_P32_TLSLE_ADD_TPREL_LO12_NC", Const, 4}, + {"R_AARCH64_P32_TLSLE_MOVW_TPREL_G0", Const, 4}, + {"R_AARCH64_P32_TLSLE_MOVW_TPREL_G0_NC", Const, 4}, + {"R_AARCH64_P32_TLSLE_MOVW_TPREL_G1", Const, 4}, + {"R_AARCH64_P32_TLS_DTPMOD", Const, 4}, + {"R_AARCH64_P32_TLS_DTPREL", Const, 4}, + {"R_AARCH64_P32_TLS_TPREL", Const, 4}, + {"R_AARCH64_P32_TSTBR14", Const, 4}, + {"R_AARCH64_PREL16", Const, 4}, + {"R_AARCH64_PREL32", Const, 4}, + {"R_AARCH64_PREL64", Const, 4}, + {"R_AARCH64_RELATIVE", Const, 4}, + {"R_AARCH64_TLSDESC", Const, 4}, + {"R_AARCH64_TLSDESC_ADD", Const, 4}, + {"R_AARCH64_TLSDESC_ADD_LO12_NC", Const, 4}, + {"R_AARCH64_TLSDESC_ADR_PAGE21", Const, 4}, + {"R_AARCH64_TLSDESC_ADR_PREL21", Const, 4}, + {"R_AARCH64_TLSDESC_CALL", Const, 4}, + {"R_AARCH64_TLSDESC_LD64_LO12_NC", Const, 4}, + {"R_AARCH64_TLSDESC_LDR", Const, 4}, + {"R_AARCH64_TLSDESC_LD_PREL19", Const, 4}, + {"R_AARCH64_TLSDESC_OFF_G0_NC", Const, 4}, + {"R_AARCH64_TLSDESC_OFF_G1", Const, 4}, + {"R_AARCH64_TLSGD_ADD_LO12_NC", Const, 4}, + {"R_AARCH64_TLSGD_ADR_PAGE21", Const, 4}, + {"R_AARCH64_TLSGD_ADR_PREL21", Const, 10}, + {"R_AARCH64_TLSGD_MOVW_G0_NC", Const, 10}, + {"R_AARCH64_TLSGD_MOVW_G1", Const, 10}, + {"R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21", Const, 4}, + {"R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC", Const, 4}, + {"R_AARCH64_TLSIE_LD_GOTTPREL_PREL19", Const, 4}, + {"R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC", Const, 4}, + {"R_AARCH64_TLSIE_MOVW_GOTTPREL_G1", Const, 4}, + {"R_AARCH64_TLSLD_ADR_PAGE21", Const, 10}, + {"R_AARCH64_TLSLD_ADR_PREL21", Const, 10}, + {"R_AARCH64_TLSLD_LDST128_DTPREL_LO12", Const, 10}, + {"R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC", Const, 10}, + {"R_AARCH64_TLSLE_ADD_TPREL_HI12", Const, 4}, + {"R_AARCH64_TLSLE_ADD_TPREL_LO12", Const, 4}, + {"R_AARCH64_TLSLE_ADD_TPREL_LO12_NC", Const, 4}, + {"R_AARCH64_TLSLE_LDST128_TPREL_LO12", Const, 10}, + {"R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC", Const, 10}, + {"R_AARCH64_TLSLE_MOVW_TPREL_G0", Const, 4}, + {"R_AARCH64_TLSLE_MOVW_TPREL_G0_NC", Const, 4}, + {"R_AARCH64_TLSLE_MOVW_TPREL_G1", Const, 4}, + {"R_AARCH64_TLSLE_MOVW_TPREL_G1_NC", Const, 4}, + {"R_AARCH64_TLSLE_MOVW_TPREL_G2", Const, 4}, + {"R_AARCH64_TLS_DTPMOD64", Const, 4}, + {"R_AARCH64_TLS_DTPREL64", Const, 4}, + {"R_AARCH64_TLS_TPREL64", Const, 4}, + {"R_AARCH64_TSTBR14", Const, 4}, + {"R_ALPHA", Type, 0}, + {"R_ALPHA_BRADDR", Const, 0}, + {"R_ALPHA_COPY", Const, 0}, + {"R_ALPHA_GLOB_DAT", Const, 0}, + {"R_ALPHA_GPDISP", Const, 0}, + {"R_ALPHA_GPREL32", Const, 0}, + {"R_ALPHA_GPRELHIGH", Const, 0}, + {"R_ALPHA_GPRELLOW", Const, 0}, + {"R_ALPHA_GPVALUE", Const, 0}, + {"R_ALPHA_HINT", Const, 0}, + {"R_ALPHA_IMMED_BR_HI32", Const, 0}, + {"R_ALPHA_IMMED_GP_16", Const, 0}, + {"R_ALPHA_IMMED_GP_HI32", Const, 0}, + {"R_ALPHA_IMMED_LO32", Const, 0}, + {"R_ALPHA_IMMED_SCN_HI32", Const, 0}, + {"R_ALPHA_JMP_SLOT", Const, 0}, + {"R_ALPHA_LITERAL", Const, 0}, + {"R_ALPHA_LITUSE", Const, 0}, + {"R_ALPHA_NONE", Const, 0}, + {"R_ALPHA_OP_PRSHIFT", Const, 0}, + {"R_ALPHA_OP_PSUB", Const, 0}, + {"R_ALPHA_OP_PUSH", Const, 0}, + {"R_ALPHA_OP_STORE", Const, 0}, + {"R_ALPHA_REFLONG", Const, 0}, + {"R_ALPHA_REFQUAD", Const, 0}, + {"R_ALPHA_RELATIVE", Const, 0}, + {"R_ALPHA_SREL16", Const, 0}, + {"R_ALPHA_SREL32", Const, 0}, + {"R_ALPHA_SREL64", Const, 0}, + {"R_ARM", Type, 0}, + {"R_ARM_ABS12", Const, 0}, + {"R_ARM_ABS16", Const, 0}, + {"R_ARM_ABS32", Const, 0}, + {"R_ARM_ABS32_NOI", Const, 10}, + {"R_ARM_ABS8", Const, 0}, + {"R_ARM_ALU_PCREL_15_8", Const, 10}, + {"R_ARM_ALU_PCREL_23_15", Const, 10}, + {"R_ARM_ALU_PCREL_7_0", Const, 10}, + {"R_ARM_ALU_PC_G0", Const, 10}, + {"R_ARM_ALU_PC_G0_NC", Const, 10}, + {"R_ARM_ALU_PC_G1", Const, 10}, + {"R_ARM_ALU_PC_G1_NC", Const, 10}, + {"R_ARM_ALU_PC_G2", Const, 10}, + {"R_ARM_ALU_SBREL_19_12_NC", Const, 10}, + {"R_ARM_ALU_SBREL_27_20_CK", Const, 10}, + {"R_ARM_ALU_SB_G0", Const, 10}, + {"R_ARM_ALU_SB_G0_NC", Const, 10}, + {"R_ARM_ALU_SB_G1", Const, 10}, + {"R_ARM_ALU_SB_G1_NC", Const, 10}, + {"R_ARM_ALU_SB_G2", Const, 10}, + {"R_ARM_AMP_VCALL9", Const, 0}, + {"R_ARM_BASE_ABS", Const, 10}, + {"R_ARM_CALL", Const, 10}, + {"R_ARM_COPY", Const, 0}, + {"R_ARM_GLOB_DAT", Const, 0}, + {"R_ARM_GNU_VTENTRY", Const, 0}, + {"R_ARM_GNU_VTINHERIT", Const, 0}, + {"R_ARM_GOT32", Const, 0}, + {"R_ARM_GOTOFF", Const, 0}, + {"R_ARM_GOTOFF12", Const, 10}, + {"R_ARM_GOTPC", Const, 0}, + {"R_ARM_GOTRELAX", Const, 10}, + {"R_ARM_GOT_ABS", Const, 10}, + {"R_ARM_GOT_BREL12", Const, 10}, + {"R_ARM_GOT_PREL", Const, 10}, + {"R_ARM_IRELATIVE", Const, 10}, + {"R_ARM_JUMP24", Const, 10}, + {"R_ARM_JUMP_SLOT", Const, 0}, + {"R_ARM_LDC_PC_G0", Const, 10}, + {"R_ARM_LDC_PC_G1", Const, 10}, + {"R_ARM_LDC_PC_G2", Const, 10}, + {"R_ARM_LDC_SB_G0", Const, 10}, + {"R_ARM_LDC_SB_G1", Const, 10}, + {"R_ARM_LDC_SB_G2", Const, 10}, + {"R_ARM_LDRS_PC_G0", Const, 10}, + {"R_ARM_LDRS_PC_G1", Const, 10}, + {"R_ARM_LDRS_PC_G2", Const, 10}, + {"R_ARM_LDRS_SB_G0", Const, 10}, + {"R_ARM_LDRS_SB_G1", Const, 10}, + {"R_ARM_LDRS_SB_G2", Const, 10}, + {"R_ARM_LDR_PC_G1", Const, 10}, + {"R_ARM_LDR_PC_G2", Const, 10}, + {"R_ARM_LDR_SBREL_11_10_NC", Const, 10}, + {"R_ARM_LDR_SB_G0", Const, 10}, + {"R_ARM_LDR_SB_G1", Const, 10}, + {"R_ARM_LDR_SB_G2", Const, 10}, + {"R_ARM_ME_TOO", Const, 10}, + {"R_ARM_MOVT_ABS", Const, 10}, + {"R_ARM_MOVT_BREL", Const, 10}, + {"R_ARM_MOVT_PREL", Const, 10}, + {"R_ARM_MOVW_ABS_NC", Const, 10}, + {"R_ARM_MOVW_BREL", Const, 10}, + {"R_ARM_MOVW_BREL_NC", Const, 10}, + {"R_ARM_MOVW_PREL_NC", Const, 10}, + {"R_ARM_NONE", Const, 0}, + {"R_ARM_PC13", Const, 0}, + {"R_ARM_PC24", Const, 0}, + {"R_ARM_PLT32", Const, 0}, + {"R_ARM_PLT32_ABS", Const, 10}, + {"R_ARM_PREL31", Const, 10}, + {"R_ARM_PRIVATE_0", Const, 10}, + {"R_ARM_PRIVATE_1", Const, 10}, + {"R_ARM_PRIVATE_10", Const, 10}, + {"R_ARM_PRIVATE_11", Const, 10}, + {"R_ARM_PRIVATE_12", Const, 10}, + {"R_ARM_PRIVATE_13", Const, 10}, + {"R_ARM_PRIVATE_14", Const, 10}, + {"R_ARM_PRIVATE_15", Const, 10}, + {"R_ARM_PRIVATE_2", Const, 10}, + {"R_ARM_PRIVATE_3", Const, 10}, + {"R_ARM_PRIVATE_4", Const, 10}, + {"R_ARM_PRIVATE_5", Const, 10}, + {"R_ARM_PRIVATE_6", Const, 10}, + {"R_ARM_PRIVATE_7", Const, 10}, + {"R_ARM_PRIVATE_8", Const, 10}, + {"R_ARM_PRIVATE_9", Const, 10}, + {"R_ARM_RABS32", Const, 0}, + {"R_ARM_RBASE", Const, 0}, + {"R_ARM_REL32", Const, 0}, + {"R_ARM_REL32_NOI", Const, 10}, + {"R_ARM_RELATIVE", Const, 0}, + {"R_ARM_RPC24", Const, 0}, + {"R_ARM_RREL32", Const, 0}, + {"R_ARM_RSBREL32", Const, 0}, + {"R_ARM_RXPC25", Const, 10}, + {"R_ARM_SBREL31", Const, 10}, + {"R_ARM_SBREL32", Const, 0}, + {"R_ARM_SWI24", Const, 0}, + {"R_ARM_TARGET1", Const, 10}, + {"R_ARM_TARGET2", Const, 10}, + {"R_ARM_THM_ABS5", Const, 0}, + {"R_ARM_THM_ALU_ABS_G0_NC", Const, 10}, + {"R_ARM_THM_ALU_ABS_G1_NC", Const, 10}, + {"R_ARM_THM_ALU_ABS_G2_NC", Const, 10}, + {"R_ARM_THM_ALU_ABS_G3", Const, 10}, + {"R_ARM_THM_ALU_PREL_11_0", Const, 10}, + {"R_ARM_THM_GOT_BREL12", Const, 10}, + {"R_ARM_THM_JUMP11", Const, 10}, + {"R_ARM_THM_JUMP19", Const, 10}, + {"R_ARM_THM_JUMP24", Const, 10}, + {"R_ARM_THM_JUMP6", Const, 10}, + {"R_ARM_THM_JUMP8", Const, 10}, + {"R_ARM_THM_MOVT_ABS", Const, 10}, + {"R_ARM_THM_MOVT_BREL", Const, 10}, + {"R_ARM_THM_MOVT_PREL", Const, 10}, + {"R_ARM_THM_MOVW_ABS_NC", Const, 10}, + {"R_ARM_THM_MOVW_BREL", Const, 10}, + {"R_ARM_THM_MOVW_BREL_NC", Const, 10}, + {"R_ARM_THM_MOVW_PREL_NC", Const, 10}, + {"R_ARM_THM_PC12", Const, 10}, + {"R_ARM_THM_PC22", Const, 0}, + {"R_ARM_THM_PC8", Const, 0}, + {"R_ARM_THM_RPC22", Const, 0}, + {"R_ARM_THM_SWI8", Const, 0}, + {"R_ARM_THM_TLS_CALL", Const, 10}, + {"R_ARM_THM_TLS_DESCSEQ16", Const, 10}, + {"R_ARM_THM_TLS_DESCSEQ32", Const, 10}, + {"R_ARM_THM_XPC22", Const, 0}, + {"R_ARM_TLS_CALL", Const, 10}, + {"R_ARM_TLS_DESCSEQ", Const, 10}, + {"R_ARM_TLS_DTPMOD32", Const, 10}, + {"R_ARM_TLS_DTPOFF32", Const, 10}, + {"R_ARM_TLS_GD32", Const, 10}, + {"R_ARM_TLS_GOTDESC", Const, 10}, + {"R_ARM_TLS_IE12GP", Const, 10}, + {"R_ARM_TLS_IE32", Const, 10}, + {"R_ARM_TLS_LDM32", Const, 10}, + {"R_ARM_TLS_LDO12", Const, 10}, + {"R_ARM_TLS_LDO32", Const, 10}, + {"R_ARM_TLS_LE12", Const, 10}, + {"R_ARM_TLS_LE32", Const, 10}, + {"R_ARM_TLS_TPOFF32", Const, 10}, + {"R_ARM_V4BX", Const, 10}, + {"R_ARM_XPC25", Const, 0}, + {"R_INFO", Func, 0}, + {"R_INFO32", Func, 0}, + {"R_LARCH", Type, 19}, + {"R_LARCH_32", Const, 19}, + {"R_LARCH_32_PCREL", Const, 20}, + {"R_LARCH_64", Const, 19}, + {"R_LARCH_64_PCREL", Const, 22}, + {"R_LARCH_ABS64_HI12", Const, 20}, + {"R_LARCH_ABS64_LO20", Const, 20}, + {"R_LARCH_ABS_HI20", Const, 20}, + {"R_LARCH_ABS_LO12", Const, 20}, + {"R_LARCH_ADD16", Const, 19}, + {"R_LARCH_ADD24", Const, 19}, + {"R_LARCH_ADD32", Const, 19}, + {"R_LARCH_ADD6", Const, 22}, + {"R_LARCH_ADD64", Const, 19}, + {"R_LARCH_ADD8", Const, 19}, + {"R_LARCH_ADD_ULEB128", Const, 22}, + {"R_LARCH_ALIGN", Const, 22}, + {"R_LARCH_B16", Const, 20}, + {"R_LARCH_B21", Const, 20}, + {"R_LARCH_B26", Const, 20}, + {"R_LARCH_CFA", Const, 22}, + {"R_LARCH_COPY", Const, 19}, + {"R_LARCH_DELETE", Const, 22}, + {"R_LARCH_GNU_VTENTRY", Const, 20}, + {"R_LARCH_GNU_VTINHERIT", Const, 20}, + {"R_LARCH_GOT64_HI12", Const, 20}, + {"R_LARCH_GOT64_LO20", Const, 20}, + {"R_LARCH_GOT64_PC_HI12", Const, 20}, + {"R_LARCH_GOT64_PC_LO20", Const, 20}, + {"R_LARCH_GOT_HI20", Const, 20}, + {"R_LARCH_GOT_LO12", Const, 20}, + {"R_LARCH_GOT_PC_HI20", Const, 20}, + {"R_LARCH_GOT_PC_LO12", Const, 20}, + {"R_LARCH_IRELATIVE", Const, 19}, + {"R_LARCH_JUMP_SLOT", Const, 19}, + {"R_LARCH_MARK_LA", Const, 19}, + {"R_LARCH_MARK_PCREL", Const, 19}, + {"R_LARCH_NONE", Const, 19}, + {"R_LARCH_PCALA64_HI12", Const, 20}, + {"R_LARCH_PCALA64_LO20", Const, 20}, + {"R_LARCH_PCALA_HI20", Const, 20}, + {"R_LARCH_PCALA_LO12", Const, 20}, + {"R_LARCH_PCREL20_S2", Const, 22}, + {"R_LARCH_RELATIVE", Const, 19}, + {"R_LARCH_RELAX", Const, 20}, + {"R_LARCH_SOP_ADD", Const, 19}, + {"R_LARCH_SOP_AND", Const, 19}, + {"R_LARCH_SOP_ASSERT", Const, 19}, + {"R_LARCH_SOP_IF_ELSE", Const, 19}, + {"R_LARCH_SOP_NOT", Const, 19}, + {"R_LARCH_SOP_POP_32_S_0_10_10_16_S2", Const, 19}, + {"R_LARCH_SOP_POP_32_S_0_5_10_16_S2", Const, 19}, + {"R_LARCH_SOP_POP_32_S_10_12", Const, 19}, + {"R_LARCH_SOP_POP_32_S_10_16", Const, 19}, + {"R_LARCH_SOP_POP_32_S_10_16_S2", Const, 19}, + {"R_LARCH_SOP_POP_32_S_10_5", Const, 19}, + {"R_LARCH_SOP_POP_32_S_5_20", Const, 19}, + {"R_LARCH_SOP_POP_32_U", Const, 19}, + {"R_LARCH_SOP_POP_32_U_10_12", Const, 19}, + {"R_LARCH_SOP_PUSH_ABSOLUTE", Const, 19}, + {"R_LARCH_SOP_PUSH_DUP", Const, 19}, + {"R_LARCH_SOP_PUSH_GPREL", Const, 19}, + {"R_LARCH_SOP_PUSH_PCREL", Const, 19}, + {"R_LARCH_SOP_PUSH_PLT_PCREL", Const, 19}, + {"R_LARCH_SOP_PUSH_TLS_GD", Const, 19}, + {"R_LARCH_SOP_PUSH_TLS_GOT", Const, 19}, + {"R_LARCH_SOP_PUSH_TLS_TPREL", Const, 19}, + {"R_LARCH_SOP_SL", Const, 19}, + {"R_LARCH_SOP_SR", Const, 19}, + {"R_LARCH_SOP_SUB", Const, 19}, + {"R_LARCH_SUB16", Const, 19}, + {"R_LARCH_SUB24", Const, 19}, + {"R_LARCH_SUB32", Const, 19}, + {"R_LARCH_SUB6", Const, 22}, + {"R_LARCH_SUB64", Const, 19}, + {"R_LARCH_SUB8", Const, 19}, + {"R_LARCH_SUB_ULEB128", Const, 22}, + {"R_LARCH_TLS_DTPMOD32", Const, 19}, + {"R_LARCH_TLS_DTPMOD64", Const, 19}, + {"R_LARCH_TLS_DTPREL32", Const, 19}, + {"R_LARCH_TLS_DTPREL64", Const, 19}, + {"R_LARCH_TLS_GD_HI20", Const, 20}, + {"R_LARCH_TLS_GD_PC_HI20", Const, 20}, + {"R_LARCH_TLS_IE64_HI12", Const, 20}, + {"R_LARCH_TLS_IE64_LO20", Const, 20}, + {"R_LARCH_TLS_IE64_PC_HI12", Const, 20}, + {"R_LARCH_TLS_IE64_PC_LO20", Const, 20}, + {"R_LARCH_TLS_IE_HI20", Const, 20}, + {"R_LARCH_TLS_IE_LO12", Const, 20}, + {"R_LARCH_TLS_IE_PC_HI20", Const, 20}, + {"R_LARCH_TLS_IE_PC_LO12", Const, 20}, + {"R_LARCH_TLS_LD_HI20", Const, 20}, + {"R_LARCH_TLS_LD_PC_HI20", Const, 20}, + {"R_LARCH_TLS_LE64_HI12", Const, 20}, + {"R_LARCH_TLS_LE64_LO20", Const, 20}, + {"R_LARCH_TLS_LE_HI20", Const, 20}, + {"R_LARCH_TLS_LE_LO12", Const, 20}, + {"R_LARCH_TLS_TPREL32", Const, 19}, + {"R_LARCH_TLS_TPREL64", Const, 19}, + {"R_MIPS", Type, 6}, + {"R_MIPS_16", Const, 6}, + {"R_MIPS_26", Const, 6}, + {"R_MIPS_32", Const, 6}, + {"R_MIPS_64", Const, 6}, + {"R_MIPS_ADD_IMMEDIATE", Const, 6}, + {"R_MIPS_CALL16", Const, 6}, + {"R_MIPS_CALL_HI16", Const, 6}, + {"R_MIPS_CALL_LO16", Const, 6}, + {"R_MIPS_DELETE", Const, 6}, + {"R_MIPS_GOT16", Const, 6}, + {"R_MIPS_GOT_DISP", Const, 6}, + {"R_MIPS_GOT_HI16", Const, 6}, + {"R_MIPS_GOT_LO16", Const, 6}, + {"R_MIPS_GOT_OFST", Const, 6}, + {"R_MIPS_GOT_PAGE", Const, 6}, + {"R_MIPS_GPREL16", Const, 6}, + {"R_MIPS_GPREL32", Const, 6}, + {"R_MIPS_HI16", Const, 6}, + {"R_MIPS_HIGHER", Const, 6}, + {"R_MIPS_HIGHEST", Const, 6}, + {"R_MIPS_INSERT_A", Const, 6}, + {"R_MIPS_INSERT_B", Const, 6}, + {"R_MIPS_JALR", Const, 6}, + {"R_MIPS_LITERAL", Const, 6}, + {"R_MIPS_LO16", Const, 6}, + {"R_MIPS_NONE", Const, 6}, + {"R_MIPS_PC16", Const, 6}, + {"R_MIPS_PC32", Const, 22}, + {"R_MIPS_PJUMP", Const, 6}, + {"R_MIPS_REL16", Const, 6}, + {"R_MIPS_REL32", Const, 6}, + {"R_MIPS_RELGOT", Const, 6}, + {"R_MIPS_SCN_DISP", Const, 6}, + {"R_MIPS_SHIFT5", Const, 6}, + {"R_MIPS_SHIFT6", Const, 6}, + {"R_MIPS_SUB", Const, 6}, + {"R_MIPS_TLS_DTPMOD32", Const, 6}, + {"R_MIPS_TLS_DTPMOD64", Const, 6}, + {"R_MIPS_TLS_DTPREL32", Const, 6}, + {"R_MIPS_TLS_DTPREL64", Const, 6}, + {"R_MIPS_TLS_DTPREL_HI16", Const, 6}, + {"R_MIPS_TLS_DTPREL_LO16", Const, 6}, + {"R_MIPS_TLS_GD", Const, 6}, + {"R_MIPS_TLS_GOTTPREL", Const, 6}, + {"R_MIPS_TLS_LDM", Const, 6}, + {"R_MIPS_TLS_TPREL32", Const, 6}, + {"R_MIPS_TLS_TPREL64", Const, 6}, + {"R_MIPS_TLS_TPREL_HI16", Const, 6}, + {"R_MIPS_TLS_TPREL_LO16", Const, 6}, + {"R_PPC", Type, 0}, + {"R_PPC64", Type, 5}, + {"R_PPC64_ADDR14", Const, 5}, + {"R_PPC64_ADDR14_BRNTAKEN", Const, 5}, + {"R_PPC64_ADDR14_BRTAKEN", Const, 5}, + {"R_PPC64_ADDR16", Const, 5}, + {"R_PPC64_ADDR16_DS", Const, 5}, + {"R_PPC64_ADDR16_HA", Const, 5}, + {"R_PPC64_ADDR16_HI", Const, 5}, + {"R_PPC64_ADDR16_HIGH", Const, 10}, + {"R_PPC64_ADDR16_HIGHA", Const, 10}, + {"R_PPC64_ADDR16_HIGHER", Const, 5}, + {"R_PPC64_ADDR16_HIGHER34", Const, 20}, + {"R_PPC64_ADDR16_HIGHERA", Const, 5}, + {"R_PPC64_ADDR16_HIGHERA34", Const, 20}, + {"R_PPC64_ADDR16_HIGHEST", Const, 5}, + {"R_PPC64_ADDR16_HIGHEST34", Const, 20}, + {"R_PPC64_ADDR16_HIGHESTA", Const, 5}, + {"R_PPC64_ADDR16_HIGHESTA34", Const, 20}, + {"R_PPC64_ADDR16_LO", Const, 5}, + {"R_PPC64_ADDR16_LO_DS", Const, 5}, + {"R_PPC64_ADDR24", Const, 5}, + {"R_PPC64_ADDR32", Const, 5}, + {"R_PPC64_ADDR64", Const, 5}, + {"R_PPC64_ADDR64_LOCAL", Const, 10}, + {"R_PPC64_COPY", Const, 20}, + {"R_PPC64_D28", Const, 20}, + {"R_PPC64_D34", Const, 20}, + {"R_PPC64_D34_HA30", Const, 20}, + {"R_PPC64_D34_HI30", Const, 20}, + {"R_PPC64_D34_LO", Const, 20}, + {"R_PPC64_DTPMOD64", Const, 5}, + {"R_PPC64_DTPREL16", Const, 5}, + {"R_PPC64_DTPREL16_DS", Const, 5}, + {"R_PPC64_DTPREL16_HA", Const, 5}, + {"R_PPC64_DTPREL16_HI", Const, 5}, + {"R_PPC64_DTPREL16_HIGH", Const, 10}, + {"R_PPC64_DTPREL16_HIGHA", Const, 10}, + {"R_PPC64_DTPREL16_HIGHER", Const, 5}, + {"R_PPC64_DTPREL16_HIGHERA", Const, 5}, + {"R_PPC64_DTPREL16_HIGHEST", Const, 5}, + {"R_PPC64_DTPREL16_HIGHESTA", Const, 5}, + {"R_PPC64_DTPREL16_LO", Const, 5}, + {"R_PPC64_DTPREL16_LO_DS", Const, 5}, + {"R_PPC64_DTPREL34", Const, 20}, + {"R_PPC64_DTPREL64", Const, 5}, + {"R_PPC64_ENTRY", Const, 10}, + {"R_PPC64_GLOB_DAT", Const, 20}, + {"R_PPC64_GNU_VTENTRY", Const, 20}, + {"R_PPC64_GNU_VTINHERIT", Const, 20}, + {"R_PPC64_GOT16", Const, 5}, + {"R_PPC64_GOT16_DS", Const, 5}, + {"R_PPC64_GOT16_HA", Const, 5}, + {"R_PPC64_GOT16_HI", Const, 5}, + {"R_PPC64_GOT16_LO", Const, 5}, + {"R_PPC64_GOT16_LO_DS", Const, 5}, + {"R_PPC64_GOT_DTPREL16_DS", Const, 5}, + {"R_PPC64_GOT_DTPREL16_HA", Const, 5}, + {"R_PPC64_GOT_DTPREL16_HI", Const, 5}, + {"R_PPC64_GOT_DTPREL16_LO_DS", Const, 5}, + {"R_PPC64_GOT_DTPREL_PCREL34", Const, 20}, + {"R_PPC64_GOT_PCREL34", Const, 20}, + {"R_PPC64_GOT_TLSGD16", Const, 5}, + {"R_PPC64_GOT_TLSGD16_HA", Const, 5}, + {"R_PPC64_GOT_TLSGD16_HI", Const, 5}, + {"R_PPC64_GOT_TLSGD16_LO", Const, 5}, + {"R_PPC64_GOT_TLSGD_PCREL34", Const, 20}, + {"R_PPC64_GOT_TLSLD16", Const, 5}, + {"R_PPC64_GOT_TLSLD16_HA", Const, 5}, + {"R_PPC64_GOT_TLSLD16_HI", Const, 5}, + {"R_PPC64_GOT_TLSLD16_LO", Const, 5}, + {"R_PPC64_GOT_TLSLD_PCREL34", Const, 20}, + {"R_PPC64_GOT_TPREL16_DS", Const, 5}, + {"R_PPC64_GOT_TPREL16_HA", Const, 5}, + {"R_PPC64_GOT_TPREL16_HI", Const, 5}, + {"R_PPC64_GOT_TPREL16_LO_DS", Const, 5}, + {"R_PPC64_GOT_TPREL_PCREL34", Const, 20}, + {"R_PPC64_IRELATIVE", Const, 10}, + {"R_PPC64_JMP_IREL", Const, 10}, + {"R_PPC64_JMP_SLOT", Const, 5}, + {"R_PPC64_NONE", Const, 5}, + {"R_PPC64_PCREL28", Const, 20}, + {"R_PPC64_PCREL34", Const, 20}, + {"R_PPC64_PCREL_OPT", Const, 20}, + {"R_PPC64_PLT16_HA", Const, 20}, + {"R_PPC64_PLT16_HI", Const, 20}, + {"R_PPC64_PLT16_LO", Const, 20}, + {"R_PPC64_PLT16_LO_DS", Const, 10}, + {"R_PPC64_PLT32", Const, 20}, + {"R_PPC64_PLT64", Const, 20}, + {"R_PPC64_PLTCALL", Const, 20}, + {"R_PPC64_PLTCALL_NOTOC", Const, 20}, + {"R_PPC64_PLTGOT16", Const, 10}, + {"R_PPC64_PLTGOT16_DS", Const, 10}, + {"R_PPC64_PLTGOT16_HA", Const, 10}, + {"R_PPC64_PLTGOT16_HI", Const, 10}, + {"R_PPC64_PLTGOT16_LO", Const, 10}, + {"R_PPC64_PLTGOT_LO_DS", Const, 10}, + {"R_PPC64_PLTREL32", Const, 20}, + {"R_PPC64_PLTREL64", Const, 20}, + {"R_PPC64_PLTSEQ", Const, 20}, + {"R_PPC64_PLTSEQ_NOTOC", Const, 20}, + {"R_PPC64_PLT_PCREL34", Const, 20}, + {"R_PPC64_PLT_PCREL34_NOTOC", Const, 20}, + {"R_PPC64_REL14", Const, 5}, + {"R_PPC64_REL14_BRNTAKEN", Const, 5}, + {"R_PPC64_REL14_BRTAKEN", Const, 5}, + {"R_PPC64_REL16", Const, 5}, + {"R_PPC64_REL16DX_HA", Const, 10}, + {"R_PPC64_REL16_HA", Const, 5}, + {"R_PPC64_REL16_HI", Const, 5}, + {"R_PPC64_REL16_HIGH", Const, 20}, + {"R_PPC64_REL16_HIGHA", Const, 20}, + {"R_PPC64_REL16_HIGHER", Const, 20}, + {"R_PPC64_REL16_HIGHER34", Const, 20}, + {"R_PPC64_REL16_HIGHERA", Const, 20}, + {"R_PPC64_REL16_HIGHERA34", Const, 20}, + {"R_PPC64_REL16_HIGHEST", Const, 20}, + {"R_PPC64_REL16_HIGHEST34", Const, 20}, + {"R_PPC64_REL16_HIGHESTA", Const, 20}, + {"R_PPC64_REL16_HIGHESTA34", Const, 20}, + {"R_PPC64_REL16_LO", Const, 5}, + {"R_PPC64_REL24", Const, 5}, + {"R_PPC64_REL24_NOTOC", Const, 10}, + {"R_PPC64_REL24_P9NOTOC", Const, 21}, + {"R_PPC64_REL30", Const, 20}, + {"R_PPC64_REL32", Const, 5}, + {"R_PPC64_REL64", Const, 5}, + {"R_PPC64_RELATIVE", Const, 18}, + {"R_PPC64_SECTOFF", Const, 20}, + {"R_PPC64_SECTOFF_DS", Const, 10}, + {"R_PPC64_SECTOFF_HA", Const, 20}, + {"R_PPC64_SECTOFF_HI", Const, 20}, + {"R_PPC64_SECTOFF_LO", Const, 20}, + {"R_PPC64_SECTOFF_LO_DS", Const, 10}, + {"R_PPC64_TLS", Const, 5}, + {"R_PPC64_TLSGD", Const, 5}, + {"R_PPC64_TLSLD", Const, 5}, + {"R_PPC64_TOC", Const, 5}, + {"R_PPC64_TOC16", Const, 5}, + {"R_PPC64_TOC16_DS", Const, 5}, + {"R_PPC64_TOC16_HA", Const, 5}, + {"R_PPC64_TOC16_HI", Const, 5}, + {"R_PPC64_TOC16_LO", Const, 5}, + {"R_PPC64_TOC16_LO_DS", Const, 5}, + {"R_PPC64_TOCSAVE", Const, 10}, + {"R_PPC64_TPREL16", Const, 5}, + {"R_PPC64_TPREL16_DS", Const, 5}, + {"R_PPC64_TPREL16_HA", Const, 5}, + {"R_PPC64_TPREL16_HI", Const, 5}, + {"R_PPC64_TPREL16_HIGH", Const, 10}, + {"R_PPC64_TPREL16_HIGHA", Const, 10}, + {"R_PPC64_TPREL16_HIGHER", Const, 5}, + {"R_PPC64_TPREL16_HIGHERA", Const, 5}, + {"R_PPC64_TPREL16_HIGHEST", Const, 5}, + {"R_PPC64_TPREL16_HIGHESTA", Const, 5}, + {"R_PPC64_TPREL16_LO", Const, 5}, + {"R_PPC64_TPREL16_LO_DS", Const, 5}, + {"R_PPC64_TPREL34", Const, 20}, + {"R_PPC64_TPREL64", Const, 5}, + {"R_PPC64_UADDR16", Const, 20}, + {"R_PPC64_UADDR32", Const, 20}, + {"R_PPC64_UADDR64", Const, 20}, + {"R_PPC_ADDR14", Const, 0}, + {"R_PPC_ADDR14_BRNTAKEN", Const, 0}, + {"R_PPC_ADDR14_BRTAKEN", Const, 0}, + {"R_PPC_ADDR16", Const, 0}, + {"R_PPC_ADDR16_HA", Const, 0}, + {"R_PPC_ADDR16_HI", Const, 0}, + {"R_PPC_ADDR16_LO", Const, 0}, + {"R_PPC_ADDR24", Const, 0}, + {"R_PPC_ADDR32", Const, 0}, + {"R_PPC_COPY", Const, 0}, + {"R_PPC_DTPMOD32", Const, 0}, + {"R_PPC_DTPREL16", Const, 0}, + {"R_PPC_DTPREL16_HA", Const, 0}, + {"R_PPC_DTPREL16_HI", Const, 0}, + {"R_PPC_DTPREL16_LO", Const, 0}, + {"R_PPC_DTPREL32", Const, 0}, + {"R_PPC_EMB_BIT_FLD", Const, 0}, + {"R_PPC_EMB_MRKREF", Const, 0}, + {"R_PPC_EMB_NADDR16", Const, 0}, + {"R_PPC_EMB_NADDR16_HA", Const, 0}, + {"R_PPC_EMB_NADDR16_HI", Const, 0}, + {"R_PPC_EMB_NADDR16_LO", Const, 0}, + {"R_PPC_EMB_NADDR32", Const, 0}, + {"R_PPC_EMB_RELSDA", Const, 0}, + {"R_PPC_EMB_RELSEC16", Const, 0}, + {"R_PPC_EMB_RELST_HA", Const, 0}, + {"R_PPC_EMB_RELST_HI", Const, 0}, + {"R_PPC_EMB_RELST_LO", Const, 0}, + {"R_PPC_EMB_SDA21", Const, 0}, + {"R_PPC_EMB_SDA2I16", Const, 0}, + {"R_PPC_EMB_SDA2REL", Const, 0}, + {"R_PPC_EMB_SDAI16", Const, 0}, + {"R_PPC_GLOB_DAT", Const, 0}, + {"R_PPC_GOT16", Const, 0}, + {"R_PPC_GOT16_HA", Const, 0}, + {"R_PPC_GOT16_HI", Const, 0}, + {"R_PPC_GOT16_LO", Const, 0}, + {"R_PPC_GOT_TLSGD16", Const, 0}, + {"R_PPC_GOT_TLSGD16_HA", Const, 0}, + {"R_PPC_GOT_TLSGD16_HI", Const, 0}, + {"R_PPC_GOT_TLSGD16_LO", Const, 0}, + {"R_PPC_GOT_TLSLD16", Const, 0}, + {"R_PPC_GOT_TLSLD16_HA", Const, 0}, + {"R_PPC_GOT_TLSLD16_HI", Const, 0}, + {"R_PPC_GOT_TLSLD16_LO", Const, 0}, + {"R_PPC_GOT_TPREL16", Const, 0}, + {"R_PPC_GOT_TPREL16_HA", Const, 0}, + {"R_PPC_GOT_TPREL16_HI", Const, 0}, + {"R_PPC_GOT_TPREL16_LO", Const, 0}, + {"R_PPC_JMP_SLOT", Const, 0}, + {"R_PPC_LOCAL24PC", Const, 0}, + {"R_PPC_NONE", Const, 0}, + {"R_PPC_PLT16_HA", Const, 0}, + {"R_PPC_PLT16_HI", Const, 0}, + {"R_PPC_PLT16_LO", Const, 0}, + {"R_PPC_PLT32", Const, 0}, + {"R_PPC_PLTREL24", Const, 0}, + {"R_PPC_PLTREL32", Const, 0}, + {"R_PPC_REL14", Const, 0}, + {"R_PPC_REL14_BRNTAKEN", Const, 0}, + {"R_PPC_REL14_BRTAKEN", Const, 0}, + {"R_PPC_REL24", Const, 0}, + {"R_PPC_REL32", Const, 0}, + {"R_PPC_RELATIVE", Const, 0}, + {"R_PPC_SDAREL16", Const, 0}, + {"R_PPC_SECTOFF", Const, 0}, + {"R_PPC_SECTOFF_HA", Const, 0}, + {"R_PPC_SECTOFF_HI", Const, 0}, + {"R_PPC_SECTOFF_LO", Const, 0}, + {"R_PPC_TLS", Const, 0}, + {"R_PPC_TPREL16", Const, 0}, + {"R_PPC_TPREL16_HA", Const, 0}, + {"R_PPC_TPREL16_HI", Const, 0}, + {"R_PPC_TPREL16_LO", Const, 0}, + {"R_PPC_TPREL32", Const, 0}, + {"R_PPC_UADDR16", Const, 0}, + {"R_PPC_UADDR32", Const, 0}, + {"R_RISCV", Type, 11}, + {"R_RISCV_32", Const, 11}, + {"R_RISCV_32_PCREL", Const, 12}, + {"R_RISCV_64", Const, 11}, + {"R_RISCV_ADD16", Const, 11}, + {"R_RISCV_ADD32", Const, 11}, + {"R_RISCV_ADD64", Const, 11}, + {"R_RISCV_ADD8", Const, 11}, + {"R_RISCV_ALIGN", Const, 11}, + {"R_RISCV_BRANCH", Const, 11}, + {"R_RISCV_CALL", Const, 11}, + {"R_RISCV_CALL_PLT", Const, 11}, + {"R_RISCV_COPY", Const, 11}, + {"R_RISCV_GNU_VTENTRY", Const, 11}, + {"R_RISCV_GNU_VTINHERIT", Const, 11}, + {"R_RISCV_GOT_HI20", Const, 11}, + {"R_RISCV_GPREL_I", Const, 11}, + {"R_RISCV_GPREL_S", Const, 11}, + {"R_RISCV_HI20", Const, 11}, + {"R_RISCV_JAL", Const, 11}, + {"R_RISCV_JUMP_SLOT", Const, 11}, + {"R_RISCV_LO12_I", Const, 11}, + {"R_RISCV_LO12_S", Const, 11}, + {"R_RISCV_NONE", Const, 11}, + {"R_RISCV_PCREL_HI20", Const, 11}, + {"R_RISCV_PCREL_LO12_I", Const, 11}, + {"R_RISCV_PCREL_LO12_S", Const, 11}, + {"R_RISCV_RELATIVE", Const, 11}, + {"R_RISCV_RELAX", Const, 11}, + {"R_RISCV_RVC_BRANCH", Const, 11}, + {"R_RISCV_RVC_JUMP", Const, 11}, + {"R_RISCV_RVC_LUI", Const, 11}, + {"R_RISCV_SET16", Const, 11}, + {"R_RISCV_SET32", Const, 11}, + {"R_RISCV_SET6", Const, 11}, + {"R_RISCV_SET8", Const, 11}, + {"R_RISCV_SUB16", Const, 11}, + {"R_RISCV_SUB32", Const, 11}, + {"R_RISCV_SUB6", Const, 11}, + {"R_RISCV_SUB64", Const, 11}, + {"R_RISCV_SUB8", Const, 11}, + {"R_RISCV_TLS_DTPMOD32", Const, 11}, + {"R_RISCV_TLS_DTPMOD64", Const, 11}, + {"R_RISCV_TLS_DTPREL32", Const, 11}, + {"R_RISCV_TLS_DTPREL64", Const, 11}, + {"R_RISCV_TLS_GD_HI20", Const, 11}, + {"R_RISCV_TLS_GOT_HI20", Const, 11}, + {"R_RISCV_TLS_TPREL32", Const, 11}, + {"R_RISCV_TLS_TPREL64", Const, 11}, + {"R_RISCV_TPREL_ADD", Const, 11}, + {"R_RISCV_TPREL_HI20", Const, 11}, + {"R_RISCV_TPREL_I", Const, 11}, + {"R_RISCV_TPREL_LO12_I", Const, 11}, + {"R_RISCV_TPREL_LO12_S", Const, 11}, + {"R_RISCV_TPREL_S", Const, 11}, + {"R_SPARC", Type, 0}, + {"R_SPARC_10", Const, 0}, + {"R_SPARC_11", Const, 0}, + {"R_SPARC_13", Const, 0}, + {"R_SPARC_16", Const, 0}, + {"R_SPARC_22", Const, 0}, + {"R_SPARC_32", Const, 0}, + {"R_SPARC_5", Const, 0}, + {"R_SPARC_6", Const, 0}, + {"R_SPARC_64", Const, 0}, + {"R_SPARC_7", Const, 0}, + {"R_SPARC_8", Const, 0}, + {"R_SPARC_COPY", Const, 0}, + {"R_SPARC_DISP16", Const, 0}, + {"R_SPARC_DISP32", Const, 0}, + {"R_SPARC_DISP64", Const, 0}, + {"R_SPARC_DISP8", Const, 0}, + {"R_SPARC_GLOB_DAT", Const, 0}, + {"R_SPARC_GLOB_JMP", Const, 0}, + {"R_SPARC_GOT10", Const, 0}, + {"R_SPARC_GOT13", Const, 0}, + {"R_SPARC_GOT22", Const, 0}, + {"R_SPARC_H44", Const, 0}, + {"R_SPARC_HH22", Const, 0}, + {"R_SPARC_HI22", Const, 0}, + {"R_SPARC_HIPLT22", Const, 0}, + {"R_SPARC_HIX22", Const, 0}, + {"R_SPARC_HM10", Const, 0}, + {"R_SPARC_JMP_SLOT", Const, 0}, + {"R_SPARC_L44", Const, 0}, + {"R_SPARC_LM22", Const, 0}, + {"R_SPARC_LO10", Const, 0}, + {"R_SPARC_LOPLT10", Const, 0}, + {"R_SPARC_LOX10", Const, 0}, + {"R_SPARC_M44", Const, 0}, + {"R_SPARC_NONE", Const, 0}, + {"R_SPARC_OLO10", Const, 0}, + {"R_SPARC_PC10", Const, 0}, + {"R_SPARC_PC22", Const, 0}, + {"R_SPARC_PCPLT10", Const, 0}, + {"R_SPARC_PCPLT22", Const, 0}, + {"R_SPARC_PCPLT32", Const, 0}, + {"R_SPARC_PC_HH22", Const, 0}, + {"R_SPARC_PC_HM10", Const, 0}, + {"R_SPARC_PC_LM22", Const, 0}, + {"R_SPARC_PLT32", Const, 0}, + {"R_SPARC_PLT64", Const, 0}, + {"R_SPARC_REGISTER", Const, 0}, + {"R_SPARC_RELATIVE", Const, 0}, + {"R_SPARC_UA16", Const, 0}, + {"R_SPARC_UA32", Const, 0}, + {"R_SPARC_UA64", Const, 0}, + {"R_SPARC_WDISP16", Const, 0}, + {"R_SPARC_WDISP19", Const, 0}, + {"R_SPARC_WDISP22", Const, 0}, + {"R_SPARC_WDISP30", Const, 0}, + {"R_SPARC_WPLT30", Const, 0}, + {"R_SYM32", Func, 0}, + {"R_SYM64", Func, 0}, + {"R_TYPE32", Func, 0}, + {"R_TYPE64", Func, 0}, + {"R_X86_64", Type, 0}, + {"R_X86_64_16", Const, 0}, + {"R_X86_64_32", Const, 0}, + {"R_X86_64_32S", Const, 0}, + {"R_X86_64_64", Const, 0}, + {"R_X86_64_8", Const, 0}, + {"R_X86_64_COPY", Const, 0}, + {"R_X86_64_DTPMOD64", Const, 0}, + {"R_X86_64_DTPOFF32", Const, 0}, + {"R_X86_64_DTPOFF64", Const, 0}, + {"R_X86_64_GLOB_DAT", Const, 0}, + {"R_X86_64_GOT32", Const, 0}, + {"R_X86_64_GOT64", Const, 10}, + {"R_X86_64_GOTOFF64", Const, 10}, + {"R_X86_64_GOTPC32", Const, 10}, + {"R_X86_64_GOTPC32_TLSDESC", Const, 10}, + {"R_X86_64_GOTPC64", Const, 10}, + {"R_X86_64_GOTPCREL", Const, 0}, + {"R_X86_64_GOTPCREL64", Const, 10}, + {"R_X86_64_GOTPCRELX", Const, 10}, + {"R_X86_64_GOTPLT64", Const, 10}, + {"R_X86_64_GOTTPOFF", Const, 0}, + {"R_X86_64_IRELATIVE", Const, 10}, + {"R_X86_64_JMP_SLOT", Const, 0}, + {"R_X86_64_NONE", Const, 0}, + {"R_X86_64_PC16", Const, 0}, + {"R_X86_64_PC32", Const, 0}, + {"R_X86_64_PC32_BND", Const, 10}, + {"R_X86_64_PC64", Const, 10}, + {"R_X86_64_PC8", Const, 0}, + {"R_X86_64_PLT32", Const, 0}, + {"R_X86_64_PLT32_BND", Const, 10}, + {"R_X86_64_PLTOFF64", Const, 10}, + {"R_X86_64_RELATIVE", Const, 0}, + {"R_X86_64_RELATIVE64", Const, 10}, + {"R_X86_64_REX_GOTPCRELX", Const, 10}, + {"R_X86_64_SIZE32", Const, 10}, + {"R_X86_64_SIZE64", Const, 10}, + {"R_X86_64_TLSDESC", Const, 10}, + {"R_X86_64_TLSDESC_CALL", Const, 10}, + {"R_X86_64_TLSGD", Const, 0}, + {"R_X86_64_TLSLD", Const, 0}, + {"R_X86_64_TPOFF32", Const, 0}, + {"R_X86_64_TPOFF64", Const, 0}, + {"Rel32", Type, 0}, + {"Rel32.Info", Field, 0}, + {"Rel32.Off", Field, 0}, + {"Rel64", Type, 0}, + {"Rel64.Info", Field, 0}, + {"Rel64.Off", Field, 0}, + {"Rela32", Type, 0}, + {"Rela32.Addend", Field, 0}, + {"Rela32.Info", Field, 0}, + {"Rela32.Off", Field, 0}, + {"Rela64", Type, 0}, + {"Rela64.Addend", Field, 0}, + {"Rela64.Info", Field, 0}, + {"Rela64.Off", Field, 0}, + {"SHF_ALLOC", Const, 0}, + {"SHF_COMPRESSED", Const, 6}, + {"SHF_EXECINSTR", Const, 0}, + {"SHF_GROUP", Const, 0}, + {"SHF_INFO_LINK", Const, 0}, + {"SHF_LINK_ORDER", Const, 0}, + {"SHF_MASKOS", Const, 0}, + {"SHF_MASKPROC", Const, 0}, + {"SHF_MERGE", Const, 0}, + {"SHF_OS_NONCONFORMING", Const, 0}, + {"SHF_STRINGS", Const, 0}, + {"SHF_TLS", Const, 0}, + {"SHF_WRITE", Const, 0}, + {"SHN_ABS", Const, 0}, + {"SHN_COMMON", Const, 0}, + {"SHN_HIOS", Const, 0}, + {"SHN_HIPROC", Const, 0}, + {"SHN_HIRESERVE", Const, 0}, + {"SHN_LOOS", Const, 0}, + {"SHN_LOPROC", Const, 0}, + {"SHN_LORESERVE", Const, 0}, + {"SHN_UNDEF", Const, 0}, + {"SHN_XINDEX", Const, 0}, + {"SHT_DYNAMIC", Const, 0}, + {"SHT_DYNSYM", Const, 0}, + {"SHT_FINI_ARRAY", Const, 0}, + {"SHT_GNU_ATTRIBUTES", Const, 0}, + {"SHT_GNU_HASH", Const, 0}, + {"SHT_GNU_LIBLIST", Const, 0}, + {"SHT_GNU_VERDEF", Const, 0}, + {"SHT_GNU_VERNEED", Const, 0}, + {"SHT_GNU_VERSYM", Const, 0}, + {"SHT_GROUP", Const, 0}, + {"SHT_HASH", Const, 0}, + {"SHT_HIOS", Const, 0}, + {"SHT_HIPROC", Const, 0}, + {"SHT_HIUSER", Const, 0}, + {"SHT_INIT_ARRAY", Const, 0}, + {"SHT_LOOS", Const, 0}, + {"SHT_LOPROC", Const, 0}, + {"SHT_LOUSER", Const, 0}, + {"SHT_MIPS_ABIFLAGS", Const, 17}, + {"SHT_NOBITS", Const, 0}, + {"SHT_NOTE", Const, 0}, + {"SHT_NULL", Const, 0}, + {"SHT_PREINIT_ARRAY", Const, 0}, + {"SHT_PROGBITS", Const, 0}, + {"SHT_REL", Const, 0}, + {"SHT_RELA", Const, 0}, + {"SHT_SHLIB", Const, 0}, + {"SHT_STRTAB", Const, 0}, + {"SHT_SYMTAB", Const, 0}, + {"SHT_SYMTAB_SHNDX", Const, 0}, + {"STB_GLOBAL", Const, 0}, + {"STB_HIOS", Const, 0}, + {"STB_HIPROC", Const, 0}, + {"STB_LOCAL", Const, 0}, + {"STB_LOOS", Const, 0}, + {"STB_LOPROC", Const, 0}, + {"STB_WEAK", Const, 0}, + {"STT_COMMON", Const, 0}, + {"STT_FILE", Const, 0}, + {"STT_FUNC", Const, 0}, + {"STT_GNU_IFUNC", Const, 23}, + {"STT_HIOS", Const, 0}, + {"STT_HIPROC", Const, 0}, + {"STT_LOOS", Const, 0}, + {"STT_LOPROC", Const, 0}, + {"STT_NOTYPE", Const, 0}, + {"STT_OBJECT", Const, 0}, + {"STT_RELC", Const, 23}, + {"STT_SECTION", Const, 0}, + {"STT_SRELC", Const, 23}, + {"STT_TLS", Const, 0}, + {"STV_DEFAULT", Const, 0}, + {"STV_HIDDEN", Const, 0}, + {"STV_INTERNAL", Const, 0}, + {"STV_PROTECTED", Const, 0}, + {"ST_BIND", Func, 0}, + {"ST_INFO", Func, 0}, + {"ST_TYPE", Func, 0}, + {"ST_VISIBILITY", Func, 0}, + {"Section", Type, 0}, + {"Section.ReaderAt", Field, 0}, + {"Section.SectionHeader", Field, 0}, + {"Section32", Type, 0}, + {"Section32.Addr", Field, 0}, + {"Section32.Addralign", Field, 0}, + {"Section32.Entsize", Field, 0}, + {"Section32.Flags", Field, 0}, + {"Section32.Info", Field, 0}, + {"Section32.Link", Field, 0}, + {"Section32.Name", Field, 0}, + {"Section32.Off", Field, 0}, + {"Section32.Size", Field, 0}, + {"Section32.Type", Field, 0}, + {"Section64", Type, 0}, + {"Section64.Addr", Field, 0}, + {"Section64.Addralign", Field, 0}, + {"Section64.Entsize", Field, 0}, + {"Section64.Flags", Field, 0}, + {"Section64.Info", Field, 0}, + {"Section64.Link", Field, 0}, + {"Section64.Name", Field, 0}, + {"Section64.Off", Field, 0}, + {"Section64.Size", Field, 0}, + {"Section64.Type", Field, 0}, + {"SectionFlag", Type, 0}, + {"SectionHeader", Type, 0}, + {"SectionHeader.Addr", Field, 0}, + {"SectionHeader.Addralign", Field, 0}, + {"SectionHeader.Entsize", Field, 0}, + {"SectionHeader.FileSize", Field, 6}, + {"SectionHeader.Flags", Field, 0}, + {"SectionHeader.Info", Field, 0}, + {"SectionHeader.Link", Field, 0}, + {"SectionHeader.Name", Field, 0}, + {"SectionHeader.Offset", Field, 0}, + {"SectionHeader.Size", Field, 0}, + {"SectionHeader.Type", Field, 0}, + {"SectionIndex", Type, 0}, + {"SectionType", Type, 0}, + {"Sym32", Type, 0}, + {"Sym32.Info", Field, 0}, + {"Sym32.Name", Field, 0}, + {"Sym32.Other", Field, 0}, + {"Sym32.Shndx", Field, 0}, + {"Sym32.Size", Field, 0}, + {"Sym32.Value", Field, 0}, + {"Sym32Size", Const, 0}, + {"Sym64", Type, 0}, + {"Sym64.Info", Field, 0}, + {"Sym64.Name", Field, 0}, + {"Sym64.Other", Field, 0}, + {"Sym64.Shndx", Field, 0}, + {"Sym64.Size", Field, 0}, + {"Sym64.Value", Field, 0}, + {"Sym64Size", Const, 0}, + {"SymBind", Type, 0}, + {"SymType", Type, 0}, + {"SymVis", Type, 0}, + {"Symbol", Type, 0}, + {"Symbol.Info", Field, 0}, + {"Symbol.Library", Field, 13}, + {"Symbol.Name", Field, 0}, + {"Symbol.Other", Field, 0}, + {"Symbol.Section", Field, 0}, + {"Symbol.Size", Field, 0}, + {"Symbol.Value", Field, 0}, + {"Symbol.Version", Field, 13}, + {"Type", Type, 0}, + {"Version", Type, 0}, + }, + "debug/gosym": { + {"(*DecodingError).Error", Method, 0}, + {"(*LineTable).LineToPC", Method, 0}, + {"(*LineTable).PCToLine", Method, 0}, + {"(*Sym).BaseName", Method, 0}, + {"(*Sym).PackageName", Method, 0}, + {"(*Sym).ReceiverName", Method, 0}, + {"(*Sym).Static", Method, 0}, + {"(*Table).LineToPC", Method, 0}, + {"(*Table).LookupFunc", Method, 0}, + {"(*Table).LookupSym", Method, 0}, + {"(*Table).PCToFunc", Method, 0}, + {"(*Table).PCToLine", Method, 0}, + {"(*Table).SymByAddr", Method, 0}, + {"(*UnknownLineError).Error", Method, 0}, + {"(Func).BaseName", Method, 0}, + {"(Func).PackageName", Method, 0}, + {"(Func).ReceiverName", Method, 0}, + {"(Func).Static", Method, 0}, + {"(UnknownFileError).Error", Method, 0}, + {"DecodingError", Type, 0}, + {"Func", Type, 0}, + {"Func.End", Field, 0}, + {"Func.Entry", Field, 0}, + {"Func.FrameSize", Field, 0}, + {"Func.LineTable", Field, 0}, + {"Func.Locals", Field, 0}, + {"Func.Obj", Field, 0}, + {"Func.Params", Field, 0}, + {"Func.Sym", Field, 0}, + {"LineTable", Type, 0}, + {"LineTable.Data", Field, 0}, + {"LineTable.Line", Field, 0}, + {"LineTable.PC", Field, 0}, + {"NewLineTable", Func, 0}, + {"NewTable", Func, 0}, + {"Obj", Type, 0}, + {"Obj.Funcs", Field, 0}, + {"Obj.Paths", Field, 0}, + {"Sym", Type, 0}, + {"Sym.Func", Field, 0}, + {"Sym.GoType", Field, 0}, + {"Sym.Name", Field, 0}, + {"Sym.Type", Field, 0}, + {"Sym.Value", Field, 0}, + {"Table", Type, 0}, + {"Table.Files", Field, 0}, + {"Table.Funcs", Field, 0}, + {"Table.Objs", Field, 0}, + {"Table.Syms", Field, 0}, + {"UnknownFileError", Type, 0}, + {"UnknownLineError", Type, 0}, + {"UnknownLineError.File", Field, 0}, + {"UnknownLineError.Line", Field, 0}, + }, + "debug/macho": { + {"(*FatFile).Close", Method, 3}, + {"(*File).Close", Method, 0}, + {"(*File).DWARF", Method, 0}, + {"(*File).ImportedLibraries", Method, 0}, + {"(*File).ImportedSymbols", Method, 0}, + {"(*File).Section", Method, 0}, + {"(*File).Segment", Method, 0}, + {"(*FormatError).Error", Method, 0}, + {"(*Section).Data", Method, 0}, + {"(*Section).Open", Method, 0}, + {"(*Segment).Data", Method, 0}, + {"(*Segment).Open", Method, 0}, + {"(Cpu).GoString", Method, 0}, + {"(Cpu).String", Method, 0}, + {"(Dylib).Raw", Method, 0}, + {"(Dysymtab).Raw", Method, 0}, + {"(FatArch).Close", Method, 3}, + {"(FatArch).DWARF", Method, 3}, + {"(FatArch).ImportedLibraries", Method, 3}, + {"(FatArch).ImportedSymbols", Method, 3}, + {"(FatArch).Section", Method, 3}, + {"(FatArch).Segment", Method, 3}, + {"(LoadBytes).Raw", Method, 0}, + {"(LoadCmd).GoString", Method, 0}, + {"(LoadCmd).String", Method, 0}, + {"(RelocTypeARM).GoString", Method, 10}, + {"(RelocTypeARM).String", Method, 10}, + {"(RelocTypeARM64).GoString", Method, 10}, + {"(RelocTypeARM64).String", Method, 10}, + {"(RelocTypeGeneric).GoString", Method, 10}, + {"(RelocTypeGeneric).String", Method, 10}, + {"(RelocTypeX86_64).GoString", Method, 10}, + {"(RelocTypeX86_64).String", Method, 10}, + {"(Rpath).Raw", Method, 10}, + {"(Section).ReadAt", Method, 0}, + {"(Segment).Raw", Method, 0}, + {"(Segment).ReadAt", Method, 0}, + {"(Symtab).Raw", Method, 0}, + {"(Type).GoString", Method, 10}, + {"(Type).String", Method, 10}, + {"ARM64_RELOC_ADDEND", Const, 10}, + {"ARM64_RELOC_BRANCH26", Const, 10}, + {"ARM64_RELOC_GOT_LOAD_PAGE21", Const, 10}, + {"ARM64_RELOC_GOT_LOAD_PAGEOFF12", Const, 10}, + {"ARM64_RELOC_PAGE21", Const, 10}, + {"ARM64_RELOC_PAGEOFF12", Const, 10}, + {"ARM64_RELOC_POINTER_TO_GOT", Const, 10}, + {"ARM64_RELOC_SUBTRACTOR", Const, 10}, + {"ARM64_RELOC_TLVP_LOAD_PAGE21", Const, 10}, + {"ARM64_RELOC_TLVP_LOAD_PAGEOFF12", Const, 10}, + {"ARM64_RELOC_UNSIGNED", Const, 10}, + {"ARM_RELOC_BR24", Const, 10}, + {"ARM_RELOC_HALF", Const, 10}, + {"ARM_RELOC_HALF_SECTDIFF", Const, 10}, + {"ARM_RELOC_LOCAL_SECTDIFF", Const, 10}, + {"ARM_RELOC_PAIR", Const, 10}, + {"ARM_RELOC_PB_LA_PTR", Const, 10}, + {"ARM_RELOC_SECTDIFF", Const, 10}, + {"ARM_RELOC_VANILLA", Const, 10}, + {"ARM_THUMB_32BIT_BRANCH", Const, 10}, + {"ARM_THUMB_RELOC_BR22", Const, 10}, + {"Cpu", Type, 0}, + {"Cpu386", Const, 0}, + {"CpuAmd64", Const, 0}, + {"CpuArm", Const, 3}, + {"CpuArm64", Const, 11}, + {"CpuPpc", Const, 3}, + {"CpuPpc64", Const, 3}, + {"Dylib", Type, 0}, + {"Dylib.CompatVersion", Field, 0}, + {"Dylib.CurrentVersion", Field, 0}, + {"Dylib.LoadBytes", Field, 0}, + {"Dylib.Name", Field, 0}, + {"Dylib.Time", Field, 0}, + {"DylibCmd", Type, 0}, + {"DylibCmd.Cmd", Field, 0}, + {"DylibCmd.CompatVersion", Field, 0}, + {"DylibCmd.CurrentVersion", Field, 0}, + {"DylibCmd.Len", Field, 0}, + {"DylibCmd.Name", Field, 0}, + {"DylibCmd.Time", Field, 0}, + {"Dysymtab", Type, 0}, + {"Dysymtab.DysymtabCmd", Field, 0}, + {"Dysymtab.IndirectSyms", Field, 0}, + {"Dysymtab.LoadBytes", Field, 0}, + {"DysymtabCmd", Type, 0}, + {"DysymtabCmd.Cmd", Field, 0}, + {"DysymtabCmd.Extrefsymoff", Field, 0}, + {"DysymtabCmd.Extreloff", Field, 0}, + {"DysymtabCmd.Iextdefsym", Field, 0}, + {"DysymtabCmd.Ilocalsym", Field, 0}, + {"DysymtabCmd.Indirectsymoff", Field, 0}, + {"DysymtabCmd.Iundefsym", Field, 0}, + {"DysymtabCmd.Len", Field, 0}, + {"DysymtabCmd.Locreloff", Field, 0}, + {"DysymtabCmd.Modtaboff", Field, 0}, + {"DysymtabCmd.Nextdefsym", Field, 0}, + {"DysymtabCmd.Nextrefsyms", Field, 0}, + {"DysymtabCmd.Nextrel", Field, 0}, + {"DysymtabCmd.Nindirectsyms", Field, 0}, + {"DysymtabCmd.Nlocalsym", Field, 0}, + {"DysymtabCmd.Nlocrel", Field, 0}, + {"DysymtabCmd.Nmodtab", Field, 0}, + {"DysymtabCmd.Ntoc", Field, 0}, + {"DysymtabCmd.Nundefsym", Field, 0}, + {"DysymtabCmd.Tocoffset", Field, 0}, + {"ErrNotFat", Var, 3}, + {"FatArch", Type, 3}, + {"FatArch.FatArchHeader", Field, 3}, + {"FatArch.File", Field, 3}, + {"FatArchHeader", Type, 3}, + {"FatArchHeader.Align", Field, 3}, + {"FatArchHeader.Cpu", Field, 3}, + {"FatArchHeader.Offset", Field, 3}, + {"FatArchHeader.Size", Field, 3}, + {"FatArchHeader.SubCpu", Field, 3}, + {"FatFile", Type, 3}, + {"FatFile.Arches", Field, 3}, + {"FatFile.Magic", Field, 3}, + {"File", Type, 0}, + {"File.ByteOrder", Field, 0}, + {"File.Dysymtab", Field, 0}, + {"File.FileHeader", Field, 0}, + {"File.Loads", Field, 0}, + {"File.Sections", Field, 0}, + {"File.Symtab", Field, 0}, + {"FileHeader", Type, 0}, + {"FileHeader.Cmdsz", Field, 0}, + {"FileHeader.Cpu", Field, 0}, + {"FileHeader.Flags", Field, 0}, + {"FileHeader.Magic", Field, 0}, + {"FileHeader.Ncmd", Field, 0}, + {"FileHeader.SubCpu", Field, 0}, + {"FileHeader.Type", Field, 0}, + {"FlagAllModsBound", Const, 10}, + {"FlagAllowStackExecution", Const, 10}, + {"FlagAppExtensionSafe", Const, 10}, + {"FlagBindAtLoad", Const, 10}, + {"FlagBindsToWeak", Const, 10}, + {"FlagCanonical", Const, 10}, + {"FlagDeadStrippableDylib", Const, 10}, + {"FlagDyldLink", Const, 10}, + {"FlagForceFlat", Const, 10}, + {"FlagHasTLVDescriptors", Const, 10}, + {"FlagIncrLink", Const, 10}, + {"FlagLazyInit", Const, 10}, + {"FlagNoFixPrebinding", Const, 10}, + {"FlagNoHeapExecution", Const, 10}, + {"FlagNoMultiDefs", Const, 10}, + {"FlagNoReexportedDylibs", Const, 10}, + {"FlagNoUndefs", Const, 10}, + {"FlagPIE", Const, 10}, + {"FlagPrebindable", Const, 10}, + {"FlagPrebound", Const, 10}, + {"FlagRootSafe", Const, 10}, + {"FlagSetuidSafe", Const, 10}, + {"FlagSplitSegs", Const, 10}, + {"FlagSubsectionsViaSymbols", Const, 10}, + {"FlagTwoLevel", Const, 10}, + {"FlagWeakDefines", Const, 10}, + {"FormatError", Type, 0}, + {"GENERIC_RELOC_LOCAL_SECTDIFF", Const, 10}, + {"GENERIC_RELOC_PAIR", Const, 10}, + {"GENERIC_RELOC_PB_LA_PTR", Const, 10}, + {"GENERIC_RELOC_SECTDIFF", Const, 10}, + {"GENERIC_RELOC_TLV", Const, 10}, + {"GENERIC_RELOC_VANILLA", Const, 10}, + {"Load", Type, 0}, + {"LoadBytes", Type, 0}, + {"LoadCmd", Type, 0}, + {"LoadCmdDylib", Const, 0}, + {"LoadCmdDylinker", Const, 0}, + {"LoadCmdDysymtab", Const, 0}, + {"LoadCmdRpath", Const, 10}, + {"LoadCmdSegment", Const, 0}, + {"LoadCmdSegment64", Const, 0}, + {"LoadCmdSymtab", Const, 0}, + {"LoadCmdThread", Const, 0}, + {"LoadCmdUnixThread", Const, 0}, + {"Magic32", Const, 0}, + {"Magic64", Const, 0}, + {"MagicFat", Const, 3}, + {"NewFatFile", Func, 3}, + {"NewFile", Func, 0}, + {"Nlist32", Type, 0}, + {"Nlist32.Desc", Field, 0}, + {"Nlist32.Name", Field, 0}, + {"Nlist32.Sect", Field, 0}, + {"Nlist32.Type", Field, 0}, + {"Nlist32.Value", Field, 0}, + {"Nlist64", Type, 0}, + {"Nlist64.Desc", Field, 0}, + {"Nlist64.Name", Field, 0}, + {"Nlist64.Sect", Field, 0}, + {"Nlist64.Type", Field, 0}, + {"Nlist64.Value", Field, 0}, + {"Open", Func, 0}, + {"OpenFat", Func, 3}, + {"Regs386", Type, 0}, + {"Regs386.AX", Field, 0}, + {"Regs386.BP", Field, 0}, + {"Regs386.BX", Field, 0}, + {"Regs386.CS", Field, 0}, + {"Regs386.CX", Field, 0}, + {"Regs386.DI", Field, 0}, + {"Regs386.DS", Field, 0}, + {"Regs386.DX", Field, 0}, + {"Regs386.ES", Field, 0}, + {"Regs386.FLAGS", Field, 0}, + {"Regs386.FS", Field, 0}, + {"Regs386.GS", Field, 0}, + {"Regs386.IP", Field, 0}, + {"Regs386.SI", Field, 0}, + {"Regs386.SP", Field, 0}, + {"Regs386.SS", Field, 0}, + {"RegsAMD64", Type, 0}, + {"RegsAMD64.AX", Field, 0}, + {"RegsAMD64.BP", Field, 0}, + {"RegsAMD64.BX", Field, 0}, + {"RegsAMD64.CS", Field, 0}, + {"RegsAMD64.CX", Field, 0}, + {"RegsAMD64.DI", Field, 0}, + {"RegsAMD64.DX", Field, 0}, + {"RegsAMD64.FLAGS", Field, 0}, + {"RegsAMD64.FS", Field, 0}, + {"RegsAMD64.GS", Field, 0}, + {"RegsAMD64.IP", Field, 0}, + {"RegsAMD64.R10", Field, 0}, + {"RegsAMD64.R11", Field, 0}, + {"RegsAMD64.R12", Field, 0}, + {"RegsAMD64.R13", Field, 0}, + {"RegsAMD64.R14", Field, 0}, + {"RegsAMD64.R15", Field, 0}, + {"RegsAMD64.R8", Field, 0}, + {"RegsAMD64.R9", Field, 0}, + {"RegsAMD64.SI", Field, 0}, + {"RegsAMD64.SP", Field, 0}, + {"Reloc", Type, 10}, + {"Reloc.Addr", Field, 10}, + {"Reloc.Extern", Field, 10}, + {"Reloc.Len", Field, 10}, + {"Reloc.Pcrel", Field, 10}, + {"Reloc.Scattered", Field, 10}, + {"Reloc.Type", Field, 10}, + {"Reloc.Value", Field, 10}, + {"RelocTypeARM", Type, 10}, + {"RelocTypeARM64", Type, 10}, + {"RelocTypeGeneric", Type, 10}, + {"RelocTypeX86_64", Type, 10}, + {"Rpath", Type, 10}, + {"Rpath.LoadBytes", Field, 10}, + {"Rpath.Path", Field, 10}, + {"RpathCmd", Type, 10}, + {"RpathCmd.Cmd", Field, 10}, + {"RpathCmd.Len", Field, 10}, + {"RpathCmd.Path", Field, 10}, + {"Section", Type, 0}, + {"Section.ReaderAt", Field, 0}, + {"Section.Relocs", Field, 10}, + {"Section.SectionHeader", Field, 0}, + {"Section32", Type, 0}, + {"Section32.Addr", Field, 0}, + {"Section32.Align", Field, 0}, + {"Section32.Flags", Field, 0}, + {"Section32.Name", Field, 0}, + {"Section32.Nreloc", Field, 0}, + {"Section32.Offset", Field, 0}, + {"Section32.Reloff", Field, 0}, + {"Section32.Reserve1", Field, 0}, + {"Section32.Reserve2", Field, 0}, + {"Section32.Seg", Field, 0}, + {"Section32.Size", Field, 0}, + {"Section64", Type, 0}, + {"Section64.Addr", Field, 0}, + {"Section64.Align", Field, 0}, + {"Section64.Flags", Field, 0}, + {"Section64.Name", Field, 0}, + {"Section64.Nreloc", Field, 0}, + {"Section64.Offset", Field, 0}, + {"Section64.Reloff", Field, 0}, + {"Section64.Reserve1", Field, 0}, + {"Section64.Reserve2", Field, 0}, + {"Section64.Reserve3", Field, 0}, + {"Section64.Seg", Field, 0}, + {"Section64.Size", Field, 0}, + {"SectionHeader", Type, 0}, + {"SectionHeader.Addr", Field, 0}, + {"SectionHeader.Align", Field, 0}, + {"SectionHeader.Flags", Field, 0}, + {"SectionHeader.Name", Field, 0}, + {"SectionHeader.Nreloc", Field, 0}, + {"SectionHeader.Offset", Field, 0}, + {"SectionHeader.Reloff", Field, 0}, + {"SectionHeader.Seg", Field, 0}, + {"SectionHeader.Size", Field, 0}, + {"Segment", Type, 0}, + {"Segment.LoadBytes", Field, 0}, + {"Segment.ReaderAt", Field, 0}, + {"Segment.SegmentHeader", Field, 0}, + {"Segment32", Type, 0}, + {"Segment32.Addr", Field, 0}, + {"Segment32.Cmd", Field, 0}, + {"Segment32.Filesz", Field, 0}, + {"Segment32.Flag", Field, 0}, + {"Segment32.Len", Field, 0}, + {"Segment32.Maxprot", Field, 0}, + {"Segment32.Memsz", Field, 0}, + {"Segment32.Name", Field, 0}, + {"Segment32.Nsect", Field, 0}, + {"Segment32.Offset", Field, 0}, + {"Segment32.Prot", Field, 0}, + {"Segment64", Type, 0}, + {"Segment64.Addr", Field, 0}, + {"Segment64.Cmd", Field, 0}, + {"Segment64.Filesz", Field, 0}, + {"Segment64.Flag", Field, 0}, + {"Segment64.Len", Field, 0}, + {"Segment64.Maxprot", Field, 0}, + {"Segment64.Memsz", Field, 0}, + {"Segment64.Name", Field, 0}, + {"Segment64.Nsect", Field, 0}, + {"Segment64.Offset", Field, 0}, + {"Segment64.Prot", Field, 0}, + {"SegmentHeader", Type, 0}, + {"SegmentHeader.Addr", Field, 0}, + {"SegmentHeader.Cmd", Field, 0}, + {"SegmentHeader.Filesz", Field, 0}, + {"SegmentHeader.Flag", Field, 0}, + {"SegmentHeader.Len", Field, 0}, + {"SegmentHeader.Maxprot", Field, 0}, + {"SegmentHeader.Memsz", Field, 0}, + {"SegmentHeader.Name", Field, 0}, + {"SegmentHeader.Nsect", Field, 0}, + {"SegmentHeader.Offset", Field, 0}, + {"SegmentHeader.Prot", Field, 0}, + {"Symbol", Type, 0}, + {"Symbol.Desc", Field, 0}, + {"Symbol.Name", Field, 0}, + {"Symbol.Sect", Field, 0}, + {"Symbol.Type", Field, 0}, + {"Symbol.Value", Field, 0}, + {"Symtab", Type, 0}, + {"Symtab.LoadBytes", Field, 0}, + {"Symtab.Syms", Field, 0}, + {"Symtab.SymtabCmd", Field, 0}, + {"SymtabCmd", Type, 0}, + {"SymtabCmd.Cmd", Field, 0}, + {"SymtabCmd.Len", Field, 0}, + {"SymtabCmd.Nsyms", Field, 0}, + {"SymtabCmd.Stroff", Field, 0}, + {"SymtabCmd.Strsize", Field, 0}, + {"SymtabCmd.Symoff", Field, 0}, + {"Thread", Type, 0}, + {"Thread.Cmd", Field, 0}, + {"Thread.Data", Field, 0}, + {"Thread.Len", Field, 0}, + {"Thread.Type", Field, 0}, + {"Type", Type, 0}, + {"TypeBundle", Const, 3}, + {"TypeDylib", Const, 3}, + {"TypeExec", Const, 0}, + {"TypeObj", Const, 0}, + {"X86_64_RELOC_BRANCH", Const, 10}, + {"X86_64_RELOC_GOT", Const, 10}, + {"X86_64_RELOC_GOT_LOAD", Const, 10}, + {"X86_64_RELOC_SIGNED", Const, 10}, + {"X86_64_RELOC_SIGNED_1", Const, 10}, + {"X86_64_RELOC_SIGNED_2", Const, 10}, + {"X86_64_RELOC_SIGNED_4", Const, 10}, + {"X86_64_RELOC_SUBTRACTOR", Const, 10}, + {"X86_64_RELOC_TLV", Const, 10}, + {"X86_64_RELOC_UNSIGNED", Const, 10}, + }, + "debug/pe": { + {"(*COFFSymbol).FullName", Method, 8}, + {"(*File).COFFSymbolReadSectionDefAux", Method, 19}, + {"(*File).Close", Method, 0}, + {"(*File).DWARF", Method, 0}, + {"(*File).ImportedLibraries", Method, 0}, + {"(*File).ImportedSymbols", Method, 0}, + {"(*File).Section", Method, 0}, + {"(*FormatError).Error", Method, 0}, + {"(*Section).Data", Method, 0}, + {"(*Section).Open", Method, 0}, + {"(Section).ReadAt", Method, 0}, + {"(StringTable).String", Method, 8}, + {"COFFSymbol", Type, 1}, + {"COFFSymbol.Name", Field, 1}, + {"COFFSymbol.NumberOfAuxSymbols", Field, 1}, + {"COFFSymbol.SectionNumber", Field, 1}, + {"COFFSymbol.StorageClass", Field, 1}, + {"COFFSymbol.Type", Field, 1}, + {"COFFSymbol.Value", Field, 1}, + {"COFFSymbolAuxFormat5", Type, 19}, + {"COFFSymbolAuxFormat5.Checksum", Field, 19}, + {"COFFSymbolAuxFormat5.NumLineNumbers", Field, 19}, + {"COFFSymbolAuxFormat5.NumRelocs", Field, 19}, + {"COFFSymbolAuxFormat5.SecNum", Field, 19}, + {"COFFSymbolAuxFormat5.Selection", Field, 19}, + {"COFFSymbolAuxFormat5.Size", Field, 19}, + {"COFFSymbolSize", Const, 1}, + {"DataDirectory", Type, 3}, + {"DataDirectory.Size", Field, 3}, + {"DataDirectory.VirtualAddress", Field, 3}, + {"File", Type, 0}, + {"File.COFFSymbols", Field, 8}, + {"File.FileHeader", Field, 0}, + {"File.OptionalHeader", Field, 3}, + {"File.Sections", Field, 0}, + {"File.StringTable", Field, 8}, + {"File.Symbols", Field, 1}, + {"FileHeader", Type, 0}, + {"FileHeader.Characteristics", Field, 0}, + {"FileHeader.Machine", Field, 0}, + {"FileHeader.NumberOfSections", Field, 0}, + {"FileHeader.NumberOfSymbols", Field, 0}, + {"FileHeader.PointerToSymbolTable", Field, 0}, + {"FileHeader.SizeOfOptionalHeader", Field, 0}, + {"FileHeader.TimeDateStamp", Field, 0}, + {"FormatError", Type, 0}, + {"IMAGE_COMDAT_SELECT_ANY", Const, 19}, + {"IMAGE_COMDAT_SELECT_ASSOCIATIVE", Const, 19}, + {"IMAGE_COMDAT_SELECT_EXACT_MATCH", Const, 19}, + {"IMAGE_COMDAT_SELECT_LARGEST", Const, 19}, + {"IMAGE_COMDAT_SELECT_NODUPLICATES", Const, 19}, + {"IMAGE_COMDAT_SELECT_SAME_SIZE", Const, 19}, + {"IMAGE_DIRECTORY_ENTRY_ARCHITECTURE", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_BASERELOC", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_DEBUG", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_EXCEPTION", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_EXPORT", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_GLOBALPTR", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_IAT", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_IMPORT", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_RESOURCE", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_SECURITY", Const, 11}, + {"IMAGE_DIRECTORY_ENTRY_TLS", Const, 11}, + {"IMAGE_DLLCHARACTERISTICS_APPCONTAINER", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_GUARD_CF", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_NO_BIND", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_NO_ISOLATION", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_NO_SEH", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_NX_COMPAT", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE", Const, 15}, + {"IMAGE_DLLCHARACTERISTICS_WDM_DRIVER", Const, 15}, + {"IMAGE_FILE_32BIT_MACHINE", Const, 15}, + {"IMAGE_FILE_AGGRESIVE_WS_TRIM", Const, 15}, + {"IMAGE_FILE_BYTES_REVERSED_HI", Const, 15}, + {"IMAGE_FILE_BYTES_REVERSED_LO", Const, 15}, + {"IMAGE_FILE_DEBUG_STRIPPED", Const, 15}, + {"IMAGE_FILE_DLL", Const, 15}, + {"IMAGE_FILE_EXECUTABLE_IMAGE", Const, 15}, + {"IMAGE_FILE_LARGE_ADDRESS_AWARE", Const, 15}, + {"IMAGE_FILE_LINE_NUMS_STRIPPED", Const, 15}, + {"IMAGE_FILE_LOCAL_SYMS_STRIPPED", Const, 15}, + {"IMAGE_FILE_MACHINE_AM33", Const, 0}, + {"IMAGE_FILE_MACHINE_AMD64", Const, 0}, + {"IMAGE_FILE_MACHINE_ARM", Const, 0}, + {"IMAGE_FILE_MACHINE_ARM64", Const, 11}, + {"IMAGE_FILE_MACHINE_ARMNT", Const, 12}, + {"IMAGE_FILE_MACHINE_EBC", Const, 0}, + {"IMAGE_FILE_MACHINE_I386", Const, 0}, + {"IMAGE_FILE_MACHINE_IA64", Const, 0}, + {"IMAGE_FILE_MACHINE_LOONGARCH32", Const, 19}, + {"IMAGE_FILE_MACHINE_LOONGARCH64", Const, 19}, + {"IMAGE_FILE_MACHINE_M32R", Const, 0}, + {"IMAGE_FILE_MACHINE_MIPS16", Const, 0}, + {"IMAGE_FILE_MACHINE_MIPSFPU", Const, 0}, + {"IMAGE_FILE_MACHINE_MIPSFPU16", Const, 0}, + {"IMAGE_FILE_MACHINE_POWERPC", Const, 0}, + {"IMAGE_FILE_MACHINE_POWERPCFP", Const, 0}, + {"IMAGE_FILE_MACHINE_R4000", Const, 0}, + {"IMAGE_FILE_MACHINE_RISCV128", Const, 20}, + {"IMAGE_FILE_MACHINE_RISCV32", Const, 20}, + {"IMAGE_FILE_MACHINE_RISCV64", Const, 20}, + {"IMAGE_FILE_MACHINE_SH3", Const, 0}, + {"IMAGE_FILE_MACHINE_SH3DSP", Const, 0}, + {"IMAGE_FILE_MACHINE_SH4", Const, 0}, + {"IMAGE_FILE_MACHINE_SH5", Const, 0}, + {"IMAGE_FILE_MACHINE_THUMB", Const, 0}, + {"IMAGE_FILE_MACHINE_UNKNOWN", Const, 0}, + {"IMAGE_FILE_MACHINE_WCEMIPSV2", Const, 0}, + {"IMAGE_FILE_NET_RUN_FROM_SWAP", Const, 15}, + {"IMAGE_FILE_RELOCS_STRIPPED", Const, 15}, + {"IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP", Const, 15}, + {"IMAGE_FILE_SYSTEM", Const, 15}, + {"IMAGE_FILE_UP_SYSTEM_ONLY", Const, 15}, + {"IMAGE_SCN_CNT_CODE", Const, 19}, + {"IMAGE_SCN_CNT_INITIALIZED_DATA", Const, 19}, + {"IMAGE_SCN_CNT_UNINITIALIZED_DATA", Const, 19}, + {"IMAGE_SCN_LNK_COMDAT", Const, 19}, + {"IMAGE_SCN_MEM_DISCARDABLE", Const, 19}, + {"IMAGE_SCN_MEM_EXECUTE", Const, 19}, + {"IMAGE_SCN_MEM_READ", Const, 19}, + {"IMAGE_SCN_MEM_WRITE", Const, 19}, + {"IMAGE_SUBSYSTEM_EFI_APPLICATION", Const, 15}, + {"IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", Const, 15}, + {"IMAGE_SUBSYSTEM_EFI_ROM", Const, 15}, + {"IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER", Const, 15}, + {"IMAGE_SUBSYSTEM_NATIVE", Const, 15}, + {"IMAGE_SUBSYSTEM_NATIVE_WINDOWS", Const, 15}, + {"IMAGE_SUBSYSTEM_OS2_CUI", Const, 15}, + {"IMAGE_SUBSYSTEM_POSIX_CUI", Const, 15}, + {"IMAGE_SUBSYSTEM_UNKNOWN", Const, 15}, + {"IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION", Const, 15}, + {"IMAGE_SUBSYSTEM_WINDOWS_CE_GUI", Const, 15}, + {"IMAGE_SUBSYSTEM_WINDOWS_CUI", Const, 15}, + {"IMAGE_SUBSYSTEM_WINDOWS_GUI", Const, 15}, + {"IMAGE_SUBSYSTEM_XBOX", Const, 15}, + {"ImportDirectory", Type, 0}, + {"ImportDirectory.FirstThunk", Field, 0}, + {"ImportDirectory.ForwarderChain", Field, 0}, + {"ImportDirectory.Name", Field, 0}, + {"ImportDirectory.OriginalFirstThunk", Field, 0}, + {"ImportDirectory.TimeDateStamp", Field, 0}, + {"NewFile", Func, 0}, + {"Open", Func, 0}, + {"OptionalHeader32", Type, 3}, + {"OptionalHeader32.AddressOfEntryPoint", Field, 3}, + {"OptionalHeader32.BaseOfCode", Field, 3}, + {"OptionalHeader32.BaseOfData", Field, 3}, + {"OptionalHeader32.CheckSum", Field, 3}, + {"OptionalHeader32.DataDirectory", Field, 3}, + {"OptionalHeader32.DllCharacteristics", Field, 3}, + {"OptionalHeader32.FileAlignment", Field, 3}, + {"OptionalHeader32.ImageBase", Field, 3}, + {"OptionalHeader32.LoaderFlags", Field, 3}, + {"OptionalHeader32.Magic", Field, 3}, + {"OptionalHeader32.MajorImageVersion", Field, 3}, + {"OptionalHeader32.MajorLinkerVersion", Field, 3}, + {"OptionalHeader32.MajorOperatingSystemVersion", Field, 3}, + {"OptionalHeader32.MajorSubsystemVersion", Field, 3}, + {"OptionalHeader32.MinorImageVersion", Field, 3}, + {"OptionalHeader32.MinorLinkerVersion", Field, 3}, + {"OptionalHeader32.MinorOperatingSystemVersion", Field, 3}, + {"OptionalHeader32.MinorSubsystemVersion", Field, 3}, + {"OptionalHeader32.NumberOfRvaAndSizes", Field, 3}, + {"OptionalHeader32.SectionAlignment", Field, 3}, + {"OptionalHeader32.SizeOfCode", Field, 3}, + {"OptionalHeader32.SizeOfHeaders", Field, 3}, + {"OptionalHeader32.SizeOfHeapCommit", Field, 3}, + {"OptionalHeader32.SizeOfHeapReserve", Field, 3}, + {"OptionalHeader32.SizeOfImage", Field, 3}, + {"OptionalHeader32.SizeOfInitializedData", Field, 3}, + {"OptionalHeader32.SizeOfStackCommit", Field, 3}, + {"OptionalHeader32.SizeOfStackReserve", Field, 3}, + {"OptionalHeader32.SizeOfUninitializedData", Field, 3}, + {"OptionalHeader32.Subsystem", Field, 3}, + {"OptionalHeader32.Win32VersionValue", Field, 3}, + {"OptionalHeader64", Type, 3}, + {"OptionalHeader64.AddressOfEntryPoint", Field, 3}, + {"OptionalHeader64.BaseOfCode", Field, 3}, + {"OptionalHeader64.CheckSum", Field, 3}, + {"OptionalHeader64.DataDirectory", Field, 3}, + {"OptionalHeader64.DllCharacteristics", Field, 3}, + {"OptionalHeader64.FileAlignment", Field, 3}, + {"OptionalHeader64.ImageBase", Field, 3}, + {"OptionalHeader64.LoaderFlags", Field, 3}, + {"OptionalHeader64.Magic", Field, 3}, + {"OptionalHeader64.MajorImageVersion", Field, 3}, + {"OptionalHeader64.MajorLinkerVersion", Field, 3}, + {"OptionalHeader64.MajorOperatingSystemVersion", Field, 3}, + {"OptionalHeader64.MajorSubsystemVersion", Field, 3}, + {"OptionalHeader64.MinorImageVersion", Field, 3}, + {"OptionalHeader64.MinorLinkerVersion", Field, 3}, + {"OptionalHeader64.MinorOperatingSystemVersion", Field, 3}, + {"OptionalHeader64.MinorSubsystemVersion", Field, 3}, + {"OptionalHeader64.NumberOfRvaAndSizes", Field, 3}, + {"OptionalHeader64.SectionAlignment", Field, 3}, + {"OptionalHeader64.SizeOfCode", Field, 3}, + {"OptionalHeader64.SizeOfHeaders", Field, 3}, + {"OptionalHeader64.SizeOfHeapCommit", Field, 3}, + {"OptionalHeader64.SizeOfHeapReserve", Field, 3}, + {"OptionalHeader64.SizeOfImage", Field, 3}, + {"OptionalHeader64.SizeOfInitializedData", Field, 3}, + {"OptionalHeader64.SizeOfStackCommit", Field, 3}, + {"OptionalHeader64.SizeOfStackReserve", Field, 3}, + {"OptionalHeader64.SizeOfUninitializedData", Field, 3}, + {"OptionalHeader64.Subsystem", Field, 3}, + {"OptionalHeader64.Win32VersionValue", Field, 3}, + {"Reloc", Type, 8}, + {"Reloc.SymbolTableIndex", Field, 8}, + {"Reloc.Type", Field, 8}, + {"Reloc.VirtualAddress", Field, 8}, + {"Section", Type, 0}, + {"Section.ReaderAt", Field, 0}, + {"Section.Relocs", Field, 8}, + {"Section.SectionHeader", Field, 0}, + {"SectionHeader", Type, 0}, + {"SectionHeader.Characteristics", Field, 0}, + {"SectionHeader.Name", Field, 0}, + {"SectionHeader.NumberOfLineNumbers", Field, 0}, + {"SectionHeader.NumberOfRelocations", Field, 0}, + {"SectionHeader.Offset", Field, 0}, + {"SectionHeader.PointerToLineNumbers", Field, 0}, + {"SectionHeader.PointerToRelocations", Field, 0}, + {"SectionHeader.Size", Field, 0}, + {"SectionHeader.VirtualAddress", Field, 0}, + {"SectionHeader.VirtualSize", Field, 0}, + {"SectionHeader32", Type, 0}, + {"SectionHeader32.Characteristics", Field, 0}, + {"SectionHeader32.Name", Field, 0}, + {"SectionHeader32.NumberOfLineNumbers", Field, 0}, + {"SectionHeader32.NumberOfRelocations", Field, 0}, + {"SectionHeader32.PointerToLineNumbers", Field, 0}, + {"SectionHeader32.PointerToRawData", Field, 0}, + {"SectionHeader32.PointerToRelocations", Field, 0}, + {"SectionHeader32.SizeOfRawData", Field, 0}, + {"SectionHeader32.VirtualAddress", Field, 0}, + {"SectionHeader32.VirtualSize", Field, 0}, + {"StringTable", Type, 8}, + {"Symbol", Type, 1}, + {"Symbol.Name", Field, 1}, + {"Symbol.SectionNumber", Field, 1}, + {"Symbol.StorageClass", Field, 1}, + {"Symbol.Type", Field, 1}, + {"Symbol.Value", Field, 1}, + }, + "debug/plan9obj": { + {"(*File).Close", Method, 3}, + {"(*File).Section", Method, 3}, + {"(*File).Symbols", Method, 3}, + {"(*Section).Data", Method, 3}, + {"(*Section).Open", Method, 3}, + {"(Section).ReadAt", Method, 3}, + {"ErrNoSymbols", Var, 18}, + {"File", Type, 3}, + {"File.FileHeader", Field, 3}, + {"File.Sections", Field, 3}, + {"FileHeader", Type, 3}, + {"FileHeader.Bss", Field, 3}, + {"FileHeader.Entry", Field, 3}, + {"FileHeader.HdrSize", Field, 4}, + {"FileHeader.LoadAddress", Field, 4}, + {"FileHeader.Magic", Field, 3}, + {"FileHeader.PtrSize", Field, 3}, + {"Magic386", Const, 3}, + {"Magic64", Const, 3}, + {"MagicAMD64", Const, 3}, + {"MagicARM", Const, 3}, + {"NewFile", Func, 3}, + {"Open", Func, 3}, + {"Section", Type, 3}, + {"Section.ReaderAt", Field, 3}, + {"Section.SectionHeader", Field, 3}, + {"SectionHeader", Type, 3}, + {"SectionHeader.Name", Field, 3}, + {"SectionHeader.Offset", Field, 3}, + {"SectionHeader.Size", Field, 3}, + {"Sym", Type, 3}, + {"Sym.Name", Field, 3}, + {"Sym.Type", Field, 3}, + {"Sym.Value", Field, 3}, + }, + "embed": { + {"(FS).Open", Method, 16}, + {"(FS).ReadDir", Method, 16}, + {"(FS).ReadFile", Method, 16}, + {"FS", Type, 16}, + }, + "encoding": { + {"BinaryMarshaler", Type, 2}, + {"BinaryUnmarshaler", Type, 2}, + {"TextMarshaler", Type, 2}, + {"TextUnmarshaler", Type, 2}, + }, + "encoding/ascii85": { + {"(CorruptInputError).Error", Method, 0}, + {"CorruptInputError", Type, 0}, + {"Decode", Func, 0}, + {"Encode", Func, 0}, + {"MaxEncodedLen", Func, 0}, + {"NewDecoder", Func, 0}, + {"NewEncoder", Func, 0}, + }, + "encoding/asn1": { + {"(BitString).At", Method, 0}, + {"(BitString).RightAlign", Method, 0}, + {"(ObjectIdentifier).Equal", Method, 0}, + {"(ObjectIdentifier).String", Method, 3}, + {"(StructuralError).Error", Method, 0}, + {"(SyntaxError).Error", Method, 0}, + {"BitString", Type, 0}, + {"BitString.BitLength", Field, 0}, + {"BitString.Bytes", Field, 0}, + {"ClassApplication", Const, 6}, + {"ClassContextSpecific", Const, 6}, + {"ClassPrivate", Const, 6}, + {"ClassUniversal", Const, 6}, + {"Enumerated", Type, 0}, + {"Flag", Type, 0}, + {"Marshal", Func, 0}, + {"MarshalWithParams", Func, 10}, + {"NullBytes", Var, 9}, + {"NullRawValue", Var, 9}, + {"ObjectIdentifier", Type, 0}, + {"RawContent", Type, 0}, + {"RawValue", Type, 0}, + {"RawValue.Bytes", Field, 0}, + {"RawValue.Class", Field, 0}, + {"RawValue.FullBytes", Field, 0}, + {"RawValue.IsCompound", Field, 0}, + {"RawValue.Tag", Field, 0}, + {"StructuralError", Type, 0}, + {"StructuralError.Msg", Field, 0}, + {"SyntaxError", Type, 0}, + {"SyntaxError.Msg", Field, 0}, + {"TagBMPString", Const, 14}, + {"TagBitString", Const, 6}, + {"TagBoolean", Const, 6}, + {"TagEnum", Const, 6}, + {"TagGeneralString", Const, 6}, + {"TagGeneralizedTime", Const, 6}, + {"TagIA5String", Const, 6}, + {"TagInteger", Const, 6}, + {"TagNull", Const, 9}, + {"TagNumericString", Const, 10}, + {"TagOID", Const, 6}, + {"TagOctetString", Const, 6}, + {"TagPrintableString", Const, 6}, + {"TagSequence", Const, 6}, + {"TagSet", Const, 6}, + {"TagT61String", Const, 6}, + {"TagUTCTime", Const, 6}, + {"TagUTF8String", Const, 6}, + {"Unmarshal", Func, 0}, + {"UnmarshalWithParams", Func, 0}, + }, + "encoding/base32": { + {"(*Encoding).AppendDecode", Method, 22}, + {"(*Encoding).AppendEncode", Method, 22}, + {"(*Encoding).Decode", Method, 0}, + {"(*Encoding).DecodeString", Method, 0}, + {"(*Encoding).DecodedLen", Method, 0}, + {"(*Encoding).Encode", Method, 0}, + {"(*Encoding).EncodeToString", Method, 0}, + {"(*Encoding).EncodedLen", Method, 0}, + {"(CorruptInputError).Error", Method, 0}, + {"(Encoding).WithPadding", Method, 9}, + {"CorruptInputError", Type, 0}, + {"Encoding", Type, 0}, + {"HexEncoding", Var, 0}, + {"NewDecoder", Func, 0}, + {"NewEncoder", Func, 0}, + {"NewEncoding", Func, 0}, + {"NoPadding", Const, 9}, + {"StdEncoding", Var, 0}, + {"StdPadding", Const, 9}, + }, + "encoding/base64": { + {"(*Encoding).AppendDecode", Method, 22}, + {"(*Encoding).AppendEncode", Method, 22}, + {"(*Encoding).Decode", Method, 0}, + {"(*Encoding).DecodeString", Method, 0}, + {"(*Encoding).DecodedLen", Method, 0}, + {"(*Encoding).Encode", Method, 0}, + {"(*Encoding).EncodeToString", Method, 0}, + {"(*Encoding).EncodedLen", Method, 0}, + {"(CorruptInputError).Error", Method, 0}, + {"(Encoding).Strict", Method, 8}, + {"(Encoding).WithPadding", Method, 5}, + {"CorruptInputError", Type, 0}, + {"Encoding", Type, 0}, + {"NewDecoder", Func, 0}, + {"NewEncoder", Func, 0}, + {"NewEncoding", Func, 0}, + {"NoPadding", Const, 5}, + {"RawStdEncoding", Var, 5}, + {"RawURLEncoding", Var, 5}, + {"StdEncoding", Var, 0}, + {"StdPadding", Const, 5}, + {"URLEncoding", Var, 0}, + }, + "encoding/binary": { + {"Append", Func, 23}, + {"AppendByteOrder", Type, 19}, + {"AppendUvarint", Func, 19}, + {"AppendVarint", Func, 19}, + {"BigEndian", Var, 0}, + {"ByteOrder", Type, 0}, + {"Decode", Func, 23}, + {"Encode", Func, 23}, + {"LittleEndian", Var, 0}, + {"MaxVarintLen16", Const, 0}, + {"MaxVarintLen32", Const, 0}, + {"MaxVarintLen64", Const, 0}, + {"NativeEndian", Var, 21}, + {"PutUvarint", Func, 0}, + {"PutVarint", Func, 0}, + {"Read", Func, 0}, + {"ReadUvarint", Func, 0}, + {"ReadVarint", Func, 0}, + {"Size", Func, 0}, + {"Uvarint", Func, 0}, + {"Varint", Func, 0}, + {"Write", Func, 0}, + }, + "encoding/csv": { + {"(*ParseError).Error", Method, 0}, + {"(*ParseError).Unwrap", Method, 13}, + {"(*Reader).FieldPos", Method, 17}, + {"(*Reader).InputOffset", Method, 19}, + {"(*Reader).Read", Method, 0}, + {"(*Reader).ReadAll", Method, 0}, + {"(*Writer).Error", Method, 1}, + {"(*Writer).Flush", Method, 0}, + {"(*Writer).Write", Method, 0}, + {"(*Writer).WriteAll", Method, 0}, + {"ErrBareQuote", Var, 0}, + {"ErrFieldCount", Var, 0}, + {"ErrQuote", Var, 0}, + {"ErrTrailingComma", Var, 0}, + {"NewReader", Func, 0}, + {"NewWriter", Func, 0}, + {"ParseError", Type, 0}, + {"ParseError.Column", Field, 0}, + {"ParseError.Err", Field, 0}, + {"ParseError.Line", Field, 0}, + {"ParseError.StartLine", Field, 10}, + {"Reader", Type, 0}, + {"Reader.Comma", Field, 0}, + {"Reader.Comment", Field, 0}, + {"Reader.FieldsPerRecord", Field, 0}, + {"Reader.LazyQuotes", Field, 0}, + {"Reader.ReuseRecord", Field, 9}, + {"Reader.TrailingComma", Field, 0}, + {"Reader.TrimLeadingSpace", Field, 0}, + {"Writer", Type, 0}, + {"Writer.Comma", Field, 0}, + {"Writer.UseCRLF", Field, 0}, + }, + "encoding/gob": { + {"(*Decoder).Decode", Method, 0}, + {"(*Decoder).DecodeValue", Method, 0}, + {"(*Encoder).Encode", Method, 0}, + {"(*Encoder).EncodeValue", Method, 0}, + {"CommonType", Type, 0}, + {"CommonType.Id", Field, 0}, + {"CommonType.Name", Field, 0}, + {"Decoder", Type, 0}, + {"Encoder", Type, 0}, + {"GobDecoder", Type, 0}, + {"GobEncoder", Type, 0}, + {"NewDecoder", Func, 0}, + {"NewEncoder", Func, 0}, + {"Register", Func, 0}, + {"RegisterName", Func, 0}, + }, + "encoding/hex": { + {"(InvalidByteError).Error", Method, 0}, + {"AppendDecode", Func, 22}, + {"AppendEncode", Func, 22}, + {"Decode", Func, 0}, + {"DecodeString", Func, 0}, + {"DecodedLen", Func, 0}, + {"Dump", Func, 0}, + {"Dumper", Func, 0}, + {"Encode", Func, 0}, + {"EncodeToString", Func, 0}, + {"EncodedLen", Func, 0}, + {"ErrLength", Var, 0}, + {"InvalidByteError", Type, 0}, + {"NewDecoder", Func, 10}, + {"NewEncoder", Func, 10}, + }, + "encoding/json": { + {"(*Decoder).Buffered", Method, 1}, + {"(*Decoder).Decode", Method, 0}, + {"(*Decoder).DisallowUnknownFields", Method, 10}, + {"(*Decoder).InputOffset", Method, 14}, + {"(*Decoder).More", Method, 5}, + {"(*Decoder).Token", Method, 5}, + {"(*Decoder).UseNumber", Method, 1}, + {"(*Encoder).Encode", Method, 0}, + {"(*Encoder).SetEscapeHTML", Method, 7}, + {"(*Encoder).SetIndent", Method, 7}, + {"(*InvalidUTF8Error).Error", Method, 0}, + {"(*InvalidUnmarshalError).Error", Method, 0}, + {"(*MarshalerError).Error", Method, 0}, + {"(*MarshalerError).Unwrap", Method, 13}, + {"(*RawMessage).MarshalJSON", Method, 0}, + {"(*RawMessage).UnmarshalJSON", Method, 0}, + {"(*SyntaxError).Error", Method, 0}, + {"(*UnmarshalFieldError).Error", Method, 0}, + {"(*UnmarshalTypeError).Error", Method, 0}, + {"(*UnsupportedTypeError).Error", Method, 0}, + {"(*UnsupportedValueError).Error", Method, 0}, + {"(Delim).String", Method, 5}, + {"(Number).Float64", Method, 1}, + {"(Number).Int64", Method, 1}, + {"(Number).String", Method, 1}, + {"(RawMessage).MarshalJSON", Method, 8}, + {"Compact", Func, 0}, + {"Decoder", Type, 0}, + {"Delim", Type, 5}, + {"Encoder", Type, 0}, + {"HTMLEscape", Func, 0}, + {"Indent", Func, 0}, + {"InvalidUTF8Error", Type, 0}, + {"InvalidUTF8Error.S", Field, 0}, + {"InvalidUnmarshalError", Type, 0}, + {"InvalidUnmarshalError.Type", Field, 0}, + {"Marshal", Func, 0}, + {"MarshalIndent", Func, 0}, + {"Marshaler", Type, 0}, + {"MarshalerError", Type, 0}, + {"MarshalerError.Err", Field, 0}, + {"MarshalerError.Type", Field, 0}, + {"NewDecoder", Func, 0}, + {"NewEncoder", Func, 0}, + {"Number", Type, 1}, + {"RawMessage", Type, 0}, + {"SyntaxError", Type, 0}, + {"SyntaxError.Offset", Field, 0}, + {"Token", Type, 5}, + {"Unmarshal", Func, 0}, + {"UnmarshalFieldError", Type, 0}, + {"UnmarshalFieldError.Field", Field, 0}, + {"UnmarshalFieldError.Key", Field, 0}, + {"UnmarshalFieldError.Type", Field, 0}, + {"UnmarshalTypeError", Type, 0}, + {"UnmarshalTypeError.Field", Field, 8}, + {"UnmarshalTypeError.Offset", Field, 5}, + {"UnmarshalTypeError.Struct", Field, 8}, + {"UnmarshalTypeError.Type", Field, 0}, + {"UnmarshalTypeError.Value", Field, 0}, + {"Unmarshaler", Type, 0}, + {"UnsupportedTypeError", Type, 0}, + {"UnsupportedTypeError.Type", Field, 0}, + {"UnsupportedValueError", Type, 0}, + {"UnsupportedValueError.Str", Field, 0}, + {"UnsupportedValueError.Value", Field, 0}, + {"Valid", Func, 9}, + }, + "encoding/pem": { + {"Block", Type, 0}, + {"Block.Bytes", Field, 0}, + {"Block.Headers", Field, 0}, + {"Block.Type", Field, 0}, + {"Decode", Func, 0}, + {"Encode", Func, 0}, + {"EncodeToMemory", Func, 0}, + }, + "encoding/xml": { + {"(*Decoder).Decode", Method, 0}, + {"(*Decoder).DecodeElement", Method, 0}, + {"(*Decoder).InputOffset", Method, 4}, + {"(*Decoder).InputPos", Method, 19}, + {"(*Decoder).RawToken", Method, 0}, + {"(*Decoder).Skip", Method, 0}, + {"(*Decoder).Token", Method, 0}, + {"(*Encoder).Close", Method, 20}, + {"(*Encoder).Encode", Method, 0}, + {"(*Encoder).EncodeElement", Method, 2}, + {"(*Encoder).EncodeToken", Method, 2}, + {"(*Encoder).Flush", Method, 2}, + {"(*Encoder).Indent", Method, 1}, + {"(*SyntaxError).Error", Method, 0}, + {"(*TagPathError).Error", Method, 0}, + {"(*UnsupportedTypeError).Error", Method, 0}, + {"(CharData).Copy", Method, 0}, + {"(Comment).Copy", Method, 0}, + {"(Directive).Copy", Method, 0}, + {"(ProcInst).Copy", Method, 0}, + {"(StartElement).Copy", Method, 0}, + {"(StartElement).End", Method, 2}, + {"(UnmarshalError).Error", Method, 0}, + {"Attr", Type, 0}, + {"Attr.Name", Field, 0}, + {"Attr.Value", Field, 0}, + {"CharData", Type, 0}, + {"Comment", Type, 0}, + {"CopyToken", Func, 0}, + {"Decoder", Type, 0}, + {"Decoder.AutoClose", Field, 0}, + {"Decoder.CharsetReader", Field, 0}, + {"Decoder.DefaultSpace", Field, 1}, + {"Decoder.Entity", Field, 0}, + {"Decoder.Strict", Field, 0}, + {"Directive", Type, 0}, + {"Encoder", Type, 0}, + {"EndElement", Type, 0}, + {"EndElement.Name", Field, 0}, + {"Escape", Func, 0}, + {"EscapeText", Func, 1}, + {"HTMLAutoClose", Var, 0}, + {"HTMLEntity", Var, 0}, + {"Header", Const, 0}, + {"Marshal", Func, 0}, + {"MarshalIndent", Func, 0}, + {"Marshaler", Type, 2}, + {"MarshalerAttr", Type, 2}, + {"Name", Type, 0}, + {"Name.Local", Field, 0}, + {"Name.Space", Field, 0}, + {"NewDecoder", Func, 0}, + {"NewEncoder", Func, 0}, + {"NewTokenDecoder", Func, 10}, + {"ProcInst", Type, 0}, + {"ProcInst.Inst", Field, 0}, + {"ProcInst.Target", Field, 0}, + {"StartElement", Type, 0}, + {"StartElement.Attr", Field, 0}, + {"StartElement.Name", Field, 0}, + {"SyntaxError", Type, 0}, + {"SyntaxError.Line", Field, 0}, + {"SyntaxError.Msg", Field, 0}, + {"TagPathError", Type, 0}, + {"TagPathError.Field1", Field, 0}, + {"TagPathError.Field2", Field, 0}, + {"TagPathError.Struct", Field, 0}, + {"TagPathError.Tag1", Field, 0}, + {"TagPathError.Tag2", Field, 0}, + {"Token", Type, 0}, + {"TokenReader", Type, 10}, + {"Unmarshal", Func, 0}, + {"UnmarshalError", Type, 0}, + {"Unmarshaler", Type, 2}, + {"UnmarshalerAttr", Type, 2}, + {"UnsupportedTypeError", Type, 0}, + {"UnsupportedTypeError.Type", Field, 0}, + }, + "errors": { + {"As", Func, 13}, + {"ErrUnsupported", Var, 21}, + {"Is", Func, 13}, + {"Join", Func, 20}, + {"New", Func, 0}, + {"Unwrap", Func, 13}, + }, + "expvar": { + {"(*Float).Add", Method, 0}, + {"(*Float).Set", Method, 0}, + {"(*Float).String", Method, 0}, + {"(*Float).Value", Method, 8}, + {"(*Int).Add", Method, 0}, + {"(*Int).Set", Method, 0}, + {"(*Int).String", Method, 0}, + {"(*Int).Value", Method, 8}, + {"(*Map).Add", Method, 0}, + {"(*Map).AddFloat", Method, 0}, + {"(*Map).Delete", Method, 12}, + {"(*Map).Do", Method, 0}, + {"(*Map).Get", Method, 0}, + {"(*Map).Init", Method, 0}, + {"(*Map).Set", Method, 0}, + {"(*Map).String", Method, 0}, + {"(*String).Set", Method, 0}, + {"(*String).String", Method, 0}, + {"(*String).Value", Method, 8}, + {"(Func).String", Method, 0}, + {"(Func).Value", Method, 8}, + {"Do", Func, 0}, + {"Float", Type, 0}, + {"Func", Type, 0}, + {"Get", Func, 0}, + {"Handler", Func, 8}, + {"Int", Type, 0}, + {"KeyValue", Type, 0}, + {"KeyValue.Key", Field, 0}, + {"KeyValue.Value", Field, 0}, + {"Map", Type, 0}, + {"NewFloat", Func, 0}, + {"NewInt", Func, 0}, + {"NewMap", Func, 0}, + {"NewString", Func, 0}, + {"Publish", Func, 0}, + {"String", Type, 0}, + {"Var", Type, 0}, + }, + "flag": { + {"(*FlagSet).Arg", Method, 0}, + {"(*FlagSet).Args", Method, 0}, + {"(*FlagSet).Bool", Method, 0}, + {"(*FlagSet).BoolFunc", Method, 21}, + {"(*FlagSet).BoolVar", Method, 0}, + {"(*FlagSet).Duration", Method, 0}, + {"(*FlagSet).DurationVar", Method, 0}, + {"(*FlagSet).ErrorHandling", Method, 10}, + {"(*FlagSet).Float64", Method, 0}, + {"(*FlagSet).Float64Var", Method, 0}, + {"(*FlagSet).Func", Method, 16}, + {"(*FlagSet).Init", Method, 0}, + {"(*FlagSet).Int", Method, 0}, + {"(*FlagSet).Int64", Method, 0}, + {"(*FlagSet).Int64Var", Method, 0}, + {"(*FlagSet).IntVar", Method, 0}, + {"(*FlagSet).Lookup", Method, 0}, + {"(*FlagSet).NArg", Method, 0}, + {"(*FlagSet).NFlag", Method, 0}, + {"(*FlagSet).Name", Method, 10}, + {"(*FlagSet).Output", Method, 10}, + {"(*FlagSet).Parse", Method, 0}, + {"(*FlagSet).Parsed", Method, 0}, + {"(*FlagSet).PrintDefaults", Method, 0}, + {"(*FlagSet).Set", Method, 0}, + {"(*FlagSet).SetOutput", Method, 0}, + {"(*FlagSet).String", Method, 0}, + {"(*FlagSet).StringVar", Method, 0}, + {"(*FlagSet).TextVar", Method, 19}, + {"(*FlagSet).Uint", Method, 0}, + {"(*FlagSet).Uint64", Method, 0}, + {"(*FlagSet).Uint64Var", Method, 0}, + {"(*FlagSet).UintVar", Method, 0}, + {"(*FlagSet).Var", Method, 0}, + {"(*FlagSet).Visit", Method, 0}, + {"(*FlagSet).VisitAll", Method, 0}, + {"Arg", Func, 0}, + {"Args", Func, 0}, + {"Bool", Func, 0}, + {"BoolFunc", Func, 21}, + {"BoolVar", Func, 0}, + {"CommandLine", Var, 2}, + {"ContinueOnError", Const, 0}, + {"Duration", Func, 0}, + {"DurationVar", Func, 0}, + {"ErrHelp", Var, 0}, + {"ErrorHandling", Type, 0}, + {"ExitOnError", Const, 0}, + {"Flag", Type, 0}, + {"Flag.DefValue", Field, 0}, + {"Flag.Name", Field, 0}, + {"Flag.Usage", Field, 0}, + {"Flag.Value", Field, 0}, + {"FlagSet", Type, 0}, + {"FlagSet.Usage", Field, 0}, + {"Float64", Func, 0}, + {"Float64Var", Func, 0}, + {"Func", Func, 16}, + {"Getter", Type, 2}, + {"Int", Func, 0}, + {"Int64", Func, 0}, + {"Int64Var", Func, 0}, + {"IntVar", Func, 0}, + {"Lookup", Func, 0}, + {"NArg", Func, 0}, + {"NFlag", Func, 0}, + {"NewFlagSet", Func, 0}, + {"PanicOnError", Const, 0}, + {"Parse", Func, 0}, + {"Parsed", Func, 0}, + {"PrintDefaults", Func, 0}, + {"Set", Func, 0}, + {"String", Func, 0}, + {"StringVar", Func, 0}, + {"TextVar", Func, 19}, + {"Uint", Func, 0}, + {"Uint64", Func, 0}, + {"Uint64Var", Func, 0}, + {"UintVar", Func, 0}, + {"UnquoteUsage", Func, 5}, + {"Usage", Var, 0}, + {"Value", Type, 0}, + {"Var", Func, 0}, + {"Visit", Func, 0}, + {"VisitAll", Func, 0}, + }, + "fmt": { + {"Append", Func, 19}, + {"Appendf", Func, 19}, + {"Appendln", Func, 19}, + {"Errorf", Func, 0}, + {"FormatString", Func, 20}, + {"Formatter", Type, 0}, + {"Fprint", Func, 0}, + {"Fprintf", Func, 0}, + {"Fprintln", Func, 0}, + {"Fscan", Func, 0}, + {"Fscanf", Func, 0}, + {"Fscanln", Func, 0}, + {"GoStringer", Type, 0}, + {"Print", Func, 0}, + {"Printf", Func, 0}, + {"Println", Func, 0}, + {"Scan", Func, 0}, + {"ScanState", Type, 0}, + {"Scanf", Func, 0}, + {"Scanln", Func, 0}, + {"Scanner", Type, 0}, + {"Sprint", Func, 0}, + {"Sprintf", Func, 0}, + {"Sprintln", Func, 0}, + {"Sscan", Func, 0}, + {"Sscanf", Func, 0}, + {"Sscanln", Func, 0}, + {"State", Type, 0}, + {"Stringer", Type, 0}, + }, + "go/ast": { + {"(*ArrayType).End", Method, 0}, + {"(*ArrayType).Pos", Method, 0}, + {"(*AssignStmt).End", Method, 0}, + {"(*AssignStmt).Pos", Method, 0}, + {"(*BadDecl).End", Method, 0}, + {"(*BadDecl).Pos", Method, 0}, + {"(*BadExpr).End", Method, 0}, + {"(*BadExpr).Pos", Method, 0}, + {"(*BadStmt).End", Method, 0}, + {"(*BadStmt).Pos", Method, 0}, + {"(*BasicLit).End", Method, 0}, + {"(*BasicLit).Pos", Method, 0}, + {"(*BinaryExpr).End", Method, 0}, + {"(*BinaryExpr).Pos", Method, 0}, + {"(*BlockStmt).End", Method, 0}, + {"(*BlockStmt).Pos", Method, 0}, + {"(*BranchStmt).End", Method, 0}, + {"(*BranchStmt).Pos", Method, 0}, + {"(*CallExpr).End", Method, 0}, + {"(*CallExpr).Pos", Method, 0}, + {"(*CaseClause).End", Method, 0}, + {"(*CaseClause).Pos", Method, 0}, + {"(*ChanType).End", Method, 0}, + {"(*ChanType).Pos", Method, 0}, + {"(*CommClause).End", Method, 0}, + {"(*CommClause).Pos", Method, 0}, + {"(*Comment).End", Method, 0}, + {"(*Comment).Pos", Method, 0}, + {"(*CommentGroup).End", Method, 0}, + {"(*CommentGroup).Pos", Method, 0}, + {"(*CommentGroup).Text", Method, 0}, + {"(*CompositeLit).End", Method, 0}, + {"(*CompositeLit).Pos", Method, 0}, + {"(*DeclStmt).End", Method, 0}, + {"(*DeclStmt).Pos", Method, 0}, + {"(*DeferStmt).End", Method, 0}, + {"(*DeferStmt).Pos", Method, 0}, + {"(*Ellipsis).End", Method, 0}, + {"(*Ellipsis).Pos", Method, 0}, + {"(*EmptyStmt).End", Method, 0}, + {"(*EmptyStmt).Pos", Method, 0}, + {"(*ExprStmt).End", Method, 0}, + {"(*ExprStmt).Pos", Method, 0}, + {"(*Field).End", Method, 0}, + {"(*Field).Pos", Method, 0}, + {"(*FieldList).End", Method, 0}, + {"(*FieldList).NumFields", Method, 0}, + {"(*FieldList).Pos", Method, 0}, + {"(*File).End", Method, 0}, + {"(*File).Pos", Method, 0}, + {"(*ForStmt).End", Method, 0}, + {"(*ForStmt).Pos", Method, 0}, + {"(*FuncDecl).End", Method, 0}, + {"(*FuncDecl).Pos", Method, 0}, + {"(*FuncLit).End", Method, 0}, + {"(*FuncLit).Pos", Method, 0}, + {"(*FuncType).End", Method, 0}, + {"(*FuncType).Pos", Method, 0}, + {"(*GenDecl).End", Method, 0}, + {"(*GenDecl).Pos", Method, 0}, + {"(*GoStmt).End", Method, 0}, + {"(*GoStmt).Pos", Method, 0}, + {"(*Ident).End", Method, 0}, + {"(*Ident).IsExported", Method, 0}, + {"(*Ident).Pos", Method, 0}, + {"(*Ident).String", Method, 0}, + {"(*IfStmt).End", Method, 0}, + {"(*IfStmt).Pos", Method, 0}, + {"(*ImportSpec).End", Method, 0}, + {"(*ImportSpec).Pos", Method, 0}, + {"(*IncDecStmt).End", Method, 0}, + {"(*IncDecStmt).Pos", Method, 0}, + {"(*IndexExpr).End", Method, 0}, + {"(*IndexExpr).Pos", Method, 0}, + {"(*IndexListExpr).End", Method, 18}, + {"(*IndexListExpr).Pos", Method, 18}, + {"(*InterfaceType).End", Method, 0}, + {"(*InterfaceType).Pos", Method, 0}, + {"(*KeyValueExpr).End", Method, 0}, + {"(*KeyValueExpr).Pos", Method, 0}, + {"(*LabeledStmt).End", Method, 0}, + {"(*LabeledStmt).Pos", Method, 0}, + {"(*MapType).End", Method, 0}, + {"(*MapType).Pos", Method, 0}, + {"(*Object).Pos", Method, 0}, + {"(*Package).End", Method, 0}, + {"(*Package).Pos", Method, 0}, + {"(*ParenExpr).End", Method, 0}, + {"(*ParenExpr).Pos", Method, 0}, + {"(*RangeStmt).End", Method, 0}, + {"(*RangeStmt).Pos", Method, 0}, + {"(*ReturnStmt).End", Method, 0}, + {"(*ReturnStmt).Pos", Method, 0}, + {"(*Scope).Insert", Method, 0}, + {"(*Scope).Lookup", Method, 0}, + {"(*Scope).String", Method, 0}, + {"(*SelectStmt).End", Method, 0}, + {"(*SelectStmt).Pos", Method, 0}, + {"(*SelectorExpr).End", Method, 0}, + {"(*SelectorExpr).Pos", Method, 0}, + {"(*SendStmt).End", Method, 0}, + {"(*SendStmt).Pos", Method, 0}, + {"(*SliceExpr).End", Method, 0}, + {"(*SliceExpr).Pos", Method, 0}, + {"(*StarExpr).End", Method, 0}, + {"(*StarExpr).Pos", Method, 0}, + {"(*StructType).End", Method, 0}, + {"(*StructType).Pos", Method, 0}, + {"(*SwitchStmt).End", Method, 0}, + {"(*SwitchStmt).Pos", Method, 0}, + {"(*TypeAssertExpr).End", Method, 0}, + {"(*TypeAssertExpr).Pos", Method, 0}, + {"(*TypeSpec).End", Method, 0}, + {"(*TypeSpec).Pos", Method, 0}, + {"(*TypeSwitchStmt).End", Method, 0}, + {"(*TypeSwitchStmt).Pos", Method, 0}, + {"(*UnaryExpr).End", Method, 0}, + {"(*UnaryExpr).Pos", Method, 0}, + {"(*ValueSpec).End", Method, 0}, + {"(*ValueSpec).Pos", Method, 0}, + {"(CommentMap).Comments", Method, 1}, + {"(CommentMap).Filter", Method, 1}, + {"(CommentMap).String", Method, 1}, + {"(CommentMap).Update", Method, 1}, + {"(ObjKind).String", Method, 0}, + {"ArrayType", Type, 0}, + {"ArrayType.Elt", Field, 0}, + {"ArrayType.Lbrack", Field, 0}, + {"ArrayType.Len", Field, 0}, + {"AssignStmt", Type, 0}, + {"AssignStmt.Lhs", Field, 0}, + {"AssignStmt.Rhs", Field, 0}, + {"AssignStmt.Tok", Field, 0}, + {"AssignStmt.TokPos", Field, 0}, + {"Bad", Const, 0}, + {"BadDecl", Type, 0}, + {"BadDecl.From", Field, 0}, + {"BadDecl.To", Field, 0}, + {"BadExpr", Type, 0}, + {"BadExpr.From", Field, 0}, + {"BadExpr.To", Field, 0}, + {"BadStmt", Type, 0}, + {"BadStmt.From", Field, 0}, + {"BadStmt.To", Field, 0}, + {"BasicLit", Type, 0}, + {"BasicLit.Kind", Field, 0}, + {"BasicLit.Value", Field, 0}, + {"BasicLit.ValuePos", Field, 0}, + {"BinaryExpr", Type, 0}, + {"BinaryExpr.Op", Field, 0}, + {"BinaryExpr.OpPos", Field, 0}, + {"BinaryExpr.X", Field, 0}, + {"BinaryExpr.Y", Field, 0}, + {"BlockStmt", Type, 0}, + {"BlockStmt.Lbrace", Field, 0}, + {"BlockStmt.List", Field, 0}, + {"BlockStmt.Rbrace", Field, 0}, + {"BranchStmt", Type, 0}, + {"BranchStmt.Label", Field, 0}, + {"BranchStmt.Tok", Field, 0}, + {"BranchStmt.TokPos", Field, 0}, + {"CallExpr", Type, 0}, + {"CallExpr.Args", Field, 0}, + {"CallExpr.Ellipsis", Field, 0}, + {"CallExpr.Fun", Field, 0}, + {"CallExpr.Lparen", Field, 0}, + {"CallExpr.Rparen", Field, 0}, + {"CaseClause", Type, 0}, + {"CaseClause.Body", Field, 0}, + {"CaseClause.Case", Field, 0}, + {"CaseClause.Colon", Field, 0}, + {"CaseClause.List", Field, 0}, + {"ChanDir", Type, 0}, + {"ChanType", Type, 0}, + {"ChanType.Arrow", Field, 1}, + {"ChanType.Begin", Field, 0}, + {"ChanType.Dir", Field, 0}, + {"ChanType.Value", Field, 0}, + {"CommClause", Type, 0}, + {"CommClause.Body", Field, 0}, + {"CommClause.Case", Field, 0}, + {"CommClause.Colon", Field, 0}, + {"CommClause.Comm", Field, 0}, + {"Comment", Type, 0}, + {"Comment.Slash", Field, 0}, + {"Comment.Text", Field, 0}, + {"CommentGroup", Type, 0}, + {"CommentGroup.List", Field, 0}, + {"CommentMap", Type, 1}, + {"CompositeLit", Type, 0}, + {"CompositeLit.Elts", Field, 0}, + {"CompositeLit.Incomplete", Field, 11}, + {"CompositeLit.Lbrace", Field, 0}, + {"CompositeLit.Rbrace", Field, 0}, + {"CompositeLit.Type", Field, 0}, + {"Con", Const, 0}, + {"Decl", Type, 0}, + {"DeclStmt", Type, 0}, + {"DeclStmt.Decl", Field, 0}, + {"DeferStmt", Type, 0}, + {"DeferStmt.Call", Field, 0}, + {"DeferStmt.Defer", Field, 0}, + {"Ellipsis", Type, 0}, + {"Ellipsis.Ellipsis", Field, 0}, + {"Ellipsis.Elt", Field, 0}, + {"EmptyStmt", Type, 0}, + {"EmptyStmt.Implicit", Field, 5}, + {"EmptyStmt.Semicolon", Field, 0}, + {"Expr", Type, 0}, + {"ExprStmt", Type, 0}, + {"ExprStmt.X", Field, 0}, + {"Field", Type, 0}, + {"Field.Comment", Field, 0}, + {"Field.Doc", Field, 0}, + {"Field.Names", Field, 0}, + {"Field.Tag", Field, 0}, + {"Field.Type", Field, 0}, + {"FieldFilter", Type, 0}, + {"FieldList", Type, 0}, + {"FieldList.Closing", Field, 0}, + {"FieldList.List", Field, 0}, + {"FieldList.Opening", Field, 0}, + {"File", Type, 0}, + {"File.Comments", Field, 0}, + {"File.Decls", Field, 0}, + {"File.Doc", Field, 0}, + {"File.FileEnd", Field, 20}, + {"File.FileStart", Field, 20}, + {"File.GoVersion", Field, 21}, + {"File.Imports", Field, 0}, + {"File.Name", Field, 0}, + {"File.Package", Field, 0}, + {"File.Scope", Field, 0}, + {"File.Unresolved", Field, 0}, + {"FileExports", Func, 0}, + {"Filter", Type, 0}, + {"FilterDecl", Func, 0}, + {"FilterFile", Func, 0}, + {"FilterFuncDuplicates", Const, 0}, + {"FilterImportDuplicates", Const, 0}, + {"FilterPackage", Func, 0}, + {"FilterUnassociatedComments", Const, 0}, + {"ForStmt", Type, 0}, + {"ForStmt.Body", Field, 0}, + {"ForStmt.Cond", Field, 0}, + {"ForStmt.For", Field, 0}, + {"ForStmt.Init", Field, 0}, + {"ForStmt.Post", Field, 0}, + {"Fprint", Func, 0}, + {"Fun", Const, 0}, + {"FuncDecl", Type, 0}, + {"FuncDecl.Body", Field, 0}, + {"FuncDecl.Doc", Field, 0}, + {"FuncDecl.Name", Field, 0}, + {"FuncDecl.Recv", Field, 0}, + {"FuncDecl.Type", Field, 0}, + {"FuncLit", Type, 0}, + {"FuncLit.Body", Field, 0}, + {"FuncLit.Type", Field, 0}, + {"FuncType", Type, 0}, + {"FuncType.Func", Field, 0}, + {"FuncType.Params", Field, 0}, + {"FuncType.Results", Field, 0}, + {"FuncType.TypeParams", Field, 18}, + {"GenDecl", Type, 0}, + {"GenDecl.Doc", Field, 0}, + {"GenDecl.Lparen", Field, 0}, + {"GenDecl.Rparen", Field, 0}, + {"GenDecl.Specs", Field, 0}, + {"GenDecl.Tok", Field, 0}, + {"GenDecl.TokPos", Field, 0}, + {"GoStmt", Type, 0}, + {"GoStmt.Call", Field, 0}, + {"GoStmt.Go", Field, 0}, + {"Ident", Type, 0}, + {"Ident.Name", Field, 0}, + {"Ident.NamePos", Field, 0}, + {"Ident.Obj", Field, 0}, + {"IfStmt", Type, 0}, + {"IfStmt.Body", Field, 0}, + {"IfStmt.Cond", Field, 0}, + {"IfStmt.Else", Field, 0}, + {"IfStmt.If", Field, 0}, + {"IfStmt.Init", Field, 0}, + {"ImportSpec", Type, 0}, + {"ImportSpec.Comment", Field, 0}, + {"ImportSpec.Doc", Field, 0}, + {"ImportSpec.EndPos", Field, 0}, + {"ImportSpec.Name", Field, 0}, + {"ImportSpec.Path", Field, 0}, + {"Importer", Type, 0}, + {"IncDecStmt", Type, 0}, + {"IncDecStmt.Tok", Field, 0}, + {"IncDecStmt.TokPos", Field, 0}, + {"IncDecStmt.X", Field, 0}, + {"IndexExpr", Type, 0}, + {"IndexExpr.Index", Field, 0}, + {"IndexExpr.Lbrack", Field, 0}, + {"IndexExpr.Rbrack", Field, 0}, + {"IndexExpr.X", Field, 0}, + {"IndexListExpr", Type, 18}, + {"IndexListExpr.Indices", Field, 18}, + {"IndexListExpr.Lbrack", Field, 18}, + {"IndexListExpr.Rbrack", Field, 18}, + {"IndexListExpr.X", Field, 18}, + {"Inspect", Func, 0}, + {"InterfaceType", Type, 0}, + {"InterfaceType.Incomplete", Field, 0}, + {"InterfaceType.Interface", Field, 0}, + {"InterfaceType.Methods", Field, 0}, + {"IsExported", Func, 0}, + {"IsGenerated", Func, 21}, + {"KeyValueExpr", Type, 0}, + {"KeyValueExpr.Colon", Field, 0}, + {"KeyValueExpr.Key", Field, 0}, + {"KeyValueExpr.Value", Field, 0}, + {"LabeledStmt", Type, 0}, + {"LabeledStmt.Colon", Field, 0}, + {"LabeledStmt.Label", Field, 0}, + {"LabeledStmt.Stmt", Field, 0}, + {"Lbl", Const, 0}, + {"MapType", Type, 0}, + {"MapType.Key", Field, 0}, + {"MapType.Map", Field, 0}, + {"MapType.Value", Field, 0}, + {"MergeMode", Type, 0}, + {"MergePackageFiles", Func, 0}, + {"NewCommentMap", Func, 1}, + {"NewIdent", Func, 0}, + {"NewObj", Func, 0}, + {"NewPackage", Func, 0}, + {"NewScope", Func, 0}, + {"Node", Type, 0}, + {"NotNilFilter", Func, 0}, + {"ObjKind", Type, 0}, + {"Object", Type, 0}, + {"Object.Data", Field, 0}, + {"Object.Decl", Field, 0}, + {"Object.Kind", Field, 0}, + {"Object.Name", Field, 0}, + {"Object.Type", Field, 0}, + {"Package", Type, 0}, + {"Package.Files", Field, 0}, + {"Package.Imports", Field, 0}, + {"Package.Name", Field, 0}, + {"Package.Scope", Field, 0}, + {"PackageExports", Func, 0}, + {"ParenExpr", Type, 0}, + {"ParenExpr.Lparen", Field, 0}, + {"ParenExpr.Rparen", Field, 0}, + {"ParenExpr.X", Field, 0}, + {"Pkg", Const, 0}, + {"Preorder", Func, 23}, + {"Print", Func, 0}, + {"RECV", Const, 0}, + {"RangeStmt", Type, 0}, + {"RangeStmt.Body", Field, 0}, + {"RangeStmt.For", Field, 0}, + {"RangeStmt.Key", Field, 0}, + {"RangeStmt.Range", Field, 20}, + {"RangeStmt.Tok", Field, 0}, + {"RangeStmt.TokPos", Field, 0}, + {"RangeStmt.Value", Field, 0}, + {"RangeStmt.X", Field, 0}, + {"ReturnStmt", Type, 0}, + {"ReturnStmt.Results", Field, 0}, + {"ReturnStmt.Return", Field, 0}, + {"SEND", Const, 0}, + {"Scope", Type, 0}, + {"Scope.Objects", Field, 0}, + {"Scope.Outer", Field, 0}, + {"SelectStmt", Type, 0}, + {"SelectStmt.Body", Field, 0}, + {"SelectStmt.Select", Field, 0}, + {"SelectorExpr", Type, 0}, + {"SelectorExpr.Sel", Field, 0}, + {"SelectorExpr.X", Field, 0}, + {"SendStmt", Type, 0}, + {"SendStmt.Arrow", Field, 0}, + {"SendStmt.Chan", Field, 0}, + {"SendStmt.Value", Field, 0}, + {"SliceExpr", Type, 0}, + {"SliceExpr.High", Field, 0}, + {"SliceExpr.Lbrack", Field, 0}, + {"SliceExpr.Low", Field, 0}, + {"SliceExpr.Max", Field, 2}, + {"SliceExpr.Rbrack", Field, 0}, + {"SliceExpr.Slice3", Field, 2}, + {"SliceExpr.X", Field, 0}, + {"SortImports", Func, 0}, + {"Spec", Type, 0}, + {"StarExpr", Type, 0}, + {"StarExpr.Star", Field, 0}, + {"StarExpr.X", Field, 0}, + {"Stmt", Type, 0}, + {"StructType", Type, 0}, + {"StructType.Fields", Field, 0}, + {"StructType.Incomplete", Field, 0}, + {"StructType.Struct", Field, 0}, + {"SwitchStmt", Type, 0}, + {"SwitchStmt.Body", Field, 0}, + {"SwitchStmt.Init", Field, 0}, + {"SwitchStmt.Switch", Field, 0}, + {"SwitchStmt.Tag", Field, 0}, + {"Typ", Const, 0}, + {"TypeAssertExpr", Type, 0}, + {"TypeAssertExpr.Lparen", Field, 2}, + {"TypeAssertExpr.Rparen", Field, 2}, + {"TypeAssertExpr.Type", Field, 0}, + {"TypeAssertExpr.X", Field, 0}, + {"TypeSpec", Type, 0}, + {"TypeSpec.Assign", Field, 9}, + {"TypeSpec.Comment", Field, 0}, + {"TypeSpec.Doc", Field, 0}, + {"TypeSpec.Name", Field, 0}, + {"TypeSpec.Type", Field, 0}, + {"TypeSpec.TypeParams", Field, 18}, + {"TypeSwitchStmt", Type, 0}, + {"TypeSwitchStmt.Assign", Field, 0}, + {"TypeSwitchStmt.Body", Field, 0}, + {"TypeSwitchStmt.Init", Field, 0}, + {"TypeSwitchStmt.Switch", Field, 0}, + {"UnaryExpr", Type, 0}, + {"UnaryExpr.Op", Field, 0}, + {"UnaryExpr.OpPos", Field, 0}, + {"UnaryExpr.X", Field, 0}, + {"Unparen", Func, 22}, + {"ValueSpec", Type, 0}, + {"ValueSpec.Comment", Field, 0}, + {"ValueSpec.Doc", Field, 0}, + {"ValueSpec.Names", Field, 0}, + {"ValueSpec.Type", Field, 0}, + {"ValueSpec.Values", Field, 0}, + {"Var", Const, 0}, + {"Visitor", Type, 0}, + {"Walk", Func, 0}, + }, + "go/build": { + {"(*Context).Import", Method, 0}, + {"(*Context).ImportDir", Method, 0}, + {"(*Context).MatchFile", Method, 2}, + {"(*Context).SrcDirs", Method, 0}, + {"(*MultiplePackageError).Error", Method, 4}, + {"(*NoGoError).Error", Method, 0}, + {"(*Package).IsCommand", Method, 0}, + {"AllowBinary", Const, 0}, + {"ArchChar", Func, 0}, + {"Context", Type, 0}, + {"Context.BuildTags", Field, 0}, + {"Context.CgoEnabled", Field, 0}, + {"Context.Compiler", Field, 0}, + {"Context.Dir", Field, 14}, + {"Context.GOARCH", Field, 0}, + {"Context.GOOS", Field, 0}, + {"Context.GOPATH", Field, 0}, + {"Context.GOROOT", Field, 0}, + {"Context.HasSubdir", Field, 0}, + {"Context.InstallSuffix", Field, 1}, + {"Context.IsAbsPath", Field, 0}, + {"Context.IsDir", Field, 0}, + {"Context.JoinPath", Field, 0}, + {"Context.OpenFile", Field, 0}, + {"Context.ReadDir", Field, 0}, + {"Context.ReleaseTags", Field, 1}, + {"Context.SplitPathList", Field, 0}, + {"Context.ToolTags", Field, 17}, + {"Context.UseAllFiles", Field, 0}, + {"Default", Var, 0}, + {"Directive", Type, 21}, + {"Directive.Pos", Field, 21}, + {"Directive.Text", Field, 21}, + {"FindOnly", Const, 0}, + {"IgnoreVendor", Const, 6}, + {"Import", Func, 0}, + {"ImportComment", Const, 4}, + {"ImportDir", Func, 0}, + {"ImportMode", Type, 0}, + {"IsLocalImport", Func, 0}, + {"MultiplePackageError", Type, 4}, + {"MultiplePackageError.Dir", Field, 4}, + {"MultiplePackageError.Files", Field, 4}, + {"MultiplePackageError.Packages", Field, 4}, + {"NoGoError", Type, 0}, + {"NoGoError.Dir", Field, 0}, + {"Package", Type, 0}, + {"Package.AllTags", Field, 2}, + {"Package.BinDir", Field, 0}, + {"Package.BinaryOnly", Field, 7}, + {"Package.CFiles", Field, 0}, + {"Package.CXXFiles", Field, 2}, + {"Package.CgoCFLAGS", Field, 0}, + {"Package.CgoCPPFLAGS", Field, 2}, + {"Package.CgoCXXFLAGS", Field, 2}, + {"Package.CgoFFLAGS", Field, 7}, + {"Package.CgoFiles", Field, 0}, + {"Package.CgoLDFLAGS", Field, 0}, + {"Package.CgoPkgConfig", Field, 0}, + {"Package.ConflictDir", Field, 2}, + {"Package.Dir", Field, 0}, + {"Package.Directives", Field, 21}, + {"Package.Doc", Field, 0}, + {"Package.EmbedPatternPos", Field, 16}, + {"Package.EmbedPatterns", Field, 16}, + {"Package.FFiles", Field, 7}, + {"Package.GoFiles", Field, 0}, + {"Package.Goroot", Field, 0}, + {"Package.HFiles", Field, 0}, + {"Package.IgnoredGoFiles", Field, 1}, + {"Package.IgnoredOtherFiles", Field, 16}, + {"Package.ImportComment", Field, 4}, + {"Package.ImportPath", Field, 0}, + {"Package.ImportPos", Field, 0}, + {"Package.Imports", Field, 0}, + {"Package.InvalidGoFiles", Field, 6}, + {"Package.MFiles", Field, 3}, + {"Package.Name", Field, 0}, + {"Package.PkgObj", Field, 0}, + {"Package.PkgRoot", Field, 0}, + {"Package.PkgTargetRoot", Field, 5}, + {"Package.Root", Field, 0}, + {"Package.SFiles", Field, 0}, + {"Package.SrcRoot", Field, 0}, + {"Package.SwigCXXFiles", Field, 1}, + {"Package.SwigFiles", Field, 1}, + {"Package.SysoFiles", Field, 0}, + {"Package.TestDirectives", Field, 21}, + {"Package.TestEmbedPatternPos", Field, 16}, + {"Package.TestEmbedPatterns", Field, 16}, + {"Package.TestGoFiles", Field, 0}, + {"Package.TestImportPos", Field, 0}, + {"Package.TestImports", Field, 0}, + {"Package.XTestDirectives", Field, 21}, + {"Package.XTestEmbedPatternPos", Field, 16}, + {"Package.XTestEmbedPatterns", Field, 16}, + {"Package.XTestGoFiles", Field, 0}, + {"Package.XTestImportPos", Field, 0}, + {"Package.XTestImports", Field, 0}, + {"ToolDir", Var, 0}, + }, + "go/build/constraint": { + {"(*AndExpr).Eval", Method, 16}, + {"(*AndExpr).String", Method, 16}, + {"(*NotExpr).Eval", Method, 16}, + {"(*NotExpr).String", Method, 16}, + {"(*OrExpr).Eval", Method, 16}, + {"(*OrExpr).String", Method, 16}, + {"(*SyntaxError).Error", Method, 16}, + {"(*TagExpr).Eval", Method, 16}, + {"(*TagExpr).String", Method, 16}, + {"AndExpr", Type, 16}, + {"AndExpr.X", Field, 16}, + {"AndExpr.Y", Field, 16}, + {"Expr", Type, 16}, + {"GoVersion", Func, 21}, + {"IsGoBuild", Func, 16}, + {"IsPlusBuild", Func, 16}, + {"NotExpr", Type, 16}, + {"NotExpr.X", Field, 16}, + {"OrExpr", Type, 16}, + {"OrExpr.X", Field, 16}, + {"OrExpr.Y", Field, 16}, + {"Parse", Func, 16}, + {"PlusBuildLines", Func, 16}, + {"SyntaxError", Type, 16}, + {"SyntaxError.Err", Field, 16}, + {"SyntaxError.Offset", Field, 16}, + {"TagExpr", Type, 16}, + {"TagExpr.Tag", Field, 16}, + }, + "go/constant": { + {"(Kind).String", Method, 18}, + {"BinaryOp", Func, 5}, + {"BitLen", Func, 5}, + {"Bool", Const, 5}, + {"BoolVal", Func, 5}, + {"Bytes", Func, 5}, + {"Compare", Func, 5}, + {"Complex", Const, 5}, + {"Denom", Func, 5}, + {"Float", Const, 5}, + {"Float32Val", Func, 5}, + {"Float64Val", Func, 5}, + {"Imag", Func, 5}, + {"Int", Const, 5}, + {"Int64Val", Func, 5}, + {"Kind", Type, 5}, + {"Make", Func, 13}, + {"MakeBool", Func, 5}, + {"MakeFloat64", Func, 5}, + {"MakeFromBytes", Func, 5}, + {"MakeFromLiteral", Func, 5}, + {"MakeImag", Func, 5}, + {"MakeInt64", Func, 5}, + {"MakeString", Func, 5}, + {"MakeUint64", Func, 5}, + {"MakeUnknown", Func, 5}, + {"Num", Func, 5}, + {"Real", Func, 5}, + {"Shift", Func, 5}, + {"Sign", Func, 5}, + {"String", Const, 5}, + {"StringVal", Func, 5}, + {"ToComplex", Func, 6}, + {"ToFloat", Func, 6}, + {"ToInt", Func, 6}, + {"Uint64Val", Func, 5}, + {"UnaryOp", Func, 5}, + {"Unknown", Const, 5}, + {"Val", Func, 13}, + {"Value", Type, 5}, + }, + "go/doc": { + {"(*Package).Filter", Method, 0}, + {"(*Package).HTML", Method, 19}, + {"(*Package).Markdown", Method, 19}, + {"(*Package).Parser", Method, 19}, + {"(*Package).Printer", Method, 19}, + {"(*Package).Synopsis", Method, 19}, + {"(*Package).Text", Method, 19}, + {"AllDecls", Const, 0}, + {"AllMethods", Const, 0}, + {"Example", Type, 0}, + {"Example.Code", Field, 0}, + {"Example.Comments", Field, 0}, + {"Example.Doc", Field, 0}, + {"Example.EmptyOutput", Field, 1}, + {"Example.Name", Field, 0}, + {"Example.Order", Field, 1}, + {"Example.Output", Field, 0}, + {"Example.Play", Field, 1}, + {"Example.Suffix", Field, 14}, + {"Example.Unordered", Field, 7}, + {"Examples", Func, 0}, + {"Filter", Type, 0}, + {"Func", Type, 0}, + {"Func.Decl", Field, 0}, + {"Func.Doc", Field, 0}, + {"Func.Examples", Field, 14}, + {"Func.Level", Field, 0}, + {"Func.Name", Field, 0}, + {"Func.Orig", Field, 0}, + {"Func.Recv", Field, 0}, + {"IllegalPrefixes", Var, 1}, + {"IsPredeclared", Func, 8}, + {"Mode", Type, 0}, + {"New", Func, 0}, + {"NewFromFiles", Func, 14}, + {"Note", Type, 1}, + {"Note.Body", Field, 1}, + {"Note.End", Field, 1}, + {"Note.Pos", Field, 1}, + {"Note.UID", Field, 1}, + {"Package", Type, 0}, + {"Package.Bugs", Field, 0}, + {"Package.Consts", Field, 0}, + {"Package.Doc", Field, 0}, + {"Package.Examples", Field, 14}, + {"Package.Filenames", Field, 0}, + {"Package.Funcs", Field, 0}, + {"Package.ImportPath", Field, 0}, + {"Package.Imports", Field, 0}, + {"Package.Name", Field, 0}, + {"Package.Notes", Field, 1}, + {"Package.Types", Field, 0}, + {"Package.Vars", Field, 0}, + {"PreserveAST", Const, 12}, + {"Synopsis", Func, 0}, + {"ToHTML", Func, 0}, + {"ToText", Func, 0}, + {"Type", Type, 0}, + {"Type.Consts", Field, 0}, + {"Type.Decl", Field, 0}, + {"Type.Doc", Field, 0}, + {"Type.Examples", Field, 14}, + {"Type.Funcs", Field, 0}, + {"Type.Methods", Field, 0}, + {"Type.Name", Field, 0}, + {"Type.Vars", Field, 0}, + {"Value", Type, 0}, + {"Value.Decl", Field, 0}, + {"Value.Doc", Field, 0}, + {"Value.Names", Field, 0}, + }, + "go/doc/comment": { + {"(*DocLink).DefaultURL", Method, 19}, + {"(*Heading).DefaultID", Method, 19}, + {"(*List).BlankBefore", Method, 19}, + {"(*List).BlankBetween", Method, 19}, + {"(*Parser).Parse", Method, 19}, + {"(*Printer).Comment", Method, 19}, + {"(*Printer).HTML", Method, 19}, + {"(*Printer).Markdown", Method, 19}, + {"(*Printer).Text", Method, 19}, + {"Block", Type, 19}, + {"Code", Type, 19}, + {"Code.Text", Field, 19}, + {"DefaultLookupPackage", Func, 19}, + {"Doc", Type, 19}, + {"Doc.Content", Field, 19}, + {"Doc.Links", Field, 19}, + {"DocLink", Type, 19}, + {"DocLink.ImportPath", Field, 19}, + {"DocLink.Name", Field, 19}, + {"DocLink.Recv", Field, 19}, + {"DocLink.Text", Field, 19}, + {"Heading", Type, 19}, + {"Heading.Text", Field, 19}, + {"Italic", Type, 19}, + {"Link", Type, 19}, + {"Link.Auto", Field, 19}, + {"Link.Text", Field, 19}, + {"Link.URL", Field, 19}, + {"LinkDef", Type, 19}, + {"LinkDef.Text", Field, 19}, + {"LinkDef.URL", Field, 19}, + {"LinkDef.Used", Field, 19}, + {"List", Type, 19}, + {"List.ForceBlankBefore", Field, 19}, + {"List.ForceBlankBetween", Field, 19}, + {"List.Items", Field, 19}, + {"ListItem", Type, 19}, + {"ListItem.Content", Field, 19}, + {"ListItem.Number", Field, 19}, + {"Paragraph", Type, 19}, + {"Paragraph.Text", Field, 19}, + {"Parser", Type, 19}, + {"Parser.LookupPackage", Field, 19}, + {"Parser.LookupSym", Field, 19}, + {"Parser.Words", Field, 19}, + {"Plain", Type, 19}, + {"Printer", Type, 19}, + {"Printer.DocLinkBaseURL", Field, 19}, + {"Printer.DocLinkURL", Field, 19}, + {"Printer.HeadingID", Field, 19}, + {"Printer.HeadingLevel", Field, 19}, + {"Printer.TextCodePrefix", Field, 19}, + {"Printer.TextPrefix", Field, 19}, + {"Printer.TextWidth", Field, 19}, + {"Text", Type, 19}, + }, + "go/format": { + {"Node", Func, 1}, + {"Source", Func, 1}, + }, + "go/importer": { + {"Default", Func, 5}, + {"For", Func, 5}, + {"ForCompiler", Func, 12}, + {"Lookup", Type, 5}, + }, + "go/parser": { + {"AllErrors", Const, 1}, + {"DeclarationErrors", Const, 0}, + {"ImportsOnly", Const, 0}, + {"Mode", Type, 0}, + {"PackageClauseOnly", Const, 0}, + {"ParseComments", Const, 0}, + {"ParseDir", Func, 0}, + {"ParseExpr", Func, 0}, + {"ParseExprFrom", Func, 5}, + {"ParseFile", Func, 0}, + {"SkipObjectResolution", Const, 17}, + {"SpuriousErrors", Const, 0}, + {"Trace", Const, 0}, + }, + "go/printer": { + {"(*Config).Fprint", Method, 0}, + {"CommentedNode", Type, 0}, + {"CommentedNode.Comments", Field, 0}, + {"CommentedNode.Node", Field, 0}, + {"Config", Type, 0}, + {"Config.Indent", Field, 1}, + {"Config.Mode", Field, 0}, + {"Config.Tabwidth", Field, 0}, + {"Fprint", Func, 0}, + {"Mode", Type, 0}, + {"RawFormat", Const, 0}, + {"SourcePos", Const, 0}, + {"TabIndent", Const, 0}, + {"UseSpaces", Const, 0}, + }, + "go/scanner": { + {"(*ErrorList).Add", Method, 0}, + {"(*ErrorList).RemoveMultiples", Method, 0}, + {"(*ErrorList).Reset", Method, 0}, + {"(*Scanner).Init", Method, 0}, + {"(*Scanner).Scan", Method, 0}, + {"(Error).Error", Method, 0}, + {"(ErrorList).Err", Method, 0}, + {"(ErrorList).Error", Method, 0}, + {"(ErrorList).Len", Method, 0}, + {"(ErrorList).Less", Method, 0}, + {"(ErrorList).Sort", Method, 0}, + {"(ErrorList).Swap", Method, 0}, + {"Error", Type, 0}, + {"Error.Msg", Field, 0}, + {"Error.Pos", Field, 0}, + {"ErrorHandler", Type, 0}, + {"ErrorList", Type, 0}, + {"Mode", Type, 0}, + {"PrintError", Func, 0}, + {"ScanComments", Const, 0}, + {"Scanner", Type, 0}, + {"Scanner.ErrorCount", Field, 0}, + }, + "go/token": { + {"(*File).AddLine", Method, 0}, + {"(*File).AddLineColumnInfo", Method, 11}, + {"(*File).AddLineInfo", Method, 0}, + {"(*File).Base", Method, 0}, + {"(*File).Line", Method, 0}, + {"(*File).LineCount", Method, 0}, + {"(*File).LineStart", Method, 12}, + {"(*File).Lines", Method, 21}, + {"(*File).MergeLine", Method, 2}, + {"(*File).Name", Method, 0}, + {"(*File).Offset", Method, 0}, + {"(*File).Pos", Method, 0}, + {"(*File).Position", Method, 0}, + {"(*File).PositionFor", Method, 4}, + {"(*File).SetLines", Method, 0}, + {"(*File).SetLinesForContent", Method, 0}, + {"(*File).Size", Method, 0}, + {"(*FileSet).AddFile", Method, 0}, + {"(*FileSet).Base", Method, 0}, + {"(*FileSet).File", Method, 0}, + {"(*FileSet).Iterate", Method, 0}, + {"(*FileSet).Position", Method, 0}, + {"(*FileSet).PositionFor", Method, 4}, + {"(*FileSet).Read", Method, 0}, + {"(*FileSet).RemoveFile", Method, 20}, + {"(*FileSet).Write", Method, 0}, + {"(*Position).IsValid", Method, 0}, + {"(Pos).IsValid", Method, 0}, + {"(Position).String", Method, 0}, + {"(Token).IsKeyword", Method, 0}, + {"(Token).IsLiteral", Method, 0}, + {"(Token).IsOperator", Method, 0}, + {"(Token).Precedence", Method, 0}, + {"(Token).String", Method, 0}, + {"ADD", Const, 0}, + {"ADD_ASSIGN", Const, 0}, + {"AND", Const, 0}, + {"AND_ASSIGN", Const, 0}, + {"AND_NOT", Const, 0}, + {"AND_NOT_ASSIGN", Const, 0}, + {"ARROW", Const, 0}, + {"ASSIGN", Const, 0}, + {"BREAK", Const, 0}, + {"CASE", Const, 0}, + {"CHAN", Const, 0}, + {"CHAR", Const, 0}, + {"COLON", Const, 0}, + {"COMMA", Const, 0}, + {"COMMENT", Const, 0}, + {"CONST", Const, 0}, + {"CONTINUE", Const, 0}, + {"DEC", Const, 0}, + {"DEFAULT", Const, 0}, + {"DEFER", Const, 0}, + {"DEFINE", Const, 0}, + {"ELLIPSIS", Const, 0}, + {"ELSE", Const, 0}, + {"EOF", Const, 0}, + {"EQL", Const, 0}, + {"FALLTHROUGH", Const, 0}, + {"FLOAT", Const, 0}, + {"FOR", Const, 0}, + {"FUNC", Const, 0}, + {"File", Type, 0}, + {"FileSet", Type, 0}, + {"GEQ", Const, 0}, + {"GO", Const, 0}, + {"GOTO", Const, 0}, + {"GTR", Const, 0}, + {"HighestPrec", Const, 0}, + {"IDENT", Const, 0}, + {"IF", Const, 0}, + {"ILLEGAL", Const, 0}, + {"IMAG", Const, 0}, + {"IMPORT", Const, 0}, + {"INC", Const, 0}, + {"INT", Const, 0}, + {"INTERFACE", Const, 0}, + {"IsExported", Func, 13}, + {"IsIdentifier", Func, 13}, + {"IsKeyword", Func, 13}, + {"LAND", Const, 0}, + {"LBRACE", Const, 0}, + {"LBRACK", Const, 0}, + {"LEQ", Const, 0}, + {"LOR", Const, 0}, + {"LPAREN", Const, 0}, + {"LSS", Const, 0}, + {"Lookup", Func, 0}, + {"LowestPrec", Const, 0}, + {"MAP", Const, 0}, + {"MUL", Const, 0}, + {"MUL_ASSIGN", Const, 0}, + {"NEQ", Const, 0}, + {"NOT", Const, 0}, + {"NewFileSet", Func, 0}, + {"NoPos", Const, 0}, + {"OR", Const, 0}, + {"OR_ASSIGN", Const, 0}, + {"PACKAGE", Const, 0}, + {"PERIOD", Const, 0}, + {"Pos", Type, 0}, + {"Position", Type, 0}, + {"Position.Column", Field, 0}, + {"Position.Filename", Field, 0}, + {"Position.Line", Field, 0}, + {"Position.Offset", Field, 0}, + {"QUO", Const, 0}, + {"QUO_ASSIGN", Const, 0}, + {"RANGE", Const, 0}, + {"RBRACE", Const, 0}, + {"RBRACK", Const, 0}, + {"REM", Const, 0}, + {"REM_ASSIGN", Const, 0}, + {"RETURN", Const, 0}, + {"RPAREN", Const, 0}, + {"SELECT", Const, 0}, + {"SEMICOLON", Const, 0}, + {"SHL", Const, 0}, + {"SHL_ASSIGN", Const, 0}, + {"SHR", Const, 0}, + {"SHR_ASSIGN", Const, 0}, + {"STRING", Const, 0}, + {"STRUCT", Const, 0}, + {"SUB", Const, 0}, + {"SUB_ASSIGN", Const, 0}, + {"SWITCH", Const, 0}, + {"TILDE", Const, 18}, + {"TYPE", Const, 0}, + {"Token", Type, 0}, + {"UnaryPrec", Const, 0}, + {"VAR", Const, 0}, + {"XOR", Const, 0}, + {"XOR_ASSIGN", Const, 0}, + }, + "go/types": { + {"(*Alias).Obj", Method, 22}, + {"(*Alias).Origin", Method, 23}, + {"(*Alias).Rhs", Method, 23}, + {"(*Alias).SetTypeParams", Method, 23}, + {"(*Alias).String", Method, 22}, + {"(*Alias).TypeArgs", Method, 23}, + {"(*Alias).TypeParams", Method, 23}, + {"(*Alias).Underlying", Method, 22}, + {"(*ArgumentError).Error", Method, 18}, + {"(*ArgumentError).Unwrap", Method, 18}, + {"(*Array).Elem", Method, 5}, + {"(*Array).Len", Method, 5}, + {"(*Array).String", Method, 5}, + {"(*Array).Underlying", Method, 5}, + {"(*Basic).Info", Method, 5}, + {"(*Basic).Kind", Method, 5}, + {"(*Basic).Name", Method, 5}, + {"(*Basic).String", Method, 5}, + {"(*Basic).Underlying", Method, 5}, + {"(*Builtin).Exported", Method, 5}, + {"(*Builtin).Id", Method, 5}, + {"(*Builtin).Name", Method, 5}, + {"(*Builtin).Parent", Method, 5}, + {"(*Builtin).Pkg", Method, 5}, + {"(*Builtin).Pos", Method, 5}, + {"(*Builtin).String", Method, 5}, + {"(*Builtin).Type", Method, 5}, + {"(*Chan).Dir", Method, 5}, + {"(*Chan).Elem", Method, 5}, + {"(*Chan).String", Method, 5}, + {"(*Chan).Underlying", Method, 5}, + {"(*Checker).Files", Method, 5}, + {"(*Config).Check", Method, 5}, + {"(*Const).Exported", Method, 5}, + {"(*Const).Id", Method, 5}, + {"(*Const).Name", Method, 5}, + {"(*Const).Parent", Method, 5}, + {"(*Const).Pkg", Method, 5}, + {"(*Const).Pos", Method, 5}, + {"(*Const).String", Method, 5}, + {"(*Const).Type", Method, 5}, + {"(*Const).Val", Method, 5}, + {"(*Func).Exported", Method, 5}, + {"(*Func).FullName", Method, 5}, + {"(*Func).Id", Method, 5}, + {"(*Func).Name", Method, 5}, + {"(*Func).Origin", Method, 19}, + {"(*Func).Parent", Method, 5}, + {"(*Func).Pkg", Method, 5}, + {"(*Func).Pos", Method, 5}, + {"(*Func).Scope", Method, 5}, + {"(*Func).Signature", Method, 23}, + {"(*Func).String", Method, 5}, + {"(*Func).Type", Method, 5}, + {"(*Info).ObjectOf", Method, 5}, + {"(*Info).PkgNameOf", Method, 22}, + {"(*Info).TypeOf", Method, 5}, + {"(*Initializer).String", Method, 5}, + {"(*Interface).Complete", Method, 5}, + {"(*Interface).Embedded", Method, 5}, + {"(*Interface).EmbeddedType", Method, 11}, + {"(*Interface).Empty", Method, 5}, + {"(*Interface).ExplicitMethod", Method, 5}, + {"(*Interface).IsComparable", Method, 18}, + {"(*Interface).IsImplicit", Method, 18}, + {"(*Interface).IsMethodSet", Method, 18}, + {"(*Interface).MarkImplicit", Method, 18}, + {"(*Interface).Method", Method, 5}, + {"(*Interface).NumEmbeddeds", Method, 5}, + {"(*Interface).NumExplicitMethods", Method, 5}, + {"(*Interface).NumMethods", Method, 5}, + {"(*Interface).String", Method, 5}, + {"(*Interface).Underlying", Method, 5}, + {"(*Label).Exported", Method, 5}, + {"(*Label).Id", Method, 5}, + {"(*Label).Name", Method, 5}, + {"(*Label).Parent", Method, 5}, + {"(*Label).Pkg", Method, 5}, + {"(*Label).Pos", Method, 5}, + {"(*Label).String", Method, 5}, + {"(*Label).Type", Method, 5}, + {"(*Map).Elem", Method, 5}, + {"(*Map).Key", Method, 5}, + {"(*Map).String", Method, 5}, + {"(*Map).Underlying", Method, 5}, + {"(*MethodSet).At", Method, 5}, + {"(*MethodSet).Len", Method, 5}, + {"(*MethodSet).Lookup", Method, 5}, + {"(*MethodSet).String", Method, 5}, + {"(*Named).AddMethod", Method, 5}, + {"(*Named).Method", Method, 5}, + {"(*Named).NumMethods", Method, 5}, + {"(*Named).Obj", Method, 5}, + {"(*Named).Origin", Method, 18}, + {"(*Named).SetTypeParams", Method, 18}, + {"(*Named).SetUnderlying", Method, 5}, + {"(*Named).String", Method, 5}, + {"(*Named).TypeArgs", Method, 18}, + {"(*Named).TypeParams", Method, 18}, + {"(*Named).Underlying", Method, 5}, + {"(*Nil).Exported", Method, 5}, + {"(*Nil).Id", Method, 5}, + {"(*Nil).Name", Method, 5}, + {"(*Nil).Parent", Method, 5}, + {"(*Nil).Pkg", Method, 5}, + {"(*Nil).Pos", Method, 5}, + {"(*Nil).String", Method, 5}, + {"(*Nil).Type", Method, 5}, + {"(*Package).Complete", Method, 5}, + {"(*Package).GoVersion", Method, 21}, + {"(*Package).Imports", Method, 5}, + {"(*Package).MarkComplete", Method, 5}, + {"(*Package).Name", Method, 5}, + {"(*Package).Path", Method, 5}, + {"(*Package).Scope", Method, 5}, + {"(*Package).SetImports", Method, 5}, + {"(*Package).SetName", Method, 6}, + {"(*Package).String", Method, 5}, + {"(*PkgName).Exported", Method, 5}, + {"(*PkgName).Id", Method, 5}, + {"(*PkgName).Imported", Method, 5}, + {"(*PkgName).Name", Method, 5}, + {"(*PkgName).Parent", Method, 5}, + {"(*PkgName).Pkg", Method, 5}, + {"(*PkgName).Pos", Method, 5}, + {"(*PkgName).String", Method, 5}, + {"(*PkgName).Type", Method, 5}, + {"(*Pointer).Elem", Method, 5}, + {"(*Pointer).String", Method, 5}, + {"(*Pointer).Underlying", Method, 5}, + {"(*Scope).Child", Method, 5}, + {"(*Scope).Contains", Method, 5}, + {"(*Scope).End", Method, 5}, + {"(*Scope).Innermost", Method, 5}, + {"(*Scope).Insert", Method, 5}, + {"(*Scope).Len", Method, 5}, + {"(*Scope).Lookup", Method, 5}, + {"(*Scope).LookupParent", Method, 5}, + {"(*Scope).Names", Method, 5}, + {"(*Scope).NumChildren", Method, 5}, + {"(*Scope).Parent", Method, 5}, + {"(*Scope).Pos", Method, 5}, + {"(*Scope).String", Method, 5}, + {"(*Scope).WriteTo", Method, 5}, + {"(*Selection).Index", Method, 5}, + {"(*Selection).Indirect", Method, 5}, + {"(*Selection).Kind", Method, 5}, + {"(*Selection).Obj", Method, 5}, + {"(*Selection).Recv", Method, 5}, + {"(*Selection).String", Method, 5}, + {"(*Selection).Type", Method, 5}, + {"(*Signature).Params", Method, 5}, + {"(*Signature).Recv", Method, 5}, + {"(*Signature).RecvTypeParams", Method, 18}, + {"(*Signature).Results", Method, 5}, + {"(*Signature).String", Method, 5}, + {"(*Signature).TypeParams", Method, 18}, + {"(*Signature).Underlying", Method, 5}, + {"(*Signature).Variadic", Method, 5}, + {"(*Slice).Elem", Method, 5}, + {"(*Slice).String", Method, 5}, + {"(*Slice).Underlying", Method, 5}, + {"(*StdSizes).Alignof", Method, 5}, + {"(*StdSizes).Offsetsof", Method, 5}, + {"(*StdSizes).Sizeof", Method, 5}, + {"(*Struct).Field", Method, 5}, + {"(*Struct).NumFields", Method, 5}, + {"(*Struct).String", Method, 5}, + {"(*Struct).Tag", Method, 5}, + {"(*Struct).Underlying", Method, 5}, + {"(*Term).String", Method, 18}, + {"(*Term).Tilde", Method, 18}, + {"(*Term).Type", Method, 18}, + {"(*Tuple).At", Method, 5}, + {"(*Tuple).Len", Method, 5}, + {"(*Tuple).String", Method, 5}, + {"(*Tuple).Underlying", Method, 5}, + {"(*TypeList).At", Method, 18}, + {"(*TypeList).Len", Method, 18}, + {"(*TypeName).Exported", Method, 5}, + {"(*TypeName).Id", Method, 5}, + {"(*TypeName).IsAlias", Method, 9}, + {"(*TypeName).Name", Method, 5}, + {"(*TypeName).Parent", Method, 5}, + {"(*TypeName).Pkg", Method, 5}, + {"(*TypeName).Pos", Method, 5}, + {"(*TypeName).String", Method, 5}, + {"(*TypeName).Type", Method, 5}, + {"(*TypeParam).Constraint", Method, 18}, + {"(*TypeParam).Index", Method, 18}, + {"(*TypeParam).Obj", Method, 18}, + {"(*TypeParam).SetConstraint", Method, 18}, + {"(*TypeParam).String", Method, 18}, + {"(*TypeParam).Underlying", Method, 18}, + {"(*TypeParamList).At", Method, 18}, + {"(*TypeParamList).Len", Method, 18}, + {"(*Union).Len", Method, 18}, + {"(*Union).String", Method, 18}, + {"(*Union).Term", Method, 18}, + {"(*Union).Underlying", Method, 18}, + {"(*Var).Anonymous", Method, 5}, + {"(*Var).Embedded", Method, 11}, + {"(*Var).Exported", Method, 5}, + {"(*Var).Id", Method, 5}, + {"(*Var).IsField", Method, 5}, + {"(*Var).Name", Method, 5}, + {"(*Var).Origin", Method, 19}, + {"(*Var).Parent", Method, 5}, + {"(*Var).Pkg", Method, 5}, + {"(*Var).Pos", Method, 5}, + {"(*Var).String", Method, 5}, + {"(*Var).Type", Method, 5}, + {"(Checker).ObjectOf", Method, 5}, + {"(Checker).PkgNameOf", Method, 22}, + {"(Checker).TypeOf", Method, 5}, + {"(Error).Error", Method, 5}, + {"(TypeAndValue).Addressable", Method, 5}, + {"(TypeAndValue).Assignable", Method, 5}, + {"(TypeAndValue).HasOk", Method, 5}, + {"(TypeAndValue).IsBuiltin", Method, 5}, + {"(TypeAndValue).IsNil", Method, 5}, + {"(TypeAndValue).IsType", Method, 5}, + {"(TypeAndValue).IsValue", Method, 5}, + {"(TypeAndValue).IsVoid", Method, 5}, + {"Alias", Type, 22}, + {"ArgumentError", Type, 18}, + {"ArgumentError.Err", Field, 18}, + {"ArgumentError.Index", Field, 18}, + {"Array", Type, 5}, + {"AssertableTo", Func, 5}, + {"AssignableTo", Func, 5}, + {"Basic", Type, 5}, + {"BasicInfo", Type, 5}, + {"BasicKind", Type, 5}, + {"Bool", Const, 5}, + {"Builtin", Type, 5}, + {"Byte", Const, 5}, + {"Chan", Type, 5}, + {"ChanDir", Type, 5}, + {"CheckExpr", Func, 13}, + {"Checker", Type, 5}, + {"Checker.Info", Field, 5}, + {"Comparable", Func, 5}, + {"Complex128", Const, 5}, + {"Complex64", Const, 5}, + {"Config", Type, 5}, + {"Config.Context", Field, 18}, + {"Config.DisableUnusedImportCheck", Field, 5}, + {"Config.Error", Field, 5}, + {"Config.FakeImportC", Field, 5}, + {"Config.GoVersion", Field, 18}, + {"Config.IgnoreFuncBodies", Field, 5}, + {"Config.Importer", Field, 5}, + {"Config.Sizes", Field, 5}, + {"Const", Type, 5}, + {"Context", Type, 18}, + {"ConvertibleTo", Func, 5}, + {"DefPredeclaredTestFuncs", Func, 5}, + {"Default", Func, 8}, + {"Error", Type, 5}, + {"Error.Fset", Field, 5}, + {"Error.Msg", Field, 5}, + {"Error.Pos", Field, 5}, + {"Error.Soft", Field, 5}, + {"Eval", Func, 5}, + {"ExprString", Func, 5}, + {"FieldVal", Const, 5}, + {"Float32", Const, 5}, + {"Float64", Const, 5}, + {"Func", Type, 5}, + {"Id", Func, 5}, + {"Identical", Func, 5}, + {"IdenticalIgnoreTags", Func, 8}, + {"Implements", Func, 5}, + {"ImportMode", Type, 6}, + {"Importer", Type, 5}, + {"ImporterFrom", Type, 6}, + {"Info", Type, 5}, + {"Info.Defs", Field, 5}, + {"Info.FileVersions", Field, 22}, + {"Info.Implicits", Field, 5}, + {"Info.InitOrder", Field, 5}, + {"Info.Instances", Field, 18}, + {"Info.Scopes", Field, 5}, + {"Info.Selections", Field, 5}, + {"Info.Types", Field, 5}, + {"Info.Uses", Field, 5}, + {"Initializer", Type, 5}, + {"Initializer.Lhs", Field, 5}, + {"Initializer.Rhs", Field, 5}, + {"Instance", Type, 18}, + {"Instance.Type", Field, 18}, + {"Instance.TypeArgs", Field, 18}, + {"Instantiate", Func, 18}, + {"Int", Const, 5}, + {"Int16", Const, 5}, + {"Int32", Const, 5}, + {"Int64", Const, 5}, + {"Int8", Const, 5}, + {"Interface", Type, 5}, + {"Invalid", Const, 5}, + {"IsBoolean", Const, 5}, + {"IsComplex", Const, 5}, + {"IsConstType", Const, 5}, + {"IsFloat", Const, 5}, + {"IsInteger", Const, 5}, + {"IsInterface", Func, 5}, + {"IsNumeric", Const, 5}, + {"IsOrdered", Const, 5}, + {"IsString", Const, 5}, + {"IsUnsigned", Const, 5}, + {"IsUntyped", Const, 5}, + {"Label", Type, 5}, + {"LookupFieldOrMethod", Func, 5}, + {"Map", Type, 5}, + {"MethodExpr", Const, 5}, + {"MethodSet", Type, 5}, + {"MethodVal", Const, 5}, + {"MissingMethod", Func, 5}, + {"Named", Type, 5}, + {"NewAlias", Func, 22}, + {"NewArray", Func, 5}, + {"NewChan", Func, 5}, + {"NewChecker", Func, 5}, + {"NewConst", Func, 5}, + {"NewContext", Func, 18}, + {"NewField", Func, 5}, + {"NewFunc", Func, 5}, + {"NewInterface", Func, 5}, + {"NewInterfaceType", Func, 11}, + {"NewLabel", Func, 5}, + {"NewMap", Func, 5}, + {"NewMethodSet", Func, 5}, + {"NewNamed", Func, 5}, + {"NewPackage", Func, 5}, + {"NewParam", Func, 5}, + {"NewPkgName", Func, 5}, + {"NewPointer", Func, 5}, + {"NewScope", Func, 5}, + {"NewSignature", Func, 5}, + {"NewSignatureType", Func, 18}, + {"NewSlice", Func, 5}, + {"NewStruct", Func, 5}, + {"NewTerm", Func, 18}, + {"NewTuple", Func, 5}, + {"NewTypeName", Func, 5}, + {"NewTypeParam", Func, 18}, + {"NewUnion", Func, 18}, + {"NewVar", Func, 5}, + {"Nil", Type, 5}, + {"Object", Type, 5}, + {"ObjectString", Func, 5}, + {"Package", Type, 5}, + {"PkgName", Type, 5}, + {"Pointer", Type, 5}, + {"Qualifier", Type, 5}, + {"RecvOnly", Const, 5}, + {"RelativeTo", Func, 5}, + {"Rune", Const, 5}, + {"Satisfies", Func, 20}, + {"Scope", Type, 5}, + {"Selection", Type, 5}, + {"SelectionKind", Type, 5}, + {"SelectionString", Func, 5}, + {"SendOnly", Const, 5}, + {"SendRecv", Const, 5}, + {"Signature", Type, 5}, + {"Sizes", Type, 5}, + {"SizesFor", Func, 9}, + {"Slice", Type, 5}, + {"StdSizes", Type, 5}, + {"StdSizes.MaxAlign", Field, 5}, + {"StdSizes.WordSize", Field, 5}, + {"String", Const, 5}, + {"Struct", Type, 5}, + {"Term", Type, 18}, + {"Tuple", Type, 5}, + {"Typ", Var, 5}, + {"Type", Type, 5}, + {"TypeAndValue", Type, 5}, + {"TypeAndValue.Type", Field, 5}, + {"TypeAndValue.Value", Field, 5}, + {"TypeList", Type, 18}, + {"TypeName", Type, 5}, + {"TypeParam", Type, 18}, + {"TypeParamList", Type, 18}, + {"TypeString", Func, 5}, + {"Uint", Const, 5}, + {"Uint16", Const, 5}, + {"Uint32", Const, 5}, + {"Uint64", Const, 5}, + {"Uint8", Const, 5}, + {"Uintptr", Const, 5}, + {"Unalias", Func, 22}, + {"Union", Type, 18}, + {"Universe", Var, 5}, + {"Unsafe", Var, 5}, + {"UnsafePointer", Const, 5}, + {"UntypedBool", Const, 5}, + {"UntypedComplex", Const, 5}, + {"UntypedFloat", Const, 5}, + {"UntypedInt", Const, 5}, + {"UntypedNil", Const, 5}, + {"UntypedRune", Const, 5}, + {"UntypedString", Const, 5}, + {"Var", Type, 5}, + {"WriteExpr", Func, 5}, + {"WriteSignature", Func, 5}, + {"WriteType", Func, 5}, + }, + "go/version": { + {"Compare", Func, 22}, + {"IsValid", Func, 22}, + {"Lang", Func, 22}, + }, + "hash": { + {"Hash", Type, 0}, + {"Hash32", Type, 0}, + {"Hash64", Type, 0}, + }, + "hash/adler32": { + {"Checksum", Func, 0}, + {"New", Func, 0}, + {"Size", Const, 0}, + }, + "hash/crc32": { + {"Castagnoli", Const, 0}, + {"Checksum", Func, 0}, + {"ChecksumIEEE", Func, 0}, + {"IEEE", Const, 0}, + {"IEEETable", Var, 0}, + {"Koopman", Const, 0}, + {"MakeTable", Func, 0}, + {"New", Func, 0}, + {"NewIEEE", Func, 0}, + {"Size", Const, 0}, + {"Table", Type, 0}, + {"Update", Func, 0}, + }, + "hash/crc64": { + {"Checksum", Func, 0}, + {"ECMA", Const, 0}, + {"ISO", Const, 0}, + {"MakeTable", Func, 0}, + {"New", Func, 0}, + {"Size", Const, 0}, + {"Table", Type, 0}, + {"Update", Func, 0}, + }, + "hash/fnv": { + {"New128", Func, 9}, + {"New128a", Func, 9}, + {"New32", Func, 0}, + {"New32a", Func, 0}, + {"New64", Func, 0}, + {"New64a", Func, 0}, + }, + "hash/maphash": { + {"(*Hash).BlockSize", Method, 14}, + {"(*Hash).Reset", Method, 14}, + {"(*Hash).Seed", Method, 14}, + {"(*Hash).SetSeed", Method, 14}, + {"(*Hash).Size", Method, 14}, + {"(*Hash).Sum", Method, 14}, + {"(*Hash).Sum64", Method, 14}, + {"(*Hash).Write", Method, 14}, + {"(*Hash).WriteByte", Method, 14}, + {"(*Hash).WriteString", Method, 14}, + {"Bytes", Func, 19}, + {"Hash", Type, 14}, + {"MakeSeed", Func, 14}, + {"Seed", Type, 14}, + {"String", Func, 19}, + }, + "html": { + {"EscapeString", Func, 0}, + {"UnescapeString", Func, 0}, + }, + "html/template": { + {"(*Error).Error", Method, 0}, + {"(*Template).AddParseTree", Method, 0}, + {"(*Template).Clone", Method, 0}, + {"(*Template).DefinedTemplates", Method, 6}, + {"(*Template).Delims", Method, 0}, + {"(*Template).Execute", Method, 0}, + {"(*Template).ExecuteTemplate", Method, 0}, + {"(*Template).Funcs", Method, 0}, + {"(*Template).Lookup", Method, 0}, + {"(*Template).Name", Method, 0}, + {"(*Template).New", Method, 0}, + {"(*Template).Option", Method, 5}, + {"(*Template).Parse", Method, 0}, + {"(*Template).ParseFS", Method, 16}, + {"(*Template).ParseFiles", Method, 0}, + {"(*Template).ParseGlob", Method, 0}, + {"(*Template).Templates", Method, 0}, + {"CSS", Type, 0}, + {"ErrAmbigContext", Const, 0}, + {"ErrBadHTML", Const, 0}, + {"ErrBranchEnd", Const, 0}, + {"ErrEndContext", Const, 0}, + {"ErrJSTemplate", Const, 21}, + {"ErrNoSuchTemplate", Const, 0}, + {"ErrOutputContext", Const, 0}, + {"ErrPartialCharset", Const, 0}, + {"ErrPartialEscape", Const, 0}, + {"ErrPredefinedEscaper", Const, 9}, + {"ErrRangeLoopReentry", Const, 0}, + {"ErrSlashAmbig", Const, 0}, + {"Error", Type, 0}, + {"Error.Description", Field, 0}, + {"Error.ErrorCode", Field, 0}, + {"Error.Line", Field, 0}, + {"Error.Name", Field, 0}, + {"Error.Node", Field, 4}, + {"ErrorCode", Type, 0}, + {"FuncMap", Type, 0}, + {"HTML", Type, 0}, + {"HTMLAttr", Type, 0}, + {"HTMLEscape", Func, 0}, + {"HTMLEscapeString", Func, 0}, + {"HTMLEscaper", Func, 0}, + {"IsTrue", Func, 6}, + {"JS", Type, 0}, + {"JSEscape", Func, 0}, + {"JSEscapeString", Func, 0}, + {"JSEscaper", Func, 0}, + {"JSStr", Type, 0}, + {"Must", Func, 0}, + {"New", Func, 0}, + {"OK", Const, 0}, + {"ParseFS", Func, 16}, + {"ParseFiles", Func, 0}, + {"ParseGlob", Func, 0}, + {"Srcset", Type, 10}, + {"Template", Type, 0}, + {"Template.Tree", Field, 2}, + {"URL", Type, 0}, + {"URLQueryEscaper", Func, 0}, + }, + "image": { + {"(*Alpha).AlphaAt", Method, 4}, + {"(*Alpha).At", Method, 0}, + {"(*Alpha).Bounds", Method, 0}, + {"(*Alpha).ColorModel", Method, 0}, + {"(*Alpha).Opaque", Method, 0}, + {"(*Alpha).PixOffset", Method, 0}, + {"(*Alpha).RGBA64At", Method, 17}, + {"(*Alpha).Set", Method, 0}, + {"(*Alpha).SetAlpha", Method, 0}, + {"(*Alpha).SetRGBA64", Method, 17}, + {"(*Alpha).SubImage", Method, 0}, + {"(*Alpha16).Alpha16At", Method, 4}, + {"(*Alpha16).At", Method, 0}, + {"(*Alpha16).Bounds", Method, 0}, + {"(*Alpha16).ColorModel", Method, 0}, + {"(*Alpha16).Opaque", Method, 0}, + {"(*Alpha16).PixOffset", Method, 0}, + {"(*Alpha16).RGBA64At", Method, 17}, + {"(*Alpha16).Set", Method, 0}, + {"(*Alpha16).SetAlpha16", Method, 0}, + {"(*Alpha16).SetRGBA64", Method, 17}, + {"(*Alpha16).SubImage", Method, 0}, + {"(*CMYK).At", Method, 5}, + {"(*CMYK).Bounds", Method, 5}, + {"(*CMYK).CMYKAt", Method, 5}, + {"(*CMYK).ColorModel", Method, 5}, + {"(*CMYK).Opaque", Method, 5}, + {"(*CMYK).PixOffset", Method, 5}, + {"(*CMYK).RGBA64At", Method, 17}, + {"(*CMYK).Set", Method, 5}, + {"(*CMYK).SetCMYK", Method, 5}, + {"(*CMYK).SetRGBA64", Method, 17}, + {"(*CMYK).SubImage", Method, 5}, + {"(*Gray).At", Method, 0}, + {"(*Gray).Bounds", Method, 0}, + {"(*Gray).ColorModel", Method, 0}, + {"(*Gray).GrayAt", Method, 4}, + {"(*Gray).Opaque", Method, 0}, + {"(*Gray).PixOffset", Method, 0}, + {"(*Gray).RGBA64At", Method, 17}, + {"(*Gray).Set", Method, 0}, + {"(*Gray).SetGray", Method, 0}, + {"(*Gray).SetRGBA64", Method, 17}, + {"(*Gray).SubImage", Method, 0}, + {"(*Gray16).At", Method, 0}, + {"(*Gray16).Bounds", Method, 0}, + {"(*Gray16).ColorModel", Method, 0}, + {"(*Gray16).Gray16At", Method, 4}, + {"(*Gray16).Opaque", Method, 0}, + {"(*Gray16).PixOffset", Method, 0}, + {"(*Gray16).RGBA64At", Method, 17}, + {"(*Gray16).Set", Method, 0}, + {"(*Gray16).SetGray16", Method, 0}, + {"(*Gray16).SetRGBA64", Method, 17}, + {"(*Gray16).SubImage", Method, 0}, + {"(*NRGBA).At", Method, 0}, + {"(*NRGBA).Bounds", Method, 0}, + {"(*NRGBA).ColorModel", Method, 0}, + {"(*NRGBA).NRGBAAt", Method, 4}, + {"(*NRGBA).Opaque", Method, 0}, + {"(*NRGBA).PixOffset", Method, 0}, + {"(*NRGBA).RGBA64At", Method, 17}, + {"(*NRGBA).Set", Method, 0}, + {"(*NRGBA).SetNRGBA", Method, 0}, + {"(*NRGBA).SetRGBA64", Method, 17}, + {"(*NRGBA).SubImage", Method, 0}, + {"(*NRGBA64).At", Method, 0}, + {"(*NRGBA64).Bounds", Method, 0}, + {"(*NRGBA64).ColorModel", Method, 0}, + {"(*NRGBA64).NRGBA64At", Method, 4}, + {"(*NRGBA64).Opaque", Method, 0}, + {"(*NRGBA64).PixOffset", Method, 0}, + {"(*NRGBA64).RGBA64At", Method, 17}, + {"(*NRGBA64).Set", Method, 0}, + {"(*NRGBA64).SetNRGBA64", Method, 0}, + {"(*NRGBA64).SetRGBA64", Method, 17}, + {"(*NRGBA64).SubImage", Method, 0}, + {"(*NYCbCrA).AOffset", Method, 6}, + {"(*NYCbCrA).At", Method, 6}, + {"(*NYCbCrA).Bounds", Method, 6}, + {"(*NYCbCrA).COffset", Method, 6}, + {"(*NYCbCrA).ColorModel", Method, 6}, + {"(*NYCbCrA).NYCbCrAAt", Method, 6}, + {"(*NYCbCrA).Opaque", Method, 6}, + {"(*NYCbCrA).RGBA64At", Method, 17}, + {"(*NYCbCrA).SubImage", Method, 6}, + {"(*NYCbCrA).YCbCrAt", Method, 6}, + {"(*NYCbCrA).YOffset", Method, 6}, + {"(*Paletted).At", Method, 0}, + {"(*Paletted).Bounds", Method, 0}, + {"(*Paletted).ColorIndexAt", Method, 0}, + {"(*Paletted).ColorModel", Method, 0}, + {"(*Paletted).Opaque", Method, 0}, + {"(*Paletted).PixOffset", Method, 0}, + {"(*Paletted).RGBA64At", Method, 17}, + {"(*Paletted).Set", Method, 0}, + {"(*Paletted).SetColorIndex", Method, 0}, + {"(*Paletted).SetRGBA64", Method, 17}, + {"(*Paletted).SubImage", Method, 0}, + {"(*RGBA).At", Method, 0}, + {"(*RGBA).Bounds", Method, 0}, + {"(*RGBA).ColorModel", Method, 0}, + {"(*RGBA).Opaque", Method, 0}, + {"(*RGBA).PixOffset", Method, 0}, + {"(*RGBA).RGBA64At", Method, 17}, + {"(*RGBA).RGBAAt", Method, 4}, + {"(*RGBA).Set", Method, 0}, + {"(*RGBA).SetRGBA", Method, 0}, + {"(*RGBA).SetRGBA64", Method, 17}, + {"(*RGBA).SubImage", Method, 0}, + {"(*RGBA64).At", Method, 0}, + {"(*RGBA64).Bounds", Method, 0}, + {"(*RGBA64).ColorModel", Method, 0}, + {"(*RGBA64).Opaque", Method, 0}, + {"(*RGBA64).PixOffset", Method, 0}, + {"(*RGBA64).RGBA64At", Method, 4}, + {"(*RGBA64).Set", Method, 0}, + {"(*RGBA64).SetRGBA64", Method, 0}, + {"(*RGBA64).SubImage", Method, 0}, + {"(*Uniform).At", Method, 0}, + {"(*Uniform).Bounds", Method, 0}, + {"(*Uniform).ColorModel", Method, 0}, + {"(*Uniform).Convert", Method, 0}, + {"(*Uniform).Opaque", Method, 0}, + {"(*Uniform).RGBA", Method, 0}, + {"(*Uniform).RGBA64At", Method, 17}, + {"(*YCbCr).At", Method, 0}, + {"(*YCbCr).Bounds", Method, 0}, + {"(*YCbCr).COffset", Method, 0}, + {"(*YCbCr).ColorModel", Method, 0}, + {"(*YCbCr).Opaque", Method, 0}, + {"(*YCbCr).RGBA64At", Method, 17}, + {"(*YCbCr).SubImage", Method, 0}, + {"(*YCbCr).YCbCrAt", Method, 4}, + {"(*YCbCr).YOffset", Method, 0}, + {"(Point).Add", Method, 0}, + {"(Point).Div", Method, 0}, + {"(Point).Eq", Method, 0}, + {"(Point).In", Method, 0}, + {"(Point).Mod", Method, 0}, + {"(Point).Mul", Method, 0}, + {"(Point).String", Method, 0}, + {"(Point).Sub", Method, 0}, + {"(Rectangle).Add", Method, 0}, + {"(Rectangle).At", Method, 5}, + {"(Rectangle).Bounds", Method, 5}, + {"(Rectangle).Canon", Method, 0}, + {"(Rectangle).ColorModel", Method, 5}, + {"(Rectangle).Dx", Method, 0}, + {"(Rectangle).Dy", Method, 0}, + {"(Rectangle).Empty", Method, 0}, + {"(Rectangle).Eq", Method, 0}, + {"(Rectangle).In", Method, 0}, + {"(Rectangle).Inset", Method, 0}, + {"(Rectangle).Intersect", Method, 0}, + {"(Rectangle).Overlaps", Method, 0}, + {"(Rectangle).RGBA64At", Method, 17}, + {"(Rectangle).Size", Method, 0}, + {"(Rectangle).String", Method, 0}, + {"(Rectangle).Sub", Method, 0}, + {"(Rectangle).Union", Method, 0}, + {"(YCbCrSubsampleRatio).String", Method, 0}, + {"Alpha", Type, 0}, + {"Alpha.Pix", Field, 0}, + {"Alpha.Rect", Field, 0}, + {"Alpha.Stride", Field, 0}, + {"Alpha16", Type, 0}, + {"Alpha16.Pix", Field, 0}, + {"Alpha16.Rect", Field, 0}, + {"Alpha16.Stride", Field, 0}, + {"Black", Var, 0}, + {"CMYK", Type, 5}, + {"CMYK.Pix", Field, 5}, + {"CMYK.Rect", Field, 5}, + {"CMYK.Stride", Field, 5}, + {"Config", Type, 0}, + {"Config.ColorModel", Field, 0}, + {"Config.Height", Field, 0}, + {"Config.Width", Field, 0}, + {"Decode", Func, 0}, + {"DecodeConfig", Func, 0}, + {"ErrFormat", Var, 0}, + {"Gray", Type, 0}, + {"Gray.Pix", Field, 0}, + {"Gray.Rect", Field, 0}, + {"Gray.Stride", Field, 0}, + {"Gray16", Type, 0}, + {"Gray16.Pix", Field, 0}, + {"Gray16.Rect", Field, 0}, + {"Gray16.Stride", Field, 0}, + {"Image", Type, 0}, + {"NRGBA", Type, 0}, + {"NRGBA.Pix", Field, 0}, + {"NRGBA.Rect", Field, 0}, + {"NRGBA.Stride", Field, 0}, + {"NRGBA64", Type, 0}, + {"NRGBA64.Pix", Field, 0}, + {"NRGBA64.Rect", Field, 0}, + {"NRGBA64.Stride", Field, 0}, + {"NYCbCrA", Type, 6}, + {"NYCbCrA.A", Field, 6}, + {"NYCbCrA.AStride", Field, 6}, + {"NYCbCrA.YCbCr", Field, 6}, + {"NewAlpha", Func, 0}, + {"NewAlpha16", Func, 0}, + {"NewCMYK", Func, 5}, + {"NewGray", Func, 0}, + {"NewGray16", Func, 0}, + {"NewNRGBA", Func, 0}, + {"NewNRGBA64", Func, 0}, + {"NewNYCbCrA", Func, 6}, + {"NewPaletted", Func, 0}, + {"NewRGBA", Func, 0}, + {"NewRGBA64", Func, 0}, + {"NewUniform", Func, 0}, + {"NewYCbCr", Func, 0}, + {"Opaque", Var, 0}, + {"Paletted", Type, 0}, + {"Paletted.Palette", Field, 0}, + {"Paletted.Pix", Field, 0}, + {"Paletted.Rect", Field, 0}, + {"Paletted.Stride", Field, 0}, + {"PalettedImage", Type, 0}, + {"Point", Type, 0}, + {"Point.X", Field, 0}, + {"Point.Y", Field, 0}, + {"Pt", Func, 0}, + {"RGBA", Type, 0}, + {"RGBA.Pix", Field, 0}, + {"RGBA.Rect", Field, 0}, + {"RGBA.Stride", Field, 0}, + {"RGBA64", Type, 0}, + {"RGBA64.Pix", Field, 0}, + {"RGBA64.Rect", Field, 0}, + {"RGBA64.Stride", Field, 0}, + {"RGBA64Image", Type, 17}, + {"Rect", Func, 0}, + {"Rectangle", Type, 0}, + {"Rectangle.Max", Field, 0}, + {"Rectangle.Min", Field, 0}, + {"RegisterFormat", Func, 0}, + {"Transparent", Var, 0}, + {"Uniform", Type, 0}, + {"Uniform.C", Field, 0}, + {"White", Var, 0}, + {"YCbCr", Type, 0}, + {"YCbCr.CStride", Field, 0}, + {"YCbCr.Cb", Field, 0}, + {"YCbCr.Cr", Field, 0}, + {"YCbCr.Rect", Field, 0}, + {"YCbCr.SubsampleRatio", Field, 0}, + {"YCbCr.Y", Field, 0}, + {"YCbCr.YStride", Field, 0}, + {"YCbCrSubsampleRatio", Type, 0}, + {"YCbCrSubsampleRatio410", Const, 5}, + {"YCbCrSubsampleRatio411", Const, 5}, + {"YCbCrSubsampleRatio420", Const, 0}, + {"YCbCrSubsampleRatio422", Const, 0}, + {"YCbCrSubsampleRatio440", Const, 1}, + {"YCbCrSubsampleRatio444", Const, 0}, + {"ZP", Var, 0}, + {"ZR", Var, 0}, + }, + "image/color": { + {"(Alpha).RGBA", Method, 0}, + {"(Alpha16).RGBA", Method, 0}, + {"(CMYK).RGBA", Method, 5}, + {"(Gray).RGBA", Method, 0}, + {"(Gray16).RGBA", Method, 0}, + {"(NRGBA).RGBA", Method, 0}, + {"(NRGBA64).RGBA", Method, 0}, + {"(NYCbCrA).RGBA", Method, 6}, + {"(Palette).Convert", Method, 0}, + {"(Palette).Index", Method, 0}, + {"(RGBA).RGBA", Method, 0}, + {"(RGBA64).RGBA", Method, 0}, + {"(YCbCr).RGBA", Method, 0}, + {"Alpha", Type, 0}, + {"Alpha.A", Field, 0}, + {"Alpha16", Type, 0}, + {"Alpha16.A", Field, 0}, + {"Alpha16Model", Var, 0}, + {"AlphaModel", Var, 0}, + {"Black", Var, 0}, + {"CMYK", Type, 5}, + {"CMYK.C", Field, 5}, + {"CMYK.K", Field, 5}, + {"CMYK.M", Field, 5}, + {"CMYK.Y", Field, 5}, + {"CMYKModel", Var, 5}, + {"CMYKToRGB", Func, 5}, + {"Color", Type, 0}, + {"Gray", Type, 0}, + {"Gray.Y", Field, 0}, + {"Gray16", Type, 0}, + {"Gray16.Y", Field, 0}, + {"Gray16Model", Var, 0}, + {"GrayModel", Var, 0}, + {"Model", Type, 0}, + {"ModelFunc", Func, 0}, + {"NRGBA", Type, 0}, + {"NRGBA.A", Field, 0}, + {"NRGBA.B", Field, 0}, + {"NRGBA.G", Field, 0}, + {"NRGBA.R", Field, 0}, + {"NRGBA64", Type, 0}, + {"NRGBA64.A", Field, 0}, + {"NRGBA64.B", Field, 0}, + {"NRGBA64.G", Field, 0}, + {"NRGBA64.R", Field, 0}, + {"NRGBA64Model", Var, 0}, + {"NRGBAModel", Var, 0}, + {"NYCbCrA", Type, 6}, + {"NYCbCrA.A", Field, 6}, + {"NYCbCrA.YCbCr", Field, 6}, + {"NYCbCrAModel", Var, 6}, + {"Opaque", Var, 0}, + {"Palette", Type, 0}, + {"RGBA", Type, 0}, + {"RGBA.A", Field, 0}, + {"RGBA.B", Field, 0}, + {"RGBA.G", Field, 0}, + {"RGBA.R", Field, 0}, + {"RGBA64", Type, 0}, + {"RGBA64.A", Field, 0}, + {"RGBA64.B", Field, 0}, + {"RGBA64.G", Field, 0}, + {"RGBA64.R", Field, 0}, + {"RGBA64Model", Var, 0}, + {"RGBAModel", Var, 0}, + {"RGBToCMYK", Func, 5}, + {"RGBToYCbCr", Func, 0}, + {"Transparent", Var, 0}, + {"White", Var, 0}, + {"YCbCr", Type, 0}, + {"YCbCr.Cb", Field, 0}, + {"YCbCr.Cr", Field, 0}, + {"YCbCr.Y", Field, 0}, + {"YCbCrModel", Var, 0}, + {"YCbCrToRGB", Func, 0}, + }, + "image/color/palette": { + {"Plan9", Var, 2}, + {"WebSafe", Var, 2}, + }, + "image/draw": { + {"(Op).Draw", Method, 2}, + {"Draw", Func, 0}, + {"DrawMask", Func, 0}, + {"Drawer", Type, 2}, + {"FloydSteinberg", Var, 2}, + {"Image", Type, 0}, + {"Op", Type, 0}, + {"Over", Const, 0}, + {"Quantizer", Type, 2}, + {"RGBA64Image", Type, 17}, + {"Src", Const, 0}, + }, + "image/gif": { + {"Decode", Func, 0}, + {"DecodeAll", Func, 0}, + {"DecodeConfig", Func, 0}, + {"DisposalBackground", Const, 5}, + {"DisposalNone", Const, 5}, + {"DisposalPrevious", Const, 5}, + {"Encode", Func, 2}, + {"EncodeAll", Func, 2}, + {"GIF", Type, 0}, + {"GIF.BackgroundIndex", Field, 5}, + {"GIF.Config", Field, 5}, + {"GIF.Delay", Field, 0}, + {"GIF.Disposal", Field, 5}, + {"GIF.Image", Field, 0}, + {"GIF.LoopCount", Field, 0}, + {"Options", Type, 2}, + {"Options.Drawer", Field, 2}, + {"Options.NumColors", Field, 2}, + {"Options.Quantizer", Field, 2}, + }, + "image/jpeg": { + {"(FormatError).Error", Method, 0}, + {"(UnsupportedError).Error", Method, 0}, + {"Decode", Func, 0}, + {"DecodeConfig", Func, 0}, + {"DefaultQuality", Const, 0}, + {"Encode", Func, 0}, + {"FormatError", Type, 0}, + {"Options", Type, 0}, + {"Options.Quality", Field, 0}, + {"Reader", Type, 0}, + {"UnsupportedError", Type, 0}, + }, + "image/png": { + {"(*Encoder).Encode", Method, 4}, + {"(FormatError).Error", Method, 0}, + {"(UnsupportedError).Error", Method, 0}, + {"BestCompression", Const, 4}, + {"BestSpeed", Const, 4}, + {"CompressionLevel", Type, 4}, + {"Decode", Func, 0}, + {"DecodeConfig", Func, 0}, + {"DefaultCompression", Const, 4}, + {"Encode", Func, 0}, + {"Encoder", Type, 4}, + {"Encoder.BufferPool", Field, 9}, + {"Encoder.CompressionLevel", Field, 4}, + {"EncoderBuffer", Type, 9}, + {"EncoderBufferPool", Type, 9}, + {"FormatError", Type, 0}, + {"NoCompression", Const, 4}, + {"UnsupportedError", Type, 0}, + }, + "index/suffixarray": { + {"(*Index).Bytes", Method, 0}, + {"(*Index).FindAllIndex", Method, 0}, + {"(*Index).Lookup", Method, 0}, + {"(*Index).Read", Method, 0}, + {"(*Index).Write", Method, 0}, + {"Index", Type, 0}, + {"New", Func, 0}, + }, + "io": { + {"(*LimitedReader).Read", Method, 0}, + {"(*OffsetWriter).Seek", Method, 20}, + {"(*OffsetWriter).Write", Method, 20}, + {"(*OffsetWriter).WriteAt", Method, 20}, + {"(*PipeReader).Close", Method, 0}, + {"(*PipeReader).CloseWithError", Method, 0}, + {"(*PipeReader).Read", Method, 0}, + {"(*PipeWriter).Close", Method, 0}, + {"(*PipeWriter).CloseWithError", Method, 0}, + {"(*PipeWriter).Write", Method, 0}, + {"(*SectionReader).Outer", Method, 22}, + {"(*SectionReader).Read", Method, 0}, + {"(*SectionReader).ReadAt", Method, 0}, + {"(*SectionReader).Seek", Method, 0}, + {"(*SectionReader).Size", Method, 0}, + {"ByteReader", Type, 0}, + {"ByteScanner", Type, 0}, + {"ByteWriter", Type, 1}, + {"Closer", Type, 0}, + {"Copy", Func, 0}, + {"CopyBuffer", Func, 5}, + {"CopyN", Func, 0}, + {"Discard", Var, 16}, + {"EOF", Var, 0}, + {"ErrClosedPipe", Var, 0}, + {"ErrNoProgress", Var, 1}, + {"ErrShortBuffer", Var, 0}, + {"ErrShortWrite", Var, 0}, + {"ErrUnexpectedEOF", Var, 0}, + {"LimitReader", Func, 0}, + {"LimitedReader", Type, 0}, + {"LimitedReader.N", Field, 0}, + {"LimitedReader.R", Field, 0}, + {"MultiReader", Func, 0}, + {"MultiWriter", Func, 0}, + {"NewOffsetWriter", Func, 20}, + {"NewSectionReader", Func, 0}, + {"NopCloser", Func, 16}, + {"OffsetWriter", Type, 20}, + {"Pipe", Func, 0}, + {"PipeReader", Type, 0}, + {"PipeWriter", Type, 0}, + {"ReadAll", Func, 16}, + {"ReadAtLeast", Func, 0}, + {"ReadCloser", Type, 0}, + {"ReadFull", Func, 0}, + {"ReadSeekCloser", Type, 16}, + {"ReadSeeker", Type, 0}, + {"ReadWriteCloser", Type, 0}, + {"ReadWriteSeeker", Type, 0}, + {"ReadWriter", Type, 0}, + {"Reader", Type, 0}, + {"ReaderAt", Type, 0}, + {"ReaderFrom", Type, 0}, + {"RuneReader", Type, 0}, + {"RuneScanner", Type, 0}, + {"SectionReader", Type, 0}, + {"SeekCurrent", Const, 7}, + {"SeekEnd", Const, 7}, + {"SeekStart", Const, 7}, + {"Seeker", Type, 0}, + {"StringWriter", Type, 12}, + {"TeeReader", Func, 0}, + {"WriteCloser", Type, 0}, + {"WriteSeeker", Type, 0}, + {"WriteString", Func, 0}, + {"Writer", Type, 0}, + {"WriterAt", Type, 0}, + {"WriterTo", Type, 0}, + }, + "io/fs": { + {"(*PathError).Error", Method, 16}, + {"(*PathError).Timeout", Method, 16}, + {"(*PathError).Unwrap", Method, 16}, + {"(FileMode).IsDir", Method, 16}, + {"(FileMode).IsRegular", Method, 16}, + {"(FileMode).Perm", Method, 16}, + {"(FileMode).String", Method, 16}, + {"(FileMode).Type", Method, 16}, + {"DirEntry", Type, 16}, + {"ErrClosed", Var, 16}, + {"ErrExist", Var, 16}, + {"ErrInvalid", Var, 16}, + {"ErrNotExist", Var, 16}, + {"ErrPermission", Var, 16}, + {"FS", Type, 16}, + {"File", Type, 16}, + {"FileInfo", Type, 16}, + {"FileInfoToDirEntry", Func, 17}, + {"FileMode", Type, 16}, + {"FormatDirEntry", Func, 21}, + {"FormatFileInfo", Func, 21}, + {"Glob", Func, 16}, + {"GlobFS", Type, 16}, + {"ModeAppend", Const, 16}, + {"ModeCharDevice", Const, 16}, + {"ModeDevice", Const, 16}, + {"ModeDir", Const, 16}, + {"ModeExclusive", Const, 16}, + {"ModeIrregular", Const, 16}, + {"ModeNamedPipe", Const, 16}, + {"ModePerm", Const, 16}, + {"ModeSetgid", Const, 16}, + {"ModeSetuid", Const, 16}, + {"ModeSocket", Const, 16}, + {"ModeSticky", Const, 16}, + {"ModeSymlink", Const, 16}, + {"ModeTemporary", Const, 16}, + {"ModeType", Const, 16}, + {"PathError", Type, 16}, + {"PathError.Err", Field, 16}, + {"PathError.Op", Field, 16}, + {"PathError.Path", Field, 16}, + {"ReadDir", Func, 16}, + {"ReadDirFS", Type, 16}, + {"ReadDirFile", Type, 16}, + {"ReadFile", Func, 16}, + {"ReadFileFS", Type, 16}, + {"SkipAll", Var, 20}, + {"SkipDir", Var, 16}, + {"Stat", Func, 16}, + {"StatFS", Type, 16}, + {"Sub", Func, 16}, + {"SubFS", Type, 16}, + {"ValidPath", Func, 16}, + {"WalkDir", Func, 16}, + {"WalkDirFunc", Type, 16}, + }, + "io/ioutil": { + {"Discard", Var, 0}, + {"NopCloser", Func, 0}, + {"ReadAll", Func, 0}, + {"ReadDir", Func, 0}, + {"ReadFile", Func, 0}, + {"TempDir", Func, 0}, + {"TempFile", Func, 0}, + {"WriteFile", Func, 0}, + }, + "iter": { + {"Pull", Func, 23}, + {"Pull2", Func, 23}, + {"Seq", Type, 23}, + {"Seq2", Type, 23}, + }, + "log": { + {"(*Logger).Fatal", Method, 0}, + {"(*Logger).Fatalf", Method, 0}, + {"(*Logger).Fatalln", Method, 0}, + {"(*Logger).Flags", Method, 0}, + {"(*Logger).Output", Method, 0}, + {"(*Logger).Panic", Method, 0}, + {"(*Logger).Panicf", Method, 0}, + {"(*Logger).Panicln", Method, 0}, + {"(*Logger).Prefix", Method, 0}, + {"(*Logger).Print", Method, 0}, + {"(*Logger).Printf", Method, 0}, + {"(*Logger).Println", Method, 0}, + {"(*Logger).SetFlags", Method, 0}, + {"(*Logger).SetOutput", Method, 5}, + {"(*Logger).SetPrefix", Method, 0}, + {"(*Logger).Writer", Method, 12}, + {"Default", Func, 16}, + {"Fatal", Func, 0}, + {"Fatalf", Func, 0}, + {"Fatalln", Func, 0}, + {"Flags", Func, 0}, + {"LUTC", Const, 5}, + {"Ldate", Const, 0}, + {"Llongfile", Const, 0}, + {"Lmicroseconds", Const, 0}, + {"Lmsgprefix", Const, 14}, + {"Logger", Type, 0}, + {"Lshortfile", Const, 0}, + {"LstdFlags", Const, 0}, + {"Ltime", Const, 0}, + {"New", Func, 0}, + {"Output", Func, 5}, + {"Panic", Func, 0}, + {"Panicf", Func, 0}, + {"Panicln", Func, 0}, + {"Prefix", Func, 0}, + {"Print", Func, 0}, + {"Printf", Func, 0}, + {"Println", Func, 0}, + {"SetFlags", Func, 0}, + {"SetOutput", Func, 0}, + {"SetPrefix", Func, 0}, + {"Writer", Func, 13}, + }, + "log/slog": { + {"(*JSONHandler).Enabled", Method, 21}, + {"(*JSONHandler).Handle", Method, 21}, + {"(*JSONHandler).WithAttrs", Method, 21}, + {"(*JSONHandler).WithGroup", Method, 21}, + {"(*Level).UnmarshalJSON", Method, 21}, + {"(*Level).UnmarshalText", Method, 21}, + {"(*LevelVar).Level", Method, 21}, + {"(*LevelVar).MarshalText", Method, 21}, + {"(*LevelVar).Set", Method, 21}, + {"(*LevelVar).String", Method, 21}, + {"(*LevelVar).UnmarshalText", Method, 21}, + {"(*Logger).Debug", Method, 21}, + {"(*Logger).DebugContext", Method, 21}, + {"(*Logger).Enabled", Method, 21}, + {"(*Logger).Error", Method, 21}, + {"(*Logger).ErrorContext", Method, 21}, + {"(*Logger).Handler", Method, 21}, + {"(*Logger).Info", Method, 21}, + {"(*Logger).InfoContext", Method, 21}, + {"(*Logger).Log", Method, 21}, + {"(*Logger).LogAttrs", Method, 21}, + {"(*Logger).Warn", Method, 21}, + {"(*Logger).WarnContext", Method, 21}, + {"(*Logger).With", Method, 21}, + {"(*Logger).WithGroup", Method, 21}, + {"(*Record).Add", Method, 21}, + {"(*Record).AddAttrs", Method, 21}, + {"(*TextHandler).Enabled", Method, 21}, + {"(*TextHandler).Handle", Method, 21}, + {"(*TextHandler).WithAttrs", Method, 21}, + {"(*TextHandler).WithGroup", Method, 21}, + {"(Attr).Equal", Method, 21}, + {"(Attr).String", Method, 21}, + {"(Kind).String", Method, 21}, + {"(Level).Level", Method, 21}, + {"(Level).MarshalJSON", Method, 21}, + {"(Level).MarshalText", Method, 21}, + {"(Level).String", Method, 21}, + {"(Record).Attrs", Method, 21}, + {"(Record).Clone", Method, 21}, + {"(Record).NumAttrs", Method, 21}, + {"(Value).Any", Method, 21}, + {"(Value).Bool", Method, 21}, + {"(Value).Duration", Method, 21}, + {"(Value).Equal", Method, 21}, + {"(Value).Float64", Method, 21}, + {"(Value).Group", Method, 21}, + {"(Value).Int64", Method, 21}, + {"(Value).Kind", Method, 21}, + {"(Value).LogValuer", Method, 21}, + {"(Value).Resolve", Method, 21}, + {"(Value).String", Method, 21}, + {"(Value).Time", Method, 21}, + {"(Value).Uint64", Method, 21}, + {"Any", Func, 21}, + {"AnyValue", Func, 21}, + {"Attr", Type, 21}, + {"Attr.Key", Field, 21}, + {"Attr.Value", Field, 21}, + {"Bool", Func, 21}, + {"BoolValue", Func, 21}, + {"Debug", Func, 21}, + {"DebugContext", Func, 21}, + {"Default", Func, 21}, + {"Duration", Func, 21}, + {"DurationValue", Func, 21}, + {"Error", Func, 21}, + {"ErrorContext", Func, 21}, + {"Float64", Func, 21}, + {"Float64Value", Func, 21}, + {"Group", Func, 21}, + {"GroupValue", Func, 21}, + {"Handler", Type, 21}, + {"HandlerOptions", Type, 21}, + {"HandlerOptions.AddSource", Field, 21}, + {"HandlerOptions.Level", Field, 21}, + {"HandlerOptions.ReplaceAttr", Field, 21}, + {"Info", Func, 21}, + {"InfoContext", Func, 21}, + {"Int", Func, 21}, + {"Int64", Func, 21}, + {"Int64Value", Func, 21}, + {"IntValue", Func, 21}, + {"JSONHandler", Type, 21}, + {"Kind", Type, 21}, + {"KindAny", Const, 21}, + {"KindBool", Const, 21}, + {"KindDuration", Const, 21}, + {"KindFloat64", Const, 21}, + {"KindGroup", Const, 21}, + {"KindInt64", Const, 21}, + {"KindLogValuer", Const, 21}, + {"KindString", Const, 21}, + {"KindTime", Const, 21}, + {"KindUint64", Const, 21}, + {"Level", Type, 21}, + {"LevelDebug", Const, 21}, + {"LevelError", Const, 21}, + {"LevelInfo", Const, 21}, + {"LevelKey", Const, 21}, + {"LevelVar", Type, 21}, + {"LevelWarn", Const, 21}, + {"Leveler", Type, 21}, + {"Log", Func, 21}, + {"LogAttrs", Func, 21}, + {"LogValuer", Type, 21}, + {"Logger", Type, 21}, + {"MessageKey", Const, 21}, + {"New", Func, 21}, + {"NewJSONHandler", Func, 21}, + {"NewLogLogger", Func, 21}, + {"NewRecord", Func, 21}, + {"NewTextHandler", Func, 21}, + {"Record", Type, 21}, + {"Record.Level", Field, 21}, + {"Record.Message", Field, 21}, + {"Record.PC", Field, 21}, + {"Record.Time", Field, 21}, + {"SetDefault", Func, 21}, + {"SetLogLoggerLevel", Func, 22}, + {"Source", Type, 21}, + {"Source.File", Field, 21}, + {"Source.Function", Field, 21}, + {"Source.Line", Field, 21}, + {"SourceKey", Const, 21}, + {"String", Func, 21}, + {"StringValue", Func, 21}, + {"TextHandler", Type, 21}, + {"Time", Func, 21}, + {"TimeKey", Const, 21}, + {"TimeValue", Func, 21}, + {"Uint64", Func, 21}, + {"Uint64Value", Func, 21}, + {"Value", Type, 21}, + {"Warn", Func, 21}, + {"WarnContext", Func, 21}, + {"With", Func, 21}, + }, + "log/syslog": { + {"(*Writer).Alert", Method, 0}, + {"(*Writer).Close", Method, 0}, + {"(*Writer).Crit", Method, 0}, + {"(*Writer).Debug", Method, 0}, + {"(*Writer).Emerg", Method, 0}, + {"(*Writer).Err", Method, 0}, + {"(*Writer).Info", Method, 0}, + {"(*Writer).Notice", Method, 0}, + {"(*Writer).Warning", Method, 0}, + {"(*Writer).Write", Method, 0}, + {"Dial", Func, 0}, + {"LOG_ALERT", Const, 0}, + {"LOG_AUTH", Const, 1}, + {"LOG_AUTHPRIV", Const, 1}, + {"LOG_CRIT", Const, 0}, + {"LOG_CRON", Const, 1}, + {"LOG_DAEMON", Const, 1}, + {"LOG_DEBUG", Const, 0}, + {"LOG_EMERG", Const, 0}, + {"LOG_ERR", Const, 0}, + {"LOG_FTP", Const, 1}, + {"LOG_INFO", Const, 0}, + {"LOG_KERN", Const, 1}, + {"LOG_LOCAL0", Const, 1}, + {"LOG_LOCAL1", Const, 1}, + {"LOG_LOCAL2", Const, 1}, + {"LOG_LOCAL3", Const, 1}, + {"LOG_LOCAL4", Const, 1}, + {"LOG_LOCAL5", Const, 1}, + {"LOG_LOCAL6", Const, 1}, + {"LOG_LOCAL7", Const, 1}, + {"LOG_LPR", Const, 1}, + {"LOG_MAIL", Const, 1}, + {"LOG_NEWS", Const, 1}, + {"LOG_NOTICE", Const, 0}, + {"LOG_SYSLOG", Const, 1}, + {"LOG_USER", Const, 1}, + {"LOG_UUCP", Const, 1}, + {"LOG_WARNING", Const, 0}, + {"New", Func, 0}, + {"NewLogger", Func, 0}, + {"Priority", Type, 0}, + {"Writer", Type, 0}, + }, + "maps": { + {"All", Func, 23}, + {"Clone", Func, 21}, + {"Collect", Func, 23}, + {"Copy", Func, 21}, + {"DeleteFunc", Func, 21}, + {"Equal", Func, 21}, + {"EqualFunc", Func, 21}, + {"Insert", Func, 23}, + {"Keys", Func, 23}, + {"Values", Func, 23}, + }, + "math": { + {"Abs", Func, 0}, + {"Acos", Func, 0}, + {"Acosh", Func, 0}, + {"Asin", Func, 0}, + {"Asinh", Func, 0}, + {"Atan", Func, 0}, + {"Atan2", Func, 0}, + {"Atanh", Func, 0}, + {"Cbrt", Func, 0}, + {"Ceil", Func, 0}, + {"Copysign", Func, 0}, + {"Cos", Func, 0}, + {"Cosh", Func, 0}, + {"Dim", Func, 0}, + {"E", Const, 0}, + {"Erf", Func, 0}, + {"Erfc", Func, 0}, + {"Erfcinv", Func, 10}, + {"Erfinv", Func, 10}, + {"Exp", Func, 0}, + {"Exp2", Func, 0}, + {"Expm1", Func, 0}, + {"FMA", Func, 14}, + {"Float32bits", Func, 0}, + {"Float32frombits", Func, 0}, + {"Float64bits", Func, 0}, + {"Float64frombits", Func, 0}, + {"Floor", Func, 0}, + {"Frexp", Func, 0}, + {"Gamma", Func, 0}, + {"Hypot", Func, 0}, + {"Ilogb", Func, 0}, + {"Inf", Func, 0}, + {"IsInf", Func, 0}, + {"IsNaN", Func, 0}, + {"J0", Func, 0}, + {"J1", Func, 0}, + {"Jn", Func, 0}, + {"Ldexp", Func, 0}, + {"Lgamma", Func, 0}, + {"Ln10", Const, 0}, + {"Ln2", Const, 0}, + {"Log", Func, 0}, + {"Log10", Func, 0}, + {"Log10E", Const, 0}, + {"Log1p", Func, 0}, + {"Log2", Func, 0}, + {"Log2E", Const, 0}, + {"Logb", Func, 0}, + {"Max", Func, 0}, + {"MaxFloat32", Const, 0}, + {"MaxFloat64", Const, 0}, + {"MaxInt", Const, 17}, + {"MaxInt16", Const, 0}, + {"MaxInt32", Const, 0}, + {"MaxInt64", Const, 0}, + {"MaxInt8", Const, 0}, + {"MaxUint", Const, 17}, + {"MaxUint16", Const, 0}, + {"MaxUint32", Const, 0}, + {"MaxUint64", Const, 0}, + {"MaxUint8", Const, 0}, + {"Min", Func, 0}, + {"MinInt", Const, 17}, + {"MinInt16", Const, 0}, + {"MinInt32", Const, 0}, + {"MinInt64", Const, 0}, + {"MinInt8", Const, 0}, + {"Mod", Func, 0}, + {"Modf", Func, 0}, + {"NaN", Func, 0}, + {"Nextafter", Func, 0}, + {"Nextafter32", Func, 4}, + {"Phi", Const, 0}, + {"Pi", Const, 0}, + {"Pow", Func, 0}, + {"Pow10", Func, 0}, + {"Remainder", Func, 0}, + {"Round", Func, 10}, + {"RoundToEven", Func, 10}, + {"Signbit", Func, 0}, + {"Sin", Func, 0}, + {"Sincos", Func, 0}, + {"Sinh", Func, 0}, + {"SmallestNonzeroFloat32", Const, 0}, + {"SmallestNonzeroFloat64", Const, 0}, + {"Sqrt", Func, 0}, + {"Sqrt2", Const, 0}, + {"SqrtE", Const, 0}, + {"SqrtPhi", Const, 0}, + {"SqrtPi", Const, 0}, + {"Tan", Func, 0}, + {"Tanh", Func, 0}, + {"Trunc", Func, 0}, + {"Y0", Func, 0}, + {"Y1", Func, 0}, + {"Yn", Func, 0}, + }, + "math/big": { + {"(*Float).Abs", Method, 5}, + {"(*Float).Acc", Method, 5}, + {"(*Float).Add", Method, 5}, + {"(*Float).Append", Method, 5}, + {"(*Float).Cmp", Method, 5}, + {"(*Float).Copy", Method, 5}, + {"(*Float).Float32", Method, 5}, + {"(*Float).Float64", Method, 5}, + {"(*Float).Format", Method, 5}, + {"(*Float).GobDecode", Method, 7}, + {"(*Float).GobEncode", Method, 7}, + {"(*Float).Int", Method, 5}, + {"(*Float).Int64", Method, 5}, + {"(*Float).IsInf", Method, 5}, + {"(*Float).IsInt", Method, 5}, + {"(*Float).MantExp", Method, 5}, + {"(*Float).MarshalText", Method, 6}, + {"(*Float).MinPrec", Method, 5}, + {"(*Float).Mode", Method, 5}, + {"(*Float).Mul", Method, 5}, + {"(*Float).Neg", Method, 5}, + {"(*Float).Parse", Method, 5}, + {"(*Float).Prec", Method, 5}, + {"(*Float).Quo", Method, 5}, + {"(*Float).Rat", Method, 5}, + {"(*Float).Scan", Method, 8}, + {"(*Float).Set", Method, 5}, + {"(*Float).SetFloat64", Method, 5}, + {"(*Float).SetInf", Method, 5}, + {"(*Float).SetInt", Method, 5}, + {"(*Float).SetInt64", Method, 5}, + {"(*Float).SetMantExp", Method, 5}, + {"(*Float).SetMode", Method, 5}, + {"(*Float).SetPrec", Method, 5}, + {"(*Float).SetRat", Method, 5}, + {"(*Float).SetString", Method, 5}, + {"(*Float).SetUint64", Method, 5}, + {"(*Float).Sign", Method, 5}, + {"(*Float).Signbit", Method, 5}, + {"(*Float).Sqrt", Method, 10}, + {"(*Float).String", Method, 5}, + {"(*Float).Sub", Method, 5}, + {"(*Float).Text", Method, 5}, + {"(*Float).Uint64", Method, 5}, + {"(*Float).UnmarshalText", Method, 6}, + {"(*Int).Abs", Method, 0}, + {"(*Int).Add", Method, 0}, + {"(*Int).And", Method, 0}, + {"(*Int).AndNot", Method, 0}, + {"(*Int).Append", Method, 6}, + {"(*Int).Binomial", Method, 0}, + {"(*Int).Bit", Method, 0}, + {"(*Int).BitLen", Method, 0}, + {"(*Int).Bits", Method, 0}, + {"(*Int).Bytes", Method, 0}, + {"(*Int).Cmp", Method, 0}, + {"(*Int).CmpAbs", Method, 10}, + {"(*Int).Div", Method, 0}, + {"(*Int).DivMod", Method, 0}, + {"(*Int).Exp", Method, 0}, + {"(*Int).FillBytes", Method, 15}, + {"(*Int).Float64", Method, 21}, + {"(*Int).Format", Method, 0}, + {"(*Int).GCD", Method, 0}, + {"(*Int).GobDecode", Method, 0}, + {"(*Int).GobEncode", Method, 0}, + {"(*Int).Int64", Method, 0}, + {"(*Int).IsInt64", Method, 9}, + {"(*Int).IsUint64", Method, 9}, + {"(*Int).Lsh", Method, 0}, + {"(*Int).MarshalJSON", Method, 1}, + {"(*Int).MarshalText", Method, 3}, + {"(*Int).Mod", Method, 0}, + {"(*Int).ModInverse", Method, 0}, + {"(*Int).ModSqrt", Method, 5}, + {"(*Int).Mul", Method, 0}, + {"(*Int).MulRange", Method, 0}, + {"(*Int).Neg", Method, 0}, + {"(*Int).Not", Method, 0}, + {"(*Int).Or", Method, 0}, + {"(*Int).ProbablyPrime", Method, 0}, + {"(*Int).Quo", Method, 0}, + {"(*Int).QuoRem", Method, 0}, + {"(*Int).Rand", Method, 0}, + {"(*Int).Rem", Method, 0}, + {"(*Int).Rsh", Method, 0}, + {"(*Int).Scan", Method, 0}, + {"(*Int).Set", Method, 0}, + {"(*Int).SetBit", Method, 0}, + {"(*Int).SetBits", Method, 0}, + {"(*Int).SetBytes", Method, 0}, + {"(*Int).SetInt64", Method, 0}, + {"(*Int).SetString", Method, 0}, + {"(*Int).SetUint64", Method, 1}, + {"(*Int).Sign", Method, 0}, + {"(*Int).Sqrt", Method, 8}, + {"(*Int).String", Method, 0}, + {"(*Int).Sub", Method, 0}, + {"(*Int).Text", Method, 6}, + {"(*Int).TrailingZeroBits", Method, 13}, + {"(*Int).Uint64", Method, 1}, + {"(*Int).UnmarshalJSON", Method, 1}, + {"(*Int).UnmarshalText", Method, 3}, + {"(*Int).Xor", Method, 0}, + {"(*Rat).Abs", Method, 0}, + {"(*Rat).Add", Method, 0}, + {"(*Rat).Cmp", Method, 0}, + {"(*Rat).Denom", Method, 0}, + {"(*Rat).Float32", Method, 4}, + {"(*Rat).Float64", Method, 1}, + {"(*Rat).FloatPrec", Method, 22}, + {"(*Rat).FloatString", Method, 0}, + {"(*Rat).GobDecode", Method, 0}, + {"(*Rat).GobEncode", Method, 0}, + {"(*Rat).Inv", Method, 0}, + {"(*Rat).IsInt", Method, 0}, + {"(*Rat).MarshalText", Method, 3}, + {"(*Rat).Mul", Method, 0}, + {"(*Rat).Neg", Method, 0}, + {"(*Rat).Num", Method, 0}, + {"(*Rat).Quo", Method, 0}, + {"(*Rat).RatString", Method, 0}, + {"(*Rat).Scan", Method, 0}, + {"(*Rat).Set", Method, 0}, + {"(*Rat).SetFloat64", Method, 1}, + {"(*Rat).SetFrac", Method, 0}, + {"(*Rat).SetFrac64", Method, 0}, + {"(*Rat).SetInt", Method, 0}, + {"(*Rat).SetInt64", Method, 0}, + {"(*Rat).SetString", Method, 0}, + {"(*Rat).SetUint64", Method, 13}, + {"(*Rat).Sign", Method, 0}, + {"(*Rat).String", Method, 0}, + {"(*Rat).Sub", Method, 0}, + {"(*Rat).UnmarshalText", Method, 3}, + {"(Accuracy).String", Method, 5}, + {"(ErrNaN).Error", Method, 5}, + {"(RoundingMode).String", Method, 5}, + {"Above", Const, 5}, + {"Accuracy", Type, 5}, + {"AwayFromZero", Const, 5}, + {"Below", Const, 5}, + {"ErrNaN", Type, 5}, + {"Exact", Const, 5}, + {"Float", Type, 5}, + {"Int", Type, 0}, + {"Jacobi", Func, 5}, + {"MaxBase", Const, 0}, + {"MaxExp", Const, 5}, + {"MaxPrec", Const, 5}, + {"MinExp", Const, 5}, + {"NewFloat", Func, 5}, + {"NewInt", Func, 0}, + {"NewRat", Func, 0}, + {"ParseFloat", Func, 5}, + {"Rat", Type, 0}, + {"RoundingMode", Type, 5}, + {"ToNearestAway", Const, 5}, + {"ToNearestEven", Const, 5}, + {"ToNegativeInf", Const, 5}, + {"ToPositiveInf", Const, 5}, + {"ToZero", Const, 5}, + {"Word", Type, 0}, + }, + "math/bits": { + {"Add", Func, 12}, + {"Add32", Func, 12}, + {"Add64", Func, 12}, + {"Div", Func, 12}, + {"Div32", Func, 12}, + {"Div64", Func, 12}, + {"LeadingZeros", Func, 9}, + {"LeadingZeros16", Func, 9}, + {"LeadingZeros32", Func, 9}, + {"LeadingZeros64", Func, 9}, + {"LeadingZeros8", Func, 9}, + {"Len", Func, 9}, + {"Len16", Func, 9}, + {"Len32", Func, 9}, + {"Len64", Func, 9}, + {"Len8", Func, 9}, + {"Mul", Func, 12}, + {"Mul32", Func, 12}, + {"Mul64", Func, 12}, + {"OnesCount", Func, 9}, + {"OnesCount16", Func, 9}, + {"OnesCount32", Func, 9}, + {"OnesCount64", Func, 9}, + {"OnesCount8", Func, 9}, + {"Rem", Func, 14}, + {"Rem32", Func, 14}, + {"Rem64", Func, 14}, + {"Reverse", Func, 9}, + {"Reverse16", Func, 9}, + {"Reverse32", Func, 9}, + {"Reverse64", Func, 9}, + {"Reverse8", Func, 9}, + {"ReverseBytes", Func, 9}, + {"ReverseBytes16", Func, 9}, + {"ReverseBytes32", Func, 9}, + {"ReverseBytes64", Func, 9}, + {"RotateLeft", Func, 9}, + {"RotateLeft16", Func, 9}, + {"RotateLeft32", Func, 9}, + {"RotateLeft64", Func, 9}, + {"RotateLeft8", Func, 9}, + {"Sub", Func, 12}, + {"Sub32", Func, 12}, + {"Sub64", Func, 12}, + {"TrailingZeros", Func, 9}, + {"TrailingZeros16", Func, 9}, + {"TrailingZeros32", Func, 9}, + {"TrailingZeros64", Func, 9}, + {"TrailingZeros8", Func, 9}, + {"UintSize", Const, 9}, + }, + "math/cmplx": { + {"Abs", Func, 0}, + {"Acos", Func, 0}, + {"Acosh", Func, 0}, + {"Asin", Func, 0}, + {"Asinh", Func, 0}, + {"Atan", Func, 0}, + {"Atanh", Func, 0}, + {"Conj", Func, 0}, + {"Cos", Func, 0}, + {"Cosh", Func, 0}, + {"Cot", Func, 0}, + {"Exp", Func, 0}, + {"Inf", Func, 0}, + {"IsInf", Func, 0}, + {"IsNaN", Func, 0}, + {"Log", Func, 0}, + {"Log10", Func, 0}, + {"NaN", Func, 0}, + {"Phase", Func, 0}, + {"Polar", Func, 0}, + {"Pow", Func, 0}, + {"Rect", Func, 0}, + {"Sin", Func, 0}, + {"Sinh", Func, 0}, + {"Sqrt", Func, 0}, + {"Tan", Func, 0}, + {"Tanh", Func, 0}, + }, + "math/rand": { + {"(*Rand).ExpFloat64", Method, 0}, + {"(*Rand).Float32", Method, 0}, + {"(*Rand).Float64", Method, 0}, + {"(*Rand).Int", Method, 0}, + {"(*Rand).Int31", Method, 0}, + {"(*Rand).Int31n", Method, 0}, + {"(*Rand).Int63", Method, 0}, + {"(*Rand).Int63n", Method, 0}, + {"(*Rand).Intn", Method, 0}, + {"(*Rand).NormFloat64", Method, 0}, + {"(*Rand).Perm", Method, 0}, + {"(*Rand).Read", Method, 6}, + {"(*Rand).Seed", Method, 0}, + {"(*Rand).Shuffle", Method, 10}, + {"(*Rand).Uint32", Method, 0}, + {"(*Rand).Uint64", Method, 8}, + {"(*Zipf).Uint64", Method, 0}, + {"ExpFloat64", Func, 0}, + {"Float32", Func, 0}, + {"Float64", Func, 0}, + {"Int", Func, 0}, + {"Int31", Func, 0}, + {"Int31n", Func, 0}, + {"Int63", Func, 0}, + {"Int63n", Func, 0}, + {"Intn", Func, 0}, + {"New", Func, 0}, + {"NewSource", Func, 0}, + {"NewZipf", Func, 0}, + {"NormFloat64", Func, 0}, + {"Perm", Func, 0}, + {"Rand", Type, 0}, + {"Read", Func, 6}, + {"Seed", Func, 0}, + {"Shuffle", Func, 10}, + {"Source", Type, 0}, + {"Source64", Type, 8}, + {"Uint32", Func, 0}, + {"Uint64", Func, 8}, + {"Zipf", Type, 0}, + }, + "math/rand/v2": { + {"(*ChaCha8).MarshalBinary", Method, 22}, + {"(*ChaCha8).Read", Method, 23}, + {"(*ChaCha8).Seed", Method, 22}, + {"(*ChaCha8).Uint64", Method, 22}, + {"(*ChaCha8).UnmarshalBinary", Method, 22}, + {"(*PCG).MarshalBinary", Method, 22}, + {"(*PCG).Seed", Method, 22}, + {"(*PCG).Uint64", Method, 22}, + {"(*PCG).UnmarshalBinary", Method, 22}, + {"(*Rand).ExpFloat64", Method, 22}, + {"(*Rand).Float32", Method, 22}, + {"(*Rand).Float64", Method, 22}, + {"(*Rand).Int", Method, 22}, + {"(*Rand).Int32", Method, 22}, + {"(*Rand).Int32N", Method, 22}, + {"(*Rand).Int64", Method, 22}, + {"(*Rand).Int64N", Method, 22}, + {"(*Rand).IntN", Method, 22}, + {"(*Rand).NormFloat64", Method, 22}, + {"(*Rand).Perm", Method, 22}, + {"(*Rand).Shuffle", Method, 22}, + {"(*Rand).Uint", Method, 23}, + {"(*Rand).Uint32", Method, 22}, + {"(*Rand).Uint32N", Method, 22}, + {"(*Rand).Uint64", Method, 22}, + {"(*Rand).Uint64N", Method, 22}, + {"(*Rand).UintN", Method, 22}, + {"(*Zipf).Uint64", Method, 22}, + {"ChaCha8", Type, 22}, + {"ExpFloat64", Func, 22}, + {"Float32", Func, 22}, + {"Float64", Func, 22}, + {"Int", Func, 22}, + {"Int32", Func, 22}, + {"Int32N", Func, 22}, + {"Int64", Func, 22}, + {"Int64N", Func, 22}, + {"IntN", Func, 22}, + {"N", Func, 22}, + {"New", Func, 22}, + {"NewChaCha8", Func, 22}, + {"NewPCG", Func, 22}, + {"NewZipf", Func, 22}, + {"NormFloat64", Func, 22}, + {"PCG", Type, 22}, + {"Perm", Func, 22}, + {"Rand", Type, 22}, + {"Shuffle", Func, 22}, + {"Source", Type, 22}, + {"Uint", Func, 23}, + {"Uint32", Func, 22}, + {"Uint32N", Func, 22}, + {"Uint64", Func, 22}, + {"Uint64N", Func, 22}, + {"UintN", Func, 22}, + {"Zipf", Type, 22}, + }, + "mime": { + {"(*WordDecoder).Decode", Method, 5}, + {"(*WordDecoder).DecodeHeader", Method, 5}, + {"(WordEncoder).Encode", Method, 5}, + {"AddExtensionType", Func, 0}, + {"BEncoding", Const, 5}, + {"ErrInvalidMediaParameter", Var, 9}, + {"ExtensionsByType", Func, 5}, + {"FormatMediaType", Func, 0}, + {"ParseMediaType", Func, 0}, + {"QEncoding", Const, 5}, + {"TypeByExtension", Func, 0}, + {"WordDecoder", Type, 5}, + {"WordDecoder.CharsetReader", Field, 5}, + {"WordEncoder", Type, 5}, + }, + "mime/multipart": { + {"(*FileHeader).Open", Method, 0}, + {"(*Form).RemoveAll", Method, 0}, + {"(*Part).Close", Method, 0}, + {"(*Part).FileName", Method, 0}, + {"(*Part).FormName", Method, 0}, + {"(*Part).Read", Method, 0}, + {"(*Reader).NextPart", Method, 0}, + {"(*Reader).NextRawPart", Method, 14}, + {"(*Reader).ReadForm", Method, 0}, + {"(*Writer).Boundary", Method, 0}, + {"(*Writer).Close", Method, 0}, + {"(*Writer).CreateFormField", Method, 0}, + {"(*Writer).CreateFormFile", Method, 0}, + {"(*Writer).CreatePart", Method, 0}, + {"(*Writer).FormDataContentType", Method, 0}, + {"(*Writer).SetBoundary", Method, 1}, + {"(*Writer).WriteField", Method, 0}, + {"ErrMessageTooLarge", Var, 9}, + {"File", Type, 0}, + {"FileHeader", Type, 0}, + {"FileHeader.Filename", Field, 0}, + {"FileHeader.Header", Field, 0}, + {"FileHeader.Size", Field, 9}, + {"Form", Type, 0}, + {"Form.File", Field, 0}, + {"Form.Value", Field, 0}, + {"NewReader", Func, 0}, + {"NewWriter", Func, 0}, + {"Part", Type, 0}, + {"Part.Header", Field, 0}, + {"Reader", Type, 0}, + {"Writer", Type, 0}, + }, + "mime/quotedprintable": { + {"(*Reader).Read", Method, 5}, + {"(*Writer).Close", Method, 5}, + {"(*Writer).Write", Method, 5}, + {"NewReader", Func, 5}, + {"NewWriter", Func, 5}, + {"Reader", Type, 5}, + {"Writer", Type, 5}, + {"Writer.Binary", Field, 5}, + }, + "net": { + {"(*AddrError).Error", Method, 0}, + {"(*AddrError).Temporary", Method, 0}, + {"(*AddrError).Timeout", Method, 0}, + {"(*Buffers).Read", Method, 8}, + {"(*Buffers).WriteTo", Method, 8}, + {"(*DNSConfigError).Error", Method, 0}, + {"(*DNSConfigError).Temporary", Method, 0}, + {"(*DNSConfigError).Timeout", Method, 0}, + {"(*DNSConfigError).Unwrap", Method, 13}, + {"(*DNSError).Error", Method, 0}, + {"(*DNSError).Temporary", Method, 0}, + {"(*DNSError).Timeout", Method, 0}, + {"(*DNSError).Unwrap", Method, 23}, + {"(*Dialer).Dial", Method, 1}, + {"(*Dialer).DialContext", Method, 7}, + {"(*Dialer).MultipathTCP", Method, 21}, + {"(*Dialer).SetMultipathTCP", Method, 21}, + {"(*IP).UnmarshalText", Method, 2}, + {"(*IPAddr).Network", Method, 0}, + {"(*IPAddr).String", Method, 0}, + {"(*IPConn).Close", Method, 0}, + {"(*IPConn).File", Method, 0}, + {"(*IPConn).LocalAddr", Method, 0}, + {"(*IPConn).Read", Method, 0}, + {"(*IPConn).ReadFrom", Method, 0}, + {"(*IPConn).ReadFromIP", Method, 0}, + {"(*IPConn).ReadMsgIP", Method, 1}, + {"(*IPConn).RemoteAddr", Method, 0}, + {"(*IPConn).SetDeadline", Method, 0}, + {"(*IPConn).SetReadBuffer", Method, 0}, + {"(*IPConn).SetReadDeadline", Method, 0}, + {"(*IPConn).SetWriteBuffer", Method, 0}, + {"(*IPConn).SetWriteDeadline", Method, 0}, + {"(*IPConn).SyscallConn", Method, 9}, + {"(*IPConn).Write", Method, 0}, + {"(*IPConn).WriteMsgIP", Method, 1}, + {"(*IPConn).WriteTo", Method, 0}, + {"(*IPConn).WriteToIP", Method, 0}, + {"(*IPNet).Contains", Method, 0}, + {"(*IPNet).Network", Method, 0}, + {"(*IPNet).String", Method, 0}, + {"(*Interface).Addrs", Method, 0}, + {"(*Interface).MulticastAddrs", Method, 0}, + {"(*ListenConfig).Listen", Method, 11}, + {"(*ListenConfig).ListenPacket", Method, 11}, + {"(*ListenConfig).MultipathTCP", Method, 21}, + {"(*ListenConfig).SetMultipathTCP", Method, 21}, + {"(*OpError).Error", Method, 0}, + {"(*OpError).Temporary", Method, 0}, + {"(*OpError).Timeout", Method, 0}, + {"(*OpError).Unwrap", Method, 13}, + {"(*ParseError).Error", Method, 0}, + {"(*ParseError).Temporary", Method, 17}, + {"(*ParseError).Timeout", Method, 17}, + {"(*Resolver).LookupAddr", Method, 8}, + {"(*Resolver).LookupCNAME", Method, 8}, + {"(*Resolver).LookupHost", Method, 8}, + {"(*Resolver).LookupIP", Method, 15}, + {"(*Resolver).LookupIPAddr", Method, 8}, + {"(*Resolver).LookupMX", Method, 8}, + {"(*Resolver).LookupNS", Method, 8}, + {"(*Resolver).LookupNetIP", Method, 18}, + {"(*Resolver).LookupPort", Method, 8}, + {"(*Resolver).LookupSRV", Method, 8}, + {"(*Resolver).LookupTXT", Method, 8}, + {"(*TCPAddr).AddrPort", Method, 18}, + {"(*TCPAddr).Network", Method, 0}, + {"(*TCPAddr).String", Method, 0}, + {"(*TCPConn).Close", Method, 0}, + {"(*TCPConn).CloseRead", Method, 0}, + {"(*TCPConn).CloseWrite", Method, 0}, + {"(*TCPConn).File", Method, 0}, + {"(*TCPConn).LocalAddr", Method, 0}, + {"(*TCPConn).MultipathTCP", Method, 21}, + {"(*TCPConn).Read", Method, 0}, + {"(*TCPConn).ReadFrom", Method, 0}, + {"(*TCPConn).RemoteAddr", Method, 0}, + {"(*TCPConn).SetDeadline", Method, 0}, + {"(*TCPConn).SetKeepAlive", Method, 0}, + {"(*TCPConn).SetKeepAliveConfig", Method, 23}, + {"(*TCPConn).SetKeepAlivePeriod", Method, 2}, + {"(*TCPConn).SetLinger", Method, 0}, + {"(*TCPConn).SetNoDelay", Method, 0}, + {"(*TCPConn).SetReadBuffer", Method, 0}, + {"(*TCPConn).SetReadDeadline", Method, 0}, + {"(*TCPConn).SetWriteBuffer", Method, 0}, + {"(*TCPConn).SetWriteDeadline", Method, 0}, + {"(*TCPConn).SyscallConn", Method, 9}, + {"(*TCPConn).Write", Method, 0}, + {"(*TCPConn).WriteTo", Method, 22}, + {"(*TCPListener).Accept", Method, 0}, + {"(*TCPListener).AcceptTCP", Method, 0}, + {"(*TCPListener).Addr", Method, 0}, + {"(*TCPListener).Close", Method, 0}, + {"(*TCPListener).File", Method, 0}, + {"(*TCPListener).SetDeadline", Method, 0}, + {"(*TCPListener).SyscallConn", Method, 10}, + {"(*UDPAddr).AddrPort", Method, 18}, + {"(*UDPAddr).Network", Method, 0}, + {"(*UDPAddr).String", Method, 0}, + {"(*UDPConn).Close", Method, 0}, + {"(*UDPConn).File", Method, 0}, + {"(*UDPConn).LocalAddr", Method, 0}, + {"(*UDPConn).Read", Method, 0}, + {"(*UDPConn).ReadFrom", Method, 0}, + {"(*UDPConn).ReadFromUDP", Method, 0}, + {"(*UDPConn).ReadFromUDPAddrPort", Method, 18}, + {"(*UDPConn).ReadMsgUDP", Method, 1}, + {"(*UDPConn).ReadMsgUDPAddrPort", Method, 18}, + {"(*UDPConn).RemoteAddr", Method, 0}, + {"(*UDPConn).SetDeadline", Method, 0}, + {"(*UDPConn).SetReadBuffer", Method, 0}, + {"(*UDPConn).SetReadDeadline", Method, 0}, + {"(*UDPConn).SetWriteBuffer", Method, 0}, + {"(*UDPConn).SetWriteDeadline", Method, 0}, + {"(*UDPConn).SyscallConn", Method, 9}, + {"(*UDPConn).Write", Method, 0}, + {"(*UDPConn).WriteMsgUDP", Method, 1}, + {"(*UDPConn).WriteMsgUDPAddrPort", Method, 18}, + {"(*UDPConn).WriteTo", Method, 0}, + {"(*UDPConn).WriteToUDP", Method, 0}, + {"(*UDPConn).WriteToUDPAddrPort", Method, 18}, + {"(*UnixAddr).Network", Method, 0}, + {"(*UnixAddr).String", Method, 0}, + {"(*UnixConn).Close", Method, 0}, + {"(*UnixConn).CloseRead", Method, 1}, + {"(*UnixConn).CloseWrite", Method, 1}, + {"(*UnixConn).File", Method, 0}, + {"(*UnixConn).LocalAddr", Method, 0}, + {"(*UnixConn).Read", Method, 0}, + {"(*UnixConn).ReadFrom", Method, 0}, + {"(*UnixConn).ReadFromUnix", Method, 0}, + {"(*UnixConn).ReadMsgUnix", Method, 0}, + {"(*UnixConn).RemoteAddr", Method, 0}, + {"(*UnixConn).SetDeadline", Method, 0}, + {"(*UnixConn).SetReadBuffer", Method, 0}, + {"(*UnixConn).SetReadDeadline", Method, 0}, + {"(*UnixConn).SetWriteBuffer", Method, 0}, + {"(*UnixConn).SetWriteDeadline", Method, 0}, + {"(*UnixConn).SyscallConn", Method, 9}, + {"(*UnixConn).Write", Method, 0}, + {"(*UnixConn).WriteMsgUnix", Method, 0}, + {"(*UnixConn).WriteTo", Method, 0}, + {"(*UnixConn).WriteToUnix", Method, 0}, + {"(*UnixListener).Accept", Method, 0}, + {"(*UnixListener).AcceptUnix", Method, 0}, + {"(*UnixListener).Addr", Method, 0}, + {"(*UnixListener).Close", Method, 0}, + {"(*UnixListener).File", Method, 0}, + {"(*UnixListener).SetDeadline", Method, 0}, + {"(*UnixListener).SetUnlinkOnClose", Method, 8}, + {"(*UnixListener).SyscallConn", Method, 10}, + {"(Flags).String", Method, 0}, + {"(HardwareAddr).String", Method, 0}, + {"(IP).DefaultMask", Method, 0}, + {"(IP).Equal", Method, 0}, + {"(IP).IsGlobalUnicast", Method, 0}, + {"(IP).IsInterfaceLocalMulticast", Method, 0}, + {"(IP).IsLinkLocalMulticast", Method, 0}, + {"(IP).IsLinkLocalUnicast", Method, 0}, + {"(IP).IsLoopback", Method, 0}, + {"(IP).IsMulticast", Method, 0}, + {"(IP).IsPrivate", Method, 17}, + {"(IP).IsUnspecified", Method, 0}, + {"(IP).MarshalText", Method, 2}, + {"(IP).Mask", Method, 0}, + {"(IP).String", Method, 0}, + {"(IP).To16", Method, 0}, + {"(IP).To4", Method, 0}, + {"(IPMask).Size", Method, 0}, + {"(IPMask).String", Method, 0}, + {"(InvalidAddrError).Error", Method, 0}, + {"(InvalidAddrError).Temporary", Method, 0}, + {"(InvalidAddrError).Timeout", Method, 0}, + {"(UnknownNetworkError).Error", Method, 0}, + {"(UnknownNetworkError).Temporary", Method, 0}, + {"(UnknownNetworkError).Timeout", Method, 0}, + {"Addr", Type, 0}, + {"AddrError", Type, 0}, + {"AddrError.Addr", Field, 0}, + {"AddrError.Err", Field, 0}, + {"Buffers", Type, 8}, + {"CIDRMask", Func, 0}, + {"Conn", Type, 0}, + {"DNSConfigError", Type, 0}, + {"DNSConfigError.Err", Field, 0}, + {"DNSError", Type, 0}, + {"DNSError.Err", Field, 0}, + {"DNSError.IsNotFound", Field, 13}, + {"DNSError.IsTemporary", Field, 6}, + {"DNSError.IsTimeout", Field, 0}, + {"DNSError.Name", Field, 0}, + {"DNSError.Server", Field, 0}, + {"DNSError.UnwrapErr", Field, 23}, + {"DefaultResolver", Var, 8}, + {"Dial", Func, 0}, + {"DialIP", Func, 0}, + {"DialTCP", Func, 0}, + {"DialTimeout", Func, 0}, + {"DialUDP", Func, 0}, + {"DialUnix", Func, 0}, + {"Dialer", Type, 1}, + {"Dialer.Cancel", Field, 6}, + {"Dialer.Control", Field, 11}, + {"Dialer.ControlContext", Field, 20}, + {"Dialer.Deadline", Field, 1}, + {"Dialer.DualStack", Field, 2}, + {"Dialer.FallbackDelay", Field, 5}, + {"Dialer.KeepAlive", Field, 3}, + {"Dialer.KeepAliveConfig", Field, 23}, + {"Dialer.LocalAddr", Field, 1}, + {"Dialer.Resolver", Field, 8}, + {"Dialer.Timeout", Field, 1}, + {"ErrClosed", Var, 16}, + {"ErrWriteToConnected", Var, 0}, + {"Error", Type, 0}, + {"FileConn", Func, 0}, + {"FileListener", Func, 0}, + {"FilePacketConn", Func, 0}, + {"FlagBroadcast", Const, 0}, + {"FlagLoopback", Const, 0}, + {"FlagMulticast", Const, 0}, + {"FlagPointToPoint", Const, 0}, + {"FlagRunning", Const, 20}, + {"FlagUp", Const, 0}, + {"Flags", Type, 0}, + {"HardwareAddr", Type, 0}, + {"IP", Type, 0}, + {"IPAddr", Type, 0}, + {"IPAddr.IP", Field, 0}, + {"IPAddr.Zone", Field, 1}, + {"IPConn", Type, 0}, + {"IPMask", Type, 0}, + {"IPNet", Type, 0}, + {"IPNet.IP", Field, 0}, + {"IPNet.Mask", Field, 0}, + {"IPv4", Func, 0}, + {"IPv4Mask", Func, 0}, + {"IPv4allrouter", Var, 0}, + {"IPv4allsys", Var, 0}, + {"IPv4bcast", Var, 0}, + {"IPv4len", Const, 0}, + {"IPv4zero", Var, 0}, + {"IPv6interfacelocalallnodes", Var, 0}, + {"IPv6len", Const, 0}, + {"IPv6linklocalallnodes", Var, 0}, + {"IPv6linklocalallrouters", Var, 0}, + {"IPv6loopback", Var, 0}, + {"IPv6unspecified", Var, 0}, + {"IPv6zero", Var, 0}, + {"Interface", Type, 0}, + {"Interface.Flags", Field, 0}, + {"Interface.HardwareAddr", Field, 0}, + {"Interface.Index", Field, 0}, + {"Interface.MTU", Field, 0}, + {"Interface.Name", Field, 0}, + {"InterfaceAddrs", Func, 0}, + {"InterfaceByIndex", Func, 0}, + {"InterfaceByName", Func, 0}, + {"Interfaces", Func, 0}, + {"InvalidAddrError", Type, 0}, + {"JoinHostPort", Func, 0}, + {"KeepAliveConfig", Type, 23}, + {"KeepAliveConfig.Count", Field, 23}, + {"KeepAliveConfig.Enable", Field, 23}, + {"KeepAliveConfig.Idle", Field, 23}, + {"KeepAliveConfig.Interval", Field, 23}, + {"Listen", Func, 0}, + {"ListenConfig", Type, 11}, + {"ListenConfig.Control", Field, 11}, + {"ListenConfig.KeepAlive", Field, 13}, + {"ListenConfig.KeepAliveConfig", Field, 23}, + {"ListenIP", Func, 0}, + {"ListenMulticastUDP", Func, 0}, + {"ListenPacket", Func, 0}, + {"ListenTCP", Func, 0}, + {"ListenUDP", Func, 0}, + {"ListenUnix", Func, 0}, + {"ListenUnixgram", Func, 0}, + {"Listener", Type, 0}, + {"LookupAddr", Func, 0}, + {"LookupCNAME", Func, 0}, + {"LookupHost", Func, 0}, + {"LookupIP", Func, 0}, + {"LookupMX", Func, 0}, + {"LookupNS", Func, 1}, + {"LookupPort", Func, 0}, + {"LookupSRV", Func, 0}, + {"LookupTXT", Func, 0}, + {"MX", Type, 0}, + {"MX.Host", Field, 0}, + {"MX.Pref", Field, 0}, + {"NS", Type, 1}, + {"NS.Host", Field, 1}, + {"OpError", Type, 0}, + {"OpError.Addr", Field, 0}, + {"OpError.Err", Field, 0}, + {"OpError.Net", Field, 0}, + {"OpError.Op", Field, 0}, + {"OpError.Source", Field, 5}, + {"PacketConn", Type, 0}, + {"ParseCIDR", Func, 0}, + {"ParseError", Type, 0}, + {"ParseError.Text", Field, 0}, + {"ParseError.Type", Field, 0}, + {"ParseIP", Func, 0}, + {"ParseMAC", Func, 0}, + {"Pipe", Func, 0}, + {"ResolveIPAddr", Func, 0}, + {"ResolveTCPAddr", Func, 0}, + {"ResolveUDPAddr", Func, 0}, + {"ResolveUnixAddr", Func, 0}, + {"Resolver", Type, 8}, + {"Resolver.Dial", Field, 9}, + {"Resolver.PreferGo", Field, 8}, + {"Resolver.StrictErrors", Field, 9}, + {"SRV", Type, 0}, + {"SRV.Port", Field, 0}, + {"SRV.Priority", Field, 0}, + {"SRV.Target", Field, 0}, + {"SRV.Weight", Field, 0}, + {"SplitHostPort", Func, 0}, + {"TCPAddr", Type, 0}, + {"TCPAddr.IP", Field, 0}, + {"TCPAddr.Port", Field, 0}, + {"TCPAddr.Zone", Field, 1}, + {"TCPAddrFromAddrPort", Func, 18}, + {"TCPConn", Type, 0}, + {"TCPListener", Type, 0}, + {"UDPAddr", Type, 0}, + {"UDPAddr.IP", Field, 0}, + {"UDPAddr.Port", Field, 0}, + {"UDPAddr.Zone", Field, 1}, + {"UDPAddrFromAddrPort", Func, 18}, + {"UDPConn", Type, 0}, + {"UnixAddr", Type, 0}, + {"UnixAddr.Name", Field, 0}, + {"UnixAddr.Net", Field, 0}, + {"UnixConn", Type, 0}, + {"UnixListener", Type, 0}, + {"UnknownNetworkError", Type, 0}, + }, + "net/http": { + {"(*Client).CloseIdleConnections", Method, 12}, + {"(*Client).Do", Method, 0}, + {"(*Client).Get", Method, 0}, + {"(*Client).Head", Method, 0}, + {"(*Client).Post", Method, 0}, + {"(*Client).PostForm", Method, 0}, + {"(*Cookie).String", Method, 0}, + {"(*Cookie).Valid", Method, 18}, + {"(*MaxBytesError).Error", Method, 19}, + {"(*ProtocolError).Error", Method, 0}, + {"(*ProtocolError).Is", Method, 21}, + {"(*Request).AddCookie", Method, 0}, + {"(*Request).BasicAuth", Method, 4}, + {"(*Request).Clone", Method, 13}, + {"(*Request).Context", Method, 7}, + {"(*Request).Cookie", Method, 0}, + {"(*Request).Cookies", Method, 0}, + {"(*Request).CookiesNamed", Method, 23}, + {"(*Request).FormFile", Method, 0}, + {"(*Request).FormValue", Method, 0}, + {"(*Request).MultipartReader", Method, 0}, + {"(*Request).ParseForm", Method, 0}, + {"(*Request).ParseMultipartForm", Method, 0}, + {"(*Request).PathValue", Method, 22}, + {"(*Request).PostFormValue", Method, 1}, + {"(*Request).ProtoAtLeast", Method, 0}, + {"(*Request).Referer", Method, 0}, + {"(*Request).SetBasicAuth", Method, 0}, + {"(*Request).SetPathValue", Method, 22}, + {"(*Request).UserAgent", Method, 0}, + {"(*Request).WithContext", Method, 7}, + {"(*Request).Write", Method, 0}, + {"(*Request).WriteProxy", Method, 0}, + {"(*Response).Cookies", Method, 0}, + {"(*Response).Location", Method, 0}, + {"(*Response).ProtoAtLeast", Method, 0}, + {"(*Response).Write", Method, 0}, + {"(*ResponseController).EnableFullDuplex", Method, 21}, + {"(*ResponseController).Flush", Method, 20}, + {"(*ResponseController).Hijack", Method, 20}, + {"(*ResponseController).SetReadDeadline", Method, 20}, + {"(*ResponseController).SetWriteDeadline", Method, 20}, + {"(*ServeMux).Handle", Method, 0}, + {"(*ServeMux).HandleFunc", Method, 0}, + {"(*ServeMux).Handler", Method, 1}, + {"(*ServeMux).ServeHTTP", Method, 0}, + {"(*Server).Close", Method, 8}, + {"(*Server).ListenAndServe", Method, 0}, + {"(*Server).ListenAndServeTLS", Method, 0}, + {"(*Server).RegisterOnShutdown", Method, 9}, + {"(*Server).Serve", Method, 0}, + {"(*Server).ServeTLS", Method, 9}, + {"(*Server).SetKeepAlivesEnabled", Method, 3}, + {"(*Server).Shutdown", Method, 8}, + {"(*Transport).CancelRequest", Method, 1}, + {"(*Transport).Clone", Method, 13}, + {"(*Transport).CloseIdleConnections", Method, 0}, + {"(*Transport).RegisterProtocol", Method, 0}, + {"(*Transport).RoundTrip", Method, 0}, + {"(ConnState).String", Method, 3}, + {"(Dir).Open", Method, 0}, + {"(HandlerFunc).ServeHTTP", Method, 0}, + {"(Header).Add", Method, 0}, + {"(Header).Clone", Method, 13}, + {"(Header).Del", Method, 0}, + {"(Header).Get", Method, 0}, + {"(Header).Set", Method, 0}, + {"(Header).Values", Method, 14}, + {"(Header).Write", Method, 0}, + {"(Header).WriteSubset", Method, 0}, + {"AllowQuerySemicolons", Func, 17}, + {"CanonicalHeaderKey", Func, 0}, + {"Client", Type, 0}, + {"Client.CheckRedirect", Field, 0}, + {"Client.Jar", Field, 0}, + {"Client.Timeout", Field, 3}, + {"Client.Transport", Field, 0}, + {"CloseNotifier", Type, 1}, + {"ConnState", Type, 3}, + {"Cookie", Type, 0}, + {"Cookie.Domain", Field, 0}, + {"Cookie.Expires", Field, 0}, + {"Cookie.HttpOnly", Field, 0}, + {"Cookie.MaxAge", Field, 0}, + {"Cookie.Name", Field, 0}, + {"Cookie.Partitioned", Field, 23}, + {"Cookie.Path", Field, 0}, + {"Cookie.Quoted", Field, 23}, + {"Cookie.Raw", Field, 0}, + {"Cookie.RawExpires", Field, 0}, + {"Cookie.SameSite", Field, 11}, + {"Cookie.Secure", Field, 0}, + {"Cookie.Unparsed", Field, 0}, + {"Cookie.Value", Field, 0}, + {"CookieJar", Type, 0}, + {"DefaultClient", Var, 0}, + {"DefaultMaxHeaderBytes", Const, 0}, + {"DefaultMaxIdleConnsPerHost", Const, 0}, + {"DefaultServeMux", Var, 0}, + {"DefaultTransport", Var, 0}, + {"DetectContentType", Func, 0}, + {"Dir", Type, 0}, + {"ErrAbortHandler", Var, 8}, + {"ErrBodyNotAllowed", Var, 0}, + {"ErrBodyReadAfterClose", Var, 0}, + {"ErrContentLength", Var, 0}, + {"ErrHandlerTimeout", Var, 0}, + {"ErrHeaderTooLong", Var, 0}, + {"ErrHijacked", Var, 0}, + {"ErrLineTooLong", Var, 0}, + {"ErrMissingBoundary", Var, 0}, + {"ErrMissingContentLength", Var, 0}, + {"ErrMissingFile", Var, 0}, + {"ErrNoCookie", Var, 0}, + {"ErrNoLocation", Var, 0}, + {"ErrNotMultipart", Var, 0}, + {"ErrNotSupported", Var, 0}, + {"ErrSchemeMismatch", Var, 21}, + {"ErrServerClosed", Var, 8}, + {"ErrShortBody", Var, 0}, + {"ErrSkipAltProtocol", Var, 6}, + {"ErrUnexpectedTrailer", Var, 0}, + {"ErrUseLastResponse", Var, 7}, + {"ErrWriteAfterFlush", Var, 0}, + {"Error", Func, 0}, + {"FS", Func, 16}, + {"File", Type, 0}, + {"FileServer", Func, 0}, + {"FileServerFS", Func, 22}, + {"FileSystem", Type, 0}, + {"Flusher", Type, 0}, + {"Get", Func, 0}, + {"Handle", Func, 0}, + {"HandleFunc", Func, 0}, + {"Handler", Type, 0}, + {"HandlerFunc", Type, 0}, + {"Head", Func, 0}, + {"Header", Type, 0}, + {"Hijacker", Type, 0}, + {"ListenAndServe", Func, 0}, + {"ListenAndServeTLS", Func, 0}, + {"LocalAddrContextKey", Var, 7}, + {"MaxBytesError", Type, 19}, + {"MaxBytesError.Limit", Field, 19}, + {"MaxBytesHandler", Func, 18}, + {"MaxBytesReader", Func, 0}, + {"MethodConnect", Const, 6}, + {"MethodDelete", Const, 6}, + {"MethodGet", Const, 6}, + {"MethodHead", Const, 6}, + {"MethodOptions", Const, 6}, + {"MethodPatch", Const, 6}, + {"MethodPost", Const, 6}, + {"MethodPut", Const, 6}, + {"MethodTrace", Const, 6}, + {"NewFileTransport", Func, 0}, + {"NewFileTransportFS", Func, 22}, + {"NewRequest", Func, 0}, + {"NewRequestWithContext", Func, 13}, + {"NewResponseController", Func, 20}, + {"NewServeMux", Func, 0}, + {"NoBody", Var, 8}, + {"NotFound", Func, 0}, + {"NotFoundHandler", Func, 0}, + {"ParseCookie", Func, 23}, + {"ParseHTTPVersion", Func, 0}, + {"ParseSetCookie", Func, 23}, + {"ParseTime", Func, 1}, + {"Post", Func, 0}, + {"PostForm", Func, 0}, + {"ProtocolError", Type, 0}, + {"ProtocolError.ErrorString", Field, 0}, + {"ProxyFromEnvironment", Func, 0}, + {"ProxyURL", Func, 0}, + {"PushOptions", Type, 8}, + {"PushOptions.Header", Field, 8}, + {"PushOptions.Method", Field, 8}, + {"Pusher", Type, 8}, + {"ReadRequest", Func, 0}, + {"ReadResponse", Func, 0}, + {"Redirect", Func, 0}, + {"RedirectHandler", Func, 0}, + {"Request", Type, 0}, + {"Request.Body", Field, 0}, + {"Request.Cancel", Field, 5}, + {"Request.Close", Field, 0}, + {"Request.ContentLength", Field, 0}, + {"Request.Form", Field, 0}, + {"Request.GetBody", Field, 8}, + {"Request.Header", Field, 0}, + {"Request.Host", Field, 0}, + {"Request.Method", Field, 0}, + {"Request.MultipartForm", Field, 0}, + {"Request.Pattern", Field, 23}, + {"Request.PostForm", Field, 1}, + {"Request.Proto", Field, 0}, + {"Request.ProtoMajor", Field, 0}, + {"Request.ProtoMinor", Field, 0}, + {"Request.RemoteAddr", Field, 0}, + {"Request.RequestURI", Field, 0}, + {"Request.Response", Field, 7}, + {"Request.TLS", Field, 0}, + {"Request.Trailer", Field, 0}, + {"Request.TransferEncoding", Field, 0}, + {"Request.URL", Field, 0}, + {"Response", Type, 0}, + {"Response.Body", Field, 0}, + {"Response.Close", Field, 0}, + {"Response.ContentLength", Field, 0}, + {"Response.Header", Field, 0}, + {"Response.Proto", Field, 0}, + {"Response.ProtoMajor", Field, 0}, + {"Response.ProtoMinor", Field, 0}, + {"Response.Request", Field, 0}, + {"Response.Status", Field, 0}, + {"Response.StatusCode", Field, 0}, + {"Response.TLS", Field, 3}, + {"Response.Trailer", Field, 0}, + {"Response.TransferEncoding", Field, 0}, + {"Response.Uncompressed", Field, 7}, + {"ResponseController", Type, 20}, + {"ResponseWriter", Type, 0}, + {"RoundTripper", Type, 0}, + {"SameSite", Type, 11}, + {"SameSiteDefaultMode", Const, 11}, + {"SameSiteLaxMode", Const, 11}, + {"SameSiteNoneMode", Const, 13}, + {"SameSiteStrictMode", Const, 11}, + {"Serve", Func, 0}, + {"ServeContent", Func, 0}, + {"ServeFile", Func, 0}, + {"ServeFileFS", Func, 22}, + {"ServeMux", Type, 0}, + {"ServeTLS", Func, 9}, + {"Server", Type, 0}, + {"Server.Addr", Field, 0}, + {"Server.BaseContext", Field, 13}, + {"Server.ConnContext", Field, 13}, + {"Server.ConnState", Field, 3}, + {"Server.DisableGeneralOptionsHandler", Field, 20}, + {"Server.ErrorLog", Field, 3}, + {"Server.Handler", Field, 0}, + {"Server.IdleTimeout", Field, 8}, + {"Server.MaxHeaderBytes", Field, 0}, + {"Server.ReadHeaderTimeout", Field, 8}, + {"Server.ReadTimeout", Field, 0}, + {"Server.TLSConfig", Field, 0}, + {"Server.TLSNextProto", Field, 1}, + {"Server.WriteTimeout", Field, 0}, + {"ServerContextKey", Var, 7}, + {"SetCookie", Func, 0}, + {"StateActive", Const, 3}, + {"StateClosed", Const, 3}, + {"StateHijacked", Const, 3}, + {"StateIdle", Const, 3}, + {"StateNew", Const, 3}, + {"StatusAccepted", Const, 0}, + {"StatusAlreadyReported", Const, 7}, + {"StatusBadGateway", Const, 0}, + {"StatusBadRequest", Const, 0}, + {"StatusConflict", Const, 0}, + {"StatusContinue", Const, 0}, + {"StatusCreated", Const, 0}, + {"StatusEarlyHints", Const, 13}, + {"StatusExpectationFailed", Const, 0}, + {"StatusFailedDependency", Const, 7}, + {"StatusForbidden", Const, 0}, + {"StatusFound", Const, 0}, + {"StatusGatewayTimeout", Const, 0}, + {"StatusGone", Const, 0}, + {"StatusHTTPVersionNotSupported", Const, 0}, + {"StatusIMUsed", Const, 7}, + {"StatusInsufficientStorage", Const, 7}, + {"StatusInternalServerError", Const, 0}, + {"StatusLengthRequired", Const, 0}, + {"StatusLocked", Const, 7}, + {"StatusLoopDetected", Const, 7}, + {"StatusMethodNotAllowed", Const, 0}, + {"StatusMisdirectedRequest", Const, 11}, + {"StatusMovedPermanently", Const, 0}, + {"StatusMultiStatus", Const, 7}, + {"StatusMultipleChoices", Const, 0}, + {"StatusNetworkAuthenticationRequired", Const, 6}, + {"StatusNoContent", Const, 0}, + {"StatusNonAuthoritativeInfo", Const, 0}, + {"StatusNotAcceptable", Const, 0}, + {"StatusNotExtended", Const, 7}, + {"StatusNotFound", Const, 0}, + {"StatusNotImplemented", Const, 0}, + {"StatusNotModified", Const, 0}, + {"StatusOK", Const, 0}, + {"StatusPartialContent", Const, 0}, + {"StatusPaymentRequired", Const, 0}, + {"StatusPermanentRedirect", Const, 7}, + {"StatusPreconditionFailed", Const, 0}, + {"StatusPreconditionRequired", Const, 6}, + {"StatusProcessing", Const, 7}, + {"StatusProxyAuthRequired", Const, 0}, + {"StatusRequestEntityTooLarge", Const, 0}, + {"StatusRequestHeaderFieldsTooLarge", Const, 6}, + {"StatusRequestTimeout", Const, 0}, + {"StatusRequestURITooLong", Const, 0}, + {"StatusRequestedRangeNotSatisfiable", Const, 0}, + {"StatusResetContent", Const, 0}, + {"StatusSeeOther", Const, 0}, + {"StatusServiceUnavailable", Const, 0}, + {"StatusSwitchingProtocols", Const, 0}, + {"StatusTeapot", Const, 0}, + {"StatusTemporaryRedirect", Const, 0}, + {"StatusText", Func, 0}, + {"StatusTooEarly", Const, 12}, + {"StatusTooManyRequests", Const, 6}, + {"StatusUnauthorized", Const, 0}, + {"StatusUnavailableForLegalReasons", Const, 6}, + {"StatusUnprocessableEntity", Const, 7}, + {"StatusUnsupportedMediaType", Const, 0}, + {"StatusUpgradeRequired", Const, 7}, + {"StatusUseProxy", Const, 0}, + {"StatusVariantAlsoNegotiates", Const, 7}, + {"StripPrefix", Func, 0}, + {"TimeFormat", Const, 0}, + {"TimeoutHandler", Func, 0}, + {"TrailerPrefix", Const, 8}, + {"Transport", Type, 0}, + {"Transport.Dial", Field, 0}, + {"Transport.DialContext", Field, 7}, + {"Transport.DialTLS", Field, 4}, + {"Transport.DialTLSContext", Field, 14}, + {"Transport.DisableCompression", Field, 0}, + {"Transport.DisableKeepAlives", Field, 0}, + {"Transport.ExpectContinueTimeout", Field, 6}, + {"Transport.ForceAttemptHTTP2", Field, 13}, + {"Transport.GetProxyConnectHeader", Field, 16}, + {"Transport.IdleConnTimeout", Field, 7}, + {"Transport.MaxConnsPerHost", Field, 11}, + {"Transport.MaxIdleConns", Field, 7}, + {"Transport.MaxIdleConnsPerHost", Field, 0}, + {"Transport.MaxResponseHeaderBytes", Field, 7}, + {"Transport.OnProxyConnectResponse", Field, 20}, + {"Transport.Proxy", Field, 0}, + {"Transport.ProxyConnectHeader", Field, 8}, + {"Transport.ReadBufferSize", Field, 13}, + {"Transport.ResponseHeaderTimeout", Field, 1}, + {"Transport.TLSClientConfig", Field, 0}, + {"Transport.TLSHandshakeTimeout", Field, 3}, + {"Transport.TLSNextProto", Field, 6}, + {"Transport.WriteBufferSize", Field, 13}, + }, + "net/http/cgi": { + {"(*Handler).ServeHTTP", Method, 0}, + {"Handler", Type, 0}, + {"Handler.Args", Field, 0}, + {"Handler.Dir", Field, 0}, + {"Handler.Env", Field, 0}, + {"Handler.InheritEnv", Field, 0}, + {"Handler.Logger", Field, 0}, + {"Handler.Path", Field, 0}, + {"Handler.PathLocationHandler", Field, 0}, + {"Handler.Root", Field, 0}, + {"Handler.Stderr", Field, 7}, + {"Request", Func, 0}, + {"RequestFromMap", Func, 0}, + {"Serve", Func, 0}, + }, + "net/http/cookiejar": { + {"(*Jar).Cookies", Method, 1}, + {"(*Jar).SetCookies", Method, 1}, + {"Jar", Type, 1}, + {"New", Func, 1}, + {"Options", Type, 1}, + {"Options.PublicSuffixList", Field, 1}, + {"PublicSuffixList", Type, 1}, + }, + "net/http/fcgi": { + {"ErrConnClosed", Var, 5}, + {"ErrRequestAborted", Var, 5}, + {"ProcessEnv", Func, 9}, + {"Serve", Func, 0}, + }, + "net/http/httptest": { + {"(*ResponseRecorder).Flush", Method, 0}, + {"(*ResponseRecorder).Header", Method, 0}, + {"(*ResponseRecorder).Result", Method, 7}, + {"(*ResponseRecorder).Write", Method, 0}, + {"(*ResponseRecorder).WriteHeader", Method, 0}, + {"(*ResponseRecorder).WriteString", Method, 6}, + {"(*Server).Certificate", Method, 9}, + {"(*Server).Client", Method, 9}, + {"(*Server).Close", Method, 0}, + {"(*Server).CloseClientConnections", Method, 0}, + {"(*Server).Start", Method, 0}, + {"(*Server).StartTLS", Method, 0}, + {"DefaultRemoteAddr", Const, 0}, + {"NewRecorder", Func, 0}, + {"NewRequest", Func, 7}, + {"NewRequestWithContext", Func, 23}, + {"NewServer", Func, 0}, + {"NewTLSServer", Func, 0}, + {"NewUnstartedServer", Func, 0}, + {"ResponseRecorder", Type, 0}, + {"ResponseRecorder.Body", Field, 0}, + {"ResponseRecorder.Code", Field, 0}, + {"ResponseRecorder.Flushed", Field, 0}, + {"ResponseRecorder.HeaderMap", Field, 0}, + {"Server", Type, 0}, + {"Server.Config", Field, 0}, + {"Server.EnableHTTP2", Field, 14}, + {"Server.Listener", Field, 0}, + {"Server.TLS", Field, 0}, + {"Server.URL", Field, 0}, + }, + "net/http/httptrace": { + {"ClientTrace", Type, 7}, + {"ClientTrace.ConnectDone", Field, 7}, + {"ClientTrace.ConnectStart", Field, 7}, + {"ClientTrace.DNSDone", Field, 7}, + {"ClientTrace.DNSStart", Field, 7}, + {"ClientTrace.GetConn", Field, 7}, + {"ClientTrace.Got100Continue", Field, 7}, + {"ClientTrace.Got1xxResponse", Field, 11}, + {"ClientTrace.GotConn", Field, 7}, + {"ClientTrace.GotFirstResponseByte", Field, 7}, + {"ClientTrace.PutIdleConn", Field, 7}, + {"ClientTrace.TLSHandshakeDone", Field, 8}, + {"ClientTrace.TLSHandshakeStart", Field, 8}, + {"ClientTrace.Wait100Continue", Field, 7}, + {"ClientTrace.WroteHeaderField", Field, 11}, + {"ClientTrace.WroteHeaders", Field, 7}, + {"ClientTrace.WroteRequest", Field, 7}, + {"ContextClientTrace", Func, 7}, + {"DNSDoneInfo", Type, 7}, + {"DNSDoneInfo.Addrs", Field, 7}, + {"DNSDoneInfo.Coalesced", Field, 7}, + {"DNSDoneInfo.Err", Field, 7}, + {"DNSStartInfo", Type, 7}, + {"DNSStartInfo.Host", Field, 7}, + {"GotConnInfo", Type, 7}, + {"GotConnInfo.Conn", Field, 7}, + {"GotConnInfo.IdleTime", Field, 7}, + {"GotConnInfo.Reused", Field, 7}, + {"GotConnInfo.WasIdle", Field, 7}, + {"WithClientTrace", Func, 7}, + {"WroteRequestInfo", Type, 7}, + {"WroteRequestInfo.Err", Field, 7}, + }, + "net/http/httputil": { + {"(*ClientConn).Close", Method, 0}, + {"(*ClientConn).Do", Method, 0}, + {"(*ClientConn).Hijack", Method, 0}, + {"(*ClientConn).Pending", Method, 0}, + {"(*ClientConn).Read", Method, 0}, + {"(*ClientConn).Write", Method, 0}, + {"(*ProxyRequest).SetURL", Method, 20}, + {"(*ProxyRequest).SetXForwarded", Method, 20}, + {"(*ReverseProxy).ServeHTTP", Method, 0}, + {"(*ServerConn).Close", Method, 0}, + {"(*ServerConn).Hijack", Method, 0}, + {"(*ServerConn).Pending", Method, 0}, + {"(*ServerConn).Read", Method, 0}, + {"(*ServerConn).Write", Method, 0}, + {"BufferPool", Type, 6}, + {"ClientConn", Type, 0}, + {"DumpRequest", Func, 0}, + {"DumpRequestOut", Func, 0}, + {"DumpResponse", Func, 0}, + {"ErrClosed", Var, 0}, + {"ErrLineTooLong", Var, 0}, + {"ErrPersistEOF", Var, 0}, + {"ErrPipeline", Var, 0}, + {"NewChunkedReader", Func, 0}, + {"NewChunkedWriter", Func, 0}, + {"NewClientConn", Func, 0}, + {"NewProxyClientConn", Func, 0}, + {"NewServerConn", Func, 0}, + {"NewSingleHostReverseProxy", Func, 0}, + {"ProxyRequest", Type, 20}, + {"ProxyRequest.In", Field, 20}, + {"ProxyRequest.Out", Field, 20}, + {"ReverseProxy", Type, 0}, + {"ReverseProxy.BufferPool", Field, 6}, + {"ReverseProxy.Director", Field, 0}, + {"ReverseProxy.ErrorHandler", Field, 11}, + {"ReverseProxy.ErrorLog", Field, 4}, + {"ReverseProxy.FlushInterval", Field, 0}, + {"ReverseProxy.ModifyResponse", Field, 8}, + {"ReverseProxy.Rewrite", Field, 20}, + {"ReverseProxy.Transport", Field, 0}, + {"ServerConn", Type, 0}, + }, + "net/http/pprof": { + {"Cmdline", Func, 0}, + {"Handler", Func, 0}, + {"Index", Func, 0}, + {"Profile", Func, 0}, + {"Symbol", Func, 0}, + {"Trace", Func, 5}, + }, + "net/mail": { + {"(*Address).String", Method, 0}, + {"(*AddressParser).Parse", Method, 5}, + {"(*AddressParser).ParseList", Method, 5}, + {"(Header).AddressList", Method, 0}, + {"(Header).Date", Method, 0}, + {"(Header).Get", Method, 0}, + {"Address", Type, 0}, + {"Address.Address", Field, 0}, + {"Address.Name", Field, 0}, + {"AddressParser", Type, 5}, + {"AddressParser.WordDecoder", Field, 5}, + {"ErrHeaderNotPresent", Var, 0}, + {"Header", Type, 0}, + {"Message", Type, 0}, + {"Message.Body", Field, 0}, + {"Message.Header", Field, 0}, + {"ParseAddress", Func, 1}, + {"ParseAddressList", Func, 1}, + {"ParseDate", Func, 8}, + {"ReadMessage", Func, 0}, + }, + "net/netip": { + {"(*Addr).UnmarshalBinary", Method, 18}, + {"(*Addr).UnmarshalText", Method, 18}, + {"(*AddrPort).UnmarshalBinary", Method, 18}, + {"(*AddrPort).UnmarshalText", Method, 18}, + {"(*Prefix).UnmarshalBinary", Method, 18}, + {"(*Prefix).UnmarshalText", Method, 18}, + {"(Addr).AppendTo", Method, 18}, + {"(Addr).As16", Method, 18}, + {"(Addr).As4", Method, 18}, + {"(Addr).AsSlice", Method, 18}, + {"(Addr).BitLen", Method, 18}, + {"(Addr).Compare", Method, 18}, + {"(Addr).Is4", Method, 18}, + {"(Addr).Is4In6", Method, 18}, + {"(Addr).Is6", Method, 18}, + {"(Addr).IsGlobalUnicast", Method, 18}, + {"(Addr).IsInterfaceLocalMulticast", Method, 18}, + {"(Addr).IsLinkLocalMulticast", Method, 18}, + {"(Addr).IsLinkLocalUnicast", Method, 18}, + {"(Addr).IsLoopback", Method, 18}, + {"(Addr).IsMulticast", Method, 18}, + {"(Addr).IsPrivate", Method, 18}, + {"(Addr).IsUnspecified", Method, 18}, + {"(Addr).IsValid", Method, 18}, + {"(Addr).Less", Method, 18}, + {"(Addr).MarshalBinary", Method, 18}, + {"(Addr).MarshalText", Method, 18}, + {"(Addr).Next", Method, 18}, + {"(Addr).Prefix", Method, 18}, + {"(Addr).Prev", Method, 18}, + {"(Addr).String", Method, 18}, + {"(Addr).StringExpanded", Method, 18}, + {"(Addr).Unmap", Method, 18}, + {"(Addr).WithZone", Method, 18}, + {"(Addr).Zone", Method, 18}, + {"(AddrPort).Addr", Method, 18}, + {"(AddrPort).AppendTo", Method, 18}, + {"(AddrPort).Compare", Method, 22}, + {"(AddrPort).IsValid", Method, 18}, + {"(AddrPort).MarshalBinary", Method, 18}, + {"(AddrPort).MarshalText", Method, 18}, + {"(AddrPort).Port", Method, 18}, + {"(AddrPort).String", Method, 18}, + {"(Prefix).Addr", Method, 18}, + {"(Prefix).AppendTo", Method, 18}, + {"(Prefix).Bits", Method, 18}, + {"(Prefix).Contains", Method, 18}, + {"(Prefix).IsSingleIP", Method, 18}, + {"(Prefix).IsValid", Method, 18}, + {"(Prefix).MarshalBinary", Method, 18}, + {"(Prefix).MarshalText", Method, 18}, + {"(Prefix).Masked", Method, 18}, + {"(Prefix).Overlaps", Method, 18}, + {"(Prefix).String", Method, 18}, + {"Addr", Type, 18}, + {"AddrFrom16", Func, 18}, + {"AddrFrom4", Func, 18}, + {"AddrFromSlice", Func, 18}, + {"AddrPort", Type, 18}, + {"AddrPortFrom", Func, 18}, + {"IPv4Unspecified", Func, 18}, + {"IPv6LinkLocalAllNodes", Func, 18}, + {"IPv6LinkLocalAllRouters", Func, 20}, + {"IPv6Loopback", Func, 20}, + {"IPv6Unspecified", Func, 18}, + {"MustParseAddr", Func, 18}, + {"MustParseAddrPort", Func, 18}, + {"MustParsePrefix", Func, 18}, + {"ParseAddr", Func, 18}, + {"ParseAddrPort", Func, 18}, + {"ParsePrefix", Func, 18}, + {"Prefix", Type, 18}, + {"PrefixFrom", Func, 18}, + }, + "net/rpc": { + {"(*Client).Call", Method, 0}, + {"(*Client).Close", Method, 0}, + {"(*Client).Go", Method, 0}, + {"(*Server).Accept", Method, 0}, + {"(*Server).HandleHTTP", Method, 0}, + {"(*Server).Register", Method, 0}, + {"(*Server).RegisterName", Method, 0}, + {"(*Server).ServeCodec", Method, 0}, + {"(*Server).ServeConn", Method, 0}, + {"(*Server).ServeHTTP", Method, 0}, + {"(*Server).ServeRequest", Method, 0}, + {"(ServerError).Error", Method, 0}, + {"Accept", Func, 0}, + {"Call", Type, 0}, + {"Call.Args", Field, 0}, + {"Call.Done", Field, 0}, + {"Call.Error", Field, 0}, + {"Call.Reply", Field, 0}, + {"Call.ServiceMethod", Field, 0}, + {"Client", Type, 0}, + {"ClientCodec", Type, 0}, + {"DefaultDebugPath", Const, 0}, + {"DefaultRPCPath", Const, 0}, + {"DefaultServer", Var, 0}, + {"Dial", Func, 0}, + {"DialHTTP", Func, 0}, + {"DialHTTPPath", Func, 0}, + {"ErrShutdown", Var, 0}, + {"HandleHTTP", Func, 0}, + {"NewClient", Func, 0}, + {"NewClientWithCodec", Func, 0}, + {"NewServer", Func, 0}, + {"Register", Func, 0}, + {"RegisterName", Func, 0}, + {"Request", Type, 0}, + {"Request.Seq", Field, 0}, + {"Request.ServiceMethod", Field, 0}, + {"Response", Type, 0}, + {"Response.Error", Field, 0}, + {"Response.Seq", Field, 0}, + {"Response.ServiceMethod", Field, 0}, + {"ServeCodec", Func, 0}, + {"ServeConn", Func, 0}, + {"ServeRequest", Func, 0}, + {"Server", Type, 0}, + {"ServerCodec", Type, 0}, + {"ServerError", Type, 0}, + }, + "net/rpc/jsonrpc": { + {"Dial", Func, 0}, + {"NewClient", Func, 0}, + {"NewClientCodec", Func, 0}, + {"NewServerCodec", Func, 0}, + {"ServeConn", Func, 0}, + }, + "net/smtp": { + {"(*Client).Auth", Method, 0}, + {"(*Client).Close", Method, 2}, + {"(*Client).Data", Method, 0}, + {"(*Client).Extension", Method, 0}, + {"(*Client).Hello", Method, 1}, + {"(*Client).Mail", Method, 0}, + {"(*Client).Noop", Method, 10}, + {"(*Client).Quit", Method, 0}, + {"(*Client).Rcpt", Method, 0}, + {"(*Client).Reset", Method, 0}, + {"(*Client).StartTLS", Method, 0}, + {"(*Client).TLSConnectionState", Method, 5}, + {"(*Client).Verify", Method, 0}, + {"Auth", Type, 0}, + {"CRAMMD5Auth", Func, 0}, + {"Client", Type, 0}, + {"Client.Text", Field, 0}, + {"Dial", Func, 0}, + {"NewClient", Func, 0}, + {"PlainAuth", Func, 0}, + {"SendMail", Func, 0}, + {"ServerInfo", Type, 0}, + {"ServerInfo.Auth", Field, 0}, + {"ServerInfo.Name", Field, 0}, + {"ServerInfo.TLS", Field, 0}, + }, + "net/textproto": { + {"(*Conn).Close", Method, 0}, + {"(*Conn).Cmd", Method, 0}, + {"(*Conn).DotReader", Method, 0}, + {"(*Conn).DotWriter", Method, 0}, + {"(*Conn).EndRequest", Method, 0}, + {"(*Conn).EndResponse", Method, 0}, + {"(*Conn).Next", Method, 0}, + {"(*Conn).PrintfLine", Method, 0}, + {"(*Conn).ReadCodeLine", Method, 0}, + {"(*Conn).ReadContinuedLine", Method, 0}, + {"(*Conn).ReadContinuedLineBytes", Method, 0}, + {"(*Conn).ReadDotBytes", Method, 0}, + {"(*Conn).ReadDotLines", Method, 0}, + {"(*Conn).ReadLine", Method, 0}, + {"(*Conn).ReadLineBytes", Method, 0}, + {"(*Conn).ReadMIMEHeader", Method, 0}, + {"(*Conn).ReadResponse", Method, 0}, + {"(*Conn).StartRequest", Method, 0}, + {"(*Conn).StartResponse", Method, 0}, + {"(*Error).Error", Method, 0}, + {"(*Pipeline).EndRequest", Method, 0}, + {"(*Pipeline).EndResponse", Method, 0}, + {"(*Pipeline).Next", Method, 0}, + {"(*Pipeline).StartRequest", Method, 0}, + {"(*Pipeline).StartResponse", Method, 0}, + {"(*Reader).DotReader", Method, 0}, + {"(*Reader).ReadCodeLine", Method, 0}, + {"(*Reader).ReadContinuedLine", Method, 0}, + {"(*Reader).ReadContinuedLineBytes", Method, 0}, + {"(*Reader).ReadDotBytes", Method, 0}, + {"(*Reader).ReadDotLines", Method, 0}, + {"(*Reader).ReadLine", Method, 0}, + {"(*Reader).ReadLineBytes", Method, 0}, + {"(*Reader).ReadMIMEHeader", Method, 0}, + {"(*Reader).ReadResponse", Method, 0}, + {"(*Writer).DotWriter", Method, 0}, + {"(*Writer).PrintfLine", Method, 0}, + {"(MIMEHeader).Add", Method, 0}, + {"(MIMEHeader).Del", Method, 0}, + {"(MIMEHeader).Get", Method, 0}, + {"(MIMEHeader).Set", Method, 0}, + {"(MIMEHeader).Values", Method, 14}, + {"(ProtocolError).Error", Method, 0}, + {"CanonicalMIMEHeaderKey", Func, 0}, + {"Conn", Type, 0}, + {"Conn.Pipeline", Field, 0}, + {"Conn.Reader", Field, 0}, + {"Conn.Writer", Field, 0}, + {"Dial", Func, 0}, + {"Error", Type, 0}, + {"Error.Code", Field, 0}, + {"Error.Msg", Field, 0}, + {"MIMEHeader", Type, 0}, + {"NewConn", Func, 0}, + {"NewReader", Func, 0}, + {"NewWriter", Func, 0}, + {"Pipeline", Type, 0}, + {"ProtocolError", Type, 0}, + {"Reader", Type, 0}, + {"Reader.R", Field, 0}, + {"TrimBytes", Func, 1}, + {"TrimString", Func, 1}, + {"Writer", Type, 0}, + {"Writer.W", Field, 0}, + }, + "net/url": { + {"(*Error).Error", Method, 0}, + {"(*Error).Temporary", Method, 6}, + {"(*Error).Timeout", Method, 6}, + {"(*Error).Unwrap", Method, 13}, + {"(*URL).EscapedFragment", Method, 15}, + {"(*URL).EscapedPath", Method, 5}, + {"(*URL).Hostname", Method, 8}, + {"(*URL).IsAbs", Method, 0}, + {"(*URL).JoinPath", Method, 19}, + {"(*URL).MarshalBinary", Method, 8}, + {"(*URL).Parse", Method, 0}, + {"(*URL).Port", Method, 8}, + {"(*URL).Query", Method, 0}, + {"(*URL).Redacted", Method, 15}, + {"(*URL).RequestURI", Method, 0}, + {"(*URL).ResolveReference", Method, 0}, + {"(*URL).String", Method, 0}, + {"(*URL).UnmarshalBinary", Method, 8}, + {"(*Userinfo).Password", Method, 0}, + {"(*Userinfo).String", Method, 0}, + {"(*Userinfo).Username", Method, 0}, + {"(EscapeError).Error", Method, 0}, + {"(InvalidHostError).Error", Method, 6}, + {"(Values).Add", Method, 0}, + {"(Values).Del", Method, 0}, + {"(Values).Encode", Method, 0}, + {"(Values).Get", Method, 0}, + {"(Values).Has", Method, 17}, + {"(Values).Set", Method, 0}, + {"Error", Type, 0}, + {"Error.Err", Field, 0}, + {"Error.Op", Field, 0}, + {"Error.URL", Field, 0}, + {"EscapeError", Type, 0}, + {"InvalidHostError", Type, 6}, + {"JoinPath", Func, 19}, + {"Parse", Func, 0}, + {"ParseQuery", Func, 0}, + {"ParseRequestURI", Func, 0}, + {"PathEscape", Func, 8}, + {"PathUnescape", Func, 8}, + {"QueryEscape", Func, 0}, + {"QueryUnescape", Func, 0}, + {"URL", Type, 0}, + {"URL.ForceQuery", Field, 7}, + {"URL.Fragment", Field, 0}, + {"URL.Host", Field, 0}, + {"URL.OmitHost", Field, 19}, + {"URL.Opaque", Field, 0}, + {"URL.Path", Field, 0}, + {"URL.RawFragment", Field, 15}, + {"URL.RawPath", Field, 5}, + {"URL.RawQuery", Field, 0}, + {"URL.Scheme", Field, 0}, + {"URL.User", Field, 0}, + {"User", Func, 0}, + {"UserPassword", Func, 0}, + {"Userinfo", Type, 0}, + {"Values", Type, 0}, + }, + "os": { + {"(*File).Chdir", Method, 0}, + {"(*File).Chmod", Method, 0}, + {"(*File).Chown", Method, 0}, + {"(*File).Close", Method, 0}, + {"(*File).Fd", Method, 0}, + {"(*File).Name", Method, 0}, + {"(*File).Read", Method, 0}, + {"(*File).ReadAt", Method, 0}, + {"(*File).ReadDir", Method, 16}, + {"(*File).ReadFrom", Method, 15}, + {"(*File).Readdir", Method, 0}, + {"(*File).Readdirnames", Method, 0}, + {"(*File).Seek", Method, 0}, + {"(*File).SetDeadline", Method, 10}, + {"(*File).SetReadDeadline", Method, 10}, + {"(*File).SetWriteDeadline", Method, 10}, + {"(*File).Stat", Method, 0}, + {"(*File).Sync", Method, 0}, + {"(*File).SyscallConn", Method, 12}, + {"(*File).Truncate", Method, 0}, + {"(*File).Write", Method, 0}, + {"(*File).WriteAt", Method, 0}, + {"(*File).WriteString", Method, 0}, + {"(*File).WriteTo", Method, 22}, + {"(*LinkError).Error", Method, 0}, + {"(*LinkError).Unwrap", Method, 13}, + {"(*PathError).Error", Method, 0}, + {"(*PathError).Timeout", Method, 10}, + {"(*PathError).Unwrap", Method, 13}, + {"(*Process).Kill", Method, 0}, + {"(*Process).Release", Method, 0}, + {"(*Process).Signal", Method, 0}, + {"(*Process).Wait", Method, 0}, + {"(*ProcessState).ExitCode", Method, 12}, + {"(*ProcessState).Exited", Method, 0}, + {"(*ProcessState).Pid", Method, 0}, + {"(*ProcessState).String", Method, 0}, + {"(*ProcessState).Success", Method, 0}, + {"(*ProcessState).Sys", Method, 0}, + {"(*ProcessState).SysUsage", Method, 0}, + {"(*ProcessState).SystemTime", Method, 0}, + {"(*ProcessState).UserTime", Method, 0}, + {"(*SyscallError).Error", Method, 0}, + {"(*SyscallError).Timeout", Method, 10}, + {"(*SyscallError).Unwrap", Method, 13}, + {"(FileMode).IsDir", Method, 0}, + {"(FileMode).IsRegular", Method, 1}, + {"(FileMode).Perm", Method, 0}, + {"(FileMode).String", Method, 0}, + {"Args", Var, 0}, + {"Chdir", Func, 0}, + {"Chmod", Func, 0}, + {"Chown", Func, 0}, + {"Chtimes", Func, 0}, + {"Clearenv", Func, 0}, + {"CopyFS", Func, 23}, + {"Create", Func, 0}, + {"CreateTemp", Func, 16}, + {"DevNull", Const, 0}, + {"DirEntry", Type, 16}, + {"DirFS", Func, 16}, + {"Environ", Func, 0}, + {"ErrClosed", Var, 8}, + {"ErrDeadlineExceeded", Var, 15}, + {"ErrExist", Var, 0}, + {"ErrInvalid", Var, 0}, + {"ErrNoDeadline", Var, 10}, + {"ErrNotExist", Var, 0}, + {"ErrPermission", Var, 0}, + {"ErrProcessDone", Var, 16}, + {"Executable", Func, 8}, + {"Exit", Func, 0}, + {"Expand", Func, 0}, + {"ExpandEnv", Func, 0}, + {"File", Type, 0}, + {"FileInfo", Type, 0}, + {"FileMode", Type, 0}, + {"FindProcess", Func, 0}, + {"Getegid", Func, 0}, + {"Getenv", Func, 0}, + {"Geteuid", Func, 0}, + {"Getgid", Func, 0}, + {"Getgroups", Func, 0}, + {"Getpagesize", Func, 0}, + {"Getpid", Func, 0}, + {"Getppid", Func, 0}, + {"Getuid", Func, 0}, + {"Getwd", Func, 0}, + {"Hostname", Func, 0}, + {"Interrupt", Var, 0}, + {"IsExist", Func, 0}, + {"IsNotExist", Func, 0}, + {"IsPathSeparator", Func, 0}, + {"IsPermission", Func, 0}, + {"IsTimeout", Func, 10}, + {"Kill", Var, 0}, + {"Lchown", Func, 0}, + {"Link", Func, 0}, + {"LinkError", Type, 0}, + {"LinkError.Err", Field, 0}, + {"LinkError.New", Field, 0}, + {"LinkError.Old", Field, 0}, + {"LinkError.Op", Field, 0}, + {"LookupEnv", Func, 5}, + {"Lstat", Func, 0}, + {"Mkdir", Func, 0}, + {"MkdirAll", Func, 0}, + {"MkdirTemp", Func, 16}, + {"ModeAppend", Const, 0}, + {"ModeCharDevice", Const, 0}, + {"ModeDevice", Const, 0}, + {"ModeDir", Const, 0}, + {"ModeExclusive", Const, 0}, + {"ModeIrregular", Const, 11}, + {"ModeNamedPipe", Const, 0}, + {"ModePerm", Const, 0}, + {"ModeSetgid", Const, 0}, + {"ModeSetuid", Const, 0}, + {"ModeSocket", Const, 0}, + {"ModeSticky", Const, 0}, + {"ModeSymlink", Const, 0}, + {"ModeTemporary", Const, 0}, + {"ModeType", Const, 0}, + {"NewFile", Func, 0}, + {"NewSyscallError", Func, 0}, + {"O_APPEND", Const, 0}, + {"O_CREATE", Const, 0}, + {"O_EXCL", Const, 0}, + {"O_RDONLY", Const, 0}, + {"O_RDWR", Const, 0}, + {"O_SYNC", Const, 0}, + {"O_TRUNC", Const, 0}, + {"O_WRONLY", Const, 0}, + {"Open", Func, 0}, + {"OpenFile", Func, 0}, + {"PathError", Type, 0}, + {"PathError.Err", Field, 0}, + {"PathError.Op", Field, 0}, + {"PathError.Path", Field, 0}, + {"PathListSeparator", Const, 0}, + {"PathSeparator", Const, 0}, + {"Pipe", Func, 0}, + {"ProcAttr", Type, 0}, + {"ProcAttr.Dir", Field, 0}, + {"ProcAttr.Env", Field, 0}, + {"ProcAttr.Files", Field, 0}, + {"ProcAttr.Sys", Field, 0}, + {"Process", Type, 0}, + {"Process.Pid", Field, 0}, + {"ProcessState", Type, 0}, + {"ReadDir", Func, 16}, + {"ReadFile", Func, 16}, + {"Readlink", Func, 0}, + {"Remove", Func, 0}, + {"RemoveAll", Func, 0}, + {"Rename", Func, 0}, + {"SEEK_CUR", Const, 0}, + {"SEEK_END", Const, 0}, + {"SEEK_SET", Const, 0}, + {"SameFile", Func, 0}, + {"Setenv", Func, 0}, + {"Signal", Type, 0}, + {"StartProcess", Func, 0}, + {"Stat", Func, 0}, + {"Stderr", Var, 0}, + {"Stdin", Var, 0}, + {"Stdout", Var, 0}, + {"Symlink", Func, 0}, + {"SyscallError", Type, 0}, + {"SyscallError.Err", Field, 0}, + {"SyscallError.Syscall", Field, 0}, + {"TempDir", Func, 0}, + {"Truncate", Func, 0}, + {"Unsetenv", Func, 4}, + {"UserCacheDir", Func, 11}, + {"UserConfigDir", Func, 13}, + {"UserHomeDir", Func, 12}, + {"WriteFile", Func, 16}, + }, + "os/exec": { + {"(*Cmd).CombinedOutput", Method, 0}, + {"(*Cmd).Environ", Method, 19}, + {"(*Cmd).Output", Method, 0}, + {"(*Cmd).Run", Method, 0}, + {"(*Cmd).Start", Method, 0}, + {"(*Cmd).StderrPipe", Method, 0}, + {"(*Cmd).StdinPipe", Method, 0}, + {"(*Cmd).StdoutPipe", Method, 0}, + {"(*Cmd).String", Method, 13}, + {"(*Cmd).Wait", Method, 0}, + {"(*Error).Error", Method, 0}, + {"(*Error).Unwrap", Method, 13}, + {"(*ExitError).Error", Method, 0}, + {"(ExitError).ExitCode", Method, 12}, + {"(ExitError).Exited", Method, 0}, + {"(ExitError).Pid", Method, 0}, + {"(ExitError).String", Method, 0}, + {"(ExitError).Success", Method, 0}, + {"(ExitError).Sys", Method, 0}, + {"(ExitError).SysUsage", Method, 0}, + {"(ExitError).SystemTime", Method, 0}, + {"(ExitError).UserTime", Method, 0}, + {"Cmd", Type, 0}, + {"Cmd.Args", Field, 0}, + {"Cmd.Cancel", Field, 20}, + {"Cmd.Dir", Field, 0}, + {"Cmd.Env", Field, 0}, + {"Cmd.Err", Field, 19}, + {"Cmd.ExtraFiles", Field, 0}, + {"Cmd.Path", Field, 0}, + {"Cmd.Process", Field, 0}, + {"Cmd.ProcessState", Field, 0}, + {"Cmd.Stderr", Field, 0}, + {"Cmd.Stdin", Field, 0}, + {"Cmd.Stdout", Field, 0}, + {"Cmd.SysProcAttr", Field, 0}, + {"Cmd.WaitDelay", Field, 20}, + {"Command", Func, 0}, + {"CommandContext", Func, 7}, + {"ErrDot", Var, 19}, + {"ErrNotFound", Var, 0}, + {"ErrWaitDelay", Var, 20}, + {"Error", Type, 0}, + {"Error.Err", Field, 0}, + {"Error.Name", Field, 0}, + {"ExitError", Type, 0}, + {"ExitError.ProcessState", Field, 0}, + {"ExitError.Stderr", Field, 6}, + {"LookPath", Func, 0}, + }, + "os/signal": { + {"Ignore", Func, 5}, + {"Ignored", Func, 11}, + {"Notify", Func, 0}, + {"NotifyContext", Func, 16}, + {"Reset", Func, 5}, + {"Stop", Func, 1}, + }, + "os/user": { + {"(*User).GroupIds", Method, 7}, + {"(UnknownGroupError).Error", Method, 7}, + {"(UnknownGroupIdError).Error", Method, 7}, + {"(UnknownUserError).Error", Method, 0}, + {"(UnknownUserIdError).Error", Method, 0}, + {"Current", Func, 0}, + {"Group", Type, 7}, + {"Group.Gid", Field, 7}, + {"Group.Name", Field, 7}, + {"Lookup", Func, 0}, + {"LookupGroup", Func, 7}, + {"LookupGroupId", Func, 7}, + {"LookupId", Func, 0}, + {"UnknownGroupError", Type, 7}, + {"UnknownGroupIdError", Type, 7}, + {"UnknownUserError", Type, 0}, + {"UnknownUserIdError", Type, 0}, + {"User", Type, 0}, + {"User.Gid", Field, 0}, + {"User.HomeDir", Field, 0}, + {"User.Name", Field, 0}, + {"User.Uid", Field, 0}, + {"User.Username", Field, 0}, + }, + "path": { + {"Base", Func, 0}, + {"Clean", Func, 0}, + {"Dir", Func, 0}, + {"ErrBadPattern", Var, 0}, + {"Ext", Func, 0}, + {"IsAbs", Func, 0}, + {"Join", Func, 0}, + {"Match", Func, 0}, + {"Split", Func, 0}, + }, + "path/filepath": { + {"Abs", Func, 0}, + {"Base", Func, 0}, + {"Clean", Func, 0}, + {"Dir", Func, 0}, + {"ErrBadPattern", Var, 0}, + {"EvalSymlinks", Func, 0}, + {"Ext", Func, 0}, + {"FromSlash", Func, 0}, + {"Glob", Func, 0}, + {"HasPrefix", Func, 0}, + {"IsAbs", Func, 0}, + {"IsLocal", Func, 20}, + {"Join", Func, 0}, + {"ListSeparator", Const, 0}, + {"Localize", Func, 23}, + {"Match", Func, 0}, + {"Rel", Func, 0}, + {"Separator", Const, 0}, + {"SkipAll", Var, 20}, + {"SkipDir", Var, 0}, + {"Split", Func, 0}, + {"SplitList", Func, 0}, + {"ToSlash", Func, 0}, + {"VolumeName", Func, 0}, + {"Walk", Func, 0}, + {"WalkDir", Func, 16}, + {"WalkFunc", Type, 0}, + }, + "plugin": { + {"(*Plugin).Lookup", Method, 8}, + {"Open", Func, 8}, + {"Plugin", Type, 8}, + {"Symbol", Type, 8}, + }, + "reflect": { + {"(*MapIter).Key", Method, 12}, + {"(*MapIter).Next", Method, 12}, + {"(*MapIter).Reset", Method, 18}, + {"(*MapIter).Value", Method, 12}, + {"(*ValueError).Error", Method, 0}, + {"(ChanDir).String", Method, 0}, + {"(Kind).String", Method, 0}, + {"(Method).IsExported", Method, 17}, + {"(StructField).IsExported", Method, 17}, + {"(StructTag).Get", Method, 0}, + {"(StructTag).Lookup", Method, 7}, + {"(Value).Addr", Method, 0}, + {"(Value).Bool", Method, 0}, + {"(Value).Bytes", Method, 0}, + {"(Value).Call", Method, 0}, + {"(Value).CallSlice", Method, 0}, + {"(Value).CanAddr", Method, 0}, + {"(Value).CanComplex", Method, 18}, + {"(Value).CanConvert", Method, 17}, + {"(Value).CanFloat", Method, 18}, + {"(Value).CanInt", Method, 18}, + {"(Value).CanInterface", Method, 0}, + {"(Value).CanSet", Method, 0}, + {"(Value).CanUint", Method, 18}, + {"(Value).Cap", Method, 0}, + {"(Value).Clear", Method, 21}, + {"(Value).Close", Method, 0}, + {"(Value).Comparable", Method, 20}, + {"(Value).Complex", Method, 0}, + {"(Value).Convert", Method, 1}, + {"(Value).Elem", Method, 0}, + {"(Value).Equal", Method, 20}, + {"(Value).Field", Method, 0}, + {"(Value).FieldByIndex", Method, 0}, + {"(Value).FieldByIndexErr", Method, 18}, + {"(Value).FieldByName", Method, 0}, + {"(Value).FieldByNameFunc", Method, 0}, + {"(Value).Float", Method, 0}, + {"(Value).Grow", Method, 20}, + {"(Value).Index", Method, 0}, + {"(Value).Int", Method, 0}, + {"(Value).Interface", Method, 0}, + {"(Value).InterfaceData", Method, 0}, + {"(Value).IsNil", Method, 0}, + {"(Value).IsValid", Method, 0}, + {"(Value).IsZero", Method, 13}, + {"(Value).Kind", Method, 0}, + {"(Value).Len", Method, 0}, + {"(Value).MapIndex", Method, 0}, + {"(Value).MapKeys", Method, 0}, + {"(Value).MapRange", Method, 12}, + {"(Value).Method", Method, 0}, + {"(Value).MethodByName", Method, 0}, + {"(Value).NumField", Method, 0}, + {"(Value).NumMethod", Method, 0}, + {"(Value).OverflowComplex", Method, 0}, + {"(Value).OverflowFloat", Method, 0}, + {"(Value).OverflowInt", Method, 0}, + {"(Value).OverflowUint", Method, 0}, + {"(Value).Pointer", Method, 0}, + {"(Value).Recv", Method, 0}, + {"(Value).Send", Method, 0}, + {"(Value).Seq", Method, 23}, + {"(Value).Seq2", Method, 23}, + {"(Value).Set", Method, 0}, + {"(Value).SetBool", Method, 0}, + {"(Value).SetBytes", Method, 0}, + {"(Value).SetCap", Method, 2}, + {"(Value).SetComplex", Method, 0}, + {"(Value).SetFloat", Method, 0}, + {"(Value).SetInt", Method, 0}, + {"(Value).SetIterKey", Method, 18}, + {"(Value).SetIterValue", Method, 18}, + {"(Value).SetLen", Method, 0}, + {"(Value).SetMapIndex", Method, 0}, + {"(Value).SetPointer", Method, 0}, + {"(Value).SetString", Method, 0}, + {"(Value).SetUint", Method, 0}, + {"(Value).SetZero", Method, 20}, + {"(Value).Slice", Method, 0}, + {"(Value).Slice3", Method, 2}, + {"(Value).String", Method, 0}, + {"(Value).TryRecv", Method, 0}, + {"(Value).TrySend", Method, 0}, + {"(Value).Type", Method, 0}, + {"(Value).Uint", Method, 0}, + {"(Value).UnsafeAddr", Method, 0}, + {"(Value).UnsafePointer", Method, 18}, + {"Append", Func, 0}, + {"AppendSlice", Func, 0}, + {"Array", Const, 0}, + {"ArrayOf", Func, 5}, + {"Bool", Const, 0}, + {"BothDir", Const, 0}, + {"Chan", Const, 0}, + {"ChanDir", Type, 0}, + {"ChanOf", Func, 1}, + {"Complex128", Const, 0}, + {"Complex64", Const, 0}, + {"Copy", Func, 0}, + {"DeepEqual", Func, 0}, + {"Float32", Const, 0}, + {"Float64", Const, 0}, + {"Func", Const, 0}, + {"FuncOf", Func, 5}, + {"Indirect", Func, 0}, + {"Int", Const, 0}, + {"Int16", Const, 0}, + {"Int32", Const, 0}, + {"Int64", Const, 0}, + {"Int8", Const, 0}, + {"Interface", Const, 0}, + {"Invalid", Const, 0}, + {"Kind", Type, 0}, + {"MakeChan", Func, 0}, + {"MakeFunc", Func, 1}, + {"MakeMap", Func, 0}, + {"MakeMapWithSize", Func, 9}, + {"MakeSlice", Func, 0}, + {"Map", Const, 0}, + {"MapIter", Type, 12}, + {"MapOf", Func, 1}, + {"Method", Type, 0}, + {"Method.Func", Field, 0}, + {"Method.Index", Field, 0}, + {"Method.Name", Field, 0}, + {"Method.PkgPath", Field, 0}, + {"Method.Type", Field, 0}, + {"New", Func, 0}, + {"NewAt", Func, 0}, + {"Pointer", Const, 18}, + {"PointerTo", Func, 18}, + {"Ptr", Const, 0}, + {"PtrTo", Func, 0}, + {"RecvDir", Const, 0}, + {"Select", Func, 1}, + {"SelectCase", Type, 1}, + {"SelectCase.Chan", Field, 1}, + {"SelectCase.Dir", Field, 1}, + {"SelectCase.Send", Field, 1}, + {"SelectDefault", Const, 1}, + {"SelectDir", Type, 1}, + {"SelectRecv", Const, 1}, + {"SelectSend", Const, 1}, + {"SendDir", Const, 0}, + {"Slice", Const, 0}, + {"SliceAt", Func, 23}, + {"SliceHeader", Type, 0}, + {"SliceHeader.Cap", Field, 0}, + {"SliceHeader.Data", Field, 0}, + {"SliceHeader.Len", Field, 0}, + {"SliceOf", Func, 1}, + {"String", Const, 0}, + {"StringHeader", Type, 0}, + {"StringHeader.Data", Field, 0}, + {"StringHeader.Len", Field, 0}, + {"Struct", Const, 0}, + {"StructField", Type, 0}, + {"StructField.Anonymous", Field, 0}, + {"StructField.Index", Field, 0}, + {"StructField.Name", Field, 0}, + {"StructField.Offset", Field, 0}, + {"StructField.PkgPath", Field, 0}, + {"StructField.Tag", Field, 0}, + {"StructField.Type", Field, 0}, + {"StructOf", Func, 7}, + {"StructTag", Type, 0}, + {"Swapper", Func, 8}, + {"Type", Type, 0}, + {"TypeFor", Func, 22}, + {"TypeOf", Func, 0}, + {"Uint", Const, 0}, + {"Uint16", Const, 0}, + {"Uint32", Const, 0}, + {"Uint64", Const, 0}, + {"Uint8", Const, 0}, + {"Uintptr", Const, 0}, + {"UnsafePointer", Const, 0}, + {"Value", Type, 0}, + {"ValueError", Type, 0}, + {"ValueError.Kind", Field, 0}, + {"ValueError.Method", Field, 0}, + {"ValueOf", Func, 0}, + {"VisibleFields", Func, 17}, + {"Zero", Func, 0}, + }, + "regexp": { + {"(*Regexp).Copy", Method, 6}, + {"(*Regexp).Expand", Method, 0}, + {"(*Regexp).ExpandString", Method, 0}, + {"(*Regexp).Find", Method, 0}, + {"(*Regexp).FindAll", Method, 0}, + {"(*Regexp).FindAllIndex", Method, 0}, + {"(*Regexp).FindAllString", Method, 0}, + {"(*Regexp).FindAllStringIndex", Method, 0}, + {"(*Regexp).FindAllStringSubmatch", Method, 0}, + {"(*Regexp).FindAllStringSubmatchIndex", Method, 0}, + {"(*Regexp).FindAllSubmatch", Method, 0}, + {"(*Regexp).FindAllSubmatchIndex", Method, 0}, + {"(*Regexp).FindIndex", Method, 0}, + {"(*Regexp).FindReaderIndex", Method, 0}, + {"(*Regexp).FindReaderSubmatchIndex", Method, 0}, + {"(*Regexp).FindString", Method, 0}, + {"(*Regexp).FindStringIndex", Method, 0}, + {"(*Regexp).FindStringSubmatch", Method, 0}, + {"(*Regexp).FindStringSubmatchIndex", Method, 0}, + {"(*Regexp).FindSubmatch", Method, 0}, + {"(*Regexp).FindSubmatchIndex", Method, 0}, + {"(*Regexp).LiteralPrefix", Method, 0}, + {"(*Regexp).Longest", Method, 1}, + {"(*Regexp).MarshalText", Method, 21}, + {"(*Regexp).Match", Method, 0}, + {"(*Regexp).MatchReader", Method, 0}, + {"(*Regexp).MatchString", Method, 0}, + {"(*Regexp).NumSubexp", Method, 0}, + {"(*Regexp).ReplaceAll", Method, 0}, + {"(*Regexp).ReplaceAllFunc", Method, 0}, + {"(*Regexp).ReplaceAllLiteral", Method, 0}, + {"(*Regexp).ReplaceAllLiteralString", Method, 0}, + {"(*Regexp).ReplaceAllString", Method, 0}, + {"(*Regexp).ReplaceAllStringFunc", Method, 0}, + {"(*Regexp).Split", Method, 1}, + {"(*Regexp).String", Method, 0}, + {"(*Regexp).SubexpIndex", Method, 15}, + {"(*Regexp).SubexpNames", Method, 0}, + {"(*Regexp).UnmarshalText", Method, 21}, + {"Compile", Func, 0}, + {"CompilePOSIX", Func, 0}, + {"Match", Func, 0}, + {"MatchReader", Func, 0}, + {"MatchString", Func, 0}, + {"MustCompile", Func, 0}, + {"MustCompilePOSIX", Func, 0}, + {"QuoteMeta", Func, 0}, + {"Regexp", Type, 0}, + }, + "regexp/syntax": { + {"(*Error).Error", Method, 0}, + {"(*Inst).MatchEmptyWidth", Method, 0}, + {"(*Inst).MatchRune", Method, 0}, + {"(*Inst).MatchRunePos", Method, 3}, + {"(*Inst).String", Method, 0}, + {"(*Prog).Prefix", Method, 0}, + {"(*Prog).StartCond", Method, 0}, + {"(*Prog).String", Method, 0}, + {"(*Regexp).CapNames", Method, 0}, + {"(*Regexp).Equal", Method, 0}, + {"(*Regexp).MaxCap", Method, 0}, + {"(*Regexp).Simplify", Method, 0}, + {"(*Regexp).String", Method, 0}, + {"(ErrorCode).String", Method, 0}, + {"(InstOp).String", Method, 3}, + {"(Op).String", Method, 11}, + {"ClassNL", Const, 0}, + {"Compile", Func, 0}, + {"DotNL", Const, 0}, + {"EmptyBeginLine", Const, 0}, + {"EmptyBeginText", Const, 0}, + {"EmptyEndLine", Const, 0}, + {"EmptyEndText", Const, 0}, + {"EmptyNoWordBoundary", Const, 0}, + {"EmptyOp", Type, 0}, + {"EmptyOpContext", Func, 0}, + {"EmptyWordBoundary", Const, 0}, + {"ErrInternalError", Const, 0}, + {"ErrInvalidCharClass", Const, 0}, + {"ErrInvalidCharRange", Const, 0}, + {"ErrInvalidEscape", Const, 0}, + {"ErrInvalidNamedCapture", Const, 0}, + {"ErrInvalidPerlOp", Const, 0}, + {"ErrInvalidRepeatOp", Const, 0}, + {"ErrInvalidRepeatSize", Const, 0}, + {"ErrInvalidUTF8", Const, 0}, + {"ErrLarge", Const, 20}, + {"ErrMissingBracket", Const, 0}, + {"ErrMissingParen", Const, 0}, + {"ErrMissingRepeatArgument", Const, 0}, + {"ErrNestingDepth", Const, 19}, + {"ErrTrailingBackslash", Const, 0}, + {"ErrUnexpectedParen", Const, 1}, + {"Error", Type, 0}, + {"Error.Code", Field, 0}, + {"Error.Expr", Field, 0}, + {"ErrorCode", Type, 0}, + {"Flags", Type, 0}, + {"FoldCase", Const, 0}, + {"Inst", Type, 0}, + {"Inst.Arg", Field, 0}, + {"Inst.Op", Field, 0}, + {"Inst.Out", Field, 0}, + {"Inst.Rune", Field, 0}, + {"InstAlt", Const, 0}, + {"InstAltMatch", Const, 0}, + {"InstCapture", Const, 0}, + {"InstEmptyWidth", Const, 0}, + {"InstFail", Const, 0}, + {"InstMatch", Const, 0}, + {"InstNop", Const, 0}, + {"InstOp", Type, 0}, + {"InstRune", Const, 0}, + {"InstRune1", Const, 0}, + {"InstRuneAny", Const, 0}, + {"InstRuneAnyNotNL", Const, 0}, + {"IsWordChar", Func, 0}, + {"Literal", Const, 0}, + {"MatchNL", Const, 0}, + {"NonGreedy", Const, 0}, + {"OneLine", Const, 0}, + {"Op", Type, 0}, + {"OpAlternate", Const, 0}, + {"OpAnyChar", Const, 0}, + {"OpAnyCharNotNL", Const, 0}, + {"OpBeginLine", Const, 0}, + {"OpBeginText", Const, 0}, + {"OpCapture", Const, 0}, + {"OpCharClass", Const, 0}, + {"OpConcat", Const, 0}, + {"OpEmptyMatch", Const, 0}, + {"OpEndLine", Const, 0}, + {"OpEndText", Const, 0}, + {"OpLiteral", Const, 0}, + {"OpNoMatch", Const, 0}, + {"OpNoWordBoundary", Const, 0}, + {"OpPlus", Const, 0}, + {"OpQuest", Const, 0}, + {"OpRepeat", Const, 0}, + {"OpStar", Const, 0}, + {"OpWordBoundary", Const, 0}, + {"POSIX", Const, 0}, + {"Parse", Func, 0}, + {"Perl", Const, 0}, + {"PerlX", Const, 0}, + {"Prog", Type, 0}, + {"Prog.Inst", Field, 0}, + {"Prog.NumCap", Field, 0}, + {"Prog.Start", Field, 0}, + {"Regexp", Type, 0}, + {"Regexp.Cap", Field, 0}, + {"Regexp.Flags", Field, 0}, + {"Regexp.Max", Field, 0}, + {"Regexp.Min", Field, 0}, + {"Regexp.Name", Field, 0}, + {"Regexp.Op", Field, 0}, + {"Regexp.Rune", Field, 0}, + {"Regexp.Rune0", Field, 0}, + {"Regexp.Sub", Field, 0}, + {"Regexp.Sub0", Field, 0}, + {"Simple", Const, 0}, + {"UnicodeGroups", Const, 0}, + {"WasDollar", Const, 0}, + }, + "runtime": { + {"(*BlockProfileRecord).Stack", Method, 1}, + {"(*Frames).Next", Method, 7}, + {"(*Func).Entry", Method, 0}, + {"(*Func).FileLine", Method, 0}, + {"(*Func).Name", Method, 0}, + {"(*MemProfileRecord).InUseBytes", Method, 0}, + {"(*MemProfileRecord).InUseObjects", Method, 0}, + {"(*MemProfileRecord).Stack", Method, 0}, + {"(*PanicNilError).Error", Method, 21}, + {"(*PanicNilError).RuntimeError", Method, 21}, + {"(*Pinner).Pin", Method, 21}, + {"(*Pinner).Unpin", Method, 21}, + {"(*StackRecord).Stack", Method, 0}, + {"(*TypeAssertionError).Error", Method, 0}, + {"(*TypeAssertionError).RuntimeError", Method, 0}, + {"BlockProfile", Func, 1}, + {"BlockProfileRecord", Type, 1}, + {"BlockProfileRecord.Count", Field, 1}, + {"BlockProfileRecord.Cycles", Field, 1}, + {"BlockProfileRecord.StackRecord", Field, 1}, + {"Breakpoint", Func, 0}, + {"CPUProfile", Func, 0}, + {"Caller", Func, 0}, + {"Callers", Func, 0}, + {"CallersFrames", Func, 7}, + {"Compiler", Const, 0}, + {"Error", Type, 0}, + {"Frame", Type, 7}, + {"Frame.Entry", Field, 7}, + {"Frame.File", Field, 7}, + {"Frame.Func", Field, 7}, + {"Frame.Function", Field, 7}, + {"Frame.Line", Field, 7}, + {"Frame.PC", Field, 7}, + {"Frames", Type, 7}, + {"Func", Type, 0}, + {"FuncForPC", Func, 0}, + {"GC", Func, 0}, + {"GOARCH", Const, 0}, + {"GOMAXPROCS", Func, 0}, + {"GOOS", Const, 0}, + {"GOROOT", Func, 0}, + {"Goexit", Func, 0}, + {"GoroutineProfile", Func, 0}, + {"Gosched", Func, 0}, + {"KeepAlive", Func, 7}, + {"LockOSThread", Func, 0}, + {"MemProfile", Func, 0}, + {"MemProfileRate", Var, 0}, + {"MemProfileRecord", Type, 0}, + {"MemProfileRecord.AllocBytes", Field, 0}, + {"MemProfileRecord.AllocObjects", Field, 0}, + {"MemProfileRecord.FreeBytes", Field, 0}, + {"MemProfileRecord.FreeObjects", Field, 0}, + {"MemProfileRecord.Stack0", Field, 0}, + {"MemStats", Type, 0}, + {"MemStats.Alloc", Field, 0}, + {"MemStats.BuckHashSys", Field, 0}, + {"MemStats.BySize", Field, 0}, + {"MemStats.DebugGC", Field, 0}, + {"MemStats.EnableGC", Field, 0}, + {"MemStats.Frees", Field, 0}, + {"MemStats.GCCPUFraction", Field, 5}, + {"MemStats.GCSys", Field, 2}, + {"MemStats.HeapAlloc", Field, 0}, + {"MemStats.HeapIdle", Field, 0}, + {"MemStats.HeapInuse", Field, 0}, + {"MemStats.HeapObjects", Field, 0}, + {"MemStats.HeapReleased", Field, 0}, + {"MemStats.HeapSys", Field, 0}, + {"MemStats.LastGC", Field, 0}, + {"MemStats.Lookups", Field, 0}, + {"MemStats.MCacheInuse", Field, 0}, + {"MemStats.MCacheSys", Field, 0}, + {"MemStats.MSpanInuse", Field, 0}, + {"MemStats.MSpanSys", Field, 0}, + {"MemStats.Mallocs", Field, 0}, + {"MemStats.NextGC", Field, 0}, + {"MemStats.NumForcedGC", Field, 8}, + {"MemStats.NumGC", Field, 0}, + {"MemStats.OtherSys", Field, 2}, + {"MemStats.PauseEnd", Field, 4}, + {"MemStats.PauseNs", Field, 0}, + {"MemStats.PauseTotalNs", Field, 0}, + {"MemStats.StackInuse", Field, 0}, + {"MemStats.StackSys", Field, 0}, + {"MemStats.Sys", Field, 0}, + {"MemStats.TotalAlloc", Field, 0}, + {"MutexProfile", Func, 8}, + {"NumCPU", Func, 0}, + {"NumCgoCall", Func, 0}, + {"NumGoroutine", Func, 0}, + {"PanicNilError", Type, 21}, + {"Pinner", Type, 21}, + {"ReadMemStats", Func, 0}, + {"ReadTrace", Func, 5}, + {"SetBlockProfileRate", Func, 1}, + {"SetCPUProfileRate", Func, 0}, + {"SetCgoTraceback", Func, 7}, + {"SetFinalizer", Func, 0}, + {"SetMutexProfileFraction", Func, 8}, + {"Stack", Func, 0}, + {"StackRecord", Type, 0}, + {"StackRecord.Stack0", Field, 0}, + {"StartTrace", Func, 5}, + {"StopTrace", Func, 5}, + {"ThreadCreateProfile", Func, 0}, + {"TypeAssertionError", Type, 0}, + {"UnlockOSThread", Func, 0}, + {"Version", Func, 0}, + }, + "runtime/cgo": { + {"(Handle).Delete", Method, 17}, + {"(Handle).Value", Method, 17}, + {"Handle", Type, 17}, + {"Incomplete", Type, 20}, + {"NewHandle", Func, 17}, + }, + "runtime/coverage": { + {"ClearCounters", Func, 20}, + {"WriteCounters", Func, 20}, + {"WriteCountersDir", Func, 20}, + {"WriteMeta", Func, 20}, + {"WriteMetaDir", Func, 20}, + }, + "runtime/debug": { + {"(*BuildInfo).String", Method, 18}, + {"BuildInfo", Type, 12}, + {"BuildInfo.Deps", Field, 12}, + {"BuildInfo.GoVersion", Field, 18}, + {"BuildInfo.Main", Field, 12}, + {"BuildInfo.Path", Field, 12}, + {"BuildInfo.Settings", Field, 18}, + {"BuildSetting", Type, 18}, + {"BuildSetting.Key", Field, 18}, + {"BuildSetting.Value", Field, 18}, + {"CrashOptions", Type, 23}, + {"FreeOSMemory", Func, 1}, + {"GCStats", Type, 1}, + {"GCStats.LastGC", Field, 1}, + {"GCStats.NumGC", Field, 1}, + {"GCStats.Pause", Field, 1}, + {"GCStats.PauseEnd", Field, 4}, + {"GCStats.PauseQuantiles", Field, 1}, + {"GCStats.PauseTotal", Field, 1}, + {"Module", Type, 12}, + {"Module.Path", Field, 12}, + {"Module.Replace", Field, 12}, + {"Module.Sum", Field, 12}, + {"Module.Version", Field, 12}, + {"ParseBuildInfo", Func, 18}, + {"PrintStack", Func, 0}, + {"ReadBuildInfo", Func, 12}, + {"ReadGCStats", Func, 1}, + {"SetCrashOutput", Func, 23}, + {"SetGCPercent", Func, 1}, + {"SetMaxStack", Func, 2}, + {"SetMaxThreads", Func, 2}, + {"SetMemoryLimit", Func, 19}, + {"SetPanicOnFault", Func, 3}, + {"SetTraceback", Func, 6}, + {"Stack", Func, 0}, + {"WriteHeapDump", Func, 3}, + }, + "runtime/metrics": { + {"(Value).Float64", Method, 16}, + {"(Value).Float64Histogram", Method, 16}, + {"(Value).Kind", Method, 16}, + {"(Value).Uint64", Method, 16}, + {"All", Func, 16}, + {"Description", Type, 16}, + {"Description.Cumulative", Field, 16}, + {"Description.Description", Field, 16}, + {"Description.Kind", Field, 16}, + {"Description.Name", Field, 16}, + {"Float64Histogram", Type, 16}, + {"Float64Histogram.Buckets", Field, 16}, + {"Float64Histogram.Counts", Field, 16}, + {"KindBad", Const, 16}, + {"KindFloat64", Const, 16}, + {"KindFloat64Histogram", Const, 16}, + {"KindUint64", Const, 16}, + {"Read", Func, 16}, + {"Sample", Type, 16}, + {"Sample.Name", Field, 16}, + {"Sample.Value", Field, 16}, + {"Value", Type, 16}, + {"ValueKind", Type, 16}, + }, + "runtime/pprof": { + {"(*Profile).Add", Method, 0}, + {"(*Profile).Count", Method, 0}, + {"(*Profile).Name", Method, 0}, + {"(*Profile).Remove", Method, 0}, + {"(*Profile).WriteTo", Method, 0}, + {"Do", Func, 9}, + {"ForLabels", Func, 9}, + {"Label", Func, 9}, + {"LabelSet", Type, 9}, + {"Labels", Func, 9}, + {"Lookup", Func, 0}, + {"NewProfile", Func, 0}, + {"Profile", Type, 0}, + {"Profiles", Func, 0}, + {"SetGoroutineLabels", Func, 9}, + {"StartCPUProfile", Func, 0}, + {"StopCPUProfile", Func, 0}, + {"WithLabels", Func, 9}, + {"WriteHeapProfile", Func, 0}, + }, + "runtime/trace": { + {"(*Region).End", Method, 11}, + {"(*Task).End", Method, 11}, + {"IsEnabled", Func, 11}, + {"Log", Func, 11}, + {"Logf", Func, 11}, + {"NewTask", Func, 11}, + {"Region", Type, 11}, + {"Start", Func, 5}, + {"StartRegion", Func, 11}, + {"Stop", Func, 5}, + {"Task", Type, 11}, + {"WithRegion", Func, 11}, + }, + "slices": { + {"All", Func, 23}, + {"AppendSeq", Func, 23}, + {"Backward", Func, 23}, + {"BinarySearch", Func, 21}, + {"BinarySearchFunc", Func, 21}, + {"Chunk", Func, 23}, + {"Clip", Func, 21}, + {"Clone", Func, 21}, + {"Collect", Func, 23}, + {"Compact", Func, 21}, + {"CompactFunc", Func, 21}, + {"Compare", Func, 21}, + {"CompareFunc", Func, 21}, + {"Concat", Func, 22}, + {"Contains", Func, 21}, + {"ContainsFunc", Func, 21}, + {"Delete", Func, 21}, + {"DeleteFunc", Func, 21}, + {"Equal", Func, 21}, + {"EqualFunc", Func, 21}, + {"Grow", Func, 21}, + {"Index", Func, 21}, + {"IndexFunc", Func, 21}, + {"Insert", Func, 21}, + {"IsSorted", Func, 21}, + {"IsSortedFunc", Func, 21}, + {"Max", Func, 21}, + {"MaxFunc", Func, 21}, + {"Min", Func, 21}, + {"MinFunc", Func, 21}, + {"Repeat", Func, 23}, + {"Replace", Func, 21}, + {"Reverse", Func, 21}, + {"Sort", Func, 21}, + {"SortFunc", Func, 21}, + {"SortStableFunc", Func, 21}, + {"Sorted", Func, 23}, + {"SortedFunc", Func, 23}, + {"SortedStableFunc", Func, 23}, + {"Values", Func, 23}, + }, + "sort": { + {"(Float64Slice).Len", Method, 0}, + {"(Float64Slice).Less", Method, 0}, + {"(Float64Slice).Search", Method, 0}, + {"(Float64Slice).Sort", Method, 0}, + {"(Float64Slice).Swap", Method, 0}, + {"(IntSlice).Len", Method, 0}, + {"(IntSlice).Less", Method, 0}, + {"(IntSlice).Search", Method, 0}, + {"(IntSlice).Sort", Method, 0}, + {"(IntSlice).Swap", Method, 0}, + {"(StringSlice).Len", Method, 0}, + {"(StringSlice).Less", Method, 0}, + {"(StringSlice).Search", Method, 0}, + {"(StringSlice).Sort", Method, 0}, + {"(StringSlice).Swap", Method, 0}, + {"Find", Func, 19}, + {"Float64Slice", Type, 0}, + {"Float64s", Func, 0}, + {"Float64sAreSorted", Func, 0}, + {"IntSlice", Type, 0}, + {"Interface", Type, 0}, + {"Ints", Func, 0}, + {"IntsAreSorted", Func, 0}, + {"IsSorted", Func, 0}, + {"Reverse", Func, 1}, + {"Search", Func, 0}, + {"SearchFloat64s", Func, 0}, + {"SearchInts", Func, 0}, + {"SearchStrings", Func, 0}, + {"Slice", Func, 8}, + {"SliceIsSorted", Func, 8}, + {"SliceStable", Func, 8}, + {"Sort", Func, 0}, + {"Stable", Func, 2}, + {"StringSlice", Type, 0}, + {"Strings", Func, 0}, + {"StringsAreSorted", Func, 0}, + }, + "strconv": { + {"(*NumError).Error", Method, 0}, + {"(*NumError).Unwrap", Method, 14}, + {"AppendBool", Func, 0}, + {"AppendFloat", Func, 0}, + {"AppendInt", Func, 0}, + {"AppendQuote", Func, 0}, + {"AppendQuoteRune", Func, 0}, + {"AppendQuoteRuneToASCII", Func, 0}, + {"AppendQuoteRuneToGraphic", Func, 6}, + {"AppendQuoteToASCII", Func, 0}, + {"AppendQuoteToGraphic", Func, 6}, + {"AppendUint", Func, 0}, + {"Atoi", Func, 0}, + {"CanBackquote", Func, 0}, + {"ErrRange", Var, 0}, + {"ErrSyntax", Var, 0}, + {"FormatBool", Func, 0}, + {"FormatComplex", Func, 15}, + {"FormatFloat", Func, 0}, + {"FormatInt", Func, 0}, + {"FormatUint", Func, 0}, + {"IntSize", Const, 0}, + {"IsGraphic", Func, 6}, + {"IsPrint", Func, 0}, + {"Itoa", Func, 0}, + {"NumError", Type, 0}, + {"NumError.Err", Field, 0}, + {"NumError.Func", Field, 0}, + {"NumError.Num", Field, 0}, + {"ParseBool", Func, 0}, + {"ParseComplex", Func, 15}, + {"ParseFloat", Func, 0}, + {"ParseInt", Func, 0}, + {"ParseUint", Func, 0}, + {"Quote", Func, 0}, + {"QuoteRune", Func, 0}, + {"QuoteRuneToASCII", Func, 0}, + {"QuoteRuneToGraphic", Func, 6}, + {"QuoteToASCII", Func, 0}, + {"QuoteToGraphic", Func, 6}, + {"QuotedPrefix", Func, 17}, + {"Unquote", Func, 0}, + {"UnquoteChar", Func, 0}, + }, + "strings": { + {"(*Builder).Cap", Method, 12}, + {"(*Builder).Grow", Method, 10}, + {"(*Builder).Len", Method, 10}, + {"(*Builder).Reset", Method, 10}, + {"(*Builder).String", Method, 10}, + {"(*Builder).Write", Method, 10}, + {"(*Builder).WriteByte", Method, 10}, + {"(*Builder).WriteRune", Method, 10}, + {"(*Builder).WriteString", Method, 10}, + {"(*Reader).Len", Method, 0}, + {"(*Reader).Read", Method, 0}, + {"(*Reader).ReadAt", Method, 0}, + {"(*Reader).ReadByte", Method, 0}, + {"(*Reader).ReadRune", Method, 0}, + {"(*Reader).Reset", Method, 7}, + {"(*Reader).Seek", Method, 0}, + {"(*Reader).Size", Method, 5}, + {"(*Reader).UnreadByte", Method, 0}, + {"(*Reader).UnreadRune", Method, 0}, + {"(*Reader).WriteTo", Method, 1}, + {"(*Replacer).Replace", Method, 0}, + {"(*Replacer).WriteString", Method, 0}, + {"Builder", Type, 10}, + {"Clone", Func, 18}, + {"Compare", Func, 5}, + {"Contains", Func, 0}, + {"ContainsAny", Func, 0}, + {"ContainsFunc", Func, 21}, + {"ContainsRune", Func, 0}, + {"Count", Func, 0}, + {"Cut", Func, 18}, + {"CutPrefix", Func, 20}, + {"CutSuffix", Func, 20}, + {"EqualFold", Func, 0}, + {"Fields", Func, 0}, + {"FieldsFunc", Func, 0}, + {"HasPrefix", Func, 0}, + {"HasSuffix", Func, 0}, + {"Index", Func, 0}, + {"IndexAny", Func, 0}, + {"IndexByte", Func, 2}, + {"IndexFunc", Func, 0}, + {"IndexRune", Func, 0}, + {"Join", Func, 0}, + {"LastIndex", Func, 0}, + {"LastIndexAny", Func, 0}, + {"LastIndexByte", Func, 5}, + {"LastIndexFunc", Func, 0}, + {"Map", Func, 0}, + {"NewReader", Func, 0}, + {"NewReplacer", Func, 0}, + {"Reader", Type, 0}, + {"Repeat", Func, 0}, + {"Replace", Func, 0}, + {"ReplaceAll", Func, 12}, + {"Replacer", Type, 0}, + {"Split", Func, 0}, + {"SplitAfter", Func, 0}, + {"SplitAfterN", Func, 0}, + {"SplitN", Func, 0}, + {"Title", Func, 0}, + {"ToLower", Func, 0}, + {"ToLowerSpecial", Func, 0}, + {"ToTitle", Func, 0}, + {"ToTitleSpecial", Func, 0}, + {"ToUpper", Func, 0}, + {"ToUpperSpecial", Func, 0}, + {"ToValidUTF8", Func, 13}, + {"Trim", Func, 0}, + {"TrimFunc", Func, 0}, + {"TrimLeft", Func, 0}, + {"TrimLeftFunc", Func, 0}, + {"TrimPrefix", Func, 1}, + {"TrimRight", Func, 0}, + {"TrimRightFunc", Func, 0}, + {"TrimSpace", Func, 0}, + {"TrimSuffix", Func, 1}, + }, + "structs": { + {"HostLayout", Type, 23}, + }, + "sync": { + {"(*Cond).Broadcast", Method, 0}, + {"(*Cond).Signal", Method, 0}, + {"(*Cond).Wait", Method, 0}, + {"(*Map).Clear", Method, 23}, + {"(*Map).CompareAndDelete", Method, 20}, + {"(*Map).CompareAndSwap", Method, 20}, + {"(*Map).Delete", Method, 9}, + {"(*Map).Load", Method, 9}, + {"(*Map).LoadAndDelete", Method, 15}, + {"(*Map).LoadOrStore", Method, 9}, + {"(*Map).Range", Method, 9}, + {"(*Map).Store", Method, 9}, + {"(*Map).Swap", Method, 20}, + {"(*Mutex).Lock", Method, 0}, + {"(*Mutex).TryLock", Method, 18}, + {"(*Mutex).Unlock", Method, 0}, + {"(*Once).Do", Method, 0}, + {"(*Pool).Get", Method, 3}, + {"(*Pool).Put", Method, 3}, + {"(*RWMutex).Lock", Method, 0}, + {"(*RWMutex).RLock", Method, 0}, + {"(*RWMutex).RLocker", Method, 0}, + {"(*RWMutex).RUnlock", Method, 0}, + {"(*RWMutex).TryLock", Method, 18}, + {"(*RWMutex).TryRLock", Method, 18}, + {"(*RWMutex).Unlock", Method, 0}, + {"(*WaitGroup).Add", Method, 0}, + {"(*WaitGroup).Done", Method, 0}, + {"(*WaitGroup).Wait", Method, 0}, + {"Cond", Type, 0}, + {"Cond.L", Field, 0}, + {"Locker", Type, 0}, + {"Map", Type, 9}, + {"Mutex", Type, 0}, + {"NewCond", Func, 0}, + {"Once", Type, 0}, + {"OnceFunc", Func, 21}, + {"OnceValue", Func, 21}, + {"OnceValues", Func, 21}, + {"Pool", Type, 3}, + {"Pool.New", Field, 3}, + {"RWMutex", Type, 0}, + {"WaitGroup", Type, 0}, + }, + "sync/atomic": { + {"(*Bool).CompareAndSwap", Method, 19}, + {"(*Bool).Load", Method, 19}, + {"(*Bool).Store", Method, 19}, + {"(*Bool).Swap", Method, 19}, + {"(*Int32).Add", Method, 19}, + {"(*Int32).And", Method, 23}, + {"(*Int32).CompareAndSwap", Method, 19}, + {"(*Int32).Load", Method, 19}, + {"(*Int32).Or", Method, 23}, + {"(*Int32).Store", Method, 19}, + {"(*Int32).Swap", Method, 19}, + {"(*Int64).Add", Method, 19}, + {"(*Int64).And", Method, 23}, + {"(*Int64).CompareAndSwap", Method, 19}, + {"(*Int64).Load", Method, 19}, + {"(*Int64).Or", Method, 23}, + {"(*Int64).Store", Method, 19}, + {"(*Int64).Swap", Method, 19}, + {"(*Pointer).CompareAndSwap", Method, 19}, + {"(*Pointer).Load", Method, 19}, + {"(*Pointer).Store", Method, 19}, + {"(*Pointer).Swap", Method, 19}, + {"(*Uint32).Add", Method, 19}, + {"(*Uint32).And", Method, 23}, + {"(*Uint32).CompareAndSwap", Method, 19}, + {"(*Uint32).Load", Method, 19}, + {"(*Uint32).Or", Method, 23}, + {"(*Uint32).Store", Method, 19}, + {"(*Uint32).Swap", Method, 19}, + {"(*Uint64).Add", Method, 19}, + {"(*Uint64).And", Method, 23}, + {"(*Uint64).CompareAndSwap", Method, 19}, + {"(*Uint64).Load", Method, 19}, + {"(*Uint64).Or", Method, 23}, + {"(*Uint64).Store", Method, 19}, + {"(*Uint64).Swap", Method, 19}, + {"(*Uintptr).Add", Method, 19}, + {"(*Uintptr).And", Method, 23}, + {"(*Uintptr).CompareAndSwap", Method, 19}, + {"(*Uintptr).Load", Method, 19}, + {"(*Uintptr).Or", Method, 23}, + {"(*Uintptr).Store", Method, 19}, + {"(*Uintptr).Swap", Method, 19}, + {"(*Value).CompareAndSwap", Method, 17}, + {"(*Value).Load", Method, 4}, + {"(*Value).Store", Method, 4}, + {"(*Value).Swap", Method, 17}, + {"AddInt32", Func, 0}, + {"AddInt64", Func, 0}, + {"AddUint32", Func, 0}, + {"AddUint64", Func, 0}, + {"AddUintptr", Func, 0}, + {"AndInt32", Func, 23}, + {"AndInt64", Func, 23}, + {"AndUint32", Func, 23}, + {"AndUint64", Func, 23}, + {"AndUintptr", Func, 23}, + {"Bool", Type, 19}, + {"CompareAndSwapInt32", Func, 0}, + {"CompareAndSwapInt64", Func, 0}, + {"CompareAndSwapPointer", Func, 0}, + {"CompareAndSwapUint32", Func, 0}, + {"CompareAndSwapUint64", Func, 0}, + {"CompareAndSwapUintptr", Func, 0}, + {"Int32", Type, 19}, + {"Int64", Type, 19}, + {"LoadInt32", Func, 0}, + {"LoadInt64", Func, 0}, + {"LoadPointer", Func, 0}, + {"LoadUint32", Func, 0}, + {"LoadUint64", Func, 0}, + {"LoadUintptr", Func, 0}, + {"OrInt32", Func, 23}, + {"OrInt64", Func, 23}, + {"OrUint32", Func, 23}, + {"OrUint64", Func, 23}, + {"OrUintptr", Func, 23}, + {"Pointer", Type, 19}, + {"StoreInt32", Func, 0}, + {"StoreInt64", Func, 0}, + {"StorePointer", Func, 0}, + {"StoreUint32", Func, 0}, + {"StoreUint64", Func, 0}, + {"StoreUintptr", Func, 0}, + {"SwapInt32", Func, 2}, + {"SwapInt64", Func, 2}, + {"SwapPointer", Func, 2}, + {"SwapUint32", Func, 2}, + {"SwapUint64", Func, 2}, + {"SwapUintptr", Func, 2}, + {"Uint32", Type, 19}, + {"Uint64", Type, 19}, + {"Uintptr", Type, 19}, + {"Value", Type, 4}, + }, + "syscall": { + {"(*Cmsghdr).SetLen", Method, 0}, + {"(*DLL).FindProc", Method, 0}, + {"(*DLL).MustFindProc", Method, 0}, + {"(*DLL).Release", Method, 0}, + {"(*DLLError).Error", Method, 0}, + {"(*DLLError).Unwrap", Method, 16}, + {"(*Filetime).Nanoseconds", Method, 0}, + {"(*Iovec).SetLen", Method, 0}, + {"(*LazyDLL).Handle", Method, 0}, + {"(*LazyDLL).Load", Method, 0}, + {"(*LazyDLL).NewProc", Method, 0}, + {"(*LazyProc).Addr", Method, 0}, + {"(*LazyProc).Call", Method, 0}, + {"(*LazyProc).Find", Method, 0}, + {"(*Msghdr).SetControllen", Method, 0}, + {"(*Proc).Addr", Method, 0}, + {"(*Proc).Call", Method, 0}, + {"(*PtraceRegs).PC", Method, 0}, + {"(*PtraceRegs).SetPC", Method, 0}, + {"(*RawSockaddrAny).Sockaddr", Method, 0}, + {"(*SID).Copy", Method, 0}, + {"(*SID).Len", Method, 0}, + {"(*SID).LookupAccount", Method, 0}, + {"(*SID).String", Method, 0}, + {"(*Timespec).Nano", Method, 0}, + {"(*Timespec).Unix", Method, 0}, + {"(*Timeval).Nano", Method, 0}, + {"(*Timeval).Nanoseconds", Method, 0}, + {"(*Timeval).Unix", Method, 0}, + {"(Errno).Error", Method, 0}, + {"(Errno).Is", Method, 13}, + {"(Errno).Temporary", Method, 0}, + {"(Errno).Timeout", Method, 0}, + {"(Signal).Signal", Method, 0}, + {"(Signal).String", Method, 0}, + {"(Token).Close", Method, 0}, + {"(Token).GetTokenPrimaryGroup", Method, 0}, + {"(Token).GetTokenUser", Method, 0}, + {"(Token).GetUserProfileDirectory", Method, 0}, + {"(WaitStatus).Continued", Method, 0}, + {"(WaitStatus).CoreDump", Method, 0}, + {"(WaitStatus).ExitStatus", Method, 0}, + {"(WaitStatus).Exited", Method, 0}, + {"(WaitStatus).Signal", Method, 0}, + {"(WaitStatus).Signaled", Method, 0}, + {"(WaitStatus).StopSignal", Method, 0}, + {"(WaitStatus).Stopped", Method, 0}, + {"(WaitStatus).TrapCause", Method, 0}, + {"AF_ALG", Const, 0}, + {"AF_APPLETALK", Const, 0}, + {"AF_ARP", Const, 0}, + {"AF_ASH", Const, 0}, + {"AF_ATM", Const, 0}, + {"AF_ATMPVC", Const, 0}, + {"AF_ATMSVC", Const, 0}, + {"AF_AX25", Const, 0}, + {"AF_BLUETOOTH", Const, 0}, + {"AF_BRIDGE", Const, 0}, + {"AF_CAIF", Const, 0}, + {"AF_CAN", Const, 0}, + {"AF_CCITT", Const, 0}, + {"AF_CHAOS", Const, 0}, + {"AF_CNT", Const, 0}, + {"AF_COIP", Const, 0}, + {"AF_DATAKIT", Const, 0}, + {"AF_DECnet", Const, 0}, + {"AF_DLI", Const, 0}, + {"AF_E164", Const, 0}, + {"AF_ECMA", Const, 0}, + {"AF_ECONET", Const, 0}, + {"AF_ENCAP", Const, 1}, + {"AF_FILE", Const, 0}, + {"AF_HYLINK", Const, 0}, + {"AF_IEEE80211", Const, 0}, + {"AF_IEEE802154", Const, 0}, + {"AF_IMPLINK", Const, 0}, + {"AF_INET", Const, 0}, + {"AF_INET6", Const, 0}, + {"AF_INET6_SDP", Const, 3}, + {"AF_INET_SDP", Const, 3}, + {"AF_IPX", Const, 0}, + {"AF_IRDA", Const, 0}, + {"AF_ISDN", Const, 0}, + {"AF_ISO", Const, 0}, + {"AF_IUCV", Const, 0}, + {"AF_KEY", Const, 0}, + {"AF_LAT", Const, 0}, + {"AF_LINK", Const, 0}, + {"AF_LLC", Const, 0}, + {"AF_LOCAL", Const, 0}, + {"AF_MAX", Const, 0}, + {"AF_MPLS", Const, 1}, + {"AF_NATM", Const, 0}, + {"AF_NDRV", Const, 0}, + {"AF_NETBEUI", Const, 0}, + {"AF_NETBIOS", Const, 0}, + {"AF_NETGRAPH", Const, 0}, + {"AF_NETLINK", Const, 0}, + {"AF_NETROM", Const, 0}, + {"AF_NS", Const, 0}, + {"AF_OROUTE", Const, 1}, + {"AF_OSI", Const, 0}, + {"AF_PACKET", Const, 0}, + {"AF_PHONET", Const, 0}, + {"AF_PPP", Const, 0}, + {"AF_PPPOX", Const, 0}, + {"AF_PUP", Const, 0}, + {"AF_RDS", Const, 0}, + {"AF_RESERVED_36", Const, 0}, + {"AF_ROSE", Const, 0}, + {"AF_ROUTE", Const, 0}, + {"AF_RXRPC", Const, 0}, + {"AF_SCLUSTER", Const, 0}, + {"AF_SECURITY", Const, 0}, + {"AF_SIP", Const, 0}, + {"AF_SLOW", Const, 0}, + {"AF_SNA", Const, 0}, + {"AF_SYSTEM", Const, 0}, + {"AF_TIPC", Const, 0}, + {"AF_UNIX", Const, 0}, + {"AF_UNSPEC", Const, 0}, + {"AF_UTUN", Const, 16}, + {"AF_VENDOR00", Const, 0}, + {"AF_VENDOR01", Const, 0}, + {"AF_VENDOR02", Const, 0}, + {"AF_VENDOR03", Const, 0}, + {"AF_VENDOR04", Const, 0}, + {"AF_VENDOR05", Const, 0}, + {"AF_VENDOR06", Const, 0}, + {"AF_VENDOR07", Const, 0}, + {"AF_VENDOR08", Const, 0}, + {"AF_VENDOR09", Const, 0}, + {"AF_VENDOR10", Const, 0}, + {"AF_VENDOR11", Const, 0}, + {"AF_VENDOR12", Const, 0}, + {"AF_VENDOR13", Const, 0}, + {"AF_VENDOR14", Const, 0}, + {"AF_VENDOR15", Const, 0}, + {"AF_VENDOR16", Const, 0}, + {"AF_VENDOR17", Const, 0}, + {"AF_VENDOR18", Const, 0}, + {"AF_VENDOR19", Const, 0}, + {"AF_VENDOR20", Const, 0}, + {"AF_VENDOR21", Const, 0}, + {"AF_VENDOR22", Const, 0}, + {"AF_VENDOR23", Const, 0}, + {"AF_VENDOR24", Const, 0}, + {"AF_VENDOR25", Const, 0}, + {"AF_VENDOR26", Const, 0}, + {"AF_VENDOR27", Const, 0}, + {"AF_VENDOR28", Const, 0}, + {"AF_VENDOR29", Const, 0}, + {"AF_VENDOR30", Const, 0}, + {"AF_VENDOR31", Const, 0}, + {"AF_VENDOR32", Const, 0}, + {"AF_VENDOR33", Const, 0}, + {"AF_VENDOR34", Const, 0}, + {"AF_VENDOR35", Const, 0}, + {"AF_VENDOR36", Const, 0}, + {"AF_VENDOR37", Const, 0}, + {"AF_VENDOR38", Const, 0}, + {"AF_VENDOR39", Const, 0}, + {"AF_VENDOR40", Const, 0}, + {"AF_VENDOR41", Const, 0}, + {"AF_VENDOR42", Const, 0}, + {"AF_VENDOR43", Const, 0}, + {"AF_VENDOR44", Const, 0}, + {"AF_VENDOR45", Const, 0}, + {"AF_VENDOR46", Const, 0}, + {"AF_VENDOR47", Const, 0}, + {"AF_WANPIPE", Const, 0}, + {"AF_X25", Const, 0}, + {"AI_CANONNAME", Const, 1}, + {"AI_NUMERICHOST", Const, 1}, + {"AI_PASSIVE", Const, 1}, + {"APPLICATION_ERROR", Const, 0}, + {"ARPHRD_ADAPT", Const, 0}, + {"ARPHRD_APPLETLK", Const, 0}, + {"ARPHRD_ARCNET", Const, 0}, + {"ARPHRD_ASH", Const, 0}, + {"ARPHRD_ATM", Const, 0}, + {"ARPHRD_AX25", Const, 0}, + {"ARPHRD_BIF", Const, 0}, + {"ARPHRD_CHAOS", Const, 0}, + {"ARPHRD_CISCO", Const, 0}, + {"ARPHRD_CSLIP", Const, 0}, + {"ARPHRD_CSLIP6", Const, 0}, + {"ARPHRD_DDCMP", Const, 0}, + {"ARPHRD_DLCI", Const, 0}, + {"ARPHRD_ECONET", Const, 0}, + {"ARPHRD_EETHER", Const, 0}, + {"ARPHRD_ETHER", Const, 0}, + {"ARPHRD_EUI64", Const, 0}, + {"ARPHRD_FCAL", Const, 0}, + {"ARPHRD_FCFABRIC", Const, 0}, + {"ARPHRD_FCPL", Const, 0}, + {"ARPHRD_FCPP", Const, 0}, + {"ARPHRD_FDDI", Const, 0}, + {"ARPHRD_FRAD", Const, 0}, + {"ARPHRD_FRELAY", Const, 1}, + {"ARPHRD_HDLC", Const, 0}, + {"ARPHRD_HIPPI", Const, 0}, + {"ARPHRD_HWX25", Const, 0}, + {"ARPHRD_IEEE1394", Const, 0}, + {"ARPHRD_IEEE802", Const, 0}, + {"ARPHRD_IEEE80211", Const, 0}, + {"ARPHRD_IEEE80211_PRISM", Const, 0}, + {"ARPHRD_IEEE80211_RADIOTAP", Const, 0}, + {"ARPHRD_IEEE802154", Const, 0}, + {"ARPHRD_IEEE802154_PHY", Const, 0}, + {"ARPHRD_IEEE802_TR", Const, 0}, + {"ARPHRD_INFINIBAND", Const, 0}, + {"ARPHRD_IPDDP", Const, 0}, + {"ARPHRD_IPGRE", Const, 0}, + {"ARPHRD_IRDA", Const, 0}, + {"ARPHRD_LAPB", Const, 0}, + {"ARPHRD_LOCALTLK", Const, 0}, + {"ARPHRD_LOOPBACK", Const, 0}, + {"ARPHRD_METRICOM", Const, 0}, + {"ARPHRD_NETROM", Const, 0}, + {"ARPHRD_NONE", Const, 0}, + {"ARPHRD_PIMREG", Const, 0}, + {"ARPHRD_PPP", Const, 0}, + {"ARPHRD_PRONET", Const, 0}, + {"ARPHRD_RAWHDLC", Const, 0}, + {"ARPHRD_ROSE", Const, 0}, + {"ARPHRD_RSRVD", Const, 0}, + {"ARPHRD_SIT", Const, 0}, + {"ARPHRD_SKIP", Const, 0}, + {"ARPHRD_SLIP", Const, 0}, + {"ARPHRD_SLIP6", Const, 0}, + {"ARPHRD_STRIP", Const, 1}, + {"ARPHRD_TUNNEL", Const, 0}, + {"ARPHRD_TUNNEL6", Const, 0}, + {"ARPHRD_VOID", Const, 0}, + {"ARPHRD_X25", Const, 0}, + {"AUTHTYPE_CLIENT", Const, 0}, + {"AUTHTYPE_SERVER", Const, 0}, + {"Accept", Func, 0}, + {"Accept4", Func, 1}, + {"AcceptEx", Func, 0}, + {"Access", Func, 0}, + {"Acct", Func, 0}, + {"AddrinfoW", Type, 1}, + {"AddrinfoW.Addr", Field, 1}, + {"AddrinfoW.Addrlen", Field, 1}, + {"AddrinfoW.Canonname", Field, 1}, + {"AddrinfoW.Family", Field, 1}, + {"AddrinfoW.Flags", Field, 1}, + {"AddrinfoW.Next", Field, 1}, + {"AddrinfoW.Protocol", Field, 1}, + {"AddrinfoW.Socktype", Field, 1}, + {"Adjtime", Func, 0}, + {"Adjtimex", Func, 0}, + {"AllThreadsSyscall", Func, 16}, + {"AllThreadsSyscall6", Func, 16}, + {"AttachLsf", Func, 0}, + {"B0", Const, 0}, + {"B1000000", Const, 0}, + {"B110", Const, 0}, + {"B115200", Const, 0}, + {"B1152000", Const, 0}, + {"B1200", Const, 0}, + {"B134", Const, 0}, + {"B14400", Const, 1}, + {"B150", Const, 0}, + {"B1500000", Const, 0}, + {"B1800", Const, 0}, + {"B19200", Const, 0}, + {"B200", Const, 0}, + {"B2000000", Const, 0}, + {"B230400", Const, 0}, + {"B2400", Const, 0}, + {"B2500000", Const, 0}, + {"B28800", Const, 1}, + {"B300", Const, 0}, + {"B3000000", Const, 0}, + {"B3500000", Const, 0}, + {"B38400", Const, 0}, + {"B4000000", Const, 0}, + {"B460800", Const, 0}, + {"B4800", Const, 0}, + {"B50", Const, 0}, + {"B500000", Const, 0}, + {"B57600", Const, 0}, + {"B576000", Const, 0}, + {"B600", Const, 0}, + {"B7200", Const, 1}, + {"B75", Const, 0}, + {"B76800", Const, 1}, + {"B921600", Const, 0}, + {"B9600", Const, 0}, + {"BASE_PROTOCOL", Const, 2}, + {"BIOCFEEDBACK", Const, 0}, + {"BIOCFLUSH", Const, 0}, + {"BIOCGBLEN", Const, 0}, + {"BIOCGDIRECTION", Const, 0}, + {"BIOCGDIRFILT", Const, 1}, + {"BIOCGDLT", Const, 0}, + {"BIOCGDLTLIST", Const, 0}, + {"BIOCGETBUFMODE", Const, 0}, + {"BIOCGETIF", Const, 0}, + {"BIOCGETZMAX", Const, 0}, + {"BIOCGFEEDBACK", Const, 1}, + {"BIOCGFILDROP", Const, 1}, + {"BIOCGHDRCMPLT", Const, 0}, + {"BIOCGRSIG", Const, 0}, + {"BIOCGRTIMEOUT", Const, 0}, + {"BIOCGSEESENT", Const, 0}, + {"BIOCGSTATS", Const, 0}, + {"BIOCGSTATSOLD", Const, 1}, + {"BIOCGTSTAMP", Const, 1}, + {"BIOCIMMEDIATE", Const, 0}, + {"BIOCLOCK", Const, 0}, + {"BIOCPROMISC", Const, 0}, + {"BIOCROTZBUF", Const, 0}, + {"BIOCSBLEN", Const, 0}, + {"BIOCSDIRECTION", Const, 0}, + {"BIOCSDIRFILT", Const, 1}, + {"BIOCSDLT", Const, 0}, + {"BIOCSETBUFMODE", Const, 0}, + {"BIOCSETF", Const, 0}, + {"BIOCSETFNR", Const, 0}, + {"BIOCSETIF", Const, 0}, + {"BIOCSETWF", Const, 0}, + {"BIOCSETZBUF", Const, 0}, + {"BIOCSFEEDBACK", Const, 1}, + {"BIOCSFILDROP", Const, 1}, + {"BIOCSHDRCMPLT", Const, 0}, + {"BIOCSRSIG", Const, 0}, + {"BIOCSRTIMEOUT", Const, 0}, + {"BIOCSSEESENT", Const, 0}, + {"BIOCSTCPF", Const, 1}, + {"BIOCSTSTAMP", Const, 1}, + {"BIOCSUDPF", Const, 1}, + {"BIOCVERSION", Const, 0}, + {"BPF_A", Const, 0}, + {"BPF_ABS", Const, 0}, + {"BPF_ADD", Const, 0}, + {"BPF_ALIGNMENT", Const, 0}, + {"BPF_ALIGNMENT32", Const, 1}, + {"BPF_ALU", Const, 0}, + {"BPF_AND", Const, 0}, + {"BPF_B", Const, 0}, + {"BPF_BUFMODE_BUFFER", Const, 0}, + {"BPF_BUFMODE_ZBUF", Const, 0}, + {"BPF_DFLTBUFSIZE", Const, 1}, + {"BPF_DIRECTION_IN", Const, 1}, + {"BPF_DIRECTION_OUT", Const, 1}, + {"BPF_DIV", Const, 0}, + {"BPF_H", Const, 0}, + {"BPF_IMM", Const, 0}, + {"BPF_IND", Const, 0}, + {"BPF_JA", Const, 0}, + {"BPF_JEQ", Const, 0}, + {"BPF_JGE", Const, 0}, + {"BPF_JGT", Const, 0}, + {"BPF_JMP", Const, 0}, + {"BPF_JSET", Const, 0}, + {"BPF_K", Const, 0}, + {"BPF_LD", Const, 0}, + {"BPF_LDX", Const, 0}, + {"BPF_LEN", Const, 0}, + {"BPF_LSH", Const, 0}, + {"BPF_MAJOR_VERSION", Const, 0}, + {"BPF_MAXBUFSIZE", Const, 0}, + {"BPF_MAXINSNS", Const, 0}, + {"BPF_MEM", Const, 0}, + {"BPF_MEMWORDS", Const, 0}, + {"BPF_MINBUFSIZE", Const, 0}, + {"BPF_MINOR_VERSION", Const, 0}, + {"BPF_MISC", Const, 0}, + {"BPF_MSH", Const, 0}, + {"BPF_MUL", Const, 0}, + {"BPF_NEG", Const, 0}, + {"BPF_OR", Const, 0}, + {"BPF_RELEASE", Const, 0}, + {"BPF_RET", Const, 0}, + {"BPF_RSH", Const, 0}, + {"BPF_ST", Const, 0}, + {"BPF_STX", Const, 0}, + {"BPF_SUB", Const, 0}, + {"BPF_TAX", Const, 0}, + {"BPF_TXA", Const, 0}, + {"BPF_T_BINTIME", Const, 1}, + {"BPF_T_BINTIME_FAST", Const, 1}, + {"BPF_T_BINTIME_MONOTONIC", Const, 1}, + {"BPF_T_BINTIME_MONOTONIC_FAST", Const, 1}, + {"BPF_T_FAST", Const, 1}, + {"BPF_T_FLAG_MASK", Const, 1}, + {"BPF_T_FORMAT_MASK", Const, 1}, + {"BPF_T_MICROTIME", Const, 1}, + {"BPF_T_MICROTIME_FAST", Const, 1}, + {"BPF_T_MICROTIME_MONOTONIC", Const, 1}, + {"BPF_T_MICROTIME_MONOTONIC_FAST", Const, 1}, + {"BPF_T_MONOTONIC", Const, 1}, + {"BPF_T_MONOTONIC_FAST", Const, 1}, + {"BPF_T_NANOTIME", Const, 1}, + {"BPF_T_NANOTIME_FAST", Const, 1}, + {"BPF_T_NANOTIME_MONOTONIC", Const, 1}, + {"BPF_T_NANOTIME_MONOTONIC_FAST", Const, 1}, + {"BPF_T_NONE", Const, 1}, + {"BPF_T_NORMAL", Const, 1}, + {"BPF_W", Const, 0}, + {"BPF_X", Const, 0}, + {"BRKINT", Const, 0}, + {"Bind", Func, 0}, + {"BindToDevice", Func, 0}, + {"BpfBuflen", Func, 0}, + {"BpfDatalink", Func, 0}, + {"BpfHdr", Type, 0}, + {"BpfHdr.Caplen", Field, 0}, + {"BpfHdr.Datalen", Field, 0}, + {"BpfHdr.Hdrlen", Field, 0}, + {"BpfHdr.Pad_cgo_0", Field, 0}, + {"BpfHdr.Tstamp", Field, 0}, + {"BpfHeadercmpl", Func, 0}, + {"BpfInsn", Type, 0}, + {"BpfInsn.Code", Field, 0}, + {"BpfInsn.Jf", Field, 0}, + {"BpfInsn.Jt", Field, 0}, + {"BpfInsn.K", Field, 0}, + {"BpfInterface", Func, 0}, + {"BpfJump", Func, 0}, + {"BpfProgram", Type, 0}, + {"BpfProgram.Insns", Field, 0}, + {"BpfProgram.Len", Field, 0}, + {"BpfProgram.Pad_cgo_0", Field, 0}, + {"BpfStat", Type, 0}, + {"BpfStat.Capt", Field, 2}, + {"BpfStat.Drop", Field, 0}, + {"BpfStat.Padding", Field, 2}, + {"BpfStat.Recv", Field, 0}, + {"BpfStats", Func, 0}, + {"BpfStmt", Func, 0}, + {"BpfTimeout", Func, 0}, + {"BpfTimeval", Type, 2}, + {"BpfTimeval.Sec", Field, 2}, + {"BpfTimeval.Usec", Field, 2}, + {"BpfVersion", Type, 0}, + {"BpfVersion.Major", Field, 0}, + {"BpfVersion.Minor", Field, 0}, + {"BpfZbuf", Type, 0}, + {"BpfZbuf.Bufa", Field, 0}, + {"BpfZbuf.Bufb", Field, 0}, + {"BpfZbuf.Buflen", Field, 0}, + {"BpfZbufHeader", Type, 0}, + {"BpfZbufHeader.Kernel_gen", Field, 0}, + {"BpfZbufHeader.Kernel_len", Field, 0}, + {"BpfZbufHeader.User_gen", Field, 0}, + {"BpfZbufHeader.X_bzh_pad", Field, 0}, + {"ByHandleFileInformation", Type, 0}, + {"ByHandleFileInformation.CreationTime", Field, 0}, + {"ByHandleFileInformation.FileAttributes", Field, 0}, + {"ByHandleFileInformation.FileIndexHigh", Field, 0}, + {"ByHandleFileInformation.FileIndexLow", Field, 0}, + {"ByHandleFileInformation.FileSizeHigh", Field, 0}, + {"ByHandleFileInformation.FileSizeLow", Field, 0}, + {"ByHandleFileInformation.LastAccessTime", Field, 0}, + {"ByHandleFileInformation.LastWriteTime", Field, 0}, + {"ByHandleFileInformation.NumberOfLinks", Field, 0}, + {"ByHandleFileInformation.VolumeSerialNumber", Field, 0}, + {"BytePtrFromString", Func, 1}, + {"ByteSliceFromString", Func, 1}, + {"CCR0_FLUSH", Const, 1}, + {"CERT_CHAIN_POLICY_AUTHENTICODE", Const, 0}, + {"CERT_CHAIN_POLICY_AUTHENTICODE_TS", Const, 0}, + {"CERT_CHAIN_POLICY_BASE", Const, 0}, + {"CERT_CHAIN_POLICY_BASIC_CONSTRAINTS", Const, 0}, + {"CERT_CHAIN_POLICY_EV", Const, 0}, + {"CERT_CHAIN_POLICY_MICROSOFT_ROOT", Const, 0}, + {"CERT_CHAIN_POLICY_NT_AUTH", Const, 0}, + {"CERT_CHAIN_POLICY_SSL", Const, 0}, + {"CERT_E_CN_NO_MATCH", Const, 0}, + {"CERT_E_EXPIRED", Const, 0}, + {"CERT_E_PURPOSE", Const, 0}, + {"CERT_E_ROLE", Const, 0}, + {"CERT_E_UNTRUSTEDROOT", Const, 0}, + {"CERT_STORE_ADD_ALWAYS", Const, 0}, + {"CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG", Const, 0}, + {"CERT_STORE_PROV_MEMORY", Const, 0}, + {"CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT", Const, 0}, + {"CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT", Const, 0}, + {"CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT", Const, 0}, + {"CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT", Const, 0}, + {"CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT", Const, 0}, + {"CERT_TRUST_INVALID_BASIC_CONSTRAINTS", Const, 0}, + {"CERT_TRUST_INVALID_EXTENSION", Const, 0}, + {"CERT_TRUST_INVALID_NAME_CONSTRAINTS", Const, 0}, + {"CERT_TRUST_INVALID_POLICY_CONSTRAINTS", Const, 0}, + {"CERT_TRUST_IS_CYCLIC", Const, 0}, + {"CERT_TRUST_IS_EXPLICIT_DISTRUST", Const, 0}, + {"CERT_TRUST_IS_NOT_SIGNATURE_VALID", Const, 0}, + {"CERT_TRUST_IS_NOT_TIME_VALID", Const, 0}, + {"CERT_TRUST_IS_NOT_VALID_FOR_USAGE", Const, 0}, + {"CERT_TRUST_IS_OFFLINE_REVOCATION", Const, 0}, + {"CERT_TRUST_IS_REVOKED", Const, 0}, + {"CERT_TRUST_IS_UNTRUSTED_ROOT", Const, 0}, + {"CERT_TRUST_NO_ERROR", Const, 0}, + {"CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY", Const, 0}, + {"CERT_TRUST_REVOCATION_STATUS_UNKNOWN", Const, 0}, + {"CFLUSH", Const, 1}, + {"CLOCAL", Const, 0}, + {"CLONE_CHILD_CLEARTID", Const, 2}, + {"CLONE_CHILD_SETTID", Const, 2}, + {"CLONE_CLEAR_SIGHAND", Const, 20}, + {"CLONE_CSIGNAL", Const, 3}, + {"CLONE_DETACHED", Const, 2}, + {"CLONE_FILES", Const, 2}, + {"CLONE_FS", Const, 2}, + {"CLONE_INTO_CGROUP", Const, 20}, + {"CLONE_IO", Const, 2}, + {"CLONE_NEWCGROUP", Const, 20}, + {"CLONE_NEWIPC", Const, 2}, + {"CLONE_NEWNET", Const, 2}, + {"CLONE_NEWNS", Const, 2}, + {"CLONE_NEWPID", Const, 2}, + {"CLONE_NEWTIME", Const, 20}, + {"CLONE_NEWUSER", Const, 2}, + {"CLONE_NEWUTS", Const, 2}, + {"CLONE_PARENT", Const, 2}, + {"CLONE_PARENT_SETTID", Const, 2}, + {"CLONE_PID", Const, 3}, + {"CLONE_PIDFD", Const, 20}, + {"CLONE_PTRACE", Const, 2}, + {"CLONE_SETTLS", Const, 2}, + {"CLONE_SIGHAND", Const, 2}, + {"CLONE_SYSVSEM", Const, 2}, + {"CLONE_THREAD", Const, 2}, + {"CLONE_UNTRACED", Const, 2}, + {"CLONE_VFORK", Const, 2}, + {"CLONE_VM", Const, 2}, + {"CPUID_CFLUSH", Const, 1}, + {"CREAD", Const, 0}, + {"CREATE_ALWAYS", Const, 0}, + {"CREATE_NEW", Const, 0}, + {"CREATE_NEW_PROCESS_GROUP", Const, 1}, + {"CREATE_UNICODE_ENVIRONMENT", Const, 0}, + {"CRYPT_DEFAULT_CONTAINER_OPTIONAL", Const, 0}, + {"CRYPT_DELETEKEYSET", Const, 0}, + {"CRYPT_MACHINE_KEYSET", Const, 0}, + {"CRYPT_NEWKEYSET", Const, 0}, + {"CRYPT_SILENT", Const, 0}, + {"CRYPT_VERIFYCONTEXT", Const, 0}, + {"CS5", Const, 0}, + {"CS6", Const, 0}, + {"CS7", Const, 0}, + {"CS8", Const, 0}, + {"CSIZE", Const, 0}, + {"CSTART", Const, 1}, + {"CSTATUS", Const, 1}, + {"CSTOP", Const, 1}, + {"CSTOPB", Const, 0}, + {"CSUSP", Const, 1}, + {"CTL_MAXNAME", Const, 0}, + {"CTL_NET", Const, 0}, + {"CTL_QUERY", Const, 1}, + {"CTRL_BREAK_EVENT", Const, 1}, + {"CTRL_CLOSE_EVENT", Const, 14}, + {"CTRL_C_EVENT", Const, 1}, + {"CTRL_LOGOFF_EVENT", Const, 14}, + {"CTRL_SHUTDOWN_EVENT", Const, 14}, + {"CancelIo", Func, 0}, + {"CancelIoEx", Func, 1}, + {"CertAddCertificateContextToStore", Func, 0}, + {"CertChainContext", Type, 0}, + {"CertChainContext.ChainCount", Field, 0}, + {"CertChainContext.Chains", Field, 0}, + {"CertChainContext.HasRevocationFreshnessTime", Field, 0}, + {"CertChainContext.LowerQualityChainCount", Field, 0}, + {"CertChainContext.LowerQualityChains", Field, 0}, + {"CertChainContext.RevocationFreshnessTime", Field, 0}, + {"CertChainContext.Size", Field, 0}, + {"CertChainContext.TrustStatus", Field, 0}, + {"CertChainElement", Type, 0}, + {"CertChainElement.ApplicationUsage", Field, 0}, + {"CertChainElement.CertContext", Field, 0}, + {"CertChainElement.ExtendedErrorInfo", Field, 0}, + {"CertChainElement.IssuanceUsage", Field, 0}, + {"CertChainElement.RevocationInfo", Field, 0}, + {"CertChainElement.Size", Field, 0}, + {"CertChainElement.TrustStatus", Field, 0}, + {"CertChainPara", Type, 0}, + {"CertChainPara.CacheResync", Field, 0}, + {"CertChainPara.CheckRevocationFreshnessTime", Field, 0}, + {"CertChainPara.RequestedUsage", Field, 0}, + {"CertChainPara.RequstedIssuancePolicy", Field, 0}, + {"CertChainPara.RevocationFreshnessTime", Field, 0}, + {"CertChainPara.Size", Field, 0}, + {"CertChainPara.URLRetrievalTimeout", Field, 0}, + {"CertChainPolicyPara", Type, 0}, + {"CertChainPolicyPara.ExtraPolicyPara", Field, 0}, + {"CertChainPolicyPara.Flags", Field, 0}, + {"CertChainPolicyPara.Size", Field, 0}, + {"CertChainPolicyStatus", Type, 0}, + {"CertChainPolicyStatus.ChainIndex", Field, 0}, + {"CertChainPolicyStatus.ElementIndex", Field, 0}, + {"CertChainPolicyStatus.Error", Field, 0}, + {"CertChainPolicyStatus.ExtraPolicyStatus", Field, 0}, + {"CertChainPolicyStatus.Size", Field, 0}, + {"CertCloseStore", Func, 0}, + {"CertContext", Type, 0}, + {"CertContext.CertInfo", Field, 0}, + {"CertContext.EncodedCert", Field, 0}, + {"CertContext.EncodingType", Field, 0}, + {"CertContext.Length", Field, 0}, + {"CertContext.Store", Field, 0}, + {"CertCreateCertificateContext", Func, 0}, + {"CertEnhKeyUsage", Type, 0}, + {"CertEnhKeyUsage.Length", Field, 0}, + {"CertEnhKeyUsage.UsageIdentifiers", Field, 0}, + {"CertEnumCertificatesInStore", Func, 0}, + {"CertFreeCertificateChain", Func, 0}, + {"CertFreeCertificateContext", Func, 0}, + {"CertGetCertificateChain", Func, 0}, + {"CertInfo", Type, 11}, + {"CertOpenStore", Func, 0}, + {"CertOpenSystemStore", Func, 0}, + {"CertRevocationCrlInfo", Type, 11}, + {"CertRevocationInfo", Type, 0}, + {"CertRevocationInfo.CrlInfo", Field, 0}, + {"CertRevocationInfo.FreshnessTime", Field, 0}, + {"CertRevocationInfo.HasFreshnessTime", Field, 0}, + {"CertRevocationInfo.OidSpecificInfo", Field, 0}, + {"CertRevocationInfo.RevocationOid", Field, 0}, + {"CertRevocationInfo.RevocationResult", Field, 0}, + {"CertRevocationInfo.Size", Field, 0}, + {"CertSimpleChain", Type, 0}, + {"CertSimpleChain.Elements", Field, 0}, + {"CertSimpleChain.HasRevocationFreshnessTime", Field, 0}, + {"CertSimpleChain.NumElements", Field, 0}, + {"CertSimpleChain.RevocationFreshnessTime", Field, 0}, + {"CertSimpleChain.Size", Field, 0}, + {"CertSimpleChain.TrustListInfo", Field, 0}, + {"CertSimpleChain.TrustStatus", Field, 0}, + {"CertTrustListInfo", Type, 11}, + {"CertTrustStatus", Type, 0}, + {"CertTrustStatus.ErrorStatus", Field, 0}, + {"CertTrustStatus.InfoStatus", Field, 0}, + {"CertUsageMatch", Type, 0}, + {"CertUsageMatch.Type", Field, 0}, + {"CertUsageMatch.Usage", Field, 0}, + {"CertVerifyCertificateChainPolicy", Func, 0}, + {"Chdir", Func, 0}, + {"CheckBpfVersion", Func, 0}, + {"Chflags", Func, 0}, + {"Chmod", Func, 0}, + {"Chown", Func, 0}, + {"Chroot", Func, 0}, + {"Clearenv", Func, 0}, + {"Close", Func, 0}, + {"CloseHandle", Func, 0}, + {"CloseOnExec", Func, 0}, + {"Closesocket", Func, 0}, + {"CmsgLen", Func, 0}, + {"CmsgSpace", Func, 0}, + {"Cmsghdr", Type, 0}, + {"Cmsghdr.Len", Field, 0}, + {"Cmsghdr.Level", Field, 0}, + {"Cmsghdr.Type", Field, 0}, + {"Cmsghdr.X__cmsg_data", Field, 0}, + {"CommandLineToArgv", Func, 0}, + {"ComputerName", Func, 0}, + {"Conn", Type, 9}, + {"Connect", Func, 0}, + {"ConnectEx", Func, 1}, + {"ConvertSidToStringSid", Func, 0}, + {"ConvertStringSidToSid", Func, 0}, + {"CopySid", Func, 0}, + {"Creat", Func, 0}, + {"CreateDirectory", Func, 0}, + {"CreateFile", Func, 0}, + {"CreateFileMapping", Func, 0}, + {"CreateHardLink", Func, 4}, + {"CreateIoCompletionPort", Func, 0}, + {"CreatePipe", Func, 0}, + {"CreateProcess", Func, 0}, + {"CreateProcessAsUser", Func, 10}, + {"CreateSymbolicLink", Func, 4}, + {"CreateToolhelp32Snapshot", Func, 4}, + {"Credential", Type, 0}, + {"Credential.Gid", Field, 0}, + {"Credential.Groups", Field, 0}, + {"Credential.NoSetGroups", Field, 9}, + {"Credential.Uid", Field, 0}, + {"CryptAcquireContext", Func, 0}, + {"CryptGenRandom", Func, 0}, + {"CryptReleaseContext", Func, 0}, + {"DIOCBSFLUSH", Const, 1}, + {"DIOCOSFPFLUSH", Const, 1}, + {"DLL", Type, 0}, + {"DLL.Handle", Field, 0}, + {"DLL.Name", Field, 0}, + {"DLLError", Type, 0}, + {"DLLError.Err", Field, 0}, + {"DLLError.Msg", Field, 0}, + {"DLLError.ObjName", Field, 0}, + {"DLT_A429", Const, 0}, + {"DLT_A653_ICM", Const, 0}, + {"DLT_AIRONET_HEADER", Const, 0}, + {"DLT_AOS", Const, 1}, + {"DLT_APPLE_IP_OVER_IEEE1394", Const, 0}, + {"DLT_ARCNET", Const, 0}, + {"DLT_ARCNET_LINUX", Const, 0}, + {"DLT_ATM_CLIP", Const, 0}, + {"DLT_ATM_RFC1483", Const, 0}, + {"DLT_AURORA", Const, 0}, + {"DLT_AX25", Const, 0}, + {"DLT_AX25_KISS", Const, 0}, + {"DLT_BACNET_MS_TP", Const, 0}, + {"DLT_BLUETOOTH_HCI_H4", Const, 0}, + {"DLT_BLUETOOTH_HCI_H4_WITH_PHDR", Const, 0}, + {"DLT_CAN20B", Const, 0}, + {"DLT_CAN_SOCKETCAN", Const, 1}, + {"DLT_CHAOS", Const, 0}, + {"DLT_CHDLC", Const, 0}, + {"DLT_CISCO_IOS", Const, 0}, + {"DLT_C_HDLC", Const, 0}, + {"DLT_C_HDLC_WITH_DIR", Const, 0}, + {"DLT_DBUS", Const, 1}, + {"DLT_DECT", Const, 1}, + {"DLT_DOCSIS", Const, 0}, + {"DLT_DVB_CI", Const, 1}, + {"DLT_ECONET", Const, 0}, + {"DLT_EN10MB", Const, 0}, + {"DLT_EN3MB", Const, 0}, + {"DLT_ENC", Const, 0}, + {"DLT_ERF", Const, 0}, + {"DLT_ERF_ETH", Const, 0}, + {"DLT_ERF_POS", Const, 0}, + {"DLT_FC_2", Const, 1}, + {"DLT_FC_2_WITH_FRAME_DELIMS", Const, 1}, + {"DLT_FDDI", Const, 0}, + {"DLT_FLEXRAY", Const, 0}, + {"DLT_FRELAY", Const, 0}, + {"DLT_FRELAY_WITH_DIR", Const, 0}, + {"DLT_GCOM_SERIAL", Const, 0}, + {"DLT_GCOM_T1E1", Const, 0}, + {"DLT_GPF_F", Const, 0}, + {"DLT_GPF_T", Const, 0}, + {"DLT_GPRS_LLC", Const, 0}, + {"DLT_GSMTAP_ABIS", Const, 1}, + {"DLT_GSMTAP_UM", Const, 1}, + {"DLT_HDLC", Const, 1}, + {"DLT_HHDLC", Const, 0}, + {"DLT_HIPPI", Const, 1}, + {"DLT_IBM_SN", Const, 0}, + {"DLT_IBM_SP", Const, 0}, + {"DLT_IEEE802", Const, 0}, + {"DLT_IEEE802_11", Const, 0}, + {"DLT_IEEE802_11_RADIO", Const, 0}, + {"DLT_IEEE802_11_RADIO_AVS", Const, 0}, + {"DLT_IEEE802_15_4", Const, 0}, + {"DLT_IEEE802_15_4_LINUX", Const, 0}, + {"DLT_IEEE802_15_4_NOFCS", Const, 1}, + {"DLT_IEEE802_15_4_NONASK_PHY", Const, 0}, + {"DLT_IEEE802_16_MAC_CPS", Const, 0}, + {"DLT_IEEE802_16_MAC_CPS_RADIO", Const, 0}, + {"DLT_IPFILTER", Const, 0}, + {"DLT_IPMB", Const, 0}, + {"DLT_IPMB_LINUX", Const, 0}, + {"DLT_IPNET", Const, 1}, + {"DLT_IPOIB", Const, 1}, + {"DLT_IPV4", Const, 1}, + {"DLT_IPV6", Const, 1}, + {"DLT_IP_OVER_FC", Const, 0}, + {"DLT_JUNIPER_ATM1", Const, 0}, + {"DLT_JUNIPER_ATM2", Const, 0}, + {"DLT_JUNIPER_ATM_CEMIC", Const, 1}, + {"DLT_JUNIPER_CHDLC", Const, 0}, + {"DLT_JUNIPER_ES", Const, 0}, + {"DLT_JUNIPER_ETHER", Const, 0}, + {"DLT_JUNIPER_FIBRECHANNEL", Const, 1}, + {"DLT_JUNIPER_FRELAY", Const, 0}, + {"DLT_JUNIPER_GGSN", Const, 0}, + {"DLT_JUNIPER_ISM", Const, 0}, + {"DLT_JUNIPER_MFR", Const, 0}, + {"DLT_JUNIPER_MLFR", Const, 0}, + {"DLT_JUNIPER_MLPPP", Const, 0}, + {"DLT_JUNIPER_MONITOR", Const, 0}, + {"DLT_JUNIPER_PIC_PEER", Const, 0}, + {"DLT_JUNIPER_PPP", Const, 0}, + {"DLT_JUNIPER_PPPOE", Const, 0}, + {"DLT_JUNIPER_PPPOE_ATM", Const, 0}, + {"DLT_JUNIPER_SERVICES", Const, 0}, + {"DLT_JUNIPER_SRX_E2E", Const, 1}, + {"DLT_JUNIPER_ST", Const, 0}, + {"DLT_JUNIPER_VP", Const, 0}, + {"DLT_JUNIPER_VS", Const, 1}, + {"DLT_LAPB_WITH_DIR", Const, 0}, + {"DLT_LAPD", Const, 0}, + {"DLT_LIN", Const, 0}, + {"DLT_LINUX_EVDEV", Const, 1}, + {"DLT_LINUX_IRDA", Const, 0}, + {"DLT_LINUX_LAPD", Const, 0}, + {"DLT_LINUX_PPP_WITHDIRECTION", Const, 0}, + {"DLT_LINUX_SLL", Const, 0}, + {"DLT_LOOP", Const, 0}, + {"DLT_LTALK", Const, 0}, + {"DLT_MATCHING_MAX", Const, 1}, + {"DLT_MATCHING_MIN", Const, 1}, + {"DLT_MFR", Const, 0}, + {"DLT_MOST", Const, 0}, + {"DLT_MPEG_2_TS", Const, 1}, + {"DLT_MPLS", Const, 1}, + {"DLT_MTP2", Const, 0}, + {"DLT_MTP2_WITH_PHDR", Const, 0}, + {"DLT_MTP3", Const, 0}, + {"DLT_MUX27010", Const, 1}, + {"DLT_NETANALYZER", Const, 1}, + {"DLT_NETANALYZER_TRANSPARENT", Const, 1}, + {"DLT_NFC_LLCP", Const, 1}, + {"DLT_NFLOG", Const, 1}, + {"DLT_NG40", Const, 1}, + {"DLT_NULL", Const, 0}, + {"DLT_PCI_EXP", Const, 0}, + {"DLT_PFLOG", Const, 0}, + {"DLT_PFSYNC", Const, 0}, + {"DLT_PPI", Const, 0}, + {"DLT_PPP", Const, 0}, + {"DLT_PPP_BSDOS", Const, 0}, + {"DLT_PPP_ETHER", Const, 0}, + {"DLT_PPP_PPPD", Const, 0}, + {"DLT_PPP_SERIAL", Const, 0}, + {"DLT_PPP_WITH_DIR", Const, 0}, + {"DLT_PPP_WITH_DIRECTION", Const, 0}, + {"DLT_PRISM_HEADER", Const, 0}, + {"DLT_PRONET", Const, 0}, + {"DLT_RAIF1", Const, 0}, + {"DLT_RAW", Const, 0}, + {"DLT_RAWAF_MASK", Const, 1}, + {"DLT_RIO", Const, 0}, + {"DLT_SCCP", Const, 0}, + {"DLT_SITA", Const, 0}, + {"DLT_SLIP", Const, 0}, + {"DLT_SLIP_BSDOS", Const, 0}, + {"DLT_STANAG_5066_D_PDU", Const, 1}, + {"DLT_SUNATM", Const, 0}, + {"DLT_SYMANTEC_FIREWALL", Const, 0}, + {"DLT_TZSP", Const, 0}, + {"DLT_USB", Const, 0}, + {"DLT_USB_LINUX", Const, 0}, + {"DLT_USB_LINUX_MMAPPED", Const, 1}, + {"DLT_USER0", Const, 0}, + {"DLT_USER1", Const, 0}, + {"DLT_USER10", Const, 0}, + {"DLT_USER11", Const, 0}, + {"DLT_USER12", Const, 0}, + {"DLT_USER13", Const, 0}, + {"DLT_USER14", Const, 0}, + {"DLT_USER15", Const, 0}, + {"DLT_USER2", Const, 0}, + {"DLT_USER3", Const, 0}, + {"DLT_USER4", Const, 0}, + {"DLT_USER5", Const, 0}, + {"DLT_USER6", Const, 0}, + {"DLT_USER7", Const, 0}, + {"DLT_USER8", Const, 0}, + {"DLT_USER9", Const, 0}, + {"DLT_WIHART", Const, 1}, + {"DLT_X2E_SERIAL", Const, 0}, + {"DLT_X2E_XORAYA", Const, 0}, + {"DNSMXData", Type, 0}, + {"DNSMXData.NameExchange", Field, 0}, + {"DNSMXData.Pad", Field, 0}, + {"DNSMXData.Preference", Field, 0}, + {"DNSPTRData", Type, 0}, + {"DNSPTRData.Host", Field, 0}, + {"DNSRecord", Type, 0}, + {"DNSRecord.Data", Field, 0}, + {"DNSRecord.Dw", Field, 0}, + {"DNSRecord.Length", Field, 0}, + {"DNSRecord.Name", Field, 0}, + {"DNSRecord.Next", Field, 0}, + {"DNSRecord.Reserved", Field, 0}, + {"DNSRecord.Ttl", Field, 0}, + {"DNSRecord.Type", Field, 0}, + {"DNSSRVData", Type, 0}, + {"DNSSRVData.Pad", Field, 0}, + {"DNSSRVData.Port", Field, 0}, + {"DNSSRVData.Priority", Field, 0}, + {"DNSSRVData.Target", Field, 0}, + {"DNSSRVData.Weight", Field, 0}, + {"DNSTXTData", Type, 0}, + {"DNSTXTData.StringArray", Field, 0}, + {"DNSTXTData.StringCount", Field, 0}, + {"DNS_INFO_NO_RECORDS", Const, 4}, + {"DNS_TYPE_A", Const, 0}, + {"DNS_TYPE_A6", Const, 0}, + {"DNS_TYPE_AAAA", Const, 0}, + {"DNS_TYPE_ADDRS", Const, 0}, + {"DNS_TYPE_AFSDB", Const, 0}, + {"DNS_TYPE_ALL", Const, 0}, + {"DNS_TYPE_ANY", Const, 0}, + {"DNS_TYPE_ATMA", Const, 0}, + {"DNS_TYPE_AXFR", Const, 0}, + {"DNS_TYPE_CERT", Const, 0}, + {"DNS_TYPE_CNAME", Const, 0}, + {"DNS_TYPE_DHCID", Const, 0}, + {"DNS_TYPE_DNAME", Const, 0}, + {"DNS_TYPE_DNSKEY", Const, 0}, + {"DNS_TYPE_DS", Const, 0}, + {"DNS_TYPE_EID", Const, 0}, + {"DNS_TYPE_GID", Const, 0}, + {"DNS_TYPE_GPOS", Const, 0}, + {"DNS_TYPE_HINFO", Const, 0}, + {"DNS_TYPE_ISDN", Const, 0}, + {"DNS_TYPE_IXFR", Const, 0}, + {"DNS_TYPE_KEY", Const, 0}, + {"DNS_TYPE_KX", Const, 0}, + {"DNS_TYPE_LOC", Const, 0}, + {"DNS_TYPE_MAILA", Const, 0}, + {"DNS_TYPE_MAILB", Const, 0}, + {"DNS_TYPE_MB", Const, 0}, + {"DNS_TYPE_MD", Const, 0}, + {"DNS_TYPE_MF", Const, 0}, + {"DNS_TYPE_MG", Const, 0}, + {"DNS_TYPE_MINFO", Const, 0}, + {"DNS_TYPE_MR", Const, 0}, + {"DNS_TYPE_MX", Const, 0}, + {"DNS_TYPE_NAPTR", Const, 0}, + {"DNS_TYPE_NBSTAT", Const, 0}, + {"DNS_TYPE_NIMLOC", Const, 0}, + {"DNS_TYPE_NS", Const, 0}, + {"DNS_TYPE_NSAP", Const, 0}, + {"DNS_TYPE_NSAPPTR", Const, 0}, + {"DNS_TYPE_NSEC", Const, 0}, + {"DNS_TYPE_NULL", Const, 0}, + {"DNS_TYPE_NXT", Const, 0}, + {"DNS_TYPE_OPT", Const, 0}, + {"DNS_TYPE_PTR", Const, 0}, + {"DNS_TYPE_PX", Const, 0}, + {"DNS_TYPE_RP", Const, 0}, + {"DNS_TYPE_RRSIG", Const, 0}, + {"DNS_TYPE_RT", Const, 0}, + {"DNS_TYPE_SIG", Const, 0}, + {"DNS_TYPE_SINK", Const, 0}, + {"DNS_TYPE_SOA", Const, 0}, + {"DNS_TYPE_SRV", Const, 0}, + {"DNS_TYPE_TEXT", Const, 0}, + {"DNS_TYPE_TKEY", Const, 0}, + {"DNS_TYPE_TSIG", Const, 0}, + {"DNS_TYPE_UID", Const, 0}, + {"DNS_TYPE_UINFO", Const, 0}, + {"DNS_TYPE_UNSPEC", Const, 0}, + {"DNS_TYPE_WINS", Const, 0}, + {"DNS_TYPE_WINSR", Const, 0}, + {"DNS_TYPE_WKS", Const, 0}, + {"DNS_TYPE_X25", Const, 0}, + {"DT_BLK", Const, 0}, + {"DT_CHR", Const, 0}, + {"DT_DIR", Const, 0}, + {"DT_FIFO", Const, 0}, + {"DT_LNK", Const, 0}, + {"DT_REG", Const, 0}, + {"DT_SOCK", Const, 0}, + {"DT_UNKNOWN", Const, 0}, + {"DT_WHT", Const, 0}, + {"DUPLICATE_CLOSE_SOURCE", Const, 0}, + {"DUPLICATE_SAME_ACCESS", Const, 0}, + {"DeleteFile", Func, 0}, + {"DetachLsf", Func, 0}, + {"DeviceIoControl", Func, 4}, + {"Dirent", Type, 0}, + {"Dirent.Fileno", Field, 0}, + {"Dirent.Ino", Field, 0}, + {"Dirent.Name", Field, 0}, + {"Dirent.Namlen", Field, 0}, + {"Dirent.Off", Field, 0}, + {"Dirent.Pad0", Field, 12}, + {"Dirent.Pad1", Field, 12}, + {"Dirent.Pad_cgo_0", Field, 0}, + {"Dirent.Reclen", Field, 0}, + {"Dirent.Seekoff", Field, 0}, + {"Dirent.Type", Field, 0}, + {"Dirent.X__d_padding", Field, 3}, + {"DnsNameCompare", Func, 4}, + {"DnsQuery", Func, 0}, + {"DnsRecordListFree", Func, 0}, + {"DnsSectionAdditional", Const, 4}, + {"DnsSectionAnswer", Const, 4}, + {"DnsSectionAuthority", Const, 4}, + {"DnsSectionQuestion", Const, 4}, + {"Dup", Func, 0}, + {"Dup2", Func, 0}, + {"Dup3", Func, 2}, + {"DuplicateHandle", Func, 0}, + {"E2BIG", Const, 0}, + {"EACCES", Const, 0}, + {"EADDRINUSE", Const, 0}, + {"EADDRNOTAVAIL", Const, 0}, + {"EADV", Const, 0}, + {"EAFNOSUPPORT", Const, 0}, + {"EAGAIN", Const, 0}, + {"EALREADY", Const, 0}, + {"EAUTH", Const, 0}, + {"EBADARCH", Const, 0}, + {"EBADE", Const, 0}, + {"EBADEXEC", Const, 0}, + {"EBADF", Const, 0}, + {"EBADFD", Const, 0}, + {"EBADMACHO", Const, 0}, + {"EBADMSG", Const, 0}, + {"EBADR", Const, 0}, + {"EBADRPC", Const, 0}, + {"EBADRQC", Const, 0}, + {"EBADSLT", Const, 0}, + {"EBFONT", Const, 0}, + {"EBUSY", Const, 0}, + {"ECANCELED", Const, 0}, + {"ECAPMODE", Const, 1}, + {"ECHILD", Const, 0}, + {"ECHO", Const, 0}, + {"ECHOCTL", Const, 0}, + {"ECHOE", Const, 0}, + {"ECHOK", Const, 0}, + {"ECHOKE", Const, 0}, + {"ECHONL", Const, 0}, + {"ECHOPRT", Const, 0}, + {"ECHRNG", Const, 0}, + {"ECOMM", Const, 0}, + {"ECONNABORTED", Const, 0}, + {"ECONNREFUSED", Const, 0}, + {"ECONNRESET", Const, 0}, + {"EDEADLK", Const, 0}, + {"EDEADLOCK", Const, 0}, + {"EDESTADDRREQ", Const, 0}, + {"EDEVERR", Const, 0}, + {"EDOM", Const, 0}, + {"EDOOFUS", Const, 0}, + {"EDOTDOT", Const, 0}, + {"EDQUOT", Const, 0}, + {"EEXIST", Const, 0}, + {"EFAULT", Const, 0}, + {"EFBIG", Const, 0}, + {"EFER_LMA", Const, 1}, + {"EFER_LME", Const, 1}, + {"EFER_NXE", Const, 1}, + {"EFER_SCE", Const, 1}, + {"EFTYPE", Const, 0}, + {"EHOSTDOWN", Const, 0}, + {"EHOSTUNREACH", Const, 0}, + {"EHWPOISON", Const, 0}, + {"EIDRM", Const, 0}, + {"EILSEQ", Const, 0}, + {"EINPROGRESS", Const, 0}, + {"EINTR", Const, 0}, + {"EINVAL", Const, 0}, + {"EIO", Const, 0}, + {"EIPSEC", Const, 1}, + {"EISCONN", Const, 0}, + {"EISDIR", Const, 0}, + {"EISNAM", Const, 0}, + {"EKEYEXPIRED", Const, 0}, + {"EKEYREJECTED", Const, 0}, + {"EKEYREVOKED", Const, 0}, + {"EL2HLT", Const, 0}, + {"EL2NSYNC", Const, 0}, + {"EL3HLT", Const, 0}, + {"EL3RST", Const, 0}, + {"ELAST", Const, 0}, + {"ELF_NGREG", Const, 0}, + {"ELF_PRARGSZ", Const, 0}, + {"ELIBACC", Const, 0}, + {"ELIBBAD", Const, 0}, + {"ELIBEXEC", Const, 0}, + {"ELIBMAX", Const, 0}, + {"ELIBSCN", Const, 0}, + {"ELNRNG", Const, 0}, + {"ELOOP", Const, 0}, + {"EMEDIUMTYPE", Const, 0}, + {"EMFILE", Const, 0}, + {"EMLINK", Const, 0}, + {"EMSGSIZE", Const, 0}, + {"EMT_TAGOVF", Const, 1}, + {"EMULTIHOP", Const, 0}, + {"EMUL_ENABLED", Const, 1}, + {"EMUL_LINUX", Const, 1}, + {"EMUL_LINUX32", Const, 1}, + {"EMUL_MAXID", Const, 1}, + {"EMUL_NATIVE", Const, 1}, + {"ENAMETOOLONG", Const, 0}, + {"ENAVAIL", Const, 0}, + {"ENDRUNDISC", Const, 1}, + {"ENEEDAUTH", Const, 0}, + {"ENETDOWN", Const, 0}, + {"ENETRESET", Const, 0}, + {"ENETUNREACH", Const, 0}, + {"ENFILE", Const, 0}, + {"ENOANO", Const, 0}, + {"ENOATTR", Const, 0}, + {"ENOBUFS", Const, 0}, + {"ENOCSI", Const, 0}, + {"ENODATA", Const, 0}, + {"ENODEV", Const, 0}, + {"ENOENT", Const, 0}, + {"ENOEXEC", Const, 0}, + {"ENOKEY", Const, 0}, + {"ENOLCK", Const, 0}, + {"ENOLINK", Const, 0}, + {"ENOMEDIUM", Const, 0}, + {"ENOMEM", Const, 0}, + {"ENOMSG", Const, 0}, + {"ENONET", Const, 0}, + {"ENOPKG", Const, 0}, + {"ENOPOLICY", Const, 0}, + {"ENOPROTOOPT", Const, 0}, + {"ENOSPC", Const, 0}, + {"ENOSR", Const, 0}, + {"ENOSTR", Const, 0}, + {"ENOSYS", Const, 0}, + {"ENOTBLK", Const, 0}, + {"ENOTCAPABLE", Const, 0}, + {"ENOTCONN", Const, 0}, + {"ENOTDIR", Const, 0}, + {"ENOTEMPTY", Const, 0}, + {"ENOTNAM", Const, 0}, + {"ENOTRECOVERABLE", Const, 0}, + {"ENOTSOCK", Const, 0}, + {"ENOTSUP", Const, 0}, + {"ENOTTY", Const, 0}, + {"ENOTUNIQ", Const, 0}, + {"ENXIO", Const, 0}, + {"EN_SW_CTL_INF", Const, 1}, + {"EN_SW_CTL_PREC", Const, 1}, + {"EN_SW_CTL_ROUND", Const, 1}, + {"EN_SW_DATACHAIN", Const, 1}, + {"EN_SW_DENORM", Const, 1}, + {"EN_SW_INVOP", Const, 1}, + {"EN_SW_OVERFLOW", Const, 1}, + {"EN_SW_PRECLOSS", Const, 1}, + {"EN_SW_UNDERFLOW", Const, 1}, + {"EN_SW_ZERODIV", Const, 1}, + {"EOPNOTSUPP", Const, 0}, + {"EOVERFLOW", Const, 0}, + {"EOWNERDEAD", Const, 0}, + {"EPERM", Const, 0}, + {"EPFNOSUPPORT", Const, 0}, + {"EPIPE", Const, 0}, + {"EPOLLERR", Const, 0}, + {"EPOLLET", Const, 0}, + {"EPOLLHUP", Const, 0}, + {"EPOLLIN", Const, 0}, + {"EPOLLMSG", Const, 0}, + {"EPOLLONESHOT", Const, 0}, + {"EPOLLOUT", Const, 0}, + {"EPOLLPRI", Const, 0}, + {"EPOLLRDBAND", Const, 0}, + {"EPOLLRDHUP", Const, 0}, + {"EPOLLRDNORM", Const, 0}, + {"EPOLLWRBAND", Const, 0}, + {"EPOLLWRNORM", Const, 0}, + {"EPOLL_CLOEXEC", Const, 0}, + {"EPOLL_CTL_ADD", Const, 0}, + {"EPOLL_CTL_DEL", Const, 0}, + {"EPOLL_CTL_MOD", Const, 0}, + {"EPOLL_NONBLOCK", Const, 0}, + {"EPROCLIM", Const, 0}, + {"EPROCUNAVAIL", Const, 0}, + {"EPROGMISMATCH", Const, 0}, + {"EPROGUNAVAIL", Const, 0}, + {"EPROTO", Const, 0}, + {"EPROTONOSUPPORT", Const, 0}, + {"EPROTOTYPE", Const, 0}, + {"EPWROFF", Const, 0}, + {"EQFULL", Const, 16}, + {"ERANGE", Const, 0}, + {"EREMCHG", Const, 0}, + {"EREMOTE", Const, 0}, + {"EREMOTEIO", Const, 0}, + {"ERESTART", Const, 0}, + {"ERFKILL", Const, 0}, + {"EROFS", Const, 0}, + {"ERPCMISMATCH", Const, 0}, + {"ERROR_ACCESS_DENIED", Const, 0}, + {"ERROR_ALREADY_EXISTS", Const, 0}, + {"ERROR_BROKEN_PIPE", Const, 0}, + {"ERROR_BUFFER_OVERFLOW", Const, 0}, + {"ERROR_DIR_NOT_EMPTY", Const, 8}, + {"ERROR_ENVVAR_NOT_FOUND", Const, 0}, + {"ERROR_FILE_EXISTS", Const, 0}, + {"ERROR_FILE_NOT_FOUND", Const, 0}, + {"ERROR_HANDLE_EOF", Const, 2}, + {"ERROR_INSUFFICIENT_BUFFER", Const, 0}, + {"ERROR_IO_PENDING", Const, 0}, + {"ERROR_MOD_NOT_FOUND", Const, 0}, + {"ERROR_MORE_DATA", Const, 3}, + {"ERROR_NETNAME_DELETED", Const, 3}, + {"ERROR_NOT_FOUND", Const, 1}, + {"ERROR_NO_MORE_FILES", Const, 0}, + {"ERROR_OPERATION_ABORTED", Const, 0}, + {"ERROR_PATH_NOT_FOUND", Const, 0}, + {"ERROR_PRIVILEGE_NOT_HELD", Const, 4}, + {"ERROR_PROC_NOT_FOUND", Const, 0}, + {"ESHLIBVERS", Const, 0}, + {"ESHUTDOWN", Const, 0}, + {"ESOCKTNOSUPPORT", Const, 0}, + {"ESPIPE", Const, 0}, + {"ESRCH", Const, 0}, + {"ESRMNT", Const, 0}, + {"ESTALE", Const, 0}, + {"ESTRPIPE", Const, 0}, + {"ETHERCAP_JUMBO_MTU", Const, 1}, + {"ETHERCAP_VLAN_HWTAGGING", Const, 1}, + {"ETHERCAP_VLAN_MTU", Const, 1}, + {"ETHERMIN", Const, 1}, + {"ETHERMTU", Const, 1}, + {"ETHERMTU_JUMBO", Const, 1}, + {"ETHERTYPE_8023", Const, 1}, + {"ETHERTYPE_AARP", Const, 1}, + {"ETHERTYPE_ACCTON", Const, 1}, + {"ETHERTYPE_AEONIC", Const, 1}, + {"ETHERTYPE_ALPHA", Const, 1}, + {"ETHERTYPE_AMBER", Const, 1}, + {"ETHERTYPE_AMOEBA", Const, 1}, + {"ETHERTYPE_AOE", Const, 1}, + {"ETHERTYPE_APOLLO", Const, 1}, + {"ETHERTYPE_APOLLODOMAIN", Const, 1}, + {"ETHERTYPE_APPLETALK", Const, 1}, + {"ETHERTYPE_APPLITEK", Const, 1}, + {"ETHERTYPE_ARGONAUT", Const, 1}, + {"ETHERTYPE_ARP", Const, 1}, + {"ETHERTYPE_AT", Const, 1}, + {"ETHERTYPE_ATALK", Const, 1}, + {"ETHERTYPE_ATOMIC", Const, 1}, + {"ETHERTYPE_ATT", Const, 1}, + {"ETHERTYPE_ATTSTANFORD", Const, 1}, + {"ETHERTYPE_AUTOPHON", Const, 1}, + {"ETHERTYPE_AXIS", Const, 1}, + {"ETHERTYPE_BCLOOP", Const, 1}, + {"ETHERTYPE_BOFL", Const, 1}, + {"ETHERTYPE_CABLETRON", Const, 1}, + {"ETHERTYPE_CHAOS", Const, 1}, + {"ETHERTYPE_COMDESIGN", Const, 1}, + {"ETHERTYPE_COMPUGRAPHIC", Const, 1}, + {"ETHERTYPE_COUNTERPOINT", Const, 1}, + {"ETHERTYPE_CRONUS", Const, 1}, + {"ETHERTYPE_CRONUSVLN", Const, 1}, + {"ETHERTYPE_DCA", Const, 1}, + {"ETHERTYPE_DDE", Const, 1}, + {"ETHERTYPE_DEBNI", Const, 1}, + {"ETHERTYPE_DECAM", Const, 1}, + {"ETHERTYPE_DECCUST", Const, 1}, + {"ETHERTYPE_DECDIAG", Const, 1}, + {"ETHERTYPE_DECDNS", Const, 1}, + {"ETHERTYPE_DECDTS", Const, 1}, + {"ETHERTYPE_DECEXPER", Const, 1}, + {"ETHERTYPE_DECLAST", Const, 1}, + {"ETHERTYPE_DECLTM", Const, 1}, + {"ETHERTYPE_DECMUMPS", Const, 1}, + {"ETHERTYPE_DECNETBIOS", Const, 1}, + {"ETHERTYPE_DELTACON", Const, 1}, + {"ETHERTYPE_DIDDLE", Const, 1}, + {"ETHERTYPE_DLOG1", Const, 1}, + {"ETHERTYPE_DLOG2", Const, 1}, + {"ETHERTYPE_DN", Const, 1}, + {"ETHERTYPE_DOGFIGHT", Const, 1}, + {"ETHERTYPE_DSMD", Const, 1}, + {"ETHERTYPE_ECMA", Const, 1}, + {"ETHERTYPE_ENCRYPT", Const, 1}, + {"ETHERTYPE_ES", Const, 1}, + {"ETHERTYPE_EXCELAN", Const, 1}, + {"ETHERTYPE_EXPERDATA", Const, 1}, + {"ETHERTYPE_FLIP", Const, 1}, + {"ETHERTYPE_FLOWCONTROL", Const, 1}, + {"ETHERTYPE_FRARP", Const, 1}, + {"ETHERTYPE_GENDYN", Const, 1}, + {"ETHERTYPE_HAYES", Const, 1}, + {"ETHERTYPE_HIPPI_FP", Const, 1}, + {"ETHERTYPE_HITACHI", Const, 1}, + {"ETHERTYPE_HP", Const, 1}, + {"ETHERTYPE_IEEEPUP", Const, 1}, + {"ETHERTYPE_IEEEPUPAT", Const, 1}, + {"ETHERTYPE_IMLBL", Const, 1}, + {"ETHERTYPE_IMLBLDIAG", Const, 1}, + {"ETHERTYPE_IP", Const, 1}, + {"ETHERTYPE_IPAS", Const, 1}, + {"ETHERTYPE_IPV6", Const, 1}, + {"ETHERTYPE_IPX", Const, 1}, + {"ETHERTYPE_IPXNEW", Const, 1}, + {"ETHERTYPE_KALPANA", Const, 1}, + {"ETHERTYPE_LANBRIDGE", Const, 1}, + {"ETHERTYPE_LANPROBE", Const, 1}, + {"ETHERTYPE_LAT", Const, 1}, + {"ETHERTYPE_LBACK", Const, 1}, + {"ETHERTYPE_LITTLE", Const, 1}, + {"ETHERTYPE_LLDP", Const, 1}, + {"ETHERTYPE_LOGICRAFT", Const, 1}, + {"ETHERTYPE_LOOPBACK", Const, 1}, + {"ETHERTYPE_MATRA", Const, 1}, + {"ETHERTYPE_MAX", Const, 1}, + {"ETHERTYPE_MERIT", Const, 1}, + {"ETHERTYPE_MICP", Const, 1}, + {"ETHERTYPE_MOPDL", Const, 1}, + {"ETHERTYPE_MOPRC", Const, 1}, + {"ETHERTYPE_MOTOROLA", Const, 1}, + {"ETHERTYPE_MPLS", Const, 1}, + {"ETHERTYPE_MPLS_MCAST", Const, 1}, + {"ETHERTYPE_MUMPS", Const, 1}, + {"ETHERTYPE_NBPCC", Const, 1}, + {"ETHERTYPE_NBPCLAIM", Const, 1}, + {"ETHERTYPE_NBPCLREQ", Const, 1}, + {"ETHERTYPE_NBPCLRSP", Const, 1}, + {"ETHERTYPE_NBPCREQ", Const, 1}, + {"ETHERTYPE_NBPCRSP", Const, 1}, + {"ETHERTYPE_NBPDG", Const, 1}, + {"ETHERTYPE_NBPDGB", Const, 1}, + {"ETHERTYPE_NBPDLTE", Const, 1}, + {"ETHERTYPE_NBPRAR", Const, 1}, + {"ETHERTYPE_NBPRAS", Const, 1}, + {"ETHERTYPE_NBPRST", Const, 1}, + {"ETHERTYPE_NBPSCD", Const, 1}, + {"ETHERTYPE_NBPVCD", Const, 1}, + {"ETHERTYPE_NBS", Const, 1}, + {"ETHERTYPE_NCD", Const, 1}, + {"ETHERTYPE_NESTAR", Const, 1}, + {"ETHERTYPE_NETBEUI", Const, 1}, + {"ETHERTYPE_NOVELL", Const, 1}, + {"ETHERTYPE_NS", Const, 1}, + {"ETHERTYPE_NSAT", Const, 1}, + {"ETHERTYPE_NSCOMPAT", Const, 1}, + {"ETHERTYPE_NTRAILER", Const, 1}, + {"ETHERTYPE_OS9", Const, 1}, + {"ETHERTYPE_OS9NET", Const, 1}, + {"ETHERTYPE_PACER", Const, 1}, + {"ETHERTYPE_PAE", Const, 1}, + {"ETHERTYPE_PCS", Const, 1}, + {"ETHERTYPE_PLANNING", Const, 1}, + {"ETHERTYPE_PPP", Const, 1}, + {"ETHERTYPE_PPPOE", Const, 1}, + {"ETHERTYPE_PPPOEDISC", Const, 1}, + {"ETHERTYPE_PRIMENTS", Const, 1}, + {"ETHERTYPE_PUP", Const, 1}, + {"ETHERTYPE_PUPAT", Const, 1}, + {"ETHERTYPE_QINQ", Const, 1}, + {"ETHERTYPE_RACAL", Const, 1}, + {"ETHERTYPE_RATIONAL", Const, 1}, + {"ETHERTYPE_RAWFR", Const, 1}, + {"ETHERTYPE_RCL", Const, 1}, + {"ETHERTYPE_RDP", Const, 1}, + {"ETHERTYPE_RETIX", Const, 1}, + {"ETHERTYPE_REVARP", Const, 1}, + {"ETHERTYPE_SCA", Const, 1}, + {"ETHERTYPE_SECTRA", Const, 1}, + {"ETHERTYPE_SECUREDATA", Const, 1}, + {"ETHERTYPE_SGITW", Const, 1}, + {"ETHERTYPE_SG_BOUNCE", Const, 1}, + {"ETHERTYPE_SG_DIAG", Const, 1}, + {"ETHERTYPE_SG_NETGAMES", Const, 1}, + {"ETHERTYPE_SG_RESV", Const, 1}, + {"ETHERTYPE_SIMNET", Const, 1}, + {"ETHERTYPE_SLOW", Const, 1}, + {"ETHERTYPE_SLOWPROTOCOLS", Const, 1}, + {"ETHERTYPE_SNA", Const, 1}, + {"ETHERTYPE_SNMP", Const, 1}, + {"ETHERTYPE_SONIX", Const, 1}, + {"ETHERTYPE_SPIDER", Const, 1}, + {"ETHERTYPE_SPRITE", Const, 1}, + {"ETHERTYPE_STP", Const, 1}, + {"ETHERTYPE_TALARIS", Const, 1}, + {"ETHERTYPE_TALARISMC", Const, 1}, + {"ETHERTYPE_TCPCOMP", Const, 1}, + {"ETHERTYPE_TCPSM", Const, 1}, + {"ETHERTYPE_TEC", Const, 1}, + {"ETHERTYPE_TIGAN", Const, 1}, + {"ETHERTYPE_TRAIL", Const, 1}, + {"ETHERTYPE_TRANSETHER", Const, 1}, + {"ETHERTYPE_TYMSHARE", Const, 1}, + {"ETHERTYPE_UBBST", Const, 1}, + {"ETHERTYPE_UBDEBUG", Const, 1}, + {"ETHERTYPE_UBDIAGLOOP", Const, 1}, + {"ETHERTYPE_UBDL", Const, 1}, + {"ETHERTYPE_UBNIU", Const, 1}, + {"ETHERTYPE_UBNMC", Const, 1}, + {"ETHERTYPE_VALID", Const, 1}, + {"ETHERTYPE_VARIAN", Const, 1}, + {"ETHERTYPE_VAXELN", Const, 1}, + {"ETHERTYPE_VEECO", Const, 1}, + {"ETHERTYPE_VEXP", Const, 1}, + {"ETHERTYPE_VGLAB", Const, 1}, + {"ETHERTYPE_VINES", Const, 1}, + {"ETHERTYPE_VINESECHO", Const, 1}, + {"ETHERTYPE_VINESLOOP", Const, 1}, + {"ETHERTYPE_VITAL", Const, 1}, + {"ETHERTYPE_VLAN", Const, 1}, + {"ETHERTYPE_VLTLMAN", Const, 1}, + {"ETHERTYPE_VPROD", Const, 1}, + {"ETHERTYPE_VURESERVED", Const, 1}, + {"ETHERTYPE_WATERLOO", Const, 1}, + {"ETHERTYPE_WELLFLEET", Const, 1}, + {"ETHERTYPE_X25", Const, 1}, + {"ETHERTYPE_X75", Const, 1}, + {"ETHERTYPE_XNSSM", Const, 1}, + {"ETHERTYPE_XTP", Const, 1}, + {"ETHER_ADDR_LEN", Const, 1}, + {"ETHER_ALIGN", Const, 1}, + {"ETHER_CRC_LEN", Const, 1}, + {"ETHER_CRC_POLY_BE", Const, 1}, + {"ETHER_CRC_POLY_LE", Const, 1}, + {"ETHER_HDR_LEN", Const, 1}, + {"ETHER_MAX_DIX_LEN", Const, 1}, + {"ETHER_MAX_LEN", Const, 1}, + {"ETHER_MAX_LEN_JUMBO", Const, 1}, + {"ETHER_MIN_LEN", Const, 1}, + {"ETHER_PPPOE_ENCAP_LEN", Const, 1}, + {"ETHER_TYPE_LEN", Const, 1}, + {"ETHER_VLAN_ENCAP_LEN", Const, 1}, + {"ETH_P_1588", Const, 0}, + {"ETH_P_8021Q", Const, 0}, + {"ETH_P_802_2", Const, 0}, + {"ETH_P_802_3", Const, 0}, + {"ETH_P_AARP", Const, 0}, + {"ETH_P_ALL", Const, 0}, + {"ETH_P_AOE", Const, 0}, + {"ETH_P_ARCNET", Const, 0}, + {"ETH_P_ARP", Const, 0}, + {"ETH_P_ATALK", Const, 0}, + {"ETH_P_ATMFATE", Const, 0}, + {"ETH_P_ATMMPOA", Const, 0}, + {"ETH_P_AX25", Const, 0}, + {"ETH_P_BPQ", Const, 0}, + {"ETH_P_CAIF", Const, 0}, + {"ETH_P_CAN", Const, 0}, + {"ETH_P_CONTROL", Const, 0}, + {"ETH_P_CUST", Const, 0}, + {"ETH_P_DDCMP", Const, 0}, + {"ETH_P_DEC", Const, 0}, + {"ETH_P_DIAG", Const, 0}, + {"ETH_P_DNA_DL", Const, 0}, + {"ETH_P_DNA_RC", Const, 0}, + {"ETH_P_DNA_RT", Const, 0}, + {"ETH_P_DSA", Const, 0}, + {"ETH_P_ECONET", Const, 0}, + {"ETH_P_EDSA", Const, 0}, + {"ETH_P_FCOE", Const, 0}, + {"ETH_P_FIP", Const, 0}, + {"ETH_P_HDLC", Const, 0}, + {"ETH_P_IEEE802154", Const, 0}, + {"ETH_P_IEEEPUP", Const, 0}, + {"ETH_P_IEEEPUPAT", Const, 0}, + {"ETH_P_IP", Const, 0}, + {"ETH_P_IPV6", Const, 0}, + {"ETH_P_IPX", Const, 0}, + {"ETH_P_IRDA", Const, 0}, + {"ETH_P_LAT", Const, 0}, + {"ETH_P_LINK_CTL", Const, 0}, + {"ETH_P_LOCALTALK", Const, 0}, + {"ETH_P_LOOP", Const, 0}, + {"ETH_P_MOBITEX", Const, 0}, + {"ETH_P_MPLS_MC", Const, 0}, + {"ETH_P_MPLS_UC", Const, 0}, + {"ETH_P_PAE", Const, 0}, + {"ETH_P_PAUSE", Const, 0}, + {"ETH_P_PHONET", Const, 0}, + {"ETH_P_PPPTALK", Const, 0}, + {"ETH_P_PPP_DISC", Const, 0}, + {"ETH_P_PPP_MP", Const, 0}, + {"ETH_P_PPP_SES", Const, 0}, + {"ETH_P_PUP", Const, 0}, + {"ETH_P_PUPAT", Const, 0}, + {"ETH_P_RARP", Const, 0}, + {"ETH_P_SCA", Const, 0}, + {"ETH_P_SLOW", Const, 0}, + {"ETH_P_SNAP", Const, 0}, + {"ETH_P_TEB", Const, 0}, + {"ETH_P_TIPC", Const, 0}, + {"ETH_P_TRAILER", Const, 0}, + {"ETH_P_TR_802_2", Const, 0}, + {"ETH_P_WAN_PPP", Const, 0}, + {"ETH_P_WCCP", Const, 0}, + {"ETH_P_X25", Const, 0}, + {"ETIME", Const, 0}, + {"ETIMEDOUT", Const, 0}, + {"ETOOMANYREFS", Const, 0}, + {"ETXTBSY", Const, 0}, + {"EUCLEAN", Const, 0}, + {"EUNATCH", Const, 0}, + {"EUSERS", Const, 0}, + {"EVFILT_AIO", Const, 0}, + {"EVFILT_FS", Const, 0}, + {"EVFILT_LIO", Const, 0}, + {"EVFILT_MACHPORT", Const, 0}, + {"EVFILT_PROC", Const, 0}, + {"EVFILT_READ", Const, 0}, + {"EVFILT_SIGNAL", Const, 0}, + {"EVFILT_SYSCOUNT", Const, 0}, + {"EVFILT_THREADMARKER", Const, 0}, + {"EVFILT_TIMER", Const, 0}, + {"EVFILT_USER", Const, 0}, + {"EVFILT_VM", Const, 0}, + {"EVFILT_VNODE", Const, 0}, + {"EVFILT_WRITE", Const, 0}, + {"EV_ADD", Const, 0}, + {"EV_CLEAR", Const, 0}, + {"EV_DELETE", Const, 0}, + {"EV_DISABLE", Const, 0}, + {"EV_DISPATCH", Const, 0}, + {"EV_DROP", Const, 3}, + {"EV_ENABLE", Const, 0}, + {"EV_EOF", Const, 0}, + {"EV_ERROR", Const, 0}, + {"EV_FLAG0", Const, 0}, + {"EV_FLAG1", Const, 0}, + {"EV_ONESHOT", Const, 0}, + {"EV_OOBAND", Const, 0}, + {"EV_POLL", Const, 0}, + {"EV_RECEIPT", Const, 0}, + {"EV_SYSFLAGS", Const, 0}, + {"EWINDOWS", Const, 0}, + {"EWOULDBLOCK", Const, 0}, + {"EXDEV", Const, 0}, + {"EXFULL", Const, 0}, + {"EXTA", Const, 0}, + {"EXTB", Const, 0}, + {"EXTPROC", Const, 0}, + {"Environ", Func, 0}, + {"EpollCreate", Func, 0}, + {"EpollCreate1", Func, 0}, + {"EpollCtl", Func, 0}, + {"EpollEvent", Type, 0}, + {"EpollEvent.Events", Field, 0}, + {"EpollEvent.Fd", Field, 0}, + {"EpollEvent.Pad", Field, 0}, + {"EpollEvent.PadFd", Field, 0}, + {"EpollWait", Func, 0}, + {"Errno", Type, 0}, + {"EscapeArg", Func, 0}, + {"Exchangedata", Func, 0}, + {"Exec", Func, 0}, + {"Exit", Func, 0}, + {"ExitProcess", Func, 0}, + {"FD_CLOEXEC", Const, 0}, + {"FD_SETSIZE", Const, 0}, + {"FILE_ACTION_ADDED", Const, 0}, + {"FILE_ACTION_MODIFIED", Const, 0}, + {"FILE_ACTION_REMOVED", Const, 0}, + {"FILE_ACTION_RENAMED_NEW_NAME", Const, 0}, + {"FILE_ACTION_RENAMED_OLD_NAME", Const, 0}, + {"FILE_APPEND_DATA", Const, 0}, + {"FILE_ATTRIBUTE_ARCHIVE", Const, 0}, + {"FILE_ATTRIBUTE_DIRECTORY", Const, 0}, + {"FILE_ATTRIBUTE_HIDDEN", Const, 0}, + {"FILE_ATTRIBUTE_NORMAL", Const, 0}, + {"FILE_ATTRIBUTE_READONLY", Const, 0}, + {"FILE_ATTRIBUTE_REPARSE_POINT", Const, 4}, + {"FILE_ATTRIBUTE_SYSTEM", Const, 0}, + {"FILE_BEGIN", Const, 0}, + {"FILE_CURRENT", Const, 0}, + {"FILE_END", Const, 0}, + {"FILE_FLAG_BACKUP_SEMANTICS", Const, 0}, + {"FILE_FLAG_OPEN_REPARSE_POINT", Const, 4}, + {"FILE_FLAG_OVERLAPPED", Const, 0}, + {"FILE_LIST_DIRECTORY", Const, 0}, + {"FILE_MAP_COPY", Const, 0}, + {"FILE_MAP_EXECUTE", Const, 0}, + {"FILE_MAP_READ", Const, 0}, + {"FILE_MAP_WRITE", Const, 0}, + {"FILE_NOTIFY_CHANGE_ATTRIBUTES", Const, 0}, + {"FILE_NOTIFY_CHANGE_CREATION", Const, 0}, + {"FILE_NOTIFY_CHANGE_DIR_NAME", Const, 0}, + {"FILE_NOTIFY_CHANGE_FILE_NAME", Const, 0}, + {"FILE_NOTIFY_CHANGE_LAST_ACCESS", Const, 0}, + {"FILE_NOTIFY_CHANGE_LAST_WRITE", Const, 0}, + {"FILE_NOTIFY_CHANGE_SIZE", Const, 0}, + {"FILE_SHARE_DELETE", Const, 0}, + {"FILE_SHARE_READ", Const, 0}, + {"FILE_SHARE_WRITE", Const, 0}, + {"FILE_SKIP_COMPLETION_PORT_ON_SUCCESS", Const, 2}, + {"FILE_SKIP_SET_EVENT_ON_HANDLE", Const, 2}, + {"FILE_TYPE_CHAR", Const, 0}, + {"FILE_TYPE_DISK", Const, 0}, + {"FILE_TYPE_PIPE", Const, 0}, + {"FILE_TYPE_REMOTE", Const, 0}, + {"FILE_TYPE_UNKNOWN", Const, 0}, + {"FILE_WRITE_ATTRIBUTES", Const, 0}, + {"FLUSHO", Const, 0}, + {"FORMAT_MESSAGE_ALLOCATE_BUFFER", Const, 0}, + {"FORMAT_MESSAGE_ARGUMENT_ARRAY", Const, 0}, + {"FORMAT_MESSAGE_FROM_HMODULE", Const, 0}, + {"FORMAT_MESSAGE_FROM_STRING", Const, 0}, + {"FORMAT_MESSAGE_FROM_SYSTEM", Const, 0}, + {"FORMAT_MESSAGE_IGNORE_INSERTS", Const, 0}, + {"FORMAT_MESSAGE_MAX_WIDTH_MASK", Const, 0}, + {"FSCTL_GET_REPARSE_POINT", Const, 4}, + {"F_ADDFILESIGS", Const, 0}, + {"F_ADDSIGS", Const, 0}, + {"F_ALLOCATEALL", Const, 0}, + {"F_ALLOCATECONTIG", Const, 0}, + {"F_CANCEL", Const, 0}, + {"F_CHKCLEAN", Const, 0}, + {"F_CLOSEM", Const, 1}, + {"F_DUP2FD", Const, 0}, + {"F_DUP2FD_CLOEXEC", Const, 1}, + {"F_DUPFD", Const, 0}, + {"F_DUPFD_CLOEXEC", Const, 0}, + {"F_EXLCK", Const, 0}, + {"F_FINDSIGS", Const, 16}, + {"F_FLUSH_DATA", Const, 0}, + {"F_FREEZE_FS", Const, 0}, + {"F_FSCTL", Const, 1}, + {"F_FSDIRMASK", Const, 1}, + {"F_FSIN", Const, 1}, + {"F_FSINOUT", Const, 1}, + {"F_FSOUT", Const, 1}, + {"F_FSPRIV", Const, 1}, + {"F_FSVOID", Const, 1}, + {"F_FULLFSYNC", Const, 0}, + {"F_GETCODEDIR", Const, 16}, + {"F_GETFD", Const, 0}, + {"F_GETFL", Const, 0}, + {"F_GETLEASE", Const, 0}, + {"F_GETLK", Const, 0}, + {"F_GETLK64", Const, 0}, + {"F_GETLKPID", Const, 0}, + {"F_GETNOSIGPIPE", Const, 0}, + {"F_GETOWN", Const, 0}, + {"F_GETOWN_EX", Const, 0}, + {"F_GETPATH", Const, 0}, + {"F_GETPATH_MTMINFO", Const, 0}, + {"F_GETPIPE_SZ", Const, 0}, + {"F_GETPROTECTIONCLASS", Const, 0}, + {"F_GETPROTECTIONLEVEL", Const, 16}, + {"F_GETSIG", Const, 0}, + {"F_GLOBAL_NOCACHE", Const, 0}, + {"F_LOCK", Const, 0}, + {"F_LOG2PHYS", Const, 0}, + {"F_LOG2PHYS_EXT", Const, 0}, + {"F_MARKDEPENDENCY", Const, 0}, + {"F_MAXFD", Const, 1}, + {"F_NOCACHE", Const, 0}, + {"F_NODIRECT", Const, 0}, + {"F_NOTIFY", Const, 0}, + {"F_OGETLK", Const, 0}, + {"F_OK", Const, 0}, + {"F_OSETLK", Const, 0}, + {"F_OSETLKW", Const, 0}, + {"F_PARAM_MASK", Const, 1}, + {"F_PARAM_MAX", Const, 1}, + {"F_PATHPKG_CHECK", Const, 0}, + {"F_PEOFPOSMODE", Const, 0}, + {"F_PREALLOCATE", Const, 0}, + {"F_RDADVISE", Const, 0}, + {"F_RDAHEAD", Const, 0}, + {"F_RDLCK", Const, 0}, + {"F_READAHEAD", Const, 0}, + {"F_READBOOTSTRAP", Const, 0}, + {"F_SETBACKINGSTORE", Const, 0}, + {"F_SETFD", Const, 0}, + {"F_SETFL", Const, 0}, + {"F_SETLEASE", Const, 0}, + {"F_SETLK", Const, 0}, + {"F_SETLK64", Const, 0}, + {"F_SETLKW", Const, 0}, + {"F_SETLKW64", Const, 0}, + {"F_SETLKWTIMEOUT", Const, 16}, + {"F_SETLK_REMOTE", Const, 0}, + {"F_SETNOSIGPIPE", Const, 0}, + {"F_SETOWN", Const, 0}, + {"F_SETOWN_EX", Const, 0}, + {"F_SETPIPE_SZ", Const, 0}, + {"F_SETPROTECTIONCLASS", Const, 0}, + {"F_SETSIG", Const, 0}, + {"F_SETSIZE", Const, 0}, + {"F_SHLCK", Const, 0}, + {"F_SINGLE_WRITER", Const, 16}, + {"F_TEST", Const, 0}, + {"F_THAW_FS", Const, 0}, + {"F_TLOCK", Const, 0}, + {"F_TRANSCODEKEY", Const, 16}, + {"F_ULOCK", Const, 0}, + {"F_UNLCK", Const, 0}, + {"F_UNLCKSYS", Const, 0}, + {"F_VOLPOSMODE", Const, 0}, + {"F_WRITEBOOTSTRAP", Const, 0}, + {"F_WRLCK", Const, 0}, + {"Faccessat", Func, 0}, + {"Fallocate", Func, 0}, + {"Fbootstraptransfer_t", Type, 0}, + {"Fbootstraptransfer_t.Buffer", Field, 0}, + {"Fbootstraptransfer_t.Length", Field, 0}, + {"Fbootstraptransfer_t.Offset", Field, 0}, + {"Fchdir", Func, 0}, + {"Fchflags", Func, 0}, + {"Fchmod", Func, 0}, + {"Fchmodat", Func, 0}, + {"Fchown", Func, 0}, + {"Fchownat", Func, 0}, + {"FcntlFlock", Func, 3}, + {"FdSet", Type, 0}, + {"FdSet.Bits", Field, 0}, + {"FdSet.X__fds_bits", Field, 0}, + {"Fdatasync", Func, 0}, + {"FileNotifyInformation", Type, 0}, + {"FileNotifyInformation.Action", Field, 0}, + {"FileNotifyInformation.FileName", Field, 0}, + {"FileNotifyInformation.FileNameLength", Field, 0}, + {"FileNotifyInformation.NextEntryOffset", Field, 0}, + {"Filetime", Type, 0}, + {"Filetime.HighDateTime", Field, 0}, + {"Filetime.LowDateTime", Field, 0}, + {"FindClose", Func, 0}, + {"FindFirstFile", Func, 0}, + {"FindNextFile", Func, 0}, + {"Flock", Func, 0}, + {"Flock_t", Type, 0}, + {"Flock_t.Len", Field, 0}, + {"Flock_t.Pad_cgo_0", Field, 0}, + {"Flock_t.Pad_cgo_1", Field, 3}, + {"Flock_t.Pid", Field, 0}, + {"Flock_t.Start", Field, 0}, + {"Flock_t.Sysid", Field, 0}, + {"Flock_t.Type", Field, 0}, + {"Flock_t.Whence", Field, 0}, + {"FlushBpf", Func, 0}, + {"FlushFileBuffers", Func, 0}, + {"FlushViewOfFile", Func, 0}, + {"ForkExec", Func, 0}, + {"ForkLock", Var, 0}, + {"FormatMessage", Func, 0}, + {"Fpathconf", Func, 0}, + {"FreeAddrInfoW", Func, 1}, + {"FreeEnvironmentStrings", Func, 0}, + {"FreeLibrary", Func, 0}, + {"Fsid", Type, 0}, + {"Fsid.Val", Field, 0}, + {"Fsid.X__fsid_val", Field, 2}, + {"Fsid.X__val", Field, 0}, + {"Fstat", Func, 0}, + {"Fstatat", Func, 12}, + {"Fstatfs", Func, 0}, + {"Fstore_t", Type, 0}, + {"Fstore_t.Bytesalloc", Field, 0}, + {"Fstore_t.Flags", Field, 0}, + {"Fstore_t.Length", Field, 0}, + {"Fstore_t.Offset", Field, 0}, + {"Fstore_t.Posmode", Field, 0}, + {"Fsync", Func, 0}, + {"Ftruncate", Func, 0}, + {"FullPath", Func, 4}, + {"Futimes", Func, 0}, + {"Futimesat", Func, 0}, + {"GENERIC_ALL", Const, 0}, + {"GENERIC_EXECUTE", Const, 0}, + {"GENERIC_READ", Const, 0}, + {"GENERIC_WRITE", Const, 0}, + {"GUID", Type, 1}, + {"GUID.Data1", Field, 1}, + {"GUID.Data2", Field, 1}, + {"GUID.Data3", Field, 1}, + {"GUID.Data4", Field, 1}, + {"GetAcceptExSockaddrs", Func, 0}, + {"GetAdaptersInfo", Func, 0}, + {"GetAddrInfoW", Func, 1}, + {"GetCommandLine", Func, 0}, + {"GetComputerName", Func, 0}, + {"GetConsoleMode", Func, 1}, + {"GetCurrentDirectory", Func, 0}, + {"GetCurrentProcess", Func, 0}, + {"GetEnvironmentStrings", Func, 0}, + {"GetEnvironmentVariable", Func, 0}, + {"GetExitCodeProcess", Func, 0}, + {"GetFileAttributes", Func, 0}, + {"GetFileAttributesEx", Func, 0}, + {"GetFileExInfoStandard", Const, 0}, + {"GetFileExMaxInfoLevel", Const, 0}, + {"GetFileInformationByHandle", Func, 0}, + {"GetFileType", Func, 0}, + {"GetFullPathName", Func, 0}, + {"GetHostByName", Func, 0}, + {"GetIfEntry", Func, 0}, + {"GetLastError", Func, 0}, + {"GetLengthSid", Func, 0}, + {"GetLongPathName", Func, 0}, + {"GetProcAddress", Func, 0}, + {"GetProcessTimes", Func, 0}, + {"GetProtoByName", Func, 0}, + {"GetQueuedCompletionStatus", Func, 0}, + {"GetServByName", Func, 0}, + {"GetShortPathName", Func, 0}, + {"GetStartupInfo", Func, 0}, + {"GetStdHandle", Func, 0}, + {"GetSystemTimeAsFileTime", Func, 0}, + {"GetTempPath", Func, 0}, + {"GetTimeZoneInformation", Func, 0}, + {"GetTokenInformation", Func, 0}, + {"GetUserNameEx", Func, 0}, + {"GetUserProfileDirectory", Func, 0}, + {"GetVersion", Func, 0}, + {"Getcwd", Func, 0}, + {"Getdents", Func, 0}, + {"Getdirentries", Func, 0}, + {"Getdtablesize", Func, 0}, + {"Getegid", Func, 0}, + {"Getenv", Func, 0}, + {"Geteuid", Func, 0}, + {"Getfsstat", Func, 0}, + {"Getgid", Func, 0}, + {"Getgroups", Func, 0}, + {"Getpagesize", Func, 0}, + {"Getpeername", Func, 0}, + {"Getpgid", Func, 0}, + {"Getpgrp", Func, 0}, + {"Getpid", Func, 0}, + {"Getppid", Func, 0}, + {"Getpriority", Func, 0}, + {"Getrlimit", Func, 0}, + {"Getrusage", Func, 0}, + {"Getsid", Func, 0}, + {"Getsockname", Func, 0}, + {"Getsockopt", Func, 1}, + {"GetsockoptByte", Func, 0}, + {"GetsockoptICMPv6Filter", Func, 2}, + {"GetsockoptIPMreq", Func, 0}, + {"GetsockoptIPMreqn", Func, 0}, + {"GetsockoptIPv6MTUInfo", Func, 2}, + {"GetsockoptIPv6Mreq", Func, 0}, + {"GetsockoptInet4Addr", Func, 0}, + {"GetsockoptInt", Func, 0}, + {"GetsockoptUcred", Func, 1}, + {"Gettid", Func, 0}, + {"Gettimeofday", Func, 0}, + {"Getuid", Func, 0}, + {"Getwd", Func, 0}, + {"Getxattr", Func, 1}, + {"HANDLE_FLAG_INHERIT", Const, 0}, + {"HKEY_CLASSES_ROOT", Const, 0}, + {"HKEY_CURRENT_CONFIG", Const, 0}, + {"HKEY_CURRENT_USER", Const, 0}, + {"HKEY_DYN_DATA", Const, 0}, + {"HKEY_LOCAL_MACHINE", Const, 0}, + {"HKEY_PERFORMANCE_DATA", Const, 0}, + {"HKEY_USERS", Const, 0}, + {"HUPCL", Const, 0}, + {"Handle", Type, 0}, + {"Hostent", Type, 0}, + {"Hostent.AddrList", Field, 0}, + {"Hostent.AddrType", Field, 0}, + {"Hostent.Aliases", Field, 0}, + {"Hostent.Length", Field, 0}, + {"Hostent.Name", Field, 0}, + {"ICANON", Const, 0}, + {"ICMP6_FILTER", Const, 2}, + {"ICMPV6_FILTER", Const, 2}, + {"ICMPv6Filter", Type, 2}, + {"ICMPv6Filter.Data", Field, 2}, + {"ICMPv6Filter.Filt", Field, 2}, + {"ICRNL", Const, 0}, + {"IEXTEN", Const, 0}, + {"IFAN_ARRIVAL", Const, 1}, + {"IFAN_DEPARTURE", Const, 1}, + {"IFA_ADDRESS", Const, 0}, + {"IFA_ANYCAST", Const, 0}, + {"IFA_BROADCAST", Const, 0}, + {"IFA_CACHEINFO", Const, 0}, + {"IFA_F_DADFAILED", Const, 0}, + {"IFA_F_DEPRECATED", Const, 0}, + {"IFA_F_HOMEADDRESS", Const, 0}, + {"IFA_F_NODAD", Const, 0}, + {"IFA_F_OPTIMISTIC", Const, 0}, + {"IFA_F_PERMANENT", Const, 0}, + {"IFA_F_SECONDARY", Const, 0}, + {"IFA_F_TEMPORARY", Const, 0}, + {"IFA_F_TENTATIVE", Const, 0}, + {"IFA_LABEL", Const, 0}, + {"IFA_LOCAL", Const, 0}, + {"IFA_MAX", Const, 0}, + {"IFA_MULTICAST", Const, 0}, + {"IFA_ROUTE", Const, 1}, + {"IFA_UNSPEC", Const, 0}, + {"IFF_ALLMULTI", Const, 0}, + {"IFF_ALTPHYS", Const, 0}, + {"IFF_AUTOMEDIA", Const, 0}, + {"IFF_BROADCAST", Const, 0}, + {"IFF_CANTCHANGE", Const, 0}, + {"IFF_CANTCONFIG", Const, 1}, + {"IFF_DEBUG", Const, 0}, + {"IFF_DRV_OACTIVE", Const, 0}, + {"IFF_DRV_RUNNING", Const, 0}, + {"IFF_DYING", Const, 0}, + {"IFF_DYNAMIC", Const, 0}, + {"IFF_LINK0", Const, 0}, + {"IFF_LINK1", Const, 0}, + {"IFF_LINK2", Const, 0}, + {"IFF_LOOPBACK", Const, 0}, + {"IFF_MASTER", Const, 0}, + {"IFF_MONITOR", Const, 0}, + {"IFF_MULTICAST", Const, 0}, + {"IFF_NOARP", Const, 0}, + {"IFF_NOTRAILERS", Const, 0}, + {"IFF_NO_PI", Const, 0}, + {"IFF_OACTIVE", Const, 0}, + {"IFF_ONE_QUEUE", Const, 0}, + {"IFF_POINTOPOINT", Const, 0}, + {"IFF_POINTTOPOINT", Const, 0}, + {"IFF_PORTSEL", Const, 0}, + {"IFF_PPROMISC", Const, 0}, + {"IFF_PROMISC", Const, 0}, + {"IFF_RENAMING", Const, 0}, + {"IFF_RUNNING", Const, 0}, + {"IFF_SIMPLEX", Const, 0}, + {"IFF_SLAVE", Const, 0}, + {"IFF_SMART", Const, 0}, + {"IFF_STATICARP", Const, 0}, + {"IFF_TAP", Const, 0}, + {"IFF_TUN", Const, 0}, + {"IFF_TUN_EXCL", Const, 0}, + {"IFF_UP", Const, 0}, + {"IFF_VNET_HDR", Const, 0}, + {"IFLA_ADDRESS", Const, 0}, + {"IFLA_BROADCAST", Const, 0}, + {"IFLA_COST", Const, 0}, + {"IFLA_IFALIAS", Const, 0}, + {"IFLA_IFNAME", Const, 0}, + {"IFLA_LINK", Const, 0}, + {"IFLA_LINKINFO", Const, 0}, + {"IFLA_LINKMODE", Const, 0}, + {"IFLA_MAP", Const, 0}, + {"IFLA_MASTER", Const, 0}, + {"IFLA_MAX", Const, 0}, + {"IFLA_MTU", Const, 0}, + {"IFLA_NET_NS_PID", Const, 0}, + {"IFLA_OPERSTATE", Const, 0}, + {"IFLA_PRIORITY", Const, 0}, + {"IFLA_PROTINFO", Const, 0}, + {"IFLA_QDISC", Const, 0}, + {"IFLA_STATS", Const, 0}, + {"IFLA_TXQLEN", Const, 0}, + {"IFLA_UNSPEC", Const, 0}, + {"IFLA_WEIGHT", Const, 0}, + {"IFLA_WIRELESS", Const, 0}, + {"IFNAMSIZ", Const, 0}, + {"IFT_1822", Const, 0}, + {"IFT_A12MPPSWITCH", Const, 0}, + {"IFT_AAL2", Const, 0}, + {"IFT_AAL5", Const, 0}, + {"IFT_ADSL", Const, 0}, + {"IFT_AFLANE8023", Const, 0}, + {"IFT_AFLANE8025", Const, 0}, + {"IFT_ARAP", Const, 0}, + {"IFT_ARCNET", Const, 0}, + {"IFT_ARCNETPLUS", Const, 0}, + {"IFT_ASYNC", Const, 0}, + {"IFT_ATM", Const, 0}, + {"IFT_ATMDXI", Const, 0}, + {"IFT_ATMFUNI", Const, 0}, + {"IFT_ATMIMA", Const, 0}, + {"IFT_ATMLOGICAL", Const, 0}, + {"IFT_ATMRADIO", Const, 0}, + {"IFT_ATMSUBINTERFACE", Const, 0}, + {"IFT_ATMVCIENDPT", Const, 0}, + {"IFT_ATMVIRTUAL", Const, 0}, + {"IFT_BGPPOLICYACCOUNTING", Const, 0}, + {"IFT_BLUETOOTH", Const, 1}, + {"IFT_BRIDGE", Const, 0}, + {"IFT_BSC", Const, 0}, + {"IFT_CARP", Const, 0}, + {"IFT_CCTEMUL", Const, 0}, + {"IFT_CELLULAR", Const, 0}, + {"IFT_CEPT", Const, 0}, + {"IFT_CES", Const, 0}, + {"IFT_CHANNEL", Const, 0}, + {"IFT_CNR", Const, 0}, + {"IFT_COFFEE", Const, 0}, + {"IFT_COMPOSITELINK", Const, 0}, + {"IFT_DCN", Const, 0}, + {"IFT_DIGITALPOWERLINE", Const, 0}, + {"IFT_DIGITALWRAPPEROVERHEADCHANNEL", Const, 0}, + {"IFT_DLSW", Const, 0}, + {"IFT_DOCSCABLEDOWNSTREAM", Const, 0}, + {"IFT_DOCSCABLEMACLAYER", Const, 0}, + {"IFT_DOCSCABLEUPSTREAM", Const, 0}, + {"IFT_DOCSCABLEUPSTREAMCHANNEL", Const, 1}, + {"IFT_DS0", Const, 0}, + {"IFT_DS0BUNDLE", Const, 0}, + {"IFT_DS1FDL", Const, 0}, + {"IFT_DS3", Const, 0}, + {"IFT_DTM", Const, 0}, + {"IFT_DUMMY", Const, 1}, + {"IFT_DVBASILN", Const, 0}, + {"IFT_DVBASIOUT", Const, 0}, + {"IFT_DVBRCCDOWNSTREAM", Const, 0}, + {"IFT_DVBRCCMACLAYER", Const, 0}, + {"IFT_DVBRCCUPSTREAM", Const, 0}, + {"IFT_ECONET", Const, 1}, + {"IFT_ENC", Const, 0}, + {"IFT_EON", Const, 0}, + {"IFT_EPLRS", Const, 0}, + {"IFT_ESCON", Const, 0}, + {"IFT_ETHER", Const, 0}, + {"IFT_FAITH", Const, 0}, + {"IFT_FAST", Const, 0}, + {"IFT_FASTETHER", Const, 0}, + {"IFT_FASTETHERFX", Const, 0}, + {"IFT_FDDI", Const, 0}, + {"IFT_FIBRECHANNEL", Const, 0}, + {"IFT_FRAMERELAYINTERCONNECT", Const, 0}, + {"IFT_FRAMERELAYMPI", Const, 0}, + {"IFT_FRDLCIENDPT", Const, 0}, + {"IFT_FRELAY", Const, 0}, + {"IFT_FRELAYDCE", Const, 0}, + {"IFT_FRF16MFRBUNDLE", Const, 0}, + {"IFT_FRFORWARD", Const, 0}, + {"IFT_G703AT2MB", Const, 0}, + {"IFT_G703AT64K", Const, 0}, + {"IFT_GIF", Const, 0}, + {"IFT_GIGABITETHERNET", Const, 0}, + {"IFT_GR303IDT", Const, 0}, + {"IFT_GR303RDT", Const, 0}, + {"IFT_H323GATEKEEPER", Const, 0}, + {"IFT_H323PROXY", Const, 0}, + {"IFT_HDH1822", Const, 0}, + {"IFT_HDLC", Const, 0}, + {"IFT_HDSL2", Const, 0}, + {"IFT_HIPERLAN2", Const, 0}, + {"IFT_HIPPI", Const, 0}, + {"IFT_HIPPIINTERFACE", Const, 0}, + {"IFT_HOSTPAD", Const, 0}, + {"IFT_HSSI", Const, 0}, + {"IFT_HY", Const, 0}, + {"IFT_IBM370PARCHAN", Const, 0}, + {"IFT_IDSL", Const, 0}, + {"IFT_IEEE1394", Const, 0}, + {"IFT_IEEE80211", Const, 0}, + {"IFT_IEEE80212", Const, 0}, + {"IFT_IEEE8023ADLAG", Const, 0}, + {"IFT_IFGSN", Const, 0}, + {"IFT_IMT", Const, 0}, + {"IFT_INFINIBAND", Const, 1}, + {"IFT_INTERLEAVE", Const, 0}, + {"IFT_IP", Const, 0}, + {"IFT_IPFORWARD", Const, 0}, + {"IFT_IPOVERATM", Const, 0}, + {"IFT_IPOVERCDLC", Const, 0}, + {"IFT_IPOVERCLAW", Const, 0}, + {"IFT_IPSWITCH", Const, 0}, + {"IFT_IPXIP", Const, 0}, + {"IFT_ISDN", Const, 0}, + {"IFT_ISDNBASIC", Const, 0}, + {"IFT_ISDNPRIMARY", Const, 0}, + {"IFT_ISDNS", Const, 0}, + {"IFT_ISDNU", Const, 0}, + {"IFT_ISO88022LLC", Const, 0}, + {"IFT_ISO88023", Const, 0}, + {"IFT_ISO88024", Const, 0}, + {"IFT_ISO88025", Const, 0}, + {"IFT_ISO88025CRFPINT", Const, 0}, + {"IFT_ISO88025DTR", Const, 0}, + {"IFT_ISO88025FIBER", Const, 0}, + {"IFT_ISO88026", Const, 0}, + {"IFT_ISUP", Const, 0}, + {"IFT_L2VLAN", Const, 0}, + {"IFT_L3IPVLAN", Const, 0}, + {"IFT_L3IPXVLAN", Const, 0}, + {"IFT_LAPB", Const, 0}, + {"IFT_LAPD", Const, 0}, + {"IFT_LAPF", Const, 0}, + {"IFT_LINEGROUP", Const, 1}, + {"IFT_LOCALTALK", Const, 0}, + {"IFT_LOOP", Const, 0}, + {"IFT_MEDIAMAILOVERIP", Const, 0}, + {"IFT_MFSIGLINK", Const, 0}, + {"IFT_MIOX25", Const, 0}, + {"IFT_MODEM", Const, 0}, + {"IFT_MPC", Const, 0}, + {"IFT_MPLS", Const, 0}, + {"IFT_MPLSTUNNEL", Const, 0}, + {"IFT_MSDSL", Const, 0}, + {"IFT_MVL", Const, 0}, + {"IFT_MYRINET", Const, 0}, + {"IFT_NFAS", Const, 0}, + {"IFT_NSIP", Const, 0}, + {"IFT_OPTICALCHANNEL", Const, 0}, + {"IFT_OPTICALTRANSPORT", Const, 0}, + {"IFT_OTHER", Const, 0}, + {"IFT_P10", Const, 0}, + {"IFT_P80", Const, 0}, + {"IFT_PARA", Const, 0}, + {"IFT_PDP", Const, 0}, + {"IFT_PFLOG", Const, 0}, + {"IFT_PFLOW", Const, 1}, + {"IFT_PFSYNC", Const, 0}, + {"IFT_PLC", Const, 0}, + {"IFT_PON155", Const, 1}, + {"IFT_PON622", Const, 1}, + {"IFT_POS", Const, 0}, + {"IFT_PPP", Const, 0}, + {"IFT_PPPMULTILINKBUNDLE", Const, 0}, + {"IFT_PROPATM", Const, 1}, + {"IFT_PROPBWAP2MP", Const, 0}, + {"IFT_PROPCNLS", Const, 0}, + {"IFT_PROPDOCSWIRELESSDOWNSTREAM", Const, 0}, + {"IFT_PROPDOCSWIRELESSMACLAYER", Const, 0}, + {"IFT_PROPDOCSWIRELESSUPSTREAM", Const, 0}, + {"IFT_PROPMUX", Const, 0}, + {"IFT_PROPVIRTUAL", Const, 0}, + {"IFT_PROPWIRELESSP2P", Const, 0}, + {"IFT_PTPSERIAL", Const, 0}, + {"IFT_PVC", Const, 0}, + {"IFT_Q2931", Const, 1}, + {"IFT_QLLC", Const, 0}, + {"IFT_RADIOMAC", Const, 0}, + {"IFT_RADSL", Const, 0}, + {"IFT_REACHDSL", Const, 0}, + {"IFT_RFC1483", Const, 0}, + {"IFT_RS232", Const, 0}, + {"IFT_RSRB", Const, 0}, + {"IFT_SDLC", Const, 0}, + {"IFT_SDSL", Const, 0}, + {"IFT_SHDSL", Const, 0}, + {"IFT_SIP", Const, 0}, + {"IFT_SIPSIG", Const, 1}, + {"IFT_SIPTG", Const, 1}, + {"IFT_SLIP", Const, 0}, + {"IFT_SMDSDXI", Const, 0}, + {"IFT_SMDSICIP", Const, 0}, + {"IFT_SONET", Const, 0}, + {"IFT_SONETOVERHEADCHANNEL", Const, 0}, + {"IFT_SONETPATH", Const, 0}, + {"IFT_SONETVT", Const, 0}, + {"IFT_SRP", Const, 0}, + {"IFT_SS7SIGLINK", Const, 0}, + {"IFT_STACKTOSTACK", Const, 0}, + {"IFT_STARLAN", Const, 0}, + {"IFT_STF", Const, 0}, + {"IFT_T1", Const, 0}, + {"IFT_TDLC", Const, 0}, + {"IFT_TELINK", Const, 1}, + {"IFT_TERMPAD", Const, 0}, + {"IFT_TR008", Const, 0}, + {"IFT_TRANSPHDLC", Const, 0}, + {"IFT_TUNNEL", Const, 0}, + {"IFT_ULTRA", Const, 0}, + {"IFT_USB", Const, 0}, + {"IFT_V11", Const, 0}, + {"IFT_V35", Const, 0}, + {"IFT_V36", Const, 0}, + {"IFT_V37", Const, 0}, + {"IFT_VDSL", Const, 0}, + {"IFT_VIRTUALIPADDRESS", Const, 0}, + {"IFT_VIRTUALTG", Const, 1}, + {"IFT_VOICEDID", Const, 1}, + {"IFT_VOICEEM", Const, 0}, + {"IFT_VOICEEMFGD", Const, 1}, + {"IFT_VOICEENCAP", Const, 0}, + {"IFT_VOICEFGDEANA", Const, 1}, + {"IFT_VOICEFXO", Const, 0}, + {"IFT_VOICEFXS", Const, 0}, + {"IFT_VOICEOVERATM", Const, 0}, + {"IFT_VOICEOVERCABLE", Const, 1}, + {"IFT_VOICEOVERFRAMERELAY", Const, 0}, + {"IFT_VOICEOVERIP", Const, 0}, + {"IFT_X213", Const, 0}, + {"IFT_X25", Const, 0}, + {"IFT_X25DDN", Const, 0}, + {"IFT_X25HUNTGROUP", Const, 0}, + {"IFT_X25MLP", Const, 0}, + {"IFT_X25PLE", Const, 0}, + {"IFT_XETHER", Const, 0}, + {"IGNBRK", Const, 0}, + {"IGNCR", Const, 0}, + {"IGNORE", Const, 0}, + {"IGNPAR", Const, 0}, + {"IMAXBEL", Const, 0}, + {"INFINITE", Const, 0}, + {"INLCR", Const, 0}, + {"INPCK", Const, 0}, + {"INVALID_FILE_ATTRIBUTES", Const, 0}, + {"IN_ACCESS", Const, 0}, + {"IN_ALL_EVENTS", Const, 0}, + {"IN_ATTRIB", Const, 0}, + {"IN_CLASSA_HOST", Const, 0}, + {"IN_CLASSA_MAX", Const, 0}, + {"IN_CLASSA_NET", Const, 0}, + {"IN_CLASSA_NSHIFT", Const, 0}, + {"IN_CLASSB_HOST", Const, 0}, + {"IN_CLASSB_MAX", Const, 0}, + {"IN_CLASSB_NET", Const, 0}, + {"IN_CLASSB_NSHIFT", Const, 0}, + {"IN_CLASSC_HOST", Const, 0}, + {"IN_CLASSC_NET", Const, 0}, + {"IN_CLASSC_NSHIFT", Const, 0}, + {"IN_CLASSD_HOST", Const, 0}, + {"IN_CLASSD_NET", Const, 0}, + {"IN_CLASSD_NSHIFT", Const, 0}, + {"IN_CLOEXEC", Const, 0}, + {"IN_CLOSE", Const, 0}, + {"IN_CLOSE_NOWRITE", Const, 0}, + {"IN_CLOSE_WRITE", Const, 0}, + {"IN_CREATE", Const, 0}, + {"IN_DELETE", Const, 0}, + {"IN_DELETE_SELF", Const, 0}, + {"IN_DONT_FOLLOW", Const, 0}, + {"IN_EXCL_UNLINK", Const, 0}, + {"IN_IGNORED", Const, 0}, + {"IN_ISDIR", Const, 0}, + {"IN_LINKLOCALNETNUM", Const, 0}, + {"IN_LOOPBACKNET", Const, 0}, + {"IN_MASK_ADD", Const, 0}, + {"IN_MODIFY", Const, 0}, + {"IN_MOVE", Const, 0}, + {"IN_MOVED_FROM", Const, 0}, + {"IN_MOVED_TO", Const, 0}, + {"IN_MOVE_SELF", Const, 0}, + {"IN_NONBLOCK", Const, 0}, + {"IN_ONESHOT", Const, 0}, + {"IN_ONLYDIR", Const, 0}, + {"IN_OPEN", Const, 0}, + {"IN_Q_OVERFLOW", Const, 0}, + {"IN_RFC3021_HOST", Const, 1}, + {"IN_RFC3021_MASK", Const, 1}, + {"IN_RFC3021_NET", Const, 1}, + {"IN_RFC3021_NSHIFT", Const, 1}, + {"IN_UNMOUNT", Const, 0}, + {"IOC_IN", Const, 1}, + {"IOC_INOUT", Const, 1}, + {"IOC_OUT", Const, 1}, + {"IOC_VENDOR", Const, 3}, + {"IOC_WS2", Const, 1}, + {"IO_REPARSE_TAG_SYMLINK", Const, 4}, + {"IPMreq", Type, 0}, + {"IPMreq.Interface", Field, 0}, + {"IPMreq.Multiaddr", Field, 0}, + {"IPMreqn", Type, 0}, + {"IPMreqn.Address", Field, 0}, + {"IPMreqn.Ifindex", Field, 0}, + {"IPMreqn.Multiaddr", Field, 0}, + {"IPPROTO_3PC", Const, 0}, + {"IPPROTO_ADFS", Const, 0}, + {"IPPROTO_AH", Const, 0}, + {"IPPROTO_AHIP", Const, 0}, + {"IPPROTO_APES", Const, 0}, + {"IPPROTO_ARGUS", Const, 0}, + {"IPPROTO_AX25", Const, 0}, + {"IPPROTO_BHA", Const, 0}, + {"IPPROTO_BLT", Const, 0}, + {"IPPROTO_BRSATMON", Const, 0}, + {"IPPROTO_CARP", Const, 0}, + {"IPPROTO_CFTP", Const, 0}, + {"IPPROTO_CHAOS", Const, 0}, + {"IPPROTO_CMTP", Const, 0}, + {"IPPROTO_COMP", Const, 0}, + {"IPPROTO_CPHB", Const, 0}, + {"IPPROTO_CPNX", Const, 0}, + {"IPPROTO_DCCP", Const, 0}, + {"IPPROTO_DDP", Const, 0}, + {"IPPROTO_DGP", Const, 0}, + {"IPPROTO_DIVERT", Const, 0}, + {"IPPROTO_DIVERT_INIT", Const, 3}, + {"IPPROTO_DIVERT_RESP", Const, 3}, + {"IPPROTO_DONE", Const, 0}, + {"IPPROTO_DSTOPTS", Const, 0}, + {"IPPROTO_EGP", Const, 0}, + {"IPPROTO_EMCON", Const, 0}, + {"IPPROTO_ENCAP", Const, 0}, + {"IPPROTO_EON", Const, 0}, + {"IPPROTO_ESP", Const, 0}, + {"IPPROTO_ETHERIP", Const, 0}, + {"IPPROTO_FRAGMENT", Const, 0}, + {"IPPROTO_GGP", Const, 0}, + {"IPPROTO_GMTP", Const, 0}, + {"IPPROTO_GRE", Const, 0}, + {"IPPROTO_HELLO", Const, 0}, + {"IPPROTO_HMP", Const, 0}, + {"IPPROTO_HOPOPTS", Const, 0}, + {"IPPROTO_ICMP", Const, 0}, + {"IPPROTO_ICMPV6", Const, 0}, + {"IPPROTO_IDP", Const, 0}, + {"IPPROTO_IDPR", Const, 0}, + {"IPPROTO_IDRP", Const, 0}, + {"IPPROTO_IGMP", Const, 0}, + {"IPPROTO_IGP", Const, 0}, + {"IPPROTO_IGRP", Const, 0}, + {"IPPROTO_IL", Const, 0}, + {"IPPROTO_INLSP", Const, 0}, + {"IPPROTO_INP", Const, 0}, + {"IPPROTO_IP", Const, 0}, + {"IPPROTO_IPCOMP", Const, 0}, + {"IPPROTO_IPCV", Const, 0}, + {"IPPROTO_IPEIP", Const, 0}, + {"IPPROTO_IPIP", Const, 0}, + {"IPPROTO_IPPC", Const, 0}, + {"IPPROTO_IPV4", Const, 0}, + {"IPPROTO_IPV6", Const, 0}, + {"IPPROTO_IPV6_ICMP", Const, 1}, + {"IPPROTO_IRTP", Const, 0}, + {"IPPROTO_KRYPTOLAN", Const, 0}, + {"IPPROTO_LARP", Const, 0}, + {"IPPROTO_LEAF1", Const, 0}, + {"IPPROTO_LEAF2", Const, 0}, + {"IPPROTO_MAX", Const, 0}, + {"IPPROTO_MAXID", Const, 0}, + {"IPPROTO_MEAS", Const, 0}, + {"IPPROTO_MH", Const, 1}, + {"IPPROTO_MHRP", Const, 0}, + {"IPPROTO_MICP", Const, 0}, + {"IPPROTO_MOBILE", Const, 0}, + {"IPPROTO_MPLS", Const, 1}, + {"IPPROTO_MTP", Const, 0}, + {"IPPROTO_MUX", Const, 0}, + {"IPPROTO_ND", Const, 0}, + {"IPPROTO_NHRP", Const, 0}, + {"IPPROTO_NONE", Const, 0}, + {"IPPROTO_NSP", Const, 0}, + {"IPPROTO_NVPII", Const, 0}, + {"IPPROTO_OLD_DIVERT", Const, 0}, + {"IPPROTO_OSPFIGP", Const, 0}, + {"IPPROTO_PFSYNC", Const, 0}, + {"IPPROTO_PGM", Const, 0}, + {"IPPROTO_PIGP", Const, 0}, + {"IPPROTO_PIM", Const, 0}, + {"IPPROTO_PRM", Const, 0}, + {"IPPROTO_PUP", Const, 0}, + {"IPPROTO_PVP", Const, 0}, + {"IPPROTO_RAW", Const, 0}, + {"IPPROTO_RCCMON", Const, 0}, + {"IPPROTO_RDP", Const, 0}, + {"IPPROTO_ROUTING", Const, 0}, + {"IPPROTO_RSVP", Const, 0}, + {"IPPROTO_RVD", Const, 0}, + {"IPPROTO_SATEXPAK", Const, 0}, + {"IPPROTO_SATMON", Const, 0}, + {"IPPROTO_SCCSP", Const, 0}, + {"IPPROTO_SCTP", Const, 0}, + {"IPPROTO_SDRP", Const, 0}, + {"IPPROTO_SEND", Const, 1}, + {"IPPROTO_SEP", Const, 0}, + {"IPPROTO_SKIP", Const, 0}, + {"IPPROTO_SPACER", Const, 0}, + {"IPPROTO_SRPC", Const, 0}, + {"IPPROTO_ST", Const, 0}, + {"IPPROTO_SVMTP", Const, 0}, + {"IPPROTO_SWIPE", Const, 0}, + {"IPPROTO_TCF", Const, 0}, + {"IPPROTO_TCP", Const, 0}, + {"IPPROTO_TLSP", Const, 0}, + {"IPPROTO_TP", Const, 0}, + {"IPPROTO_TPXX", Const, 0}, + {"IPPROTO_TRUNK1", Const, 0}, + {"IPPROTO_TRUNK2", Const, 0}, + {"IPPROTO_TTP", Const, 0}, + {"IPPROTO_UDP", Const, 0}, + {"IPPROTO_UDPLITE", Const, 0}, + {"IPPROTO_VINES", Const, 0}, + {"IPPROTO_VISA", Const, 0}, + {"IPPROTO_VMTP", Const, 0}, + {"IPPROTO_VRRP", Const, 1}, + {"IPPROTO_WBEXPAK", Const, 0}, + {"IPPROTO_WBMON", Const, 0}, + {"IPPROTO_WSN", Const, 0}, + {"IPPROTO_XNET", Const, 0}, + {"IPPROTO_XTP", Const, 0}, + {"IPV6_2292DSTOPTS", Const, 0}, + {"IPV6_2292HOPLIMIT", Const, 0}, + {"IPV6_2292HOPOPTS", Const, 0}, + {"IPV6_2292NEXTHOP", Const, 0}, + {"IPV6_2292PKTINFO", Const, 0}, + {"IPV6_2292PKTOPTIONS", Const, 0}, + {"IPV6_2292RTHDR", Const, 0}, + {"IPV6_ADDRFORM", Const, 0}, + {"IPV6_ADD_MEMBERSHIP", Const, 0}, + {"IPV6_AUTHHDR", Const, 0}, + {"IPV6_AUTH_LEVEL", Const, 1}, + {"IPV6_AUTOFLOWLABEL", Const, 0}, + {"IPV6_BINDANY", Const, 0}, + {"IPV6_BINDV6ONLY", Const, 0}, + {"IPV6_BOUND_IF", Const, 0}, + {"IPV6_CHECKSUM", Const, 0}, + {"IPV6_DEFAULT_MULTICAST_HOPS", Const, 0}, + {"IPV6_DEFAULT_MULTICAST_LOOP", Const, 0}, + {"IPV6_DEFHLIM", Const, 0}, + {"IPV6_DONTFRAG", Const, 0}, + {"IPV6_DROP_MEMBERSHIP", Const, 0}, + {"IPV6_DSTOPTS", Const, 0}, + {"IPV6_ESP_NETWORK_LEVEL", Const, 1}, + {"IPV6_ESP_TRANS_LEVEL", Const, 1}, + {"IPV6_FAITH", Const, 0}, + {"IPV6_FLOWINFO_MASK", Const, 0}, + {"IPV6_FLOWLABEL_MASK", Const, 0}, + {"IPV6_FRAGTTL", Const, 0}, + {"IPV6_FW_ADD", Const, 0}, + {"IPV6_FW_DEL", Const, 0}, + {"IPV6_FW_FLUSH", Const, 0}, + {"IPV6_FW_GET", Const, 0}, + {"IPV6_FW_ZERO", Const, 0}, + {"IPV6_HLIMDEC", Const, 0}, + {"IPV6_HOPLIMIT", Const, 0}, + {"IPV6_HOPOPTS", Const, 0}, + {"IPV6_IPCOMP_LEVEL", Const, 1}, + {"IPV6_IPSEC_POLICY", Const, 0}, + {"IPV6_JOIN_ANYCAST", Const, 0}, + {"IPV6_JOIN_GROUP", Const, 0}, + {"IPV6_LEAVE_ANYCAST", Const, 0}, + {"IPV6_LEAVE_GROUP", Const, 0}, + {"IPV6_MAXHLIM", Const, 0}, + {"IPV6_MAXOPTHDR", Const, 0}, + {"IPV6_MAXPACKET", Const, 0}, + {"IPV6_MAX_GROUP_SRC_FILTER", Const, 0}, + {"IPV6_MAX_MEMBERSHIPS", Const, 0}, + {"IPV6_MAX_SOCK_SRC_FILTER", Const, 0}, + {"IPV6_MIN_MEMBERSHIPS", Const, 0}, + {"IPV6_MMTU", Const, 0}, + {"IPV6_MSFILTER", Const, 0}, + {"IPV6_MTU", Const, 0}, + {"IPV6_MTU_DISCOVER", Const, 0}, + {"IPV6_MULTICAST_HOPS", Const, 0}, + {"IPV6_MULTICAST_IF", Const, 0}, + {"IPV6_MULTICAST_LOOP", Const, 0}, + {"IPV6_NEXTHOP", Const, 0}, + {"IPV6_OPTIONS", Const, 1}, + {"IPV6_PATHMTU", Const, 0}, + {"IPV6_PIPEX", Const, 1}, + {"IPV6_PKTINFO", Const, 0}, + {"IPV6_PMTUDISC_DO", Const, 0}, + {"IPV6_PMTUDISC_DONT", Const, 0}, + {"IPV6_PMTUDISC_PROBE", Const, 0}, + {"IPV6_PMTUDISC_WANT", Const, 0}, + {"IPV6_PORTRANGE", Const, 0}, + {"IPV6_PORTRANGE_DEFAULT", Const, 0}, + {"IPV6_PORTRANGE_HIGH", Const, 0}, + {"IPV6_PORTRANGE_LOW", Const, 0}, + {"IPV6_PREFER_TEMPADDR", Const, 0}, + {"IPV6_RECVDSTOPTS", Const, 0}, + {"IPV6_RECVDSTPORT", Const, 3}, + {"IPV6_RECVERR", Const, 0}, + {"IPV6_RECVHOPLIMIT", Const, 0}, + {"IPV6_RECVHOPOPTS", Const, 0}, + {"IPV6_RECVPATHMTU", Const, 0}, + {"IPV6_RECVPKTINFO", Const, 0}, + {"IPV6_RECVRTHDR", Const, 0}, + {"IPV6_RECVTCLASS", Const, 0}, + {"IPV6_ROUTER_ALERT", Const, 0}, + {"IPV6_RTABLE", Const, 1}, + {"IPV6_RTHDR", Const, 0}, + {"IPV6_RTHDRDSTOPTS", Const, 0}, + {"IPV6_RTHDR_LOOSE", Const, 0}, + {"IPV6_RTHDR_STRICT", Const, 0}, + {"IPV6_RTHDR_TYPE_0", Const, 0}, + {"IPV6_RXDSTOPTS", Const, 0}, + {"IPV6_RXHOPOPTS", Const, 0}, + {"IPV6_SOCKOPT_RESERVED1", Const, 0}, + {"IPV6_TCLASS", Const, 0}, + {"IPV6_UNICAST_HOPS", Const, 0}, + {"IPV6_USE_MIN_MTU", Const, 0}, + {"IPV6_V6ONLY", Const, 0}, + {"IPV6_VERSION", Const, 0}, + {"IPV6_VERSION_MASK", Const, 0}, + {"IPV6_XFRM_POLICY", Const, 0}, + {"IP_ADD_MEMBERSHIP", Const, 0}, + {"IP_ADD_SOURCE_MEMBERSHIP", Const, 0}, + {"IP_AUTH_LEVEL", Const, 1}, + {"IP_BINDANY", Const, 0}, + {"IP_BLOCK_SOURCE", Const, 0}, + {"IP_BOUND_IF", Const, 0}, + {"IP_DEFAULT_MULTICAST_LOOP", Const, 0}, + {"IP_DEFAULT_MULTICAST_TTL", Const, 0}, + {"IP_DF", Const, 0}, + {"IP_DIVERTFL", Const, 3}, + {"IP_DONTFRAG", Const, 0}, + {"IP_DROP_MEMBERSHIP", Const, 0}, + {"IP_DROP_SOURCE_MEMBERSHIP", Const, 0}, + {"IP_DUMMYNET3", Const, 0}, + {"IP_DUMMYNET_CONFIGURE", Const, 0}, + {"IP_DUMMYNET_DEL", Const, 0}, + {"IP_DUMMYNET_FLUSH", Const, 0}, + {"IP_DUMMYNET_GET", Const, 0}, + {"IP_EF", Const, 1}, + {"IP_ERRORMTU", Const, 1}, + {"IP_ESP_NETWORK_LEVEL", Const, 1}, + {"IP_ESP_TRANS_LEVEL", Const, 1}, + {"IP_FAITH", Const, 0}, + {"IP_FREEBIND", Const, 0}, + {"IP_FW3", Const, 0}, + {"IP_FW_ADD", Const, 0}, + {"IP_FW_DEL", Const, 0}, + {"IP_FW_FLUSH", Const, 0}, + {"IP_FW_GET", Const, 0}, + {"IP_FW_NAT_CFG", Const, 0}, + {"IP_FW_NAT_DEL", Const, 0}, + {"IP_FW_NAT_GET_CONFIG", Const, 0}, + {"IP_FW_NAT_GET_LOG", Const, 0}, + {"IP_FW_RESETLOG", Const, 0}, + {"IP_FW_TABLE_ADD", Const, 0}, + {"IP_FW_TABLE_DEL", Const, 0}, + {"IP_FW_TABLE_FLUSH", Const, 0}, + {"IP_FW_TABLE_GETSIZE", Const, 0}, + {"IP_FW_TABLE_LIST", Const, 0}, + {"IP_FW_ZERO", Const, 0}, + {"IP_HDRINCL", Const, 0}, + {"IP_IPCOMP_LEVEL", Const, 1}, + {"IP_IPSECFLOWINFO", Const, 1}, + {"IP_IPSEC_LOCAL_AUTH", Const, 1}, + {"IP_IPSEC_LOCAL_CRED", Const, 1}, + {"IP_IPSEC_LOCAL_ID", Const, 1}, + {"IP_IPSEC_POLICY", Const, 0}, + {"IP_IPSEC_REMOTE_AUTH", Const, 1}, + {"IP_IPSEC_REMOTE_CRED", Const, 1}, + {"IP_IPSEC_REMOTE_ID", Const, 1}, + {"IP_MAXPACKET", Const, 0}, + {"IP_MAX_GROUP_SRC_FILTER", Const, 0}, + {"IP_MAX_MEMBERSHIPS", Const, 0}, + {"IP_MAX_SOCK_MUTE_FILTER", Const, 0}, + {"IP_MAX_SOCK_SRC_FILTER", Const, 0}, + {"IP_MAX_SOURCE_FILTER", Const, 0}, + {"IP_MF", Const, 0}, + {"IP_MINFRAGSIZE", Const, 1}, + {"IP_MINTTL", Const, 0}, + {"IP_MIN_MEMBERSHIPS", Const, 0}, + {"IP_MSFILTER", Const, 0}, + {"IP_MSS", Const, 0}, + {"IP_MTU", Const, 0}, + {"IP_MTU_DISCOVER", Const, 0}, + {"IP_MULTICAST_IF", Const, 0}, + {"IP_MULTICAST_IFINDEX", Const, 0}, + {"IP_MULTICAST_LOOP", Const, 0}, + {"IP_MULTICAST_TTL", Const, 0}, + {"IP_MULTICAST_VIF", Const, 0}, + {"IP_NAT__XXX", Const, 0}, + {"IP_OFFMASK", Const, 0}, + {"IP_OLD_FW_ADD", Const, 0}, + {"IP_OLD_FW_DEL", Const, 0}, + {"IP_OLD_FW_FLUSH", Const, 0}, + {"IP_OLD_FW_GET", Const, 0}, + {"IP_OLD_FW_RESETLOG", Const, 0}, + {"IP_OLD_FW_ZERO", Const, 0}, + {"IP_ONESBCAST", Const, 0}, + {"IP_OPTIONS", Const, 0}, + {"IP_ORIGDSTADDR", Const, 0}, + {"IP_PASSSEC", Const, 0}, + {"IP_PIPEX", Const, 1}, + {"IP_PKTINFO", Const, 0}, + {"IP_PKTOPTIONS", Const, 0}, + {"IP_PMTUDISC", Const, 0}, + {"IP_PMTUDISC_DO", Const, 0}, + {"IP_PMTUDISC_DONT", Const, 0}, + {"IP_PMTUDISC_PROBE", Const, 0}, + {"IP_PMTUDISC_WANT", Const, 0}, + {"IP_PORTRANGE", Const, 0}, + {"IP_PORTRANGE_DEFAULT", Const, 0}, + {"IP_PORTRANGE_HIGH", Const, 0}, + {"IP_PORTRANGE_LOW", Const, 0}, + {"IP_RECVDSTADDR", Const, 0}, + {"IP_RECVDSTPORT", Const, 1}, + {"IP_RECVERR", Const, 0}, + {"IP_RECVIF", Const, 0}, + {"IP_RECVOPTS", Const, 0}, + {"IP_RECVORIGDSTADDR", Const, 0}, + {"IP_RECVPKTINFO", Const, 0}, + {"IP_RECVRETOPTS", Const, 0}, + {"IP_RECVRTABLE", Const, 1}, + {"IP_RECVTOS", Const, 0}, + {"IP_RECVTTL", Const, 0}, + {"IP_RETOPTS", Const, 0}, + {"IP_RF", Const, 0}, + {"IP_ROUTER_ALERT", Const, 0}, + {"IP_RSVP_OFF", Const, 0}, + {"IP_RSVP_ON", Const, 0}, + {"IP_RSVP_VIF_OFF", Const, 0}, + {"IP_RSVP_VIF_ON", Const, 0}, + {"IP_RTABLE", Const, 1}, + {"IP_SENDSRCADDR", Const, 0}, + {"IP_STRIPHDR", Const, 0}, + {"IP_TOS", Const, 0}, + {"IP_TRAFFIC_MGT_BACKGROUND", Const, 0}, + {"IP_TRANSPARENT", Const, 0}, + {"IP_TTL", Const, 0}, + {"IP_UNBLOCK_SOURCE", Const, 0}, + {"IP_XFRM_POLICY", Const, 0}, + {"IPv6MTUInfo", Type, 2}, + {"IPv6MTUInfo.Addr", Field, 2}, + {"IPv6MTUInfo.Mtu", Field, 2}, + {"IPv6Mreq", Type, 0}, + {"IPv6Mreq.Interface", Field, 0}, + {"IPv6Mreq.Multiaddr", Field, 0}, + {"ISIG", Const, 0}, + {"ISTRIP", Const, 0}, + {"IUCLC", Const, 0}, + {"IUTF8", Const, 0}, + {"IXANY", Const, 0}, + {"IXOFF", Const, 0}, + {"IXON", Const, 0}, + {"IfAddrmsg", Type, 0}, + {"IfAddrmsg.Family", Field, 0}, + {"IfAddrmsg.Flags", Field, 0}, + {"IfAddrmsg.Index", Field, 0}, + {"IfAddrmsg.Prefixlen", Field, 0}, + {"IfAddrmsg.Scope", Field, 0}, + {"IfAnnounceMsghdr", Type, 1}, + {"IfAnnounceMsghdr.Hdrlen", Field, 2}, + {"IfAnnounceMsghdr.Index", Field, 1}, + {"IfAnnounceMsghdr.Msglen", Field, 1}, + {"IfAnnounceMsghdr.Name", Field, 1}, + {"IfAnnounceMsghdr.Type", Field, 1}, + {"IfAnnounceMsghdr.Version", Field, 1}, + {"IfAnnounceMsghdr.What", Field, 1}, + {"IfData", Type, 0}, + {"IfData.Addrlen", Field, 0}, + {"IfData.Baudrate", Field, 0}, + {"IfData.Capabilities", Field, 2}, + {"IfData.Collisions", Field, 0}, + {"IfData.Datalen", Field, 0}, + {"IfData.Epoch", Field, 0}, + {"IfData.Hdrlen", Field, 0}, + {"IfData.Hwassist", Field, 0}, + {"IfData.Ibytes", Field, 0}, + {"IfData.Ierrors", Field, 0}, + {"IfData.Imcasts", Field, 0}, + {"IfData.Ipackets", Field, 0}, + {"IfData.Iqdrops", Field, 0}, + {"IfData.Lastchange", Field, 0}, + {"IfData.Link_state", Field, 0}, + {"IfData.Mclpool", Field, 2}, + {"IfData.Metric", Field, 0}, + {"IfData.Mtu", Field, 0}, + {"IfData.Noproto", Field, 0}, + {"IfData.Obytes", Field, 0}, + {"IfData.Oerrors", Field, 0}, + {"IfData.Omcasts", Field, 0}, + {"IfData.Opackets", Field, 0}, + {"IfData.Pad", Field, 2}, + {"IfData.Pad_cgo_0", Field, 2}, + {"IfData.Pad_cgo_1", Field, 2}, + {"IfData.Physical", Field, 0}, + {"IfData.Recvquota", Field, 0}, + {"IfData.Recvtiming", Field, 0}, + {"IfData.Reserved1", Field, 0}, + {"IfData.Reserved2", Field, 0}, + {"IfData.Spare_char1", Field, 0}, + {"IfData.Spare_char2", Field, 0}, + {"IfData.Type", Field, 0}, + {"IfData.Typelen", Field, 0}, + {"IfData.Unused1", Field, 0}, + {"IfData.Unused2", Field, 0}, + {"IfData.Xmitquota", Field, 0}, + {"IfData.Xmittiming", Field, 0}, + {"IfInfomsg", Type, 0}, + {"IfInfomsg.Change", Field, 0}, + {"IfInfomsg.Family", Field, 0}, + {"IfInfomsg.Flags", Field, 0}, + {"IfInfomsg.Index", Field, 0}, + {"IfInfomsg.Type", Field, 0}, + {"IfInfomsg.X__ifi_pad", Field, 0}, + {"IfMsghdr", Type, 0}, + {"IfMsghdr.Addrs", Field, 0}, + {"IfMsghdr.Data", Field, 0}, + {"IfMsghdr.Flags", Field, 0}, + {"IfMsghdr.Hdrlen", Field, 2}, + {"IfMsghdr.Index", Field, 0}, + {"IfMsghdr.Msglen", Field, 0}, + {"IfMsghdr.Pad1", Field, 2}, + {"IfMsghdr.Pad2", Field, 2}, + {"IfMsghdr.Pad_cgo_0", Field, 0}, + {"IfMsghdr.Pad_cgo_1", Field, 2}, + {"IfMsghdr.Tableid", Field, 2}, + {"IfMsghdr.Type", Field, 0}, + {"IfMsghdr.Version", Field, 0}, + {"IfMsghdr.Xflags", Field, 2}, + {"IfaMsghdr", Type, 0}, + {"IfaMsghdr.Addrs", Field, 0}, + {"IfaMsghdr.Flags", Field, 0}, + {"IfaMsghdr.Hdrlen", Field, 2}, + {"IfaMsghdr.Index", Field, 0}, + {"IfaMsghdr.Metric", Field, 0}, + {"IfaMsghdr.Msglen", Field, 0}, + {"IfaMsghdr.Pad1", Field, 2}, + {"IfaMsghdr.Pad2", Field, 2}, + {"IfaMsghdr.Pad_cgo_0", Field, 0}, + {"IfaMsghdr.Tableid", Field, 2}, + {"IfaMsghdr.Type", Field, 0}, + {"IfaMsghdr.Version", Field, 0}, + {"IfmaMsghdr", Type, 0}, + {"IfmaMsghdr.Addrs", Field, 0}, + {"IfmaMsghdr.Flags", Field, 0}, + {"IfmaMsghdr.Index", Field, 0}, + {"IfmaMsghdr.Msglen", Field, 0}, + {"IfmaMsghdr.Pad_cgo_0", Field, 0}, + {"IfmaMsghdr.Type", Field, 0}, + {"IfmaMsghdr.Version", Field, 0}, + {"IfmaMsghdr2", Type, 0}, + {"IfmaMsghdr2.Addrs", Field, 0}, + {"IfmaMsghdr2.Flags", Field, 0}, + {"IfmaMsghdr2.Index", Field, 0}, + {"IfmaMsghdr2.Msglen", Field, 0}, + {"IfmaMsghdr2.Pad_cgo_0", Field, 0}, + {"IfmaMsghdr2.Refcount", Field, 0}, + {"IfmaMsghdr2.Type", Field, 0}, + {"IfmaMsghdr2.Version", Field, 0}, + {"ImplementsGetwd", Const, 0}, + {"Inet4Pktinfo", Type, 0}, + {"Inet4Pktinfo.Addr", Field, 0}, + {"Inet4Pktinfo.Ifindex", Field, 0}, + {"Inet4Pktinfo.Spec_dst", Field, 0}, + {"Inet6Pktinfo", Type, 0}, + {"Inet6Pktinfo.Addr", Field, 0}, + {"Inet6Pktinfo.Ifindex", Field, 0}, + {"InotifyAddWatch", Func, 0}, + {"InotifyEvent", Type, 0}, + {"InotifyEvent.Cookie", Field, 0}, + {"InotifyEvent.Len", Field, 0}, + {"InotifyEvent.Mask", Field, 0}, + {"InotifyEvent.Name", Field, 0}, + {"InotifyEvent.Wd", Field, 0}, + {"InotifyInit", Func, 0}, + {"InotifyInit1", Func, 0}, + {"InotifyRmWatch", Func, 0}, + {"InterfaceAddrMessage", Type, 0}, + {"InterfaceAddrMessage.Data", Field, 0}, + {"InterfaceAddrMessage.Header", Field, 0}, + {"InterfaceAnnounceMessage", Type, 1}, + {"InterfaceAnnounceMessage.Header", Field, 1}, + {"InterfaceInfo", Type, 0}, + {"InterfaceInfo.Address", Field, 0}, + {"InterfaceInfo.BroadcastAddress", Field, 0}, + {"InterfaceInfo.Flags", Field, 0}, + {"InterfaceInfo.Netmask", Field, 0}, + {"InterfaceMessage", Type, 0}, + {"InterfaceMessage.Data", Field, 0}, + {"InterfaceMessage.Header", Field, 0}, + {"InterfaceMulticastAddrMessage", Type, 0}, + {"InterfaceMulticastAddrMessage.Data", Field, 0}, + {"InterfaceMulticastAddrMessage.Header", Field, 0}, + {"InvalidHandle", Const, 0}, + {"Ioperm", Func, 0}, + {"Iopl", Func, 0}, + {"Iovec", Type, 0}, + {"Iovec.Base", Field, 0}, + {"Iovec.Len", Field, 0}, + {"IpAdapterInfo", Type, 0}, + {"IpAdapterInfo.AdapterName", Field, 0}, + {"IpAdapterInfo.Address", Field, 0}, + {"IpAdapterInfo.AddressLength", Field, 0}, + {"IpAdapterInfo.ComboIndex", Field, 0}, + {"IpAdapterInfo.CurrentIpAddress", Field, 0}, + {"IpAdapterInfo.Description", Field, 0}, + {"IpAdapterInfo.DhcpEnabled", Field, 0}, + {"IpAdapterInfo.DhcpServer", Field, 0}, + {"IpAdapterInfo.GatewayList", Field, 0}, + {"IpAdapterInfo.HaveWins", Field, 0}, + {"IpAdapterInfo.Index", Field, 0}, + {"IpAdapterInfo.IpAddressList", Field, 0}, + {"IpAdapterInfo.LeaseExpires", Field, 0}, + {"IpAdapterInfo.LeaseObtained", Field, 0}, + {"IpAdapterInfo.Next", Field, 0}, + {"IpAdapterInfo.PrimaryWinsServer", Field, 0}, + {"IpAdapterInfo.SecondaryWinsServer", Field, 0}, + {"IpAdapterInfo.Type", Field, 0}, + {"IpAddrString", Type, 0}, + {"IpAddrString.Context", Field, 0}, + {"IpAddrString.IpAddress", Field, 0}, + {"IpAddrString.IpMask", Field, 0}, + {"IpAddrString.Next", Field, 0}, + {"IpAddressString", Type, 0}, + {"IpAddressString.String", Field, 0}, + {"IpMaskString", Type, 0}, + {"IpMaskString.String", Field, 2}, + {"Issetugid", Func, 0}, + {"KEY_ALL_ACCESS", Const, 0}, + {"KEY_CREATE_LINK", Const, 0}, + {"KEY_CREATE_SUB_KEY", Const, 0}, + {"KEY_ENUMERATE_SUB_KEYS", Const, 0}, + {"KEY_EXECUTE", Const, 0}, + {"KEY_NOTIFY", Const, 0}, + {"KEY_QUERY_VALUE", Const, 0}, + {"KEY_READ", Const, 0}, + {"KEY_SET_VALUE", Const, 0}, + {"KEY_WOW64_32KEY", Const, 0}, + {"KEY_WOW64_64KEY", Const, 0}, + {"KEY_WRITE", Const, 0}, + {"Kevent", Func, 0}, + {"Kevent_t", Type, 0}, + {"Kevent_t.Data", Field, 0}, + {"Kevent_t.Fflags", Field, 0}, + {"Kevent_t.Filter", Field, 0}, + {"Kevent_t.Flags", Field, 0}, + {"Kevent_t.Ident", Field, 0}, + {"Kevent_t.Pad_cgo_0", Field, 2}, + {"Kevent_t.Udata", Field, 0}, + {"Kill", Func, 0}, + {"Klogctl", Func, 0}, + {"Kqueue", Func, 0}, + {"LANG_ENGLISH", Const, 0}, + {"LAYERED_PROTOCOL", Const, 2}, + {"LCNT_OVERLOAD_FLUSH", Const, 1}, + {"LINUX_REBOOT_CMD_CAD_OFF", Const, 0}, + {"LINUX_REBOOT_CMD_CAD_ON", Const, 0}, + {"LINUX_REBOOT_CMD_HALT", Const, 0}, + {"LINUX_REBOOT_CMD_KEXEC", Const, 0}, + {"LINUX_REBOOT_CMD_POWER_OFF", Const, 0}, + {"LINUX_REBOOT_CMD_RESTART", Const, 0}, + {"LINUX_REBOOT_CMD_RESTART2", Const, 0}, + {"LINUX_REBOOT_CMD_SW_SUSPEND", Const, 0}, + {"LINUX_REBOOT_MAGIC1", Const, 0}, + {"LINUX_REBOOT_MAGIC2", Const, 0}, + {"LOCK_EX", Const, 0}, + {"LOCK_NB", Const, 0}, + {"LOCK_SH", Const, 0}, + {"LOCK_UN", Const, 0}, + {"LazyDLL", Type, 0}, + {"LazyDLL.Name", Field, 0}, + {"LazyProc", Type, 0}, + {"LazyProc.Name", Field, 0}, + {"Lchown", Func, 0}, + {"Linger", Type, 0}, + {"Linger.Linger", Field, 0}, + {"Linger.Onoff", Field, 0}, + {"Link", Func, 0}, + {"Listen", Func, 0}, + {"Listxattr", Func, 1}, + {"LoadCancelIoEx", Func, 1}, + {"LoadConnectEx", Func, 1}, + {"LoadCreateSymbolicLink", Func, 4}, + {"LoadDLL", Func, 0}, + {"LoadGetAddrInfo", Func, 1}, + {"LoadLibrary", Func, 0}, + {"LoadSetFileCompletionNotificationModes", Func, 2}, + {"LocalFree", Func, 0}, + {"Log2phys_t", Type, 0}, + {"Log2phys_t.Contigbytes", Field, 0}, + {"Log2phys_t.Devoffset", Field, 0}, + {"Log2phys_t.Flags", Field, 0}, + {"LookupAccountName", Func, 0}, + {"LookupAccountSid", Func, 0}, + {"LookupSID", Func, 0}, + {"LsfJump", Func, 0}, + {"LsfSocket", Func, 0}, + {"LsfStmt", Func, 0}, + {"Lstat", Func, 0}, + {"MADV_AUTOSYNC", Const, 1}, + {"MADV_CAN_REUSE", Const, 0}, + {"MADV_CORE", Const, 1}, + {"MADV_DOFORK", Const, 0}, + {"MADV_DONTFORK", Const, 0}, + {"MADV_DONTNEED", Const, 0}, + {"MADV_FREE", Const, 0}, + {"MADV_FREE_REUSABLE", Const, 0}, + {"MADV_FREE_REUSE", Const, 0}, + {"MADV_HUGEPAGE", Const, 0}, + {"MADV_HWPOISON", Const, 0}, + {"MADV_MERGEABLE", Const, 0}, + {"MADV_NOCORE", Const, 1}, + {"MADV_NOHUGEPAGE", Const, 0}, + {"MADV_NORMAL", Const, 0}, + {"MADV_NOSYNC", Const, 1}, + {"MADV_PROTECT", Const, 1}, + {"MADV_RANDOM", Const, 0}, + {"MADV_REMOVE", Const, 0}, + {"MADV_SEQUENTIAL", Const, 0}, + {"MADV_SPACEAVAIL", Const, 3}, + {"MADV_UNMERGEABLE", Const, 0}, + {"MADV_WILLNEED", Const, 0}, + {"MADV_ZERO_WIRED_PAGES", Const, 0}, + {"MAP_32BIT", Const, 0}, + {"MAP_ALIGNED_SUPER", Const, 3}, + {"MAP_ALIGNMENT_16MB", Const, 3}, + {"MAP_ALIGNMENT_1TB", Const, 3}, + {"MAP_ALIGNMENT_256TB", Const, 3}, + {"MAP_ALIGNMENT_4GB", Const, 3}, + {"MAP_ALIGNMENT_64KB", Const, 3}, + {"MAP_ALIGNMENT_64PB", Const, 3}, + {"MAP_ALIGNMENT_MASK", Const, 3}, + {"MAP_ALIGNMENT_SHIFT", Const, 3}, + {"MAP_ANON", Const, 0}, + {"MAP_ANONYMOUS", Const, 0}, + {"MAP_COPY", Const, 0}, + {"MAP_DENYWRITE", Const, 0}, + {"MAP_EXECUTABLE", Const, 0}, + {"MAP_FILE", Const, 0}, + {"MAP_FIXED", Const, 0}, + {"MAP_FLAGMASK", Const, 3}, + {"MAP_GROWSDOWN", Const, 0}, + {"MAP_HASSEMAPHORE", Const, 0}, + {"MAP_HUGETLB", Const, 0}, + {"MAP_INHERIT", Const, 3}, + {"MAP_INHERIT_COPY", Const, 3}, + {"MAP_INHERIT_DEFAULT", Const, 3}, + {"MAP_INHERIT_DONATE_COPY", Const, 3}, + {"MAP_INHERIT_NONE", Const, 3}, + {"MAP_INHERIT_SHARE", Const, 3}, + {"MAP_JIT", Const, 0}, + {"MAP_LOCKED", Const, 0}, + {"MAP_NOCACHE", Const, 0}, + {"MAP_NOCORE", Const, 1}, + {"MAP_NOEXTEND", Const, 0}, + {"MAP_NONBLOCK", Const, 0}, + {"MAP_NORESERVE", Const, 0}, + {"MAP_NOSYNC", Const, 1}, + {"MAP_POPULATE", Const, 0}, + {"MAP_PREFAULT_READ", Const, 1}, + {"MAP_PRIVATE", Const, 0}, + {"MAP_RENAME", Const, 0}, + {"MAP_RESERVED0080", Const, 0}, + {"MAP_RESERVED0100", Const, 1}, + {"MAP_SHARED", Const, 0}, + {"MAP_STACK", Const, 0}, + {"MAP_TRYFIXED", Const, 3}, + {"MAP_TYPE", Const, 0}, + {"MAP_WIRED", Const, 3}, + {"MAXIMUM_REPARSE_DATA_BUFFER_SIZE", Const, 4}, + {"MAXLEN_IFDESCR", Const, 0}, + {"MAXLEN_PHYSADDR", Const, 0}, + {"MAX_ADAPTER_ADDRESS_LENGTH", Const, 0}, + {"MAX_ADAPTER_DESCRIPTION_LENGTH", Const, 0}, + {"MAX_ADAPTER_NAME_LENGTH", Const, 0}, + {"MAX_COMPUTERNAME_LENGTH", Const, 0}, + {"MAX_INTERFACE_NAME_LEN", Const, 0}, + {"MAX_LONG_PATH", Const, 0}, + {"MAX_PATH", Const, 0}, + {"MAX_PROTOCOL_CHAIN", Const, 2}, + {"MCL_CURRENT", Const, 0}, + {"MCL_FUTURE", Const, 0}, + {"MNT_DETACH", Const, 0}, + {"MNT_EXPIRE", Const, 0}, + {"MNT_FORCE", Const, 0}, + {"MSG_BCAST", Const, 1}, + {"MSG_CMSG_CLOEXEC", Const, 0}, + {"MSG_COMPAT", Const, 0}, + {"MSG_CONFIRM", Const, 0}, + {"MSG_CONTROLMBUF", Const, 1}, + {"MSG_CTRUNC", Const, 0}, + {"MSG_DONTROUTE", Const, 0}, + {"MSG_DONTWAIT", Const, 0}, + {"MSG_EOF", Const, 0}, + {"MSG_EOR", Const, 0}, + {"MSG_ERRQUEUE", Const, 0}, + {"MSG_FASTOPEN", Const, 1}, + {"MSG_FIN", Const, 0}, + {"MSG_FLUSH", Const, 0}, + {"MSG_HAVEMORE", Const, 0}, + {"MSG_HOLD", Const, 0}, + {"MSG_IOVUSRSPACE", Const, 1}, + {"MSG_LENUSRSPACE", Const, 1}, + {"MSG_MCAST", Const, 1}, + {"MSG_MORE", Const, 0}, + {"MSG_NAMEMBUF", Const, 1}, + {"MSG_NBIO", Const, 0}, + {"MSG_NEEDSA", Const, 0}, + {"MSG_NOSIGNAL", Const, 0}, + {"MSG_NOTIFICATION", Const, 0}, + {"MSG_OOB", Const, 0}, + {"MSG_PEEK", Const, 0}, + {"MSG_PROXY", Const, 0}, + {"MSG_RCVMORE", Const, 0}, + {"MSG_RST", Const, 0}, + {"MSG_SEND", Const, 0}, + {"MSG_SYN", Const, 0}, + {"MSG_TRUNC", Const, 0}, + {"MSG_TRYHARD", Const, 0}, + {"MSG_USERFLAGS", Const, 1}, + {"MSG_WAITALL", Const, 0}, + {"MSG_WAITFORONE", Const, 0}, + {"MSG_WAITSTREAM", Const, 0}, + {"MS_ACTIVE", Const, 0}, + {"MS_ASYNC", Const, 0}, + {"MS_BIND", Const, 0}, + {"MS_DEACTIVATE", Const, 0}, + {"MS_DIRSYNC", Const, 0}, + {"MS_INVALIDATE", Const, 0}, + {"MS_I_VERSION", Const, 0}, + {"MS_KERNMOUNT", Const, 0}, + {"MS_KILLPAGES", Const, 0}, + {"MS_MANDLOCK", Const, 0}, + {"MS_MGC_MSK", Const, 0}, + {"MS_MGC_VAL", Const, 0}, + {"MS_MOVE", Const, 0}, + {"MS_NOATIME", Const, 0}, + {"MS_NODEV", Const, 0}, + {"MS_NODIRATIME", Const, 0}, + {"MS_NOEXEC", Const, 0}, + {"MS_NOSUID", Const, 0}, + {"MS_NOUSER", Const, 0}, + {"MS_POSIXACL", Const, 0}, + {"MS_PRIVATE", Const, 0}, + {"MS_RDONLY", Const, 0}, + {"MS_REC", Const, 0}, + {"MS_RELATIME", Const, 0}, + {"MS_REMOUNT", Const, 0}, + {"MS_RMT_MASK", Const, 0}, + {"MS_SHARED", Const, 0}, + {"MS_SILENT", Const, 0}, + {"MS_SLAVE", Const, 0}, + {"MS_STRICTATIME", Const, 0}, + {"MS_SYNC", Const, 0}, + {"MS_SYNCHRONOUS", Const, 0}, + {"MS_UNBINDABLE", Const, 0}, + {"Madvise", Func, 0}, + {"MapViewOfFile", Func, 0}, + {"MaxTokenInfoClass", Const, 0}, + {"Mclpool", Type, 2}, + {"Mclpool.Alive", Field, 2}, + {"Mclpool.Cwm", Field, 2}, + {"Mclpool.Grown", Field, 2}, + {"Mclpool.Hwm", Field, 2}, + {"Mclpool.Lwm", Field, 2}, + {"MibIfRow", Type, 0}, + {"MibIfRow.AdminStatus", Field, 0}, + {"MibIfRow.Descr", Field, 0}, + {"MibIfRow.DescrLen", Field, 0}, + {"MibIfRow.InDiscards", Field, 0}, + {"MibIfRow.InErrors", Field, 0}, + {"MibIfRow.InNUcastPkts", Field, 0}, + {"MibIfRow.InOctets", Field, 0}, + {"MibIfRow.InUcastPkts", Field, 0}, + {"MibIfRow.InUnknownProtos", Field, 0}, + {"MibIfRow.Index", Field, 0}, + {"MibIfRow.LastChange", Field, 0}, + {"MibIfRow.Mtu", Field, 0}, + {"MibIfRow.Name", Field, 0}, + {"MibIfRow.OperStatus", Field, 0}, + {"MibIfRow.OutDiscards", Field, 0}, + {"MibIfRow.OutErrors", Field, 0}, + {"MibIfRow.OutNUcastPkts", Field, 0}, + {"MibIfRow.OutOctets", Field, 0}, + {"MibIfRow.OutQLen", Field, 0}, + {"MibIfRow.OutUcastPkts", Field, 0}, + {"MibIfRow.PhysAddr", Field, 0}, + {"MibIfRow.PhysAddrLen", Field, 0}, + {"MibIfRow.Speed", Field, 0}, + {"MibIfRow.Type", Field, 0}, + {"Mkdir", Func, 0}, + {"Mkdirat", Func, 0}, + {"Mkfifo", Func, 0}, + {"Mknod", Func, 0}, + {"Mknodat", Func, 0}, + {"Mlock", Func, 0}, + {"Mlockall", Func, 0}, + {"Mmap", Func, 0}, + {"Mount", Func, 0}, + {"MoveFile", Func, 0}, + {"Mprotect", Func, 0}, + {"Msghdr", Type, 0}, + {"Msghdr.Control", Field, 0}, + {"Msghdr.Controllen", Field, 0}, + {"Msghdr.Flags", Field, 0}, + {"Msghdr.Iov", Field, 0}, + {"Msghdr.Iovlen", Field, 0}, + {"Msghdr.Name", Field, 0}, + {"Msghdr.Namelen", Field, 0}, + {"Msghdr.Pad_cgo_0", Field, 0}, + {"Msghdr.Pad_cgo_1", Field, 0}, + {"Munlock", Func, 0}, + {"Munlockall", Func, 0}, + {"Munmap", Func, 0}, + {"MustLoadDLL", Func, 0}, + {"NAME_MAX", Const, 0}, + {"NETLINK_ADD_MEMBERSHIP", Const, 0}, + {"NETLINK_AUDIT", Const, 0}, + {"NETLINK_BROADCAST_ERROR", Const, 0}, + {"NETLINK_CONNECTOR", Const, 0}, + {"NETLINK_DNRTMSG", Const, 0}, + {"NETLINK_DROP_MEMBERSHIP", Const, 0}, + {"NETLINK_ECRYPTFS", Const, 0}, + {"NETLINK_FIB_LOOKUP", Const, 0}, + {"NETLINK_FIREWALL", Const, 0}, + {"NETLINK_GENERIC", Const, 0}, + {"NETLINK_INET_DIAG", Const, 0}, + {"NETLINK_IP6_FW", Const, 0}, + {"NETLINK_ISCSI", Const, 0}, + {"NETLINK_KOBJECT_UEVENT", Const, 0}, + {"NETLINK_NETFILTER", Const, 0}, + {"NETLINK_NFLOG", Const, 0}, + {"NETLINK_NO_ENOBUFS", Const, 0}, + {"NETLINK_PKTINFO", Const, 0}, + {"NETLINK_RDMA", Const, 0}, + {"NETLINK_ROUTE", Const, 0}, + {"NETLINK_SCSITRANSPORT", Const, 0}, + {"NETLINK_SELINUX", Const, 0}, + {"NETLINK_UNUSED", Const, 0}, + {"NETLINK_USERSOCK", Const, 0}, + {"NETLINK_XFRM", Const, 0}, + {"NET_RT_DUMP", Const, 0}, + {"NET_RT_DUMP2", Const, 0}, + {"NET_RT_FLAGS", Const, 0}, + {"NET_RT_IFLIST", Const, 0}, + {"NET_RT_IFLIST2", Const, 0}, + {"NET_RT_IFLISTL", Const, 1}, + {"NET_RT_IFMALIST", Const, 0}, + {"NET_RT_MAXID", Const, 0}, + {"NET_RT_OIFLIST", Const, 1}, + {"NET_RT_OOIFLIST", Const, 1}, + {"NET_RT_STAT", Const, 0}, + {"NET_RT_STATS", Const, 1}, + {"NET_RT_TABLE", Const, 1}, + {"NET_RT_TRASH", Const, 0}, + {"NLA_ALIGNTO", Const, 0}, + {"NLA_F_NESTED", Const, 0}, + {"NLA_F_NET_BYTEORDER", Const, 0}, + {"NLA_HDRLEN", Const, 0}, + {"NLMSG_ALIGNTO", Const, 0}, + {"NLMSG_DONE", Const, 0}, + {"NLMSG_ERROR", Const, 0}, + {"NLMSG_HDRLEN", Const, 0}, + {"NLMSG_MIN_TYPE", Const, 0}, + {"NLMSG_NOOP", Const, 0}, + {"NLMSG_OVERRUN", Const, 0}, + {"NLM_F_ACK", Const, 0}, + {"NLM_F_APPEND", Const, 0}, + {"NLM_F_ATOMIC", Const, 0}, + {"NLM_F_CREATE", Const, 0}, + {"NLM_F_DUMP", Const, 0}, + {"NLM_F_ECHO", Const, 0}, + {"NLM_F_EXCL", Const, 0}, + {"NLM_F_MATCH", Const, 0}, + {"NLM_F_MULTI", Const, 0}, + {"NLM_F_REPLACE", Const, 0}, + {"NLM_F_REQUEST", Const, 0}, + {"NLM_F_ROOT", Const, 0}, + {"NOFLSH", Const, 0}, + {"NOTE_ABSOLUTE", Const, 0}, + {"NOTE_ATTRIB", Const, 0}, + {"NOTE_BACKGROUND", Const, 16}, + {"NOTE_CHILD", Const, 0}, + {"NOTE_CRITICAL", Const, 16}, + {"NOTE_DELETE", Const, 0}, + {"NOTE_EOF", Const, 1}, + {"NOTE_EXEC", Const, 0}, + {"NOTE_EXIT", Const, 0}, + {"NOTE_EXITSTATUS", Const, 0}, + {"NOTE_EXIT_CSERROR", Const, 16}, + {"NOTE_EXIT_DECRYPTFAIL", Const, 16}, + {"NOTE_EXIT_DETAIL", Const, 16}, + {"NOTE_EXIT_DETAIL_MASK", Const, 16}, + {"NOTE_EXIT_MEMORY", Const, 16}, + {"NOTE_EXIT_REPARENTED", Const, 16}, + {"NOTE_EXTEND", Const, 0}, + {"NOTE_FFAND", Const, 0}, + {"NOTE_FFCOPY", Const, 0}, + {"NOTE_FFCTRLMASK", Const, 0}, + {"NOTE_FFLAGSMASK", Const, 0}, + {"NOTE_FFNOP", Const, 0}, + {"NOTE_FFOR", Const, 0}, + {"NOTE_FORK", Const, 0}, + {"NOTE_LEEWAY", Const, 16}, + {"NOTE_LINK", Const, 0}, + {"NOTE_LOWAT", Const, 0}, + {"NOTE_NONE", Const, 0}, + {"NOTE_NSECONDS", Const, 0}, + {"NOTE_PCTRLMASK", Const, 0}, + {"NOTE_PDATAMASK", Const, 0}, + {"NOTE_REAP", Const, 0}, + {"NOTE_RENAME", Const, 0}, + {"NOTE_RESOURCEEND", Const, 0}, + {"NOTE_REVOKE", Const, 0}, + {"NOTE_SECONDS", Const, 0}, + {"NOTE_SIGNAL", Const, 0}, + {"NOTE_TRACK", Const, 0}, + {"NOTE_TRACKERR", Const, 0}, + {"NOTE_TRIGGER", Const, 0}, + {"NOTE_TRUNCATE", Const, 1}, + {"NOTE_USECONDS", Const, 0}, + {"NOTE_VM_ERROR", Const, 0}, + {"NOTE_VM_PRESSURE", Const, 0}, + {"NOTE_VM_PRESSURE_SUDDEN_TERMINATE", Const, 0}, + {"NOTE_VM_PRESSURE_TERMINATE", Const, 0}, + {"NOTE_WRITE", Const, 0}, + {"NameCanonical", Const, 0}, + {"NameCanonicalEx", Const, 0}, + {"NameDisplay", Const, 0}, + {"NameDnsDomain", Const, 0}, + {"NameFullyQualifiedDN", Const, 0}, + {"NameSamCompatible", Const, 0}, + {"NameServicePrincipal", Const, 0}, + {"NameUniqueId", Const, 0}, + {"NameUnknown", Const, 0}, + {"NameUserPrincipal", Const, 0}, + {"Nanosleep", Func, 0}, + {"NetApiBufferFree", Func, 0}, + {"NetGetJoinInformation", Func, 2}, + {"NetSetupDomainName", Const, 2}, + {"NetSetupUnjoined", Const, 2}, + {"NetSetupUnknownStatus", Const, 2}, + {"NetSetupWorkgroupName", Const, 2}, + {"NetUserGetInfo", Func, 0}, + {"NetlinkMessage", Type, 0}, + {"NetlinkMessage.Data", Field, 0}, + {"NetlinkMessage.Header", Field, 0}, + {"NetlinkRIB", Func, 0}, + {"NetlinkRouteAttr", Type, 0}, + {"NetlinkRouteAttr.Attr", Field, 0}, + {"NetlinkRouteAttr.Value", Field, 0}, + {"NetlinkRouteRequest", Type, 0}, + {"NetlinkRouteRequest.Data", Field, 0}, + {"NetlinkRouteRequest.Header", Field, 0}, + {"NewCallback", Func, 0}, + {"NewCallbackCDecl", Func, 3}, + {"NewLazyDLL", Func, 0}, + {"NlAttr", Type, 0}, + {"NlAttr.Len", Field, 0}, + {"NlAttr.Type", Field, 0}, + {"NlMsgerr", Type, 0}, + {"NlMsgerr.Error", Field, 0}, + {"NlMsgerr.Msg", Field, 0}, + {"NlMsghdr", Type, 0}, + {"NlMsghdr.Flags", Field, 0}, + {"NlMsghdr.Len", Field, 0}, + {"NlMsghdr.Pid", Field, 0}, + {"NlMsghdr.Seq", Field, 0}, + {"NlMsghdr.Type", Field, 0}, + {"NsecToFiletime", Func, 0}, + {"NsecToTimespec", Func, 0}, + {"NsecToTimeval", Func, 0}, + {"Ntohs", Func, 0}, + {"OCRNL", Const, 0}, + {"OFDEL", Const, 0}, + {"OFILL", Const, 0}, + {"OFIOGETBMAP", Const, 1}, + {"OID_PKIX_KP_SERVER_AUTH", Var, 0}, + {"OID_SERVER_GATED_CRYPTO", Var, 0}, + {"OID_SGC_NETSCAPE", Var, 0}, + {"OLCUC", Const, 0}, + {"ONLCR", Const, 0}, + {"ONLRET", Const, 0}, + {"ONOCR", Const, 0}, + {"ONOEOT", Const, 1}, + {"OPEN_ALWAYS", Const, 0}, + {"OPEN_EXISTING", Const, 0}, + {"OPOST", Const, 0}, + {"O_ACCMODE", Const, 0}, + {"O_ALERT", Const, 0}, + {"O_ALT_IO", Const, 1}, + {"O_APPEND", Const, 0}, + {"O_ASYNC", Const, 0}, + {"O_CLOEXEC", Const, 0}, + {"O_CREAT", Const, 0}, + {"O_DIRECT", Const, 0}, + {"O_DIRECTORY", Const, 0}, + {"O_DP_GETRAWENCRYPTED", Const, 16}, + {"O_DSYNC", Const, 0}, + {"O_EVTONLY", Const, 0}, + {"O_EXCL", Const, 0}, + {"O_EXEC", Const, 0}, + {"O_EXLOCK", Const, 0}, + {"O_FSYNC", Const, 0}, + {"O_LARGEFILE", Const, 0}, + {"O_NDELAY", Const, 0}, + {"O_NOATIME", Const, 0}, + {"O_NOCTTY", Const, 0}, + {"O_NOFOLLOW", Const, 0}, + {"O_NONBLOCK", Const, 0}, + {"O_NOSIGPIPE", Const, 1}, + {"O_POPUP", Const, 0}, + {"O_RDONLY", Const, 0}, + {"O_RDWR", Const, 0}, + {"O_RSYNC", Const, 0}, + {"O_SHLOCK", Const, 0}, + {"O_SYMLINK", Const, 0}, + {"O_SYNC", Const, 0}, + {"O_TRUNC", Const, 0}, + {"O_TTY_INIT", Const, 0}, + {"O_WRONLY", Const, 0}, + {"Open", Func, 0}, + {"OpenCurrentProcessToken", Func, 0}, + {"OpenProcess", Func, 0}, + {"OpenProcessToken", Func, 0}, + {"Openat", Func, 0}, + {"Overlapped", Type, 0}, + {"Overlapped.HEvent", Field, 0}, + {"Overlapped.Internal", Field, 0}, + {"Overlapped.InternalHigh", Field, 0}, + {"Overlapped.Offset", Field, 0}, + {"Overlapped.OffsetHigh", Field, 0}, + {"PACKET_ADD_MEMBERSHIP", Const, 0}, + {"PACKET_BROADCAST", Const, 0}, + {"PACKET_DROP_MEMBERSHIP", Const, 0}, + {"PACKET_FASTROUTE", Const, 0}, + {"PACKET_HOST", Const, 0}, + {"PACKET_LOOPBACK", Const, 0}, + {"PACKET_MR_ALLMULTI", Const, 0}, + {"PACKET_MR_MULTICAST", Const, 0}, + {"PACKET_MR_PROMISC", Const, 0}, + {"PACKET_MULTICAST", Const, 0}, + {"PACKET_OTHERHOST", Const, 0}, + {"PACKET_OUTGOING", Const, 0}, + {"PACKET_RECV_OUTPUT", Const, 0}, + {"PACKET_RX_RING", Const, 0}, + {"PACKET_STATISTICS", Const, 0}, + {"PAGE_EXECUTE_READ", Const, 0}, + {"PAGE_EXECUTE_READWRITE", Const, 0}, + {"PAGE_EXECUTE_WRITECOPY", Const, 0}, + {"PAGE_READONLY", Const, 0}, + {"PAGE_READWRITE", Const, 0}, + {"PAGE_WRITECOPY", Const, 0}, + {"PARENB", Const, 0}, + {"PARMRK", Const, 0}, + {"PARODD", Const, 0}, + {"PENDIN", Const, 0}, + {"PFL_HIDDEN", Const, 2}, + {"PFL_MATCHES_PROTOCOL_ZERO", Const, 2}, + {"PFL_MULTIPLE_PROTO_ENTRIES", Const, 2}, + {"PFL_NETWORKDIRECT_PROVIDER", Const, 2}, + {"PFL_RECOMMENDED_PROTO_ENTRY", Const, 2}, + {"PF_FLUSH", Const, 1}, + {"PKCS_7_ASN_ENCODING", Const, 0}, + {"PMC5_PIPELINE_FLUSH", Const, 1}, + {"PRIO_PGRP", Const, 2}, + {"PRIO_PROCESS", Const, 2}, + {"PRIO_USER", Const, 2}, + {"PRI_IOFLUSH", Const, 1}, + {"PROCESS_QUERY_INFORMATION", Const, 0}, + {"PROCESS_TERMINATE", Const, 2}, + {"PROT_EXEC", Const, 0}, + {"PROT_GROWSDOWN", Const, 0}, + {"PROT_GROWSUP", Const, 0}, + {"PROT_NONE", Const, 0}, + {"PROT_READ", Const, 0}, + {"PROT_WRITE", Const, 0}, + {"PROV_DH_SCHANNEL", Const, 0}, + {"PROV_DSS", Const, 0}, + {"PROV_DSS_DH", Const, 0}, + {"PROV_EC_ECDSA_FULL", Const, 0}, + {"PROV_EC_ECDSA_SIG", Const, 0}, + {"PROV_EC_ECNRA_FULL", Const, 0}, + {"PROV_EC_ECNRA_SIG", Const, 0}, + {"PROV_FORTEZZA", Const, 0}, + {"PROV_INTEL_SEC", Const, 0}, + {"PROV_MS_EXCHANGE", Const, 0}, + {"PROV_REPLACE_OWF", Const, 0}, + {"PROV_RNG", Const, 0}, + {"PROV_RSA_AES", Const, 0}, + {"PROV_RSA_FULL", Const, 0}, + {"PROV_RSA_SCHANNEL", Const, 0}, + {"PROV_RSA_SIG", Const, 0}, + {"PROV_SPYRUS_LYNKS", Const, 0}, + {"PROV_SSL", Const, 0}, + {"PR_CAPBSET_DROP", Const, 0}, + {"PR_CAPBSET_READ", Const, 0}, + {"PR_CLEAR_SECCOMP_FILTER", Const, 0}, + {"PR_ENDIAN_BIG", Const, 0}, + {"PR_ENDIAN_LITTLE", Const, 0}, + {"PR_ENDIAN_PPC_LITTLE", Const, 0}, + {"PR_FPEMU_NOPRINT", Const, 0}, + {"PR_FPEMU_SIGFPE", Const, 0}, + {"PR_FP_EXC_ASYNC", Const, 0}, + {"PR_FP_EXC_DISABLED", Const, 0}, + {"PR_FP_EXC_DIV", Const, 0}, + {"PR_FP_EXC_INV", Const, 0}, + {"PR_FP_EXC_NONRECOV", Const, 0}, + {"PR_FP_EXC_OVF", Const, 0}, + {"PR_FP_EXC_PRECISE", Const, 0}, + {"PR_FP_EXC_RES", Const, 0}, + {"PR_FP_EXC_SW_ENABLE", Const, 0}, + {"PR_FP_EXC_UND", Const, 0}, + {"PR_GET_DUMPABLE", Const, 0}, + {"PR_GET_ENDIAN", Const, 0}, + {"PR_GET_FPEMU", Const, 0}, + {"PR_GET_FPEXC", Const, 0}, + {"PR_GET_KEEPCAPS", Const, 0}, + {"PR_GET_NAME", Const, 0}, + {"PR_GET_PDEATHSIG", Const, 0}, + {"PR_GET_SECCOMP", Const, 0}, + {"PR_GET_SECCOMP_FILTER", Const, 0}, + {"PR_GET_SECUREBITS", Const, 0}, + {"PR_GET_TIMERSLACK", Const, 0}, + {"PR_GET_TIMING", Const, 0}, + {"PR_GET_TSC", Const, 0}, + {"PR_GET_UNALIGN", Const, 0}, + {"PR_MCE_KILL", Const, 0}, + {"PR_MCE_KILL_CLEAR", Const, 0}, + {"PR_MCE_KILL_DEFAULT", Const, 0}, + {"PR_MCE_KILL_EARLY", Const, 0}, + {"PR_MCE_KILL_GET", Const, 0}, + {"PR_MCE_KILL_LATE", Const, 0}, + {"PR_MCE_KILL_SET", Const, 0}, + {"PR_SECCOMP_FILTER_EVENT", Const, 0}, + {"PR_SECCOMP_FILTER_SYSCALL", Const, 0}, + {"PR_SET_DUMPABLE", Const, 0}, + {"PR_SET_ENDIAN", Const, 0}, + {"PR_SET_FPEMU", Const, 0}, + {"PR_SET_FPEXC", Const, 0}, + {"PR_SET_KEEPCAPS", Const, 0}, + {"PR_SET_NAME", Const, 0}, + {"PR_SET_PDEATHSIG", Const, 0}, + {"PR_SET_PTRACER", Const, 0}, + {"PR_SET_SECCOMP", Const, 0}, + {"PR_SET_SECCOMP_FILTER", Const, 0}, + {"PR_SET_SECUREBITS", Const, 0}, + {"PR_SET_TIMERSLACK", Const, 0}, + {"PR_SET_TIMING", Const, 0}, + {"PR_SET_TSC", Const, 0}, + {"PR_SET_UNALIGN", Const, 0}, + {"PR_TASK_PERF_EVENTS_DISABLE", Const, 0}, + {"PR_TASK_PERF_EVENTS_ENABLE", Const, 0}, + {"PR_TIMING_STATISTICAL", Const, 0}, + {"PR_TIMING_TIMESTAMP", Const, 0}, + {"PR_TSC_ENABLE", Const, 0}, + {"PR_TSC_SIGSEGV", Const, 0}, + {"PR_UNALIGN_NOPRINT", Const, 0}, + {"PR_UNALIGN_SIGBUS", Const, 0}, + {"PTRACE_ARCH_PRCTL", Const, 0}, + {"PTRACE_ATTACH", Const, 0}, + {"PTRACE_CONT", Const, 0}, + {"PTRACE_DETACH", Const, 0}, + {"PTRACE_EVENT_CLONE", Const, 0}, + {"PTRACE_EVENT_EXEC", Const, 0}, + {"PTRACE_EVENT_EXIT", Const, 0}, + {"PTRACE_EVENT_FORK", Const, 0}, + {"PTRACE_EVENT_VFORK", Const, 0}, + {"PTRACE_EVENT_VFORK_DONE", Const, 0}, + {"PTRACE_GETCRUNCHREGS", Const, 0}, + {"PTRACE_GETEVENTMSG", Const, 0}, + {"PTRACE_GETFPREGS", Const, 0}, + {"PTRACE_GETFPXREGS", Const, 0}, + {"PTRACE_GETHBPREGS", Const, 0}, + {"PTRACE_GETREGS", Const, 0}, + {"PTRACE_GETREGSET", Const, 0}, + {"PTRACE_GETSIGINFO", Const, 0}, + {"PTRACE_GETVFPREGS", Const, 0}, + {"PTRACE_GETWMMXREGS", Const, 0}, + {"PTRACE_GET_THREAD_AREA", Const, 0}, + {"PTRACE_KILL", Const, 0}, + {"PTRACE_OLDSETOPTIONS", Const, 0}, + {"PTRACE_O_MASK", Const, 0}, + {"PTRACE_O_TRACECLONE", Const, 0}, + {"PTRACE_O_TRACEEXEC", Const, 0}, + {"PTRACE_O_TRACEEXIT", Const, 0}, + {"PTRACE_O_TRACEFORK", Const, 0}, + {"PTRACE_O_TRACESYSGOOD", Const, 0}, + {"PTRACE_O_TRACEVFORK", Const, 0}, + {"PTRACE_O_TRACEVFORKDONE", Const, 0}, + {"PTRACE_PEEKDATA", Const, 0}, + {"PTRACE_PEEKTEXT", Const, 0}, + {"PTRACE_PEEKUSR", Const, 0}, + {"PTRACE_POKEDATA", Const, 0}, + {"PTRACE_POKETEXT", Const, 0}, + {"PTRACE_POKEUSR", Const, 0}, + {"PTRACE_SETCRUNCHREGS", Const, 0}, + {"PTRACE_SETFPREGS", Const, 0}, + {"PTRACE_SETFPXREGS", Const, 0}, + {"PTRACE_SETHBPREGS", Const, 0}, + {"PTRACE_SETOPTIONS", Const, 0}, + {"PTRACE_SETREGS", Const, 0}, + {"PTRACE_SETREGSET", Const, 0}, + {"PTRACE_SETSIGINFO", Const, 0}, + {"PTRACE_SETVFPREGS", Const, 0}, + {"PTRACE_SETWMMXREGS", Const, 0}, + {"PTRACE_SET_SYSCALL", Const, 0}, + {"PTRACE_SET_THREAD_AREA", Const, 0}, + {"PTRACE_SINGLEBLOCK", Const, 0}, + {"PTRACE_SINGLESTEP", Const, 0}, + {"PTRACE_SYSCALL", Const, 0}, + {"PTRACE_SYSEMU", Const, 0}, + {"PTRACE_SYSEMU_SINGLESTEP", Const, 0}, + {"PTRACE_TRACEME", Const, 0}, + {"PT_ATTACH", Const, 0}, + {"PT_ATTACHEXC", Const, 0}, + {"PT_CONTINUE", Const, 0}, + {"PT_DATA_ADDR", Const, 0}, + {"PT_DENY_ATTACH", Const, 0}, + {"PT_DETACH", Const, 0}, + {"PT_FIRSTMACH", Const, 0}, + {"PT_FORCEQUOTA", Const, 0}, + {"PT_KILL", Const, 0}, + {"PT_MASK", Const, 1}, + {"PT_READ_D", Const, 0}, + {"PT_READ_I", Const, 0}, + {"PT_READ_U", Const, 0}, + {"PT_SIGEXC", Const, 0}, + {"PT_STEP", Const, 0}, + {"PT_TEXT_ADDR", Const, 0}, + {"PT_TEXT_END_ADDR", Const, 0}, + {"PT_THUPDATE", Const, 0}, + {"PT_TRACE_ME", Const, 0}, + {"PT_WRITE_D", Const, 0}, + {"PT_WRITE_I", Const, 0}, + {"PT_WRITE_U", Const, 0}, + {"ParseDirent", Func, 0}, + {"ParseNetlinkMessage", Func, 0}, + {"ParseNetlinkRouteAttr", Func, 0}, + {"ParseRoutingMessage", Func, 0}, + {"ParseRoutingSockaddr", Func, 0}, + {"ParseSocketControlMessage", Func, 0}, + {"ParseUnixCredentials", Func, 0}, + {"ParseUnixRights", Func, 0}, + {"PathMax", Const, 0}, + {"Pathconf", Func, 0}, + {"Pause", Func, 0}, + {"Pipe", Func, 0}, + {"Pipe2", Func, 1}, + {"PivotRoot", Func, 0}, + {"Pointer", Type, 11}, + {"PostQueuedCompletionStatus", Func, 0}, + {"Pread", Func, 0}, + {"Proc", Type, 0}, + {"Proc.Dll", Field, 0}, + {"Proc.Name", Field, 0}, + {"ProcAttr", Type, 0}, + {"ProcAttr.Dir", Field, 0}, + {"ProcAttr.Env", Field, 0}, + {"ProcAttr.Files", Field, 0}, + {"ProcAttr.Sys", Field, 0}, + {"Process32First", Func, 4}, + {"Process32Next", Func, 4}, + {"ProcessEntry32", Type, 4}, + {"ProcessEntry32.DefaultHeapID", Field, 4}, + {"ProcessEntry32.ExeFile", Field, 4}, + {"ProcessEntry32.Flags", Field, 4}, + {"ProcessEntry32.ModuleID", Field, 4}, + {"ProcessEntry32.ParentProcessID", Field, 4}, + {"ProcessEntry32.PriClassBase", Field, 4}, + {"ProcessEntry32.ProcessID", Field, 4}, + {"ProcessEntry32.Size", Field, 4}, + {"ProcessEntry32.Threads", Field, 4}, + {"ProcessEntry32.Usage", Field, 4}, + {"ProcessInformation", Type, 0}, + {"ProcessInformation.Process", Field, 0}, + {"ProcessInformation.ProcessId", Field, 0}, + {"ProcessInformation.Thread", Field, 0}, + {"ProcessInformation.ThreadId", Field, 0}, + {"Protoent", Type, 0}, + {"Protoent.Aliases", Field, 0}, + {"Protoent.Name", Field, 0}, + {"Protoent.Proto", Field, 0}, + {"PtraceAttach", Func, 0}, + {"PtraceCont", Func, 0}, + {"PtraceDetach", Func, 0}, + {"PtraceGetEventMsg", Func, 0}, + {"PtraceGetRegs", Func, 0}, + {"PtracePeekData", Func, 0}, + {"PtracePeekText", Func, 0}, + {"PtracePokeData", Func, 0}, + {"PtracePokeText", Func, 0}, + {"PtraceRegs", Type, 0}, + {"PtraceRegs.Cs", Field, 0}, + {"PtraceRegs.Ds", Field, 0}, + {"PtraceRegs.Eax", Field, 0}, + {"PtraceRegs.Ebp", Field, 0}, + {"PtraceRegs.Ebx", Field, 0}, + {"PtraceRegs.Ecx", Field, 0}, + {"PtraceRegs.Edi", Field, 0}, + {"PtraceRegs.Edx", Field, 0}, + {"PtraceRegs.Eflags", Field, 0}, + {"PtraceRegs.Eip", Field, 0}, + {"PtraceRegs.Es", Field, 0}, + {"PtraceRegs.Esi", Field, 0}, + {"PtraceRegs.Esp", Field, 0}, + {"PtraceRegs.Fs", Field, 0}, + {"PtraceRegs.Fs_base", Field, 0}, + {"PtraceRegs.Gs", Field, 0}, + {"PtraceRegs.Gs_base", Field, 0}, + {"PtraceRegs.Orig_eax", Field, 0}, + {"PtraceRegs.Orig_rax", Field, 0}, + {"PtraceRegs.R10", Field, 0}, + {"PtraceRegs.R11", Field, 0}, + {"PtraceRegs.R12", Field, 0}, + {"PtraceRegs.R13", Field, 0}, + {"PtraceRegs.R14", Field, 0}, + {"PtraceRegs.R15", Field, 0}, + {"PtraceRegs.R8", Field, 0}, + {"PtraceRegs.R9", Field, 0}, + {"PtraceRegs.Rax", Field, 0}, + {"PtraceRegs.Rbp", Field, 0}, + {"PtraceRegs.Rbx", Field, 0}, + {"PtraceRegs.Rcx", Field, 0}, + {"PtraceRegs.Rdi", Field, 0}, + {"PtraceRegs.Rdx", Field, 0}, + {"PtraceRegs.Rip", Field, 0}, + {"PtraceRegs.Rsi", Field, 0}, + {"PtraceRegs.Rsp", Field, 0}, + {"PtraceRegs.Ss", Field, 0}, + {"PtraceRegs.Uregs", Field, 0}, + {"PtraceRegs.Xcs", Field, 0}, + {"PtraceRegs.Xds", Field, 0}, + {"PtraceRegs.Xes", Field, 0}, + {"PtraceRegs.Xfs", Field, 0}, + {"PtraceRegs.Xgs", Field, 0}, + {"PtraceRegs.Xss", Field, 0}, + {"PtraceSetOptions", Func, 0}, + {"PtraceSetRegs", Func, 0}, + {"PtraceSingleStep", Func, 0}, + {"PtraceSyscall", Func, 1}, + {"Pwrite", Func, 0}, + {"REG_BINARY", Const, 0}, + {"REG_DWORD", Const, 0}, + {"REG_DWORD_BIG_ENDIAN", Const, 0}, + {"REG_DWORD_LITTLE_ENDIAN", Const, 0}, + {"REG_EXPAND_SZ", Const, 0}, + {"REG_FULL_RESOURCE_DESCRIPTOR", Const, 0}, + {"REG_LINK", Const, 0}, + {"REG_MULTI_SZ", Const, 0}, + {"REG_NONE", Const, 0}, + {"REG_QWORD", Const, 0}, + {"REG_QWORD_LITTLE_ENDIAN", Const, 0}, + {"REG_RESOURCE_LIST", Const, 0}, + {"REG_RESOURCE_REQUIREMENTS_LIST", Const, 0}, + {"REG_SZ", Const, 0}, + {"RLIMIT_AS", Const, 0}, + {"RLIMIT_CORE", Const, 0}, + {"RLIMIT_CPU", Const, 0}, + {"RLIMIT_CPU_USAGE_MONITOR", Const, 16}, + {"RLIMIT_DATA", Const, 0}, + {"RLIMIT_FSIZE", Const, 0}, + {"RLIMIT_NOFILE", Const, 0}, + {"RLIMIT_STACK", Const, 0}, + {"RLIM_INFINITY", Const, 0}, + {"RTAX_ADVMSS", Const, 0}, + {"RTAX_AUTHOR", Const, 0}, + {"RTAX_BRD", Const, 0}, + {"RTAX_CWND", Const, 0}, + {"RTAX_DST", Const, 0}, + {"RTAX_FEATURES", Const, 0}, + {"RTAX_FEATURE_ALLFRAG", Const, 0}, + {"RTAX_FEATURE_ECN", Const, 0}, + {"RTAX_FEATURE_SACK", Const, 0}, + {"RTAX_FEATURE_TIMESTAMP", Const, 0}, + {"RTAX_GATEWAY", Const, 0}, + {"RTAX_GENMASK", Const, 0}, + {"RTAX_HOPLIMIT", Const, 0}, + {"RTAX_IFA", Const, 0}, + {"RTAX_IFP", Const, 0}, + {"RTAX_INITCWND", Const, 0}, + {"RTAX_INITRWND", Const, 0}, + {"RTAX_LABEL", Const, 1}, + {"RTAX_LOCK", Const, 0}, + {"RTAX_MAX", Const, 0}, + {"RTAX_MTU", Const, 0}, + {"RTAX_NETMASK", Const, 0}, + {"RTAX_REORDERING", Const, 0}, + {"RTAX_RTO_MIN", Const, 0}, + {"RTAX_RTT", Const, 0}, + {"RTAX_RTTVAR", Const, 0}, + {"RTAX_SRC", Const, 1}, + {"RTAX_SRCMASK", Const, 1}, + {"RTAX_SSTHRESH", Const, 0}, + {"RTAX_TAG", Const, 1}, + {"RTAX_UNSPEC", Const, 0}, + {"RTAX_WINDOW", Const, 0}, + {"RTA_ALIGNTO", Const, 0}, + {"RTA_AUTHOR", Const, 0}, + {"RTA_BRD", Const, 0}, + {"RTA_CACHEINFO", Const, 0}, + {"RTA_DST", Const, 0}, + {"RTA_FLOW", Const, 0}, + {"RTA_GATEWAY", Const, 0}, + {"RTA_GENMASK", Const, 0}, + {"RTA_IFA", Const, 0}, + {"RTA_IFP", Const, 0}, + {"RTA_IIF", Const, 0}, + {"RTA_LABEL", Const, 1}, + {"RTA_MAX", Const, 0}, + {"RTA_METRICS", Const, 0}, + {"RTA_MULTIPATH", Const, 0}, + {"RTA_NETMASK", Const, 0}, + {"RTA_OIF", Const, 0}, + {"RTA_PREFSRC", Const, 0}, + {"RTA_PRIORITY", Const, 0}, + {"RTA_SRC", Const, 0}, + {"RTA_SRCMASK", Const, 1}, + {"RTA_TABLE", Const, 0}, + {"RTA_TAG", Const, 1}, + {"RTA_UNSPEC", Const, 0}, + {"RTCF_DIRECTSRC", Const, 0}, + {"RTCF_DOREDIRECT", Const, 0}, + {"RTCF_LOG", Const, 0}, + {"RTCF_MASQ", Const, 0}, + {"RTCF_NAT", Const, 0}, + {"RTCF_VALVE", Const, 0}, + {"RTF_ADDRCLASSMASK", Const, 0}, + {"RTF_ADDRCONF", Const, 0}, + {"RTF_ALLONLINK", Const, 0}, + {"RTF_ANNOUNCE", Const, 1}, + {"RTF_BLACKHOLE", Const, 0}, + {"RTF_BROADCAST", Const, 0}, + {"RTF_CACHE", Const, 0}, + {"RTF_CLONED", Const, 1}, + {"RTF_CLONING", Const, 0}, + {"RTF_CONDEMNED", Const, 0}, + {"RTF_DEFAULT", Const, 0}, + {"RTF_DELCLONE", Const, 0}, + {"RTF_DONE", Const, 0}, + {"RTF_DYNAMIC", Const, 0}, + {"RTF_FLOW", Const, 0}, + {"RTF_FMASK", Const, 0}, + {"RTF_GATEWAY", Const, 0}, + {"RTF_GWFLAG_COMPAT", Const, 3}, + {"RTF_HOST", Const, 0}, + {"RTF_IFREF", Const, 0}, + {"RTF_IFSCOPE", Const, 0}, + {"RTF_INTERFACE", Const, 0}, + {"RTF_IRTT", Const, 0}, + {"RTF_LINKRT", Const, 0}, + {"RTF_LLDATA", Const, 0}, + {"RTF_LLINFO", Const, 0}, + {"RTF_LOCAL", Const, 0}, + {"RTF_MASK", Const, 1}, + {"RTF_MODIFIED", Const, 0}, + {"RTF_MPATH", Const, 1}, + {"RTF_MPLS", Const, 1}, + {"RTF_MSS", Const, 0}, + {"RTF_MTU", Const, 0}, + {"RTF_MULTICAST", Const, 0}, + {"RTF_NAT", Const, 0}, + {"RTF_NOFORWARD", Const, 0}, + {"RTF_NONEXTHOP", Const, 0}, + {"RTF_NOPMTUDISC", Const, 0}, + {"RTF_PERMANENT_ARP", Const, 1}, + {"RTF_PINNED", Const, 0}, + {"RTF_POLICY", Const, 0}, + {"RTF_PRCLONING", Const, 0}, + {"RTF_PROTO1", Const, 0}, + {"RTF_PROTO2", Const, 0}, + {"RTF_PROTO3", Const, 0}, + {"RTF_PROXY", Const, 16}, + {"RTF_REINSTATE", Const, 0}, + {"RTF_REJECT", Const, 0}, + {"RTF_RNH_LOCKED", Const, 0}, + {"RTF_ROUTER", Const, 16}, + {"RTF_SOURCE", Const, 1}, + {"RTF_SRC", Const, 1}, + {"RTF_STATIC", Const, 0}, + {"RTF_STICKY", Const, 0}, + {"RTF_THROW", Const, 0}, + {"RTF_TUNNEL", Const, 1}, + {"RTF_UP", Const, 0}, + {"RTF_USETRAILERS", Const, 1}, + {"RTF_WASCLONED", Const, 0}, + {"RTF_WINDOW", Const, 0}, + {"RTF_XRESOLVE", Const, 0}, + {"RTM_ADD", Const, 0}, + {"RTM_BASE", Const, 0}, + {"RTM_CHANGE", Const, 0}, + {"RTM_CHGADDR", Const, 1}, + {"RTM_DELACTION", Const, 0}, + {"RTM_DELADDR", Const, 0}, + {"RTM_DELADDRLABEL", Const, 0}, + {"RTM_DELETE", Const, 0}, + {"RTM_DELLINK", Const, 0}, + {"RTM_DELMADDR", Const, 0}, + {"RTM_DELNEIGH", Const, 0}, + {"RTM_DELQDISC", Const, 0}, + {"RTM_DELROUTE", Const, 0}, + {"RTM_DELRULE", Const, 0}, + {"RTM_DELTCLASS", Const, 0}, + {"RTM_DELTFILTER", Const, 0}, + {"RTM_DESYNC", Const, 1}, + {"RTM_F_CLONED", Const, 0}, + {"RTM_F_EQUALIZE", Const, 0}, + {"RTM_F_NOTIFY", Const, 0}, + {"RTM_F_PREFIX", Const, 0}, + {"RTM_GET", Const, 0}, + {"RTM_GET2", Const, 0}, + {"RTM_GETACTION", Const, 0}, + {"RTM_GETADDR", Const, 0}, + {"RTM_GETADDRLABEL", Const, 0}, + {"RTM_GETANYCAST", Const, 0}, + {"RTM_GETDCB", Const, 0}, + {"RTM_GETLINK", Const, 0}, + {"RTM_GETMULTICAST", Const, 0}, + {"RTM_GETNEIGH", Const, 0}, + {"RTM_GETNEIGHTBL", Const, 0}, + {"RTM_GETQDISC", Const, 0}, + {"RTM_GETROUTE", Const, 0}, + {"RTM_GETRULE", Const, 0}, + {"RTM_GETTCLASS", Const, 0}, + {"RTM_GETTFILTER", Const, 0}, + {"RTM_IEEE80211", Const, 0}, + {"RTM_IFANNOUNCE", Const, 0}, + {"RTM_IFINFO", Const, 0}, + {"RTM_IFINFO2", Const, 0}, + {"RTM_LLINFO_UPD", Const, 1}, + {"RTM_LOCK", Const, 0}, + {"RTM_LOSING", Const, 0}, + {"RTM_MAX", Const, 0}, + {"RTM_MAXSIZE", Const, 1}, + {"RTM_MISS", Const, 0}, + {"RTM_NEWACTION", Const, 0}, + {"RTM_NEWADDR", Const, 0}, + {"RTM_NEWADDRLABEL", Const, 0}, + {"RTM_NEWLINK", Const, 0}, + {"RTM_NEWMADDR", Const, 0}, + {"RTM_NEWMADDR2", Const, 0}, + {"RTM_NEWNDUSEROPT", Const, 0}, + {"RTM_NEWNEIGH", Const, 0}, + {"RTM_NEWNEIGHTBL", Const, 0}, + {"RTM_NEWPREFIX", Const, 0}, + {"RTM_NEWQDISC", Const, 0}, + {"RTM_NEWROUTE", Const, 0}, + {"RTM_NEWRULE", Const, 0}, + {"RTM_NEWTCLASS", Const, 0}, + {"RTM_NEWTFILTER", Const, 0}, + {"RTM_NR_FAMILIES", Const, 0}, + {"RTM_NR_MSGTYPES", Const, 0}, + {"RTM_OIFINFO", Const, 1}, + {"RTM_OLDADD", Const, 0}, + {"RTM_OLDDEL", Const, 0}, + {"RTM_OOIFINFO", Const, 1}, + {"RTM_REDIRECT", Const, 0}, + {"RTM_RESOLVE", Const, 0}, + {"RTM_RTTUNIT", Const, 0}, + {"RTM_SETDCB", Const, 0}, + {"RTM_SETGATE", Const, 1}, + {"RTM_SETLINK", Const, 0}, + {"RTM_SETNEIGHTBL", Const, 0}, + {"RTM_VERSION", Const, 0}, + {"RTNH_ALIGNTO", Const, 0}, + {"RTNH_F_DEAD", Const, 0}, + {"RTNH_F_ONLINK", Const, 0}, + {"RTNH_F_PERVASIVE", Const, 0}, + {"RTNLGRP_IPV4_IFADDR", Const, 1}, + {"RTNLGRP_IPV4_MROUTE", Const, 1}, + {"RTNLGRP_IPV4_ROUTE", Const, 1}, + {"RTNLGRP_IPV4_RULE", Const, 1}, + {"RTNLGRP_IPV6_IFADDR", Const, 1}, + {"RTNLGRP_IPV6_IFINFO", Const, 1}, + {"RTNLGRP_IPV6_MROUTE", Const, 1}, + {"RTNLGRP_IPV6_PREFIX", Const, 1}, + {"RTNLGRP_IPV6_ROUTE", Const, 1}, + {"RTNLGRP_IPV6_RULE", Const, 1}, + {"RTNLGRP_LINK", Const, 1}, + {"RTNLGRP_ND_USEROPT", Const, 1}, + {"RTNLGRP_NEIGH", Const, 1}, + {"RTNLGRP_NONE", Const, 1}, + {"RTNLGRP_NOTIFY", Const, 1}, + {"RTNLGRP_TC", Const, 1}, + {"RTN_ANYCAST", Const, 0}, + {"RTN_BLACKHOLE", Const, 0}, + {"RTN_BROADCAST", Const, 0}, + {"RTN_LOCAL", Const, 0}, + {"RTN_MAX", Const, 0}, + {"RTN_MULTICAST", Const, 0}, + {"RTN_NAT", Const, 0}, + {"RTN_PROHIBIT", Const, 0}, + {"RTN_THROW", Const, 0}, + {"RTN_UNICAST", Const, 0}, + {"RTN_UNREACHABLE", Const, 0}, + {"RTN_UNSPEC", Const, 0}, + {"RTN_XRESOLVE", Const, 0}, + {"RTPROT_BIRD", Const, 0}, + {"RTPROT_BOOT", Const, 0}, + {"RTPROT_DHCP", Const, 0}, + {"RTPROT_DNROUTED", Const, 0}, + {"RTPROT_GATED", Const, 0}, + {"RTPROT_KERNEL", Const, 0}, + {"RTPROT_MRT", Const, 0}, + {"RTPROT_NTK", Const, 0}, + {"RTPROT_RA", Const, 0}, + {"RTPROT_REDIRECT", Const, 0}, + {"RTPROT_STATIC", Const, 0}, + {"RTPROT_UNSPEC", Const, 0}, + {"RTPROT_XORP", Const, 0}, + {"RTPROT_ZEBRA", Const, 0}, + {"RTV_EXPIRE", Const, 0}, + {"RTV_HOPCOUNT", Const, 0}, + {"RTV_MTU", Const, 0}, + {"RTV_RPIPE", Const, 0}, + {"RTV_RTT", Const, 0}, + {"RTV_RTTVAR", Const, 0}, + {"RTV_SPIPE", Const, 0}, + {"RTV_SSTHRESH", Const, 0}, + {"RTV_WEIGHT", Const, 0}, + {"RT_CACHING_CONTEXT", Const, 1}, + {"RT_CLASS_DEFAULT", Const, 0}, + {"RT_CLASS_LOCAL", Const, 0}, + {"RT_CLASS_MAIN", Const, 0}, + {"RT_CLASS_MAX", Const, 0}, + {"RT_CLASS_UNSPEC", Const, 0}, + {"RT_DEFAULT_FIB", Const, 1}, + {"RT_NORTREF", Const, 1}, + {"RT_SCOPE_HOST", Const, 0}, + {"RT_SCOPE_LINK", Const, 0}, + {"RT_SCOPE_NOWHERE", Const, 0}, + {"RT_SCOPE_SITE", Const, 0}, + {"RT_SCOPE_UNIVERSE", Const, 0}, + {"RT_TABLEID_MAX", Const, 1}, + {"RT_TABLE_COMPAT", Const, 0}, + {"RT_TABLE_DEFAULT", Const, 0}, + {"RT_TABLE_LOCAL", Const, 0}, + {"RT_TABLE_MAIN", Const, 0}, + {"RT_TABLE_MAX", Const, 0}, + {"RT_TABLE_UNSPEC", Const, 0}, + {"RUSAGE_CHILDREN", Const, 0}, + {"RUSAGE_SELF", Const, 0}, + {"RUSAGE_THREAD", Const, 0}, + {"Radvisory_t", Type, 0}, + {"Radvisory_t.Count", Field, 0}, + {"Radvisory_t.Offset", Field, 0}, + {"Radvisory_t.Pad_cgo_0", Field, 0}, + {"RawConn", Type, 9}, + {"RawSockaddr", Type, 0}, + {"RawSockaddr.Data", Field, 0}, + {"RawSockaddr.Family", Field, 0}, + {"RawSockaddr.Len", Field, 0}, + {"RawSockaddrAny", Type, 0}, + {"RawSockaddrAny.Addr", Field, 0}, + {"RawSockaddrAny.Pad", Field, 0}, + {"RawSockaddrDatalink", Type, 0}, + {"RawSockaddrDatalink.Alen", Field, 0}, + {"RawSockaddrDatalink.Data", Field, 0}, + {"RawSockaddrDatalink.Family", Field, 0}, + {"RawSockaddrDatalink.Index", Field, 0}, + {"RawSockaddrDatalink.Len", Field, 0}, + {"RawSockaddrDatalink.Nlen", Field, 0}, + {"RawSockaddrDatalink.Pad_cgo_0", Field, 2}, + {"RawSockaddrDatalink.Slen", Field, 0}, + {"RawSockaddrDatalink.Type", Field, 0}, + {"RawSockaddrInet4", Type, 0}, + {"RawSockaddrInet4.Addr", Field, 0}, + {"RawSockaddrInet4.Family", Field, 0}, + {"RawSockaddrInet4.Len", Field, 0}, + {"RawSockaddrInet4.Port", Field, 0}, + {"RawSockaddrInet4.Zero", Field, 0}, + {"RawSockaddrInet6", Type, 0}, + {"RawSockaddrInet6.Addr", Field, 0}, + {"RawSockaddrInet6.Family", Field, 0}, + {"RawSockaddrInet6.Flowinfo", Field, 0}, + {"RawSockaddrInet6.Len", Field, 0}, + {"RawSockaddrInet6.Port", Field, 0}, + {"RawSockaddrInet6.Scope_id", Field, 0}, + {"RawSockaddrLinklayer", Type, 0}, + {"RawSockaddrLinklayer.Addr", Field, 0}, + {"RawSockaddrLinklayer.Family", Field, 0}, + {"RawSockaddrLinklayer.Halen", Field, 0}, + {"RawSockaddrLinklayer.Hatype", Field, 0}, + {"RawSockaddrLinklayer.Ifindex", Field, 0}, + {"RawSockaddrLinklayer.Pkttype", Field, 0}, + {"RawSockaddrLinklayer.Protocol", Field, 0}, + {"RawSockaddrNetlink", Type, 0}, + {"RawSockaddrNetlink.Family", Field, 0}, + {"RawSockaddrNetlink.Groups", Field, 0}, + {"RawSockaddrNetlink.Pad", Field, 0}, + {"RawSockaddrNetlink.Pid", Field, 0}, + {"RawSockaddrUnix", Type, 0}, + {"RawSockaddrUnix.Family", Field, 0}, + {"RawSockaddrUnix.Len", Field, 0}, + {"RawSockaddrUnix.Pad_cgo_0", Field, 2}, + {"RawSockaddrUnix.Path", Field, 0}, + {"RawSyscall", Func, 0}, + {"RawSyscall6", Func, 0}, + {"Read", Func, 0}, + {"ReadConsole", Func, 1}, + {"ReadDirectoryChanges", Func, 0}, + {"ReadDirent", Func, 0}, + {"ReadFile", Func, 0}, + {"Readlink", Func, 0}, + {"Reboot", Func, 0}, + {"Recvfrom", Func, 0}, + {"Recvmsg", Func, 0}, + {"RegCloseKey", Func, 0}, + {"RegEnumKeyEx", Func, 0}, + {"RegOpenKeyEx", Func, 0}, + {"RegQueryInfoKey", Func, 0}, + {"RegQueryValueEx", Func, 0}, + {"RemoveDirectory", Func, 0}, + {"Removexattr", Func, 1}, + {"Rename", Func, 0}, + {"Renameat", Func, 0}, + {"Revoke", Func, 0}, + {"Rlimit", Type, 0}, + {"Rlimit.Cur", Field, 0}, + {"Rlimit.Max", Field, 0}, + {"Rmdir", Func, 0}, + {"RouteMessage", Type, 0}, + {"RouteMessage.Data", Field, 0}, + {"RouteMessage.Header", Field, 0}, + {"RouteRIB", Func, 0}, + {"RoutingMessage", Type, 0}, + {"RtAttr", Type, 0}, + {"RtAttr.Len", Field, 0}, + {"RtAttr.Type", Field, 0}, + {"RtGenmsg", Type, 0}, + {"RtGenmsg.Family", Field, 0}, + {"RtMetrics", Type, 0}, + {"RtMetrics.Expire", Field, 0}, + {"RtMetrics.Filler", Field, 0}, + {"RtMetrics.Hopcount", Field, 0}, + {"RtMetrics.Locks", Field, 0}, + {"RtMetrics.Mtu", Field, 0}, + {"RtMetrics.Pad", Field, 3}, + {"RtMetrics.Pksent", Field, 0}, + {"RtMetrics.Recvpipe", Field, 0}, + {"RtMetrics.Refcnt", Field, 2}, + {"RtMetrics.Rtt", Field, 0}, + {"RtMetrics.Rttvar", Field, 0}, + {"RtMetrics.Sendpipe", Field, 0}, + {"RtMetrics.Ssthresh", Field, 0}, + {"RtMetrics.Weight", Field, 0}, + {"RtMsg", Type, 0}, + {"RtMsg.Dst_len", Field, 0}, + {"RtMsg.Family", Field, 0}, + {"RtMsg.Flags", Field, 0}, + {"RtMsg.Protocol", Field, 0}, + {"RtMsg.Scope", Field, 0}, + {"RtMsg.Src_len", Field, 0}, + {"RtMsg.Table", Field, 0}, + {"RtMsg.Tos", Field, 0}, + {"RtMsg.Type", Field, 0}, + {"RtMsghdr", Type, 0}, + {"RtMsghdr.Addrs", Field, 0}, + {"RtMsghdr.Errno", Field, 0}, + {"RtMsghdr.Flags", Field, 0}, + {"RtMsghdr.Fmask", Field, 0}, + {"RtMsghdr.Hdrlen", Field, 2}, + {"RtMsghdr.Index", Field, 0}, + {"RtMsghdr.Inits", Field, 0}, + {"RtMsghdr.Mpls", Field, 2}, + {"RtMsghdr.Msglen", Field, 0}, + {"RtMsghdr.Pad_cgo_0", Field, 0}, + {"RtMsghdr.Pad_cgo_1", Field, 2}, + {"RtMsghdr.Pid", Field, 0}, + {"RtMsghdr.Priority", Field, 2}, + {"RtMsghdr.Rmx", Field, 0}, + {"RtMsghdr.Seq", Field, 0}, + {"RtMsghdr.Tableid", Field, 2}, + {"RtMsghdr.Type", Field, 0}, + {"RtMsghdr.Use", Field, 0}, + {"RtMsghdr.Version", Field, 0}, + {"RtNexthop", Type, 0}, + {"RtNexthop.Flags", Field, 0}, + {"RtNexthop.Hops", Field, 0}, + {"RtNexthop.Ifindex", Field, 0}, + {"RtNexthop.Len", Field, 0}, + {"Rusage", Type, 0}, + {"Rusage.CreationTime", Field, 0}, + {"Rusage.ExitTime", Field, 0}, + {"Rusage.Idrss", Field, 0}, + {"Rusage.Inblock", Field, 0}, + {"Rusage.Isrss", Field, 0}, + {"Rusage.Ixrss", Field, 0}, + {"Rusage.KernelTime", Field, 0}, + {"Rusage.Majflt", Field, 0}, + {"Rusage.Maxrss", Field, 0}, + {"Rusage.Minflt", Field, 0}, + {"Rusage.Msgrcv", Field, 0}, + {"Rusage.Msgsnd", Field, 0}, + {"Rusage.Nivcsw", Field, 0}, + {"Rusage.Nsignals", Field, 0}, + {"Rusage.Nswap", Field, 0}, + {"Rusage.Nvcsw", Field, 0}, + {"Rusage.Oublock", Field, 0}, + {"Rusage.Stime", Field, 0}, + {"Rusage.UserTime", Field, 0}, + {"Rusage.Utime", Field, 0}, + {"SCM_BINTIME", Const, 0}, + {"SCM_CREDENTIALS", Const, 0}, + {"SCM_CREDS", Const, 0}, + {"SCM_RIGHTS", Const, 0}, + {"SCM_TIMESTAMP", Const, 0}, + {"SCM_TIMESTAMPING", Const, 0}, + {"SCM_TIMESTAMPNS", Const, 0}, + {"SCM_TIMESTAMP_MONOTONIC", Const, 0}, + {"SHUT_RD", Const, 0}, + {"SHUT_RDWR", Const, 0}, + {"SHUT_WR", Const, 0}, + {"SID", Type, 0}, + {"SIDAndAttributes", Type, 0}, + {"SIDAndAttributes.Attributes", Field, 0}, + {"SIDAndAttributes.Sid", Field, 0}, + {"SIGABRT", Const, 0}, + {"SIGALRM", Const, 0}, + {"SIGBUS", Const, 0}, + {"SIGCHLD", Const, 0}, + {"SIGCLD", Const, 0}, + {"SIGCONT", Const, 0}, + {"SIGEMT", Const, 0}, + {"SIGFPE", Const, 0}, + {"SIGHUP", Const, 0}, + {"SIGILL", Const, 0}, + {"SIGINFO", Const, 0}, + {"SIGINT", Const, 0}, + {"SIGIO", Const, 0}, + {"SIGIOT", Const, 0}, + {"SIGKILL", Const, 0}, + {"SIGLIBRT", Const, 1}, + {"SIGLWP", Const, 0}, + {"SIGPIPE", Const, 0}, + {"SIGPOLL", Const, 0}, + {"SIGPROF", Const, 0}, + {"SIGPWR", Const, 0}, + {"SIGQUIT", Const, 0}, + {"SIGSEGV", Const, 0}, + {"SIGSTKFLT", Const, 0}, + {"SIGSTOP", Const, 0}, + {"SIGSYS", Const, 0}, + {"SIGTERM", Const, 0}, + {"SIGTHR", Const, 0}, + {"SIGTRAP", Const, 0}, + {"SIGTSTP", Const, 0}, + {"SIGTTIN", Const, 0}, + {"SIGTTOU", Const, 0}, + {"SIGUNUSED", Const, 0}, + {"SIGURG", Const, 0}, + {"SIGUSR1", Const, 0}, + {"SIGUSR2", Const, 0}, + {"SIGVTALRM", Const, 0}, + {"SIGWINCH", Const, 0}, + {"SIGXCPU", Const, 0}, + {"SIGXFSZ", Const, 0}, + {"SIOCADDDLCI", Const, 0}, + {"SIOCADDMULTI", Const, 0}, + {"SIOCADDRT", Const, 0}, + {"SIOCAIFADDR", Const, 0}, + {"SIOCAIFGROUP", Const, 0}, + {"SIOCALIFADDR", Const, 0}, + {"SIOCARPIPLL", Const, 0}, + {"SIOCATMARK", Const, 0}, + {"SIOCAUTOADDR", Const, 0}, + {"SIOCAUTONETMASK", Const, 0}, + {"SIOCBRDGADD", Const, 1}, + {"SIOCBRDGADDS", Const, 1}, + {"SIOCBRDGARL", Const, 1}, + {"SIOCBRDGDADDR", Const, 1}, + {"SIOCBRDGDEL", Const, 1}, + {"SIOCBRDGDELS", Const, 1}, + {"SIOCBRDGFLUSH", Const, 1}, + {"SIOCBRDGFRL", Const, 1}, + {"SIOCBRDGGCACHE", Const, 1}, + {"SIOCBRDGGFD", Const, 1}, + {"SIOCBRDGGHT", Const, 1}, + {"SIOCBRDGGIFFLGS", Const, 1}, + {"SIOCBRDGGMA", Const, 1}, + {"SIOCBRDGGPARAM", Const, 1}, + {"SIOCBRDGGPRI", Const, 1}, + {"SIOCBRDGGRL", Const, 1}, + {"SIOCBRDGGSIFS", Const, 1}, + {"SIOCBRDGGTO", Const, 1}, + {"SIOCBRDGIFS", Const, 1}, + {"SIOCBRDGRTS", Const, 1}, + {"SIOCBRDGSADDR", Const, 1}, + {"SIOCBRDGSCACHE", Const, 1}, + {"SIOCBRDGSFD", Const, 1}, + {"SIOCBRDGSHT", Const, 1}, + {"SIOCBRDGSIFCOST", Const, 1}, + {"SIOCBRDGSIFFLGS", Const, 1}, + {"SIOCBRDGSIFPRIO", Const, 1}, + {"SIOCBRDGSMA", Const, 1}, + {"SIOCBRDGSPRI", Const, 1}, + {"SIOCBRDGSPROTO", Const, 1}, + {"SIOCBRDGSTO", Const, 1}, + {"SIOCBRDGSTXHC", Const, 1}, + {"SIOCDARP", Const, 0}, + {"SIOCDELDLCI", Const, 0}, + {"SIOCDELMULTI", Const, 0}, + {"SIOCDELRT", Const, 0}, + {"SIOCDEVPRIVATE", Const, 0}, + {"SIOCDIFADDR", Const, 0}, + {"SIOCDIFGROUP", Const, 0}, + {"SIOCDIFPHYADDR", Const, 0}, + {"SIOCDLIFADDR", Const, 0}, + {"SIOCDRARP", Const, 0}, + {"SIOCGARP", Const, 0}, + {"SIOCGDRVSPEC", Const, 0}, + {"SIOCGETKALIVE", Const, 1}, + {"SIOCGETLABEL", Const, 1}, + {"SIOCGETPFLOW", Const, 1}, + {"SIOCGETPFSYNC", Const, 1}, + {"SIOCGETSGCNT", Const, 0}, + {"SIOCGETVIFCNT", Const, 0}, + {"SIOCGETVLAN", Const, 0}, + {"SIOCGHIWAT", Const, 0}, + {"SIOCGIFADDR", Const, 0}, + {"SIOCGIFADDRPREF", Const, 1}, + {"SIOCGIFALIAS", Const, 1}, + {"SIOCGIFALTMTU", Const, 0}, + {"SIOCGIFASYNCMAP", Const, 0}, + {"SIOCGIFBOND", Const, 0}, + {"SIOCGIFBR", Const, 0}, + {"SIOCGIFBRDADDR", Const, 0}, + {"SIOCGIFCAP", Const, 0}, + {"SIOCGIFCONF", Const, 0}, + {"SIOCGIFCOUNT", Const, 0}, + {"SIOCGIFDATA", Const, 1}, + {"SIOCGIFDESCR", Const, 0}, + {"SIOCGIFDEVMTU", Const, 0}, + {"SIOCGIFDLT", Const, 1}, + {"SIOCGIFDSTADDR", Const, 0}, + {"SIOCGIFENCAP", Const, 0}, + {"SIOCGIFFIB", Const, 1}, + {"SIOCGIFFLAGS", Const, 0}, + {"SIOCGIFGATTR", Const, 1}, + {"SIOCGIFGENERIC", Const, 0}, + {"SIOCGIFGMEMB", Const, 0}, + {"SIOCGIFGROUP", Const, 0}, + {"SIOCGIFHARDMTU", Const, 3}, + {"SIOCGIFHWADDR", Const, 0}, + {"SIOCGIFINDEX", Const, 0}, + {"SIOCGIFKPI", Const, 0}, + {"SIOCGIFMAC", Const, 0}, + {"SIOCGIFMAP", Const, 0}, + {"SIOCGIFMEDIA", Const, 0}, + {"SIOCGIFMEM", Const, 0}, + {"SIOCGIFMETRIC", Const, 0}, + {"SIOCGIFMTU", Const, 0}, + {"SIOCGIFNAME", Const, 0}, + {"SIOCGIFNETMASK", Const, 0}, + {"SIOCGIFPDSTADDR", Const, 0}, + {"SIOCGIFPFLAGS", Const, 0}, + {"SIOCGIFPHYS", Const, 0}, + {"SIOCGIFPRIORITY", Const, 1}, + {"SIOCGIFPSRCADDR", Const, 0}, + {"SIOCGIFRDOMAIN", Const, 1}, + {"SIOCGIFRTLABEL", Const, 1}, + {"SIOCGIFSLAVE", Const, 0}, + {"SIOCGIFSTATUS", Const, 0}, + {"SIOCGIFTIMESLOT", Const, 1}, + {"SIOCGIFTXQLEN", Const, 0}, + {"SIOCGIFVLAN", Const, 0}, + {"SIOCGIFWAKEFLAGS", Const, 0}, + {"SIOCGIFXFLAGS", Const, 1}, + {"SIOCGLIFADDR", Const, 0}, + {"SIOCGLIFPHYADDR", Const, 0}, + {"SIOCGLIFPHYRTABLE", Const, 1}, + {"SIOCGLIFPHYTTL", Const, 3}, + {"SIOCGLINKSTR", Const, 1}, + {"SIOCGLOWAT", Const, 0}, + {"SIOCGPGRP", Const, 0}, + {"SIOCGPRIVATE_0", Const, 0}, + {"SIOCGPRIVATE_1", Const, 0}, + {"SIOCGRARP", Const, 0}, + {"SIOCGSPPPPARAMS", Const, 3}, + {"SIOCGSTAMP", Const, 0}, + {"SIOCGSTAMPNS", Const, 0}, + {"SIOCGVH", Const, 1}, + {"SIOCGVNETID", Const, 3}, + {"SIOCIFCREATE", Const, 0}, + {"SIOCIFCREATE2", Const, 0}, + {"SIOCIFDESTROY", Const, 0}, + {"SIOCIFGCLONERS", Const, 0}, + {"SIOCINITIFADDR", Const, 1}, + {"SIOCPROTOPRIVATE", Const, 0}, + {"SIOCRSLVMULTI", Const, 0}, + {"SIOCRTMSG", Const, 0}, + {"SIOCSARP", Const, 0}, + {"SIOCSDRVSPEC", Const, 0}, + {"SIOCSETKALIVE", Const, 1}, + {"SIOCSETLABEL", Const, 1}, + {"SIOCSETPFLOW", Const, 1}, + {"SIOCSETPFSYNC", Const, 1}, + {"SIOCSETVLAN", Const, 0}, + {"SIOCSHIWAT", Const, 0}, + {"SIOCSIFADDR", Const, 0}, + {"SIOCSIFADDRPREF", Const, 1}, + {"SIOCSIFALTMTU", Const, 0}, + {"SIOCSIFASYNCMAP", Const, 0}, + {"SIOCSIFBOND", Const, 0}, + {"SIOCSIFBR", Const, 0}, + {"SIOCSIFBRDADDR", Const, 0}, + {"SIOCSIFCAP", Const, 0}, + {"SIOCSIFDESCR", Const, 0}, + {"SIOCSIFDSTADDR", Const, 0}, + {"SIOCSIFENCAP", Const, 0}, + {"SIOCSIFFIB", Const, 1}, + {"SIOCSIFFLAGS", Const, 0}, + {"SIOCSIFGATTR", Const, 1}, + {"SIOCSIFGENERIC", Const, 0}, + {"SIOCSIFHWADDR", Const, 0}, + {"SIOCSIFHWBROADCAST", Const, 0}, + {"SIOCSIFKPI", Const, 0}, + {"SIOCSIFLINK", Const, 0}, + {"SIOCSIFLLADDR", Const, 0}, + {"SIOCSIFMAC", Const, 0}, + {"SIOCSIFMAP", Const, 0}, + {"SIOCSIFMEDIA", Const, 0}, + {"SIOCSIFMEM", Const, 0}, + {"SIOCSIFMETRIC", Const, 0}, + {"SIOCSIFMTU", Const, 0}, + {"SIOCSIFNAME", Const, 0}, + {"SIOCSIFNETMASK", Const, 0}, + {"SIOCSIFPFLAGS", Const, 0}, + {"SIOCSIFPHYADDR", Const, 0}, + {"SIOCSIFPHYS", Const, 0}, + {"SIOCSIFPRIORITY", Const, 1}, + {"SIOCSIFRDOMAIN", Const, 1}, + {"SIOCSIFRTLABEL", Const, 1}, + {"SIOCSIFRVNET", Const, 0}, + {"SIOCSIFSLAVE", Const, 0}, + {"SIOCSIFTIMESLOT", Const, 1}, + {"SIOCSIFTXQLEN", Const, 0}, + {"SIOCSIFVLAN", Const, 0}, + {"SIOCSIFVNET", Const, 0}, + {"SIOCSIFXFLAGS", Const, 1}, + {"SIOCSLIFPHYADDR", Const, 0}, + {"SIOCSLIFPHYRTABLE", Const, 1}, + {"SIOCSLIFPHYTTL", Const, 3}, + {"SIOCSLINKSTR", Const, 1}, + {"SIOCSLOWAT", Const, 0}, + {"SIOCSPGRP", Const, 0}, + {"SIOCSRARP", Const, 0}, + {"SIOCSSPPPPARAMS", Const, 3}, + {"SIOCSVH", Const, 1}, + {"SIOCSVNETID", Const, 3}, + {"SIOCZIFDATA", Const, 1}, + {"SIO_GET_EXTENSION_FUNCTION_POINTER", Const, 1}, + {"SIO_GET_INTERFACE_LIST", Const, 0}, + {"SIO_KEEPALIVE_VALS", Const, 3}, + {"SIO_UDP_CONNRESET", Const, 4}, + {"SOCK_CLOEXEC", Const, 0}, + {"SOCK_DCCP", Const, 0}, + {"SOCK_DGRAM", Const, 0}, + {"SOCK_FLAGS_MASK", Const, 1}, + {"SOCK_MAXADDRLEN", Const, 0}, + {"SOCK_NONBLOCK", Const, 0}, + {"SOCK_NOSIGPIPE", Const, 1}, + {"SOCK_PACKET", Const, 0}, + {"SOCK_RAW", Const, 0}, + {"SOCK_RDM", Const, 0}, + {"SOCK_SEQPACKET", Const, 0}, + {"SOCK_STREAM", Const, 0}, + {"SOL_AAL", Const, 0}, + {"SOL_ATM", Const, 0}, + {"SOL_DECNET", Const, 0}, + {"SOL_ICMPV6", Const, 0}, + {"SOL_IP", Const, 0}, + {"SOL_IPV6", Const, 0}, + {"SOL_IRDA", Const, 0}, + {"SOL_PACKET", Const, 0}, + {"SOL_RAW", Const, 0}, + {"SOL_SOCKET", Const, 0}, + {"SOL_TCP", Const, 0}, + {"SOL_X25", Const, 0}, + {"SOMAXCONN", Const, 0}, + {"SO_ACCEPTCONN", Const, 0}, + {"SO_ACCEPTFILTER", Const, 0}, + {"SO_ATTACH_FILTER", Const, 0}, + {"SO_BINDANY", Const, 1}, + {"SO_BINDTODEVICE", Const, 0}, + {"SO_BINTIME", Const, 0}, + {"SO_BROADCAST", Const, 0}, + {"SO_BSDCOMPAT", Const, 0}, + {"SO_DEBUG", Const, 0}, + {"SO_DETACH_FILTER", Const, 0}, + {"SO_DOMAIN", Const, 0}, + {"SO_DONTROUTE", Const, 0}, + {"SO_DONTTRUNC", Const, 0}, + {"SO_ERROR", Const, 0}, + {"SO_KEEPALIVE", Const, 0}, + {"SO_LABEL", Const, 0}, + {"SO_LINGER", Const, 0}, + {"SO_LINGER_SEC", Const, 0}, + {"SO_LISTENINCQLEN", Const, 0}, + {"SO_LISTENQLEN", Const, 0}, + {"SO_LISTENQLIMIT", Const, 0}, + {"SO_MARK", Const, 0}, + {"SO_NETPROC", Const, 1}, + {"SO_NKE", Const, 0}, + {"SO_NOADDRERR", Const, 0}, + {"SO_NOHEADER", Const, 1}, + {"SO_NOSIGPIPE", Const, 0}, + {"SO_NOTIFYCONFLICT", Const, 0}, + {"SO_NO_CHECK", Const, 0}, + {"SO_NO_DDP", Const, 0}, + {"SO_NO_OFFLOAD", Const, 0}, + {"SO_NP_EXTENSIONS", Const, 0}, + {"SO_NREAD", Const, 0}, + {"SO_NUMRCVPKT", Const, 16}, + {"SO_NWRITE", Const, 0}, + {"SO_OOBINLINE", Const, 0}, + {"SO_OVERFLOWED", Const, 1}, + {"SO_PASSCRED", Const, 0}, + {"SO_PASSSEC", Const, 0}, + {"SO_PEERCRED", Const, 0}, + {"SO_PEERLABEL", Const, 0}, + {"SO_PEERNAME", Const, 0}, + {"SO_PEERSEC", Const, 0}, + {"SO_PRIORITY", Const, 0}, + {"SO_PROTOCOL", Const, 0}, + {"SO_PROTOTYPE", Const, 1}, + {"SO_RANDOMPORT", Const, 0}, + {"SO_RCVBUF", Const, 0}, + {"SO_RCVBUFFORCE", Const, 0}, + {"SO_RCVLOWAT", Const, 0}, + {"SO_RCVTIMEO", Const, 0}, + {"SO_RESTRICTIONS", Const, 0}, + {"SO_RESTRICT_DENYIN", Const, 0}, + {"SO_RESTRICT_DENYOUT", Const, 0}, + {"SO_RESTRICT_DENYSET", Const, 0}, + {"SO_REUSEADDR", Const, 0}, + {"SO_REUSEPORT", Const, 0}, + {"SO_REUSESHAREUID", Const, 0}, + {"SO_RTABLE", Const, 1}, + {"SO_RXQ_OVFL", Const, 0}, + {"SO_SECURITY_AUTHENTICATION", Const, 0}, + {"SO_SECURITY_ENCRYPTION_NETWORK", Const, 0}, + {"SO_SECURITY_ENCRYPTION_TRANSPORT", Const, 0}, + {"SO_SETFIB", Const, 0}, + {"SO_SNDBUF", Const, 0}, + {"SO_SNDBUFFORCE", Const, 0}, + {"SO_SNDLOWAT", Const, 0}, + {"SO_SNDTIMEO", Const, 0}, + {"SO_SPLICE", Const, 1}, + {"SO_TIMESTAMP", Const, 0}, + {"SO_TIMESTAMPING", Const, 0}, + {"SO_TIMESTAMPNS", Const, 0}, + {"SO_TIMESTAMP_MONOTONIC", Const, 0}, + {"SO_TYPE", Const, 0}, + {"SO_UPCALLCLOSEWAIT", Const, 0}, + {"SO_UPDATE_ACCEPT_CONTEXT", Const, 0}, + {"SO_UPDATE_CONNECT_CONTEXT", Const, 1}, + {"SO_USELOOPBACK", Const, 0}, + {"SO_USER_COOKIE", Const, 1}, + {"SO_VENDOR", Const, 3}, + {"SO_WANTMORE", Const, 0}, + {"SO_WANTOOBFLAG", Const, 0}, + {"SSLExtraCertChainPolicyPara", Type, 0}, + {"SSLExtraCertChainPolicyPara.AuthType", Field, 0}, + {"SSLExtraCertChainPolicyPara.Checks", Field, 0}, + {"SSLExtraCertChainPolicyPara.ServerName", Field, 0}, + {"SSLExtraCertChainPolicyPara.Size", Field, 0}, + {"STANDARD_RIGHTS_ALL", Const, 0}, + {"STANDARD_RIGHTS_EXECUTE", Const, 0}, + {"STANDARD_RIGHTS_READ", Const, 0}, + {"STANDARD_RIGHTS_REQUIRED", Const, 0}, + {"STANDARD_RIGHTS_WRITE", Const, 0}, + {"STARTF_USESHOWWINDOW", Const, 0}, + {"STARTF_USESTDHANDLES", Const, 0}, + {"STD_ERROR_HANDLE", Const, 0}, + {"STD_INPUT_HANDLE", Const, 0}, + {"STD_OUTPUT_HANDLE", Const, 0}, + {"SUBLANG_ENGLISH_US", Const, 0}, + {"SW_FORCEMINIMIZE", Const, 0}, + {"SW_HIDE", Const, 0}, + {"SW_MAXIMIZE", Const, 0}, + {"SW_MINIMIZE", Const, 0}, + {"SW_NORMAL", Const, 0}, + {"SW_RESTORE", Const, 0}, + {"SW_SHOW", Const, 0}, + {"SW_SHOWDEFAULT", Const, 0}, + {"SW_SHOWMAXIMIZED", Const, 0}, + {"SW_SHOWMINIMIZED", Const, 0}, + {"SW_SHOWMINNOACTIVE", Const, 0}, + {"SW_SHOWNA", Const, 0}, + {"SW_SHOWNOACTIVATE", Const, 0}, + {"SW_SHOWNORMAL", Const, 0}, + {"SYMBOLIC_LINK_FLAG_DIRECTORY", Const, 4}, + {"SYNCHRONIZE", Const, 0}, + {"SYSCTL_VERSION", Const, 1}, + {"SYSCTL_VERS_0", Const, 1}, + {"SYSCTL_VERS_1", Const, 1}, + {"SYSCTL_VERS_MASK", Const, 1}, + {"SYS_ABORT2", Const, 0}, + {"SYS_ACCEPT", Const, 0}, + {"SYS_ACCEPT4", Const, 0}, + {"SYS_ACCEPT_NOCANCEL", Const, 0}, + {"SYS_ACCESS", Const, 0}, + {"SYS_ACCESS_EXTENDED", Const, 0}, + {"SYS_ACCT", Const, 0}, + {"SYS_ADD_KEY", Const, 0}, + {"SYS_ADD_PROFIL", Const, 0}, + {"SYS_ADJFREQ", Const, 1}, + {"SYS_ADJTIME", Const, 0}, + {"SYS_ADJTIMEX", Const, 0}, + {"SYS_AFS_SYSCALL", Const, 0}, + {"SYS_AIO_CANCEL", Const, 0}, + {"SYS_AIO_ERROR", Const, 0}, + {"SYS_AIO_FSYNC", Const, 0}, + {"SYS_AIO_MLOCK", Const, 14}, + {"SYS_AIO_READ", Const, 0}, + {"SYS_AIO_RETURN", Const, 0}, + {"SYS_AIO_SUSPEND", Const, 0}, + {"SYS_AIO_SUSPEND_NOCANCEL", Const, 0}, + {"SYS_AIO_WAITCOMPLETE", Const, 14}, + {"SYS_AIO_WRITE", Const, 0}, + {"SYS_ALARM", Const, 0}, + {"SYS_ARCH_PRCTL", Const, 0}, + {"SYS_ARM_FADVISE64_64", Const, 0}, + {"SYS_ARM_SYNC_FILE_RANGE", Const, 0}, + {"SYS_ATGETMSG", Const, 0}, + {"SYS_ATPGETREQ", Const, 0}, + {"SYS_ATPGETRSP", Const, 0}, + {"SYS_ATPSNDREQ", Const, 0}, + {"SYS_ATPSNDRSP", Const, 0}, + {"SYS_ATPUTMSG", Const, 0}, + {"SYS_ATSOCKET", Const, 0}, + {"SYS_AUDIT", Const, 0}, + {"SYS_AUDITCTL", Const, 0}, + {"SYS_AUDITON", Const, 0}, + {"SYS_AUDIT_SESSION_JOIN", Const, 0}, + {"SYS_AUDIT_SESSION_PORT", Const, 0}, + {"SYS_AUDIT_SESSION_SELF", Const, 0}, + {"SYS_BDFLUSH", Const, 0}, + {"SYS_BIND", Const, 0}, + {"SYS_BINDAT", Const, 3}, + {"SYS_BREAK", Const, 0}, + {"SYS_BRK", Const, 0}, + {"SYS_BSDTHREAD_CREATE", Const, 0}, + {"SYS_BSDTHREAD_REGISTER", Const, 0}, + {"SYS_BSDTHREAD_TERMINATE", Const, 0}, + {"SYS_CAPGET", Const, 0}, + {"SYS_CAPSET", Const, 0}, + {"SYS_CAP_ENTER", Const, 0}, + {"SYS_CAP_FCNTLS_GET", Const, 1}, + {"SYS_CAP_FCNTLS_LIMIT", Const, 1}, + {"SYS_CAP_GETMODE", Const, 0}, + {"SYS_CAP_GETRIGHTS", Const, 0}, + {"SYS_CAP_IOCTLS_GET", Const, 1}, + {"SYS_CAP_IOCTLS_LIMIT", Const, 1}, + {"SYS_CAP_NEW", Const, 0}, + {"SYS_CAP_RIGHTS_GET", Const, 1}, + {"SYS_CAP_RIGHTS_LIMIT", Const, 1}, + {"SYS_CHDIR", Const, 0}, + {"SYS_CHFLAGS", Const, 0}, + {"SYS_CHFLAGSAT", Const, 3}, + {"SYS_CHMOD", Const, 0}, + {"SYS_CHMOD_EXTENDED", Const, 0}, + {"SYS_CHOWN", Const, 0}, + {"SYS_CHOWN32", Const, 0}, + {"SYS_CHROOT", Const, 0}, + {"SYS_CHUD", Const, 0}, + {"SYS_CLOCK_ADJTIME", Const, 0}, + {"SYS_CLOCK_GETCPUCLOCKID2", Const, 1}, + {"SYS_CLOCK_GETRES", Const, 0}, + {"SYS_CLOCK_GETTIME", Const, 0}, + {"SYS_CLOCK_NANOSLEEP", Const, 0}, + {"SYS_CLOCK_SETTIME", Const, 0}, + {"SYS_CLONE", Const, 0}, + {"SYS_CLOSE", Const, 0}, + {"SYS_CLOSEFROM", Const, 0}, + {"SYS_CLOSE_NOCANCEL", Const, 0}, + {"SYS_CONNECT", Const, 0}, + {"SYS_CONNECTAT", Const, 3}, + {"SYS_CONNECT_NOCANCEL", Const, 0}, + {"SYS_COPYFILE", Const, 0}, + {"SYS_CPUSET", Const, 0}, + {"SYS_CPUSET_GETAFFINITY", Const, 0}, + {"SYS_CPUSET_GETID", Const, 0}, + {"SYS_CPUSET_SETAFFINITY", Const, 0}, + {"SYS_CPUSET_SETID", Const, 0}, + {"SYS_CREAT", Const, 0}, + {"SYS_CREATE_MODULE", Const, 0}, + {"SYS_CSOPS", Const, 0}, + {"SYS_CSOPS_AUDITTOKEN", Const, 16}, + {"SYS_DELETE", Const, 0}, + {"SYS_DELETE_MODULE", Const, 0}, + {"SYS_DUP", Const, 0}, + {"SYS_DUP2", Const, 0}, + {"SYS_DUP3", Const, 0}, + {"SYS_EACCESS", Const, 0}, + {"SYS_EPOLL_CREATE", Const, 0}, + {"SYS_EPOLL_CREATE1", Const, 0}, + {"SYS_EPOLL_CTL", Const, 0}, + {"SYS_EPOLL_CTL_OLD", Const, 0}, + {"SYS_EPOLL_PWAIT", Const, 0}, + {"SYS_EPOLL_WAIT", Const, 0}, + {"SYS_EPOLL_WAIT_OLD", Const, 0}, + {"SYS_EVENTFD", Const, 0}, + {"SYS_EVENTFD2", Const, 0}, + {"SYS_EXCHANGEDATA", Const, 0}, + {"SYS_EXECVE", Const, 0}, + {"SYS_EXIT", Const, 0}, + {"SYS_EXIT_GROUP", Const, 0}, + {"SYS_EXTATTRCTL", Const, 0}, + {"SYS_EXTATTR_DELETE_FD", Const, 0}, + {"SYS_EXTATTR_DELETE_FILE", Const, 0}, + {"SYS_EXTATTR_DELETE_LINK", Const, 0}, + {"SYS_EXTATTR_GET_FD", Const, 0}, + {"SYS_EXTATTR_GET_FILE", Const, 0}, + {"SYS_EXTATTR_GET_LINK", Const, 0}, + {"SYS_EXTATTR_LIST_FD", Const, 0}, + {"SYS_EXTATTR_LIST_FILE", Const, 0}, + {"SYS_EXTATTR_LIST_LINK", Const, 0}, + {"SYS_EXTATTR_SET_FD", Const, 0}, + {"SYS_EXTATTR_SET_FILE", Const, 0}, + {"SYS_EXTATTR_SET_LINK", Const, 0}, + {"SYS_FACCESSAT", Const, 0}, + {"SYS_FADVISE64", Const, 0}, + {"SYS_FADVISE64_64", Const, 0}, + {"SYS_FALLOCATE", Const, 0}, + {"SYS_FANOTIFY_INIT", Const, 0}, + {"SYS_FANOTIFY_MARK", Const, 0}, + {"SYS_FCHDIR", Const, 0}, + {"SYS_FCHFLAGS", Const, 0}, + {"SYS_FCHMOD", Const, 0}, + {"SYS_FCHMODAT", Const, 0}, + {"SYS_FCHMOD_EXTENDED", Const, 0}, + {"SYS_FCHOWN", Const, 0}, + {"SYS_FCHOWN32", Const, 0}, + {"SYS_FCHOWNAT", Const, 0}, + {"SYS_FCHROOT", Const, 1}, + {"SYS_FCNTL", Const, 0}, + {"SYS_FCNTL64", Const, 0}, + {"SYS_FCNTL_NOCANCEL", Const, 0}, + {"SYS_FDATASYNC", Const, 0}, + {"SYS_FEXECVE", Const, 0}, + {"SYS_FFCLOCK_GETCOUNTER", Const, 0}, + {"SYS_FFCLOCK_GETESTIMATE", Const, 0}, + {"SYS_FFCLOCK_SETESTIMATE", Const, 0}, + {"SYS_FFSCTL", Const, 0}, + {"SYS_FGETATTRLIST", Const, 0}, + {"SYS_FGETXATTR", Const, 0}, + {"SYS_FHOPEN", Const, 0}, + {"SYS_FHSTAT", Const, 0}, + {"SYS_FHSTATFS", Const, 0}, + {"SYS_FILEPORT_MAKEFD", Const, 0}, + {"SYS_FILEPORT_MAKEPORT", Const, 0}, + {"SYS_FKTRACE", Const, 1}, + {"SYS_FLISTXATTR", Const, 0}, + {"SYS_FLOCK", Const, 0}, + {"SYS_FORK", Const, 0}, + {"SYS_FPATHCONF", Const, 0}, + {"SYS_FREEBSD6_FTRUNCATE", Const, 0}, + {"SYS_FREEBSD6_LSEEK", Const, 0}, + {"SYS_FREEBSD6_MMAP", Const, 0}, + {"SYS_FREEBSD6_PREAD", Const, 0}, + {"SYS_FREEBSD6_PWRITE", Const, 0}, + {"SYS_FREEBSD6_TRUNCATE", Const, 0}, + {"SYS_FREMOVEXATTR", Const, 0}, + {"SYS_FSCTL", Const, 0}, + {"SYS_FSETATTRLIST", Const, 0}, + {"SYS_FSETXATTR", Const, 0}, + {"SYS_FSGETPATH", Const, 0}, + {"SYS_FSTAT", Const, 0}, + {"SYS_FSTAT64", Const, 0}, + {"SYS_FSTAT64_EXTENDED", Const, 0}, + {"SYS_FSTATAT", Const, 0}, + {"SYS_FSTATAT64", Const, 0}, + {"SYS_FSTATFS", Const, 0}, + {"SYS_FSTATFS64", Const, 0}, + {"SYS_FSTATV", Const, 0}, + {"SYS_FSTATVFS1", Const, 1}, + {"SYS_FSTAT_EXTENDED", Const, 0}, + {"SYS_FSYNC", Const, 0}, + {"SYS_FSYNC_NOCANCEL", Const, 0}, + {"SYS_FSYNC_RANGE", Const, 1}, + {"SYS_FTIME", Const, 0}, + {"SYS_FTRUNCATE", Const, 0}, + {"SYS_FTRUNCATE64", Const, 0}, + {"SYS_FUTEX", Const, 0}, + {"SYS_FUTIMENS", Const, 1}, + {"SYS_FUTIMES", Const, 0}, + {"SYS_FUTIMESAT", Const, 0}, + {"SYS_GETATTRLIST", Const, 0}, + {"SYS_GETAUDIT", Const, 0}, + {"SYS_GETAUDIT_ADDR", Const, 0}, + {"SYS_GETAUID", Const, 0}, + {"SYS_GETCONTEXT", Const, 0}, + {"SYS_GETCPU", Const, 0}, + {"SYS_GETCWD", Const, 0}, + {"SYS_GETDENTS", Const, 0}, + {"SYS_GETDENTS64", Const, 0}, + {"SYS_GETDIRENTRIES", Const, 0}, + {"SYS_GETDIRENTRIES64", Const, 0}, + {"SYS_GETDIRENTRIESATTR", Const, 0}, + {"SYS_GETDTABLECOUNT", Const, 1}, + {"SYS_GETDTABLESIZE", Const, 0}, + {"SYS_GETEGID", Const, 0}, + {"SYS_GETEGID32", Const, 0}, + {"SYS_GETEUID", Const, 0}, + {"SYS_GETEUID32", Const, 0}, + {"SYS_GETFH", Const, 0}, + {"SYS_GETFSSTAT", Const, 0}, + {"SYS_GETFSSTAT64", Const, 0}, + {"SYS_GETGID", Const, 0}, + {"SYS_GETGID32", Const, 0}, + {"SYS_GETGROUPS", Const, 0}, + {"SYS_GETGROUPS32", Const, 0}, + {"SYS_GETHOSTUUID", Const, 0}, + {"SYS_GETITIMER", Const, 0}, + {"SYS_GETLCID", Const, 0}, + {"SYS_GETLOGIN", Const, 0}, + {"SYS_GETLOGINCLASS", Const, 0}, + {"SYS_GETPEERNAME", Const, 0}, + {"SYS_GETPGID", Const, 0}, + {"SYS_GETPGRP", Const, 0}, + {"SYS_GETPID", Const, 0}, + {"SYS_GETPMSG", Const, 0}, + {"SYS_GETPPID", Const, 0}, + {"SYS_GETPRIORITY", Const, 0}, + {"SYS_GETRESGID", Const, 0}, + {"SYS_GETRESGID32", Const, 0}, + {"SYS_GETRESUID", Const, 0}, + {"SYS_GETRESUID32", Const, 0}, + {"SYS_GETRLIMIT", Const, 0}, + {"SYS_GETRTABLE", Const, 1}, + {"SYS_GETRUSAGE", Const, 0}, + {"SYS_GETSGROUPS", Const, 0}, + {"SYS_GETSID", Const, 0}, + {"SYS_GETSOCKNAME", Const, 0}, + {"SYS_GETSOCKOPT", Const, 0}, + {"SYS_GETTHRID", Const, 1}, + {"SYS_GETTID", Const, 0}, + {"SYS_GETTIMEOFDAY", Const, 0}, + {"SYS_GETUID", Const, 0}, + {"SYS_GETUID32", Const, 0}, + {"SYS_GETVFSSTAT", Const, 1}, + {"SYS_GETWGROUPS", Const, 0}, + {"SYS_GETXATTR", Const, 0}, + {"SYS_GET_KERNEL_SYMS", Const, 0}, + {"SYS_GET_MEMPOLICY", Const, 0}, + {"SYS_GET_ROBUST_LIST", Const, 0}, + {"SYS_GET_THREAD_AREA", Const, 0}, + {"SYS_GSSD_SYSCALL", Const, 14}, + {"SYS_GTTY", Const, 0}, + {"SYS_IDENTITYSVC", Const, 0}, + {"SYS_IDLE", Const, 0}, + {"SYS_INITGROUPS", Const, 0}, + {"SYS_INIT_MODULE", Const, 0}, + {"SYS_INOTIFY_ADD_WATCH", Const, 0}, + {"SYS_INOTIFY_INIT", Const, 0}, + {"SYS_INOTIFY_INIT1", Const, 0}, + {"SYS_INOTIFY_RM_WATCH", Const, 0}, + {"SYS_IOCTL", Const, 0}, + {"SYS_IOPERM", Const, 0}, + {"SYS_IOPL", Const, 0}, + {"SYS_IOPOLICYSYS", Const, 0}, + {"SYS_IOPRIO_GET", Const, 0}, + {"SYS_IOPRIO_SET", Const, 0}, + {"SYS_IO_CANCEL", Const, 0}, + {"SYS_IO_DESTROY", Const, 0}, + {"SYS_IO_GETEVENTS", Const, 0}, + {"SYS_IO_SETUP", Const, 0}, + {"SYS_IO_SUBMIT", Const, 0}, + {"SYS_IPC", Const, 0}, + {"SYS_ISSETUGID", Const, 0}, + {"SYS_JAIL", Const, 0}, + {"SYS_JAIL_ATTACH", Const, 0}, + {"SYS_JAIL_GET", Const, 0}, + {"SYS_JAIL_REMOVE", Const, 0}, + {"SYS_JAIL_SET", Const, 0}, + {"SYS_KAS_INFO", Const, 16}, + {"SYS_KDEBUG_TRACE", Const, 0}, + {"SYS_KENV", Const, 0}, + {"SYS_KEVENT", Const, 0}, + {"SYS_KEVENT64", Const, 0}, + {"SYS_KEXEC_LOAD", Const, 0}, + {"SYS_KEYCTL", Const, 0}, + {"SYS_KILL", Const, 0}, + {"SYS_KLDFIND", Const, 0}, + {"SYS_KLDFIRSTMOD", Const, 0}, + {"SYS_KLDLOAD", Const, 0}, + {"SYS_KLDNEXT", Const, 0}, + {"SYS_KLDSTAT", Const, 0}, + {"SYS_KLDSYM", Const, 0}, + {"SYS_KLDUNLOAD", Const, 0}, + {"SYS_KLDUNLOADF", Const, 0}, + {"SYS_KMQ_NOTIFY", Const, 14}, + {"SYS_KMQ_OPEN", Const, 14}, + {"SYS_KMQ_SETATTR", Const, 14}, + {"SYS_KMQ_TIMEDRECEIVE", Const, 14}, + {"SYS_KMQ_TIMEDSEND", Const, 14}, + {"SYS_KMQ_UNLINK", Const, 14}, + {"SYS_KQUEUE", Const, 0}, + {"SYS_KQUEUE1", Const, 1}, + {"SYS_KSEM_CLOSE", Const, 14}, + {"SYS_KSEM_DESTROY", Const, 14}, + {"SYS_KSEM_GETVALUE", Const, 14}, + {"SYS_KSEM_INIT", Const, 14}, + {"SYS_KSEM_OPEN", Const, 14}, + {"SYS_KSEM_POST", Const, 14}, + {"SYS_KSEM_TIMEDWAIT", Const, 14}, + {"SYS_KSEM_TRYWAIT", Const, 14}, + {"SYS_KSEM_UNLINK", Const, 14}, + {"SYS_KSEM_WAIT", Const, 14}, + {"SYS_KTIMER_CREATE", Const, 0}, + {"SYS_KTIMER_DELETE", Const, 0}, + {"SYS_KTIMER_GETOVERRUN", Const, 0}, + {"SYS_KTIMER_GETTIME", Const, 0}, + {"SYS_KTIMER_SETTIME", Const, 0}, + {"SYS_KTRACE", Const, 0}, + {"SYS_LCHFLAGS", Const, 0}, + {"SYS_LCHMOD", Const, 0}, + {"SYS_LCHOWN", Const, 0}, + {"SYS_LCHOWN32", Const, 0}, + {"SYS_LEDGER", Const, 16}, + {"SYS_LGETFH", Const, 0}, + {"SYS_LGETXATTR", Const, 0}, + {"SYS_LINK", Const, 0}, + {"SYS_LINKAT", Const, 0}, + {"SYS_LIO_LISTIO", Const, 0}, + {"SYS_LISTEN", Const, 0}, + {"SYS_LISTXATTR", Const, 0}, + {"SYS_LLISTXATTR", Const, 0}, + {"SYS_LOCK", Const, 0}, + {"SYS_LOOKUP_DCOOKIE", Const, 0}, + {"SYS_LPATHCONF", Const, 0}, + {"SYS_LREMOVEXATTR", Const, 0}, + {"SYS_LSEEK", Const, 0}, + {"SYS_LSETXATTR", Const, 0}, + {"SYS_LSTAT", Const, 0}, + {"SYS_LSTAT64", Const, 0}, + {"SYS_LSTAT64_EXTENDED", Const, 0}, + {"SYS_LSTATV", Const, 0}, + {"SYS_LSTAT_EXTENDED", Const, 0}, + {"SYS_LUTIMES", Const, 0}, + {"SYS_MAC_SYSCALL", Const, 0}, + {"SYS_MADVISE", Const, 0}, + {"SYS_MADVISE1", Const, 0}, + {"SYS_MAXSYSCALL", Const, 0}, + {"SYS_MBIND", Const, 0}, + {"SYS_MIGRATE_PAGES", Const, 0}, + {"SYS_MINCORE", Const, 0}, + {"SYS_MINHERIT", Const, 0}, + {"SYS_MKCOMPLEX", Const, 0}, + {"SYS_MKDIR", Const, 0}, + {"SYS_MKDIRAT", Const, 0}, + {"SYS_MKDIR_EXTENDED", Const, 0}, + {"SYS_MKFIFO", Const, 0}, + {"SYS_MKFIFOAT", Const, 0}, + {"SYS_MKFIFO_EXTENDED", Const, 0}, + {"SYS_MKNOD", Const, 0}, + {"SYS_MKNODAT", Const, 0}, + {"SYS_MLOCK", Const, 0}, + {"SYS_MLOCKALL", Const, 0}, + {"SYS_MMAP", Const, 0}, + {"SYS_MMAP2", Const, 0}, + {"SYS_MODCTL", Const, 1}, + {"SYS_MODFIND", Const, 0}, + {"SYS_MODFNEXT", Const, 0}, + {"SYS_MODIFY_LDT", Const, 0}, + {"SYS_MODNEXT", Const, 0}, + {"SYS_MODSTAT", Const, 0}, + {"SYS_MODWATCH", Const, 0}, + {"SYS_MOUNT", Const, 0}, + {"SYS_MOVE_PAGES", Const, 0}, + {"SYS_MPROTECT", Const, 0}, + {"SYS_MPX", Const, 0}, + {"SYS_MQUERY", Const, 1}, + {"SYS_MQ_GETSETATTR", Const, 0}, + {"SYS_MQ_NOTIFY", Const, 0}, + {"SYS_MQ_OPEN", Const, 0}, + {"SYS_MQ_TIMEDRECEIVE", Const, 0}, + {"SYS_MQ_TIMEDSEND", Const, 0}, + {"SYS_MQ_UNLINK", Const, 0}, + {"SYS_MREMAP", Const, 0}, + {"SYS_MSGCTL", Const, 0}, + {"SYS_MSGGET", Const, 0}, + {"SYS_MSGRCV", Const, 0}, + {"SYS_MSGRCV_NOCANCEL", Const, 0}, + {"SYS_MSGSND", Const, 0}, + {"SYS_MSGSND_NOCANCEL", Const, 0}, + {"SYS_MSGSYS", Const, 0}, + {"SYS_MSYNC", Const, 0}, + {"SYS_MSYNC_NOCANCEL", Const, 0}, + {"SYS_MUNLOCK", Const, 0}, + {"SYS_MUNLOCKALL", Const, 0}, + {"SYS_MUNMAP", Const, 0}, + {"SYS_NAME_TO_HANDLE_AT", Const, 0}, + {"SYS_NANOSLEEP", Const, 0}, + {"SYS_NEWFSTATAT", Const, 0}, + {"SYS_NFSCLNT", Const, 0}, + {"SYS_NFSSERVCTL", Const, 0}, + {"SYS_NFSSVC", Const, 0}, + {"SYS_NFSTAT", Const, 0}, + {"SYS_NICE", Const, 0}, + {"SYS_NLM_SYSCALL", Const, 14}, + {"SYS_NLSTAT", Const, 0}, + {"SYS_NMOUNT", Const, 0}, + {"SYS_NSTAT", Const, 0}, + {"SYS_NTP_ADJTIME", Const, 0}, + {"SYS_NTP_GETTIME", Const, 0}, + {"SYS_NUMA_GETAFFINITY", Const, 14}, + {"SYS_NUMA_SETAFFINITY", Const, 14}, + {"SYS_OABI_SYSCALL_BASE", Const, 0}, + {"SYS_OBREAK", Const, 0}, + {"SYS_OLDFSTAT", Const, 0}, + {"SYS_OLDLSTAT", Const, 0}, + {"SYS_OLDOLDUNAME", Const, 0}, + {"SYS_OLDSTAT", Const, 0}, + {"SYS_OLDUNAME", Const, 0}, + {"SYS_OPEN", Const, 0}, + {"SYS_OPENAT", Const, 0}, + {"SYS_OPENBSD_POLL", Const, 0}, + {"SYS_OPEN_BY_HANDLE_AT", Const, 0}, + {"SYS_OPEN_DPROTECTED_NP", Const, 16}, + {"SYS_OPEN_EXTENDED", Const, 0}, + {"SYS_OPEN_NOCANCEL", Const, 0}, + {"SYS_OVADVISE", Const, 0}, + {"SYS_PACCEPT", Const, 1}, + {"SYS_PATHCONF", Const, 0}, + {"SYS_PAUSE", Const, 0}, + {"SYS_PCICONFIG_IOBASE", Const, 0}, + {"SYS_PCICONFIG_READ", Const, 0}, + {"SYS_PCICONFIG_WRITE", Const, 0}, + {"SYS_PDFORK", Const, 0}, + {"SYS_PDGETPID", Const, 0}, + {"SYS_PDKILL", Const, 0}, + {"SYS_PERF_EVENT_OPEN", Const, 0}, + {"SYS_PERSONALITY", Const, 0}, + {"SYS_PID_HIBERNATE", Const, 0}, + {"SYS_PID_RESUME", Const, 0}, + {"SYS_PID_SHUTDOWN_SOCKETS", Const, 0}, + {"SYS_PID_SUSPEND", Const, 0}, + {"SYS_PIPE", Const, 0}, + {"SYS_PIPE2", Const, 0}, + {"SYS_PIVOT_ROOT", Const, 0}, + {"SYS_PMC_CONTROL", Const, 1}, + {"SYS_PMC_GET_INFO", Const, 1}, + {"SYS_POLL", Const, 0}, + {"SYS_POLLTS", Const, 1}, + {"SYS_POLL_NOCANCEL", Const, 0}, + {"SYS_POSIX_FADVISE", Const, 0}, + {"SYS_POSIX_FALLOCATE", Const, 0}, + {"SYS_POSIX_OPENPT", Const, 0}, + {"SYS_POSIX_SPAWN", Const, 0}, + {"SYS_PPOLL", Const, 0}, + {"SYS_PRCTL", Const, 0}, + {"SYS_PREAD", Const, 0}, + {"SYS_PREAD64", Const, 0}, + {"SYS_PREADV", Const, 0}, + {"SYS_PREAD_NOCANCEL", Const, 0}, + {"SYS_PRLIMIT64", Const, 0}, + {"SYS_PROCCTL", Const, 3}, + {"SYS_PROCESS_POLICY", Const, 0}, + {"SYS_PROCESS_VM_READV", Const, 0}, + {"SYS_PROCESS_VM_WRITEV", Const, 0}, + {"SYS_PROC_INFO", Const, 0}, + {"SYS_PROF", Const, 0}, + {"SYS_PROFIL", Const, 0}, + {"SYS_PSELECT", Const, 0}, + {"SYS_PSELECT6", Const, 0}, + {"SYS_PSET_ASSIGN", Const, 1}, + {"SYS_PSET_CREATE", Const, 1}, + {"SYS_PSET_DESTROY", Const, 1}, + {"SYS_PSYNCH_CVBROAD", Const, 0}, + {"SYS_PSYNCH_CVCLRPREPOST", Const, 0}, + {"SYS_PSYNCH_CVSIGNAL", Const, 0}, + {"SYS_PSYNCH_CVWAIT", Const, 0}, + {"SYS_PSYNCH_MUTEXDROP", Const, 0}, + {"SYS_PSYNCH_MUTEXWAIT", Const, 0}, + {"SYS_PSYNCH_RW_DOWNGRADE", Const, 0}, + {"SYS_PSYNCH_RW_LONGRDLOCK", Const, 0}, + {"SYS_PSYNCH_RW_RDLOCK", Const, 0}, + {"SYS_PSYNCH_RW_UNLOCK", Const, 0}, + {"SYS_PSYNCH_RW_UNLOCK2", Const, 0}, + {"SYS_PSYNCH_RW_UPGRADE", Const, 0}, + {"SYS_PSYNCH_RW_WRLOCK", Const, 0}, + {"SYS_PSYNCH_RW_YIELDWRLOCK", Const, 0}, + {"SYS_PTRACE", Const, 0}, + {"SYS_PUTPMSG", Const, 0}, + {"SYS_PWRITE", Const, 0}, + {"SYS_PWRITE64", Const, 0}, + {"SYS_PWRITEV", Const, 0}, + {"SYS_PWRITE_NOCANCEL", Const, 0}, + {"SYS_QUERY_MODULE", Const, 0}, + {"SYS_QUOTACTL", Const, 0}, + {"SYS_RASCTL", Const, 1}, + {"SYS_RCTL_ADD_RULE", Const, 0}, + {"SYS_RCTL_GET_LIMITS", Const, 0}, + {"SYS_RCTL_GET_RACCT", Const, 0}, + {"SYS_RCTL_GET_RULES", Const, 0}, + {"SYS_RCTL_REMOVE_RULE", Const, 0}, + {"SYS_READ", Const, 0}, + {"SYS_READAHEAD", Const, 0}, + {"SYS_READDIR", Const, 0}, + {"SYS_READLINK", Const, 0}, + {"SYS_READLINKAT", Const, 0}, + {"SYS_READV", Const, 0}, + {"SYS_READV_NOCANCEL", Const, 0}, + {"SYS_READ_NOCANCEL", Const, 0}, + {"SYS_REBOOT", Const, 0}, + {"SYS_RECV", Const, 0}, + {"SYS_RECVFROM", Const, 0}, + {"SYS_RECVFROM_NOCANCEL", Const, 0}, + {"SYS_RECVMMSG", Const, 0}, + {"SYS_RECVMSG", Const, 0}, + {"SYS_RECVMSG_NOCANCEL", Const, 0}, + {"SYS_REMAP_FILE_PAGES", Const, 0}, + {"SYS_REMOVEXATTR", Const, 0}, + {"SYS_RENAME", Const, 0}, + {"SYS_RENAMEAT", Const, 0}, + {"SYS_REQUEST_KEY", Const, 0}, + {"SYS_RESTART_SYSCALL", Const, 0}, + {"SYS_REVOKE", Const, 0}, + {"SYS_RFORK", Const, 0}, + {"SYS_RMDIR", Const, 0}, + {"SYS_RTPRIO", Const, 0}, + {"SYS_RTPRIO_THREAD", Const, 0}, + {"SYS_RT_SIGACTION", Const, 0}, + {"SYS_RT_SIGPENDING", Const, 0}, + {"SYS_RT_SIGPROCMASK", Const, 0}, + {"SYS_RT_SIGQUEUEINFO", Const, 0}, + {"SYS_RT_SIGRETURN", Const, 0}, + {"SYS_RT_SIGSUSPEND", Const, 0}, + {"SYS_RT_SIGTIMEDWAIT", Const, 0}, + {"SYS_RT_TGSIGQUEUEINFO", Const, 0}, + {"SYS_SBRK", Const, 0}, + {"SYS_SCHED_GETAFFINITY", Const, 0}, + {"SYS_SCHED_GETPARAM", Const, 0}, + {"SYS_SCHED_GETSCHEDULER", Const, 0}, + {"SYS_SCHED_GET_PRIORITY_MAX", Const, 0}, + {"SYS_SCHED_GET_PRIORITY_MIN", Const, 0}, + {"SYS_SCHED_RR_GET_INTERVAL", Const, 0}, + {"SYS_SCHED_SETAFFINITY", Const, 0}, + {"SYS_SCHED_SETPARAM", Const, 0}, + {"SYS_SCHED_SETSCHEDULER", Const, 0}, + {"SYS_SCHED_YIELD", Const, 0}, + {"SYS_SCTP_GENERIC_RECVMSG", Const, 0}, + {"SYS_SCTP_GENERIC_SENDMSG", Const, 0}, + {"SYS_SCTP_GENERIC_SENDMSG_IOV", Const, 0}, + {"SYS_SCTP_PEELOFF", Const, 0}, + {"SYS_SEARCHFS", Const, 0}, + {"SYS_SECURITY", Const, 0}, + {"SYS_SELECT", Const, 0}, + {"SYS_SELECT_NOCANCEL", Const, 0}, + {"SYS_SEMCONFIG", Const, 1}, + {"SYS_SEMCTL", Const, 0}, + {"SYS_SEMGET", Const, 0}, + {"SYS_SEMOP", Const, 0}, + {"SYS_SEMSYS", Const, 0}, + {"SYS_SEMTIMEDOP", Const, 0}, + {"SYS_SEM_CLOSE", Const, 0}, + {"SYS_SEM_DESTROY", Const, 0}, + {"SYS_SEM_GETVALUE", Const, 0}, + {"SYS_SEM_INIT", Const, 0}, + {"SYS_SEM_OPEN", Const, 0}, + {"SYS_SEM_POST", Const, 0}, + {"SYS_SEM_TRYWAIT", Const, 0}, + {"SYS_SEM_UNLINK", Const, 0}, + {"SYS_SEM_WAIT", Const, 0}, + {"SYS_SEM_WAIT_NOCANCEL", Const, 0}, + {"SYS_SEND", Const, 0}, + {"SYS_SENDFILE", Const, 0}, + {"SYS_SENDFILE64", Const, 0}, + {"SYS_SENDMMSG", Const, 0}, + {"SYS_SENDMSG", Const, 0}, + {"SYS_SENDMSG_NOCANCEL", Const, 0}, + {"SYS_SENDTO", Const, 0}, + {"SYS_SENDTO_NOCANCEL", Const, 0}, + {"SYS_SETATTRLIST", Const, 0}, + {"SYS_SETAUDIT", Const, 0}, + {"SYS_SETAUDIT_ADDR", Const, 0}, + {"SYS_SETAUID", Const, 0}, + {"SYS_SETCONTEXT", Const, 0}, + {"SYS_SETDOMAINNAME", Const, 0}, + {"SYS_SETEGID", Const, 0}, + {"SYS_SETEUID", Const, 0}, + {"SYS_SETFIB", Const, 0}, + {"SYS_SETFSGID", Const, 0}, + {"SYS_SETFSGID32", Const, 0}, + {"SYS_SETFSUID", Const, 0}, + {"SYS_SETFSUID32", Const, 0}, + {"SYS_SETGID", Const, 0}, + {"SYS_SETGID32", Const, 0}, + {"SYS_SETGROUPS", Const, 0}, + {"SYS_SETGROUPS32", Const, 0}, + {"SYS_SETHOSTNAME", Const, 0}, + {"SYS_SETITIMER", Const, 0}, + {"SYS_SETLCID", Const, 0}, + {"SYS_SETLOGIN", Const, 0}, + {"SYS_SETLOGINCLASS", Const, 0}, + {"SYS_SETNS", Const, 0}, + {"SYS_SETPGID", Const, 0}, + {"SYS_SETPRIORITY", Const, 0}, + {"SYS_SETPRIVEXEC", Const, 0}, + {"SYS_SETREGID", Const, 0}, + {"SYS_SETREGID32", Const, 0}, + {"SYS_SETRESGID", Const, 0}, + {"SYS_SETRESGID32", Const, 0}, + {"SYS_SETRESUID", Const, 0}, + {"SYS_SETRESUID32", Const, 0}, + {"SYS_SETREUID", Const, 0}, + {"SYS_SETREUID32", Const, 0}, + {"SYS_SETRLIMIT", Const, 0}, + {"SYS_SETRTABLE", Const, 1}, + {"SYS_SETSGROUPS", Const, 0}, + {"SYS_SETSID", Const, 0}, + {"SYS_SETSOCKOPT", Const, 0}, + {"SYS_SETTID", Const, 0}, + {"SYS_SETTID_WITH_PID", Const, 0}, + {"SYS_SETTIMEOFDAY", Const, 0}, + {"SYS_SETUID", Const, 0}, + {"SYS_SETUID32", Const, 0}, + {"SYS_SETWGROUPS", Const, 0}, + {"SYS_SETXATTR", Const, 0}, + {"SYS_SET_MEMPOLICY", Const, 0}, + {"SYS_SET_ROBUST_LIST", Const, 0}, + {"SYS_SET_THREAD_AREA", Const, 0}, + {"SYS_SET_TID_ADDRESS", Const, 0}, + {"SYS_SGETMASK", Const, 0}, + {"SYS_SHARED_REGION_CHECK_NP", Const, 0}, + {"SYS_SHARED_REGION_MAP_AND_SLIDE_NP", Const, 0}, + {"SYS_SHMAT", Const, 0}, + {"SYS_SHMCTL", Const, 0}, + {"SYS_SHMDT", Const, 0}, + {"SYS_SHMGET", Const, 0}, + {"SYS_SHMSYS", Const, 0}, + {"SYS_SHM_OPEN", Const, 0}, + {"SYS_SHM_UNLINK", Const, 0}, + {"SYS_SHUTDOWN", Const, 0}, + {"SYS_SIGACTION", Const, 0}, + {"SYS_SIGALTSTACK", Const, 0}, + {"SYS_SIGNAL", Const, 0}, + {"SYS_SIGNALFD", Const, 0}, + {"SYS_SIGNALFD4", Const, 0}, + {"SYS_SIGPENDING", Const, 0}, + {"SYS_SIGPROCMASK", Const, 0}, + {"SYS_SIGQUEUE", Const, 0}, + {"SYS_SIGQUEUEINFO", Const, 1}, + {"SYS_SIGRETURN", Const, 0}, + {"SYS_SIGSUSPEND", Const, 0}, + {"SYS_SIGSUSPEND_NOCANCEL", Const, 0}, + {"SYS_SIGTIMEDWAIT", Const, 0}, + {"SYS_SIGWAIT", Const, 0}, + {"SYS_SIGWAITINFO", Const, 0}, + {"SYS_SOCKET", Const, 0}, + {"SYS_SOCKETCALL", Const, 0}, + {"SYS_SOCKETPAIR", Const, 0}, + {"SYS_SPLICE", Const, 0}, + {"SYS_SSETMASK", Const, 0}, + {"SYS_SSTK", Const, 0}, + {"SYS_STACK_SNAPSHOT", Const, 0}, + {"SYS_STAT", Const, 0}, + {"SYS_STAT64", Const, 0}, + {"SYS_STAT64_EXTENDED", Const, 0}, + {"SYS_STATFS", Const, 0}, + {"SYS_STATFS64", Const, 0}, + {"SYS_STATV", Const, 0}, + {"SYS_STATVFS1", Const, 1}, + {"SYS_STAT_EXTENDED", Const, 0}, + {"SYS_STIME", Const, 0}, + {"SYS_STTY", Const, 0}, + {"SYS_SWAPCONTEXT", Const, 0}, + {"SYS_SWAPCTL", Const, 1}, + {"SYS_SWAPOFF", Const, 0}, + {"SYS_SWAPON", Const, 0}, + {"SYS_SYMLINK", Const, 0}, + {"SYS_SYMLINKAT", Const, 0}, + {"SYS_SYNC", Const, 0}, + {"SYS_SYNCFS", Const, 0}, + {"SYS_SYNC_FILE_RANGE", Const, 0}, + {"SYS_SYSARCH", Const, 0}, + {"SYS_SYSCALL", Const, 0}, + {"SYS_SYSCALL_BASE", Const, 0}, + {"SYS_SYSFS", Const, 0}, + {"SYS_SYSINFO", Const, 0}, + {"SYS_SYSLOG", Const, 0}, + {"SYS_TEE", Const, 0}, + {"SYS_TGKILL", Const, 0}, + {"SYS_THREAD_SELFID", Const, 0}, + {"SYS_THR_CREATE", Const, 0}, + {"SYS_THR_EXIT", Const, 0}, + {"SYS_THR_KILL", Const, 0}, + {"SYS_THR_KILL2", Const, 0}, + {"SYS_THR_NEW", Const, 0}, + {"SYS_THR_SELF", Const, 0}, + {"SYS_THR_SET_NAME", Const, 0}, + {"SYS_THR_SUSPEND", Const, 0}, + {"SYS_THR_WAKE", Const, 0}, + {"SYS_TIME", Const, 0}, + {"SYS_TIMERFD_CREATE", Const, 0}, + {"SYS_TIMERFD_GETTIME", Const, 0}, + {"SYS_TIMERFD_SETTIME", Const, 0}, + {"SYS_TIMER_CREATE", Const, 0}, + {"SYS_TIMER_DELETE", Const, 0}, + {"SYS_TIMER_GETOVERRUN", Const, 0}, + {"SYS_TIMER_GETTIME", Const, 0}, + {"SYS_TIMER_SETTIME", Const, 0}, + {"SYS_TIMES", Const, 0}, + {"SYS_TKILL", Const, 0}, + {"SYS_TRUNCATE", Const, 0}, + {"SYS_TRUNCATE64", Const, 0}, + {"SYS_TUXCALL", Const, 0}, + {"SYS_UGETRLIMIT", Const, 0}, + {"SYS_ULIMIT", Const, 0}, + {"SYS_UMASK", Const, 0}, + {"SYS_UMASK_EXTENDED", Const, 0}, + {"SYS_UMOUNT", Const, 0}, + {"SYS_UMOUNT2", Const, 0}, + {"SYS_UNAME", Const, 0}, + {"SYS_UNDELETE", Const, 0}, + {"SYS_UNLINK", Const, 0}, + {"SYS_UNLINKAT", Const, 0}, + {"SYS_UNMOUNT", Const, 0}, + {"SYS_UNSHARE", Const, 0}, + {"SYS_USELIB", Const, 0}, + {"SYS_USTAT", Const, 0}, + {"SYS_UTIME", Const, 0}, + {"SYS_UTIMENSAT", Const, 0}, + {"SYS_UTIMES", Const, 0}, + {"SYS_UTRACE", Const, 0}, + {"SYS_UUIDGEN", Const, 0}, + {"SYS_VADVISE", Const, 1}, + {"SYS_VFORK", Const, 0}, + {"SYS_VHANGUP", Const, 0}, + {"SYS_VM86", Const, 0}, + {"SYS_VM86OLD", Const, 0}, + {"SYS_VMSPLICE", Const, 0}, + {"SYS_VM_PRESSURE_MONITOR", Const, 0}, + {"SYS_VSERVER", Const, 0}, + {"SYS_WAIT4", Const, 0}, + {"SYS_WAIT4_NOCANCEL", Const, 0}, + {"SYS_WAIT6", Const, 1}, + {"SYS_WAITEVENT", Const, 0}, + {"SYS_WAITID", Const, 0}, + {"SYS_WAITID_NOCANCEL", Const, 0}, + {"SYS_WAITPID", Const, 0}, + {"SYS_WATCHEVENT", Const, 0}, + {"SYS_WORKQ_KERNRETURN", Const, 0}, + {"SYS_WORKQ_OPEN", Const, 0}, + {"SYS_WRITE", Const, 0}, + {"SYS_WRITEV", Const, 0}, + {"SYS_WRITEV_NOCANCEL", Const, 0}, + {"SYS_WRITE_NOCANCEL", Const, 0}, + {"SYS_YIELD", Const, 0}, + {"SYS__LLSEEK", Const, 0}, + {"SYS__LWP_CONTINUE", Const, 1}, + {"SYS__LWP_CREATE", Const, 1}, + {"SYS__LWP_CTL", Const, 1}, + {"SYS__LWP_DETACH", Const, 1}, + {"SYS__LWP_EXIT", Const, 1}, + {"SYS__LWP_GETNAME", Const, 1}, + {"SYS__LWP_GETPRIVATE", Const, 1}, + {"SYS__LWP_KILL", Const, 1}, + {"SYS__LWP_PARK", Const, 1}, + {"SYS__LWP_SELF", Const, 1}, + {"SYS__LWP_SETNAME", Const, 1}, + {"SYS__LWP_SETPRIVATE", Const, 1}, + {"SYS__LWP_SUSPEND", Const, 1}, + {"SYS__LWP_UNPARK", Const, 1}, + {"SYS__LWP_UNPARK_ALL", Const, 1}, + {"SYS__LWP_WAIT", Const, 1}, + {"SYS__LWP_WAKEUP", Const, 1}, + {"SYS__NEWSELECT", Const, 0}, + {"SYS__PSET_BIND", Const, 1}, + {"SYS__SCHED_GETAFFINITY", Const, 1}, + {"SYS__SCHED_GETPARAM", Const, 1}, + {"SYS__SCHED_SETAFFINITY", Const, 1}, + {"SYS__SCHED_SETPARAM", Const, 1}, + {"SYS__SYSCTL", Const, 0}, + {"SYS__UMTX_LOCK", Const, 0}, + {"SYS__UMTX_OP", Const, 0}, + {"SYS__UMTX_UNLOCK", Const, 0}, + {"SYS___ACL_ACLCHECK_FD", Const, 0}, + {"SYS___ACL_ACLCHECK_FILE", Const, 0}, + {"SYS___ACL_ACLCHECK_LINK", Const, 0}, + {"SYS___ACL_DELETE_FD", Const, 0}, + {"SYS___ACL_DELETE_FILE", Const, 0}, + {"SYS___ACL_DELETE_LINK", Const, 0}, + {"SYS___ACL_GET_FD", Const, 0}, + {"SYS___ACL_GET_FILE", Const, 0}, + {"SYS___ACL_GET_LINK", Const, 0}, + {"SYS___ACL_SET_FD", Const, 0}, + {"SYS___ACL_SET_FILE", Const, 0}, + {"SYS___ACL_SET_LINK", Const, 0}, + {"SYS___CAP_RIGHTS_GET", Const, 14}, + {"SYS___CLONE", Const, 1}, + {"SYS___DISABLE_THREADSIGNAL", Const, 0}, + {"SYS___GETCWD", Const, 0}, + {"SYS___GETLOGIN", Const, 1}, + {"SYS___GET_TCB", Const, 1}, + {"SYS___MAC_EXECVE", Const, 0}, + {"SYS___MAC_GETFSSTAT", Const, 0}, + {"SYS___MAC_GET_FD", Const, 0}, + {"SYS___MAC_GET_FILE", Const, 0}, + {"SYS___MAC_GET_LCID", Const, 0}, + {"SYS___MAC_GET_LCTX", Const, 0}, + {"SYS___MAC_GET_LINK", Const, 0}, + {"SYS___MAC_GET_MOUNT", Const, 0}, + {"SYS___MAC_GET_PID", Const, 0}, + {"SYS___MAC_GET_PROC", Const, 0}, + {"SYS___MAC_MOUNT", Const, 0}, + {"SYS___MAC_SET_FD", Const, 0}, + {"SYS___MAC_SET_FILE", Const, 0}, + {"SYS___MAC_SET_LCTX", Const, 0}, + {"SYS___MAC_SET_LINK", Const, 0}, + {"SYS___MAC_SET_PROC", Const, 0}, + {"SYS___MAC_SYSCALL", Const, 0}, + {"SYS___OLD_SEMWAIT_SIGNAL", Const, 0}, + {"SYS___OLD_SEMWAIT_SIGNAL_NOCANCEL", Const, 0}, + {"SYS___POSIX_CHOWN", Const, 1}, + {"SYS___POSIX_FCHOWN", Const, 1}, + {"SYS___POSIX_LCHOWN", Const, 1}, + {"SYS___POSIX_RENAME", Const, 1}, + {"SYS___PTHREAD_CANCELED", Const, 0}, + {"SYS___PTHREAD_CHDIR", Const, 0}, + {"SYS___PTHREAD_FCHDIR", Const, 0}, + {"SYS___PTHREAD_KILL", Const, 0}, + {"SYS___PTHREAD_MARKCANCEL", Const, 0}, + {"SYS___PTHREAD_SIGMASK", Const, 0}, + {"SYS___QUOTACTL", Const, 1}, + {"SYS___SEMCTL", Const, 1}, + {"SYS___SEMWAIT_SIGNAL", Const, 0}, + {"SYS___SEMWAIT_SIGNAL_NOCANCEL", Const, 0}, + {"SYS___SETLOGIN", Const, 1}, + {"SYS___SETUGID", Const, 0}, + {"SYS___SET_TCB", Const, 1}, + {"SYS___SIGACTION_SIGTRAMP", Const, 1}, + {"SYS___SIGTIMEDWAIT", Const, 1}, + {"SYS___SIGWAIT", Const, 0}, + {"SYS___SIGWAIT_NOCANCEL", Const, 0}, + {"SYS___SYSCTL", Const, 0}, + {"SYS___TFORK", Const, 1}, + {"SYS___THREXIT", Const, 1}, + {"SYS___THRSIGDIVERT", Const, 1}, + {"SYS___THRSLEEP", Const, 1}, + {"SYS___THRWAKEUP", Const, 1}, + {"S_ARCH1", Const, 1}, + {"S_ARCH2", Const, 1}, + {"S_BLKSIZE", Const, 0}, + {"S_IEXEC", Const, 0}, + {"S_IFBLK", Const, 0}, + {"S_IFCHR", Const, 0}, + {"S_IFDIR", Const, 0}, + {"S_IFIFO", Const, 0}, + {"S_IFLNK", Const, 0}, + {"S_IFMT", Const, 0}, + {"S_IFREG", Const, 0}, + {"S_IFSOCK", Const, 0}, + {"S_IFWHT", Const, 0}, + {"S_IREAD", Const, 0}, + {"S_IRGRP", Const, 0}, + {"S_IROTH", Const, 0}, + {"S_IRUSR", Const, 0}, + {"S_IRWXG", Const, 0}, + {"S_IRWXO", Const, 0}, + {"S_IRWXU", Const, 0}, + {"S_ISGID", Const, 0}, + {"S_ISTXT", Const, 0}, + {"S_ISUID", Const, 0}, + {"S_ISVTX", Const, 0}, + {"S_IWGRP", Const, 0}, + {"S_IWOTH", Const, 0}, + {"S_IWRITE", Const, 0}, + {"S_IWUSR", Const, 0}, + {"S_IXGRP", Const, 0}, + {"S_IXOTH", Const, 0}, + {"S_IXUSR", Const, 0}, + {"S_LOGIN_SET", Const, 1}, + {"SecurityAttributes", Type, 0}, + {"SecurityAttributes.InheritHandle", Field, 0}, + {"SecurityAttributes.Length", Field, 0}, + {"SecurityAttributes.SecurityDescriptor", Field, 0}, + {"Seek", Func, 0}, + {"Select", Func, 0}, + {"Sendfile", Func, 0}, + {"Sendmsg", Func, 0}, + {"SendmsgN", Func, 3}, + {"Sendto", Func, 0}, + {"Servent", Type, 0}, + {"Servent.Aliases", Field, 0}, + {"Servent.Name", Field, 0}, + {"Servent.Port", Field, 0}, + {"Servent.Proto", Field, 0}, + {"SetBpf", Func, 0}, + {"SetBpfBuflen", Func, 0}, + {"SetBpfDatalink", Func, 0}, + {"SetBpfHeadercmpl", Func, 0}, + {"SetBpfImmediate", Func, 0}, + {"SetBpfInterface", Func, 0}, + {"SetBpfPromisc", Func, 0}, + {"SetBpfTimeout", Func, 0}, + {"SetCurrentDirectory", Func, 0}, + {"SetEndOfFile", Func, 0}, + {"SetEnvironmentVariable", Func, 0}, + {"SetFileAttributes", Func, 0}, + {"SetFileCompletionNotificationModes", Func, 2}, + {"SetFilePointer", Func, 0}, + {"SetFileTime", Func, 0}, + {"SetHandleInformation", Func, 0}, + {"SetKevent", Func, 0}, + {"SetLsfPromisc", Func, 0}, + {"SetNonblock", Func, 0}, + {"Setdomainname", Func, 0}, + {"Setegid", Func, 0}, + {"Setenv", Func, 0}, + {"Seteuid", Func, 0}, + {"Setfsgid", Func, 0}, + {"Setfsuid", Func, 0}, + {"Setgid", Func, 0}, + {"Setgroups", Func, 0}, + {"Sethostname", Func, 0}, + {"Setlogin", Func, 0}, + {"Setpgid", Func, 0}, + {"Setpriority", Func, 0}, + {"Setprivexec", Func, 0}, + {"Setregid", Func, 0}, + {"Setresgid", Func, 0}, + {"Setresuid", Func, 0}, + {"Setreuid", Func, 0}, + {"Setrlimit", Func, 0}, + {"Setsid", Func, 0}, + {"Setsockopt", Func, 0}, + {"SetsockoptByte", Func, 0}, + {"SetsockoptICMPv6Filter", Func, 2}, + {"SetsockoptIPMreq", Func, 0}, + {"SetsockoptIPMreqn", Func, 0}, + {"SetsockoptIPv6Mreq", Func, 0}, + {"SetsockoptInet4Addr", Func, 0}, + {"SetsockoptInt", Func, 0}, + {"SetsockoptLinger", Func, 0}, + {"SetsockoptString", Func, 0}, + {"SetsockoptTimeval", Func, 0}, + {"Settimeofday", Func, 0}, + {"Setuid", Func, 0}, + {"Setxattr", Func, 1}, + {"Shutdown", Func, 0}, + {"SidTypeAlias", Const, 0}, + {"SidTypeComputer", Const, 0}, + {"SidTypeDeletedAccount", Const, 0}, + {"SidTypeDomain", Const, 0}, + {"SidTypeGroup", Const, 0}, + {"SidTypeInvalid", Const, 0}, + {"SidTypeLabel", Const, 0}, + {"SidTypeUnknown", Const, 0}, + {"SidTypeUser", Const, 0}, + {"SidTypeWellKnownGroup", Const, 0}, + {"Signal", Type, 0}, + {"SizeofBpfHdr", Const, 0}, + {"SizeofBpfInsn", Const, 0}, + {"SizeofBpfProgram", Const, 0}, + {"SizeofBpfStat", Const, 0}, + {"SizeofBpfVersion", Const, 0}, + {"SizeofBpfZbuf", Const, 0}, + {"SizeofBpfZbufHeader", Const, 0}, + {"SizeofCmsghdr", Const, 0}, + {"SizeofICMPv6Filter", Const, 2}, + {"SizeofIPMreq", Const, 0}, + {"SizeofIPMreqn", Const, 0}, + {"SizeofIPv6MTUInfo", Const, 2}, + {"SizeofIPv6Mreq", Const, 0}, + {"SizeofIfAddrmsg", Const, 0}, + {"SizeofIfAnnounceMsghdr", Const, 1}, + {"SizeofIfData", Const, 0}, + {"SizeofIfInfomsg", Const, 0}, + {"SizeofIfMsghdr", Const, 0}, + {"SizeofIfaMsghdr", Const, 0}, + {"SizeofIfmaMsghdr", Const, 0}, + {"SizeofIfmaMsghdr2", Const, 0}, + {"SizeofInet4Pktinfo", Const, 0}, + {"SizeofInet6Pktinfo", Const, 0}, + {"SizeofInotifyEvent", Const, 0}, + {"SizeofLinger", Const, 0}, + {"SizeofMsghdr", Const, 0}, + {"SizeofNlAttr", Const, 0}, + {"SizeofNlMsgerr", Const, 0}, + {"SizeofNlMsghdr", Const, 0}, + {"SizeofRtAttr", Const, 0}, + {"SizeofRtGenmsg", Const, 0}, + {"SizeofRtMetrics", Const, 0}, + {"SizeofRtMsg", Const, 0}, + {"SizeofRtMsghdr", Const, 0}, + {"SizeofRtNexthop", Const, 0}, + {"SizeofSockFilter", Const, 0}, + {"SizeofSockFprog", Const, 0}, + {"SizeofSockaddrAny", Const, 0}, + {"SizeofSockaddrDatalink", Const, 0}, + {"SizeofSockaddrInet4", Const, 0}, + {"SizeofSockaddrInet6", Const, 0}, + {"SizeofSockaddrLinklayer", Const, 0}, + {"SizeofSockaddrNetlink", Const, 0}, + {"SizeofSockaddrUnix", Const, 0}, + {"SizeofTCPInfo", Const, 1}, + {"SizeofUcred", Const, 0}, + {"SlicePtrFromStrings", Func, 1}, + {"SockFilter", Type, 0}, + {"SockFilter.Code", Field, 0}, + {"SockFilter.Jf", Field, 0}, + {"SockFilter.Jt", Field, 0}, + {"SockFilter.K", Field, 0}, + {"SockFprog", Type, 0}, + {"SockFprog.Filter", Field, 0}, + {"SockFprog.Len", Field, 0}, + {"SockFprog.Pad_cgo_0", Field, 0}, + {"Sockaddr", Type, 0}, + {"SockaddrDatalink", Type, 0}, + {"SockaddrDatalink.Alen", Field, 0}, + {"SockaddrDatalink.Data", Field, 0}, + {"SockaddrDatalink.Family", Field, 0}, + {"SockaddrDatalink.Index", Field, 0}, + {"SockaddrDatalink.Len", Field, 0}, + {"SockaddrDatalink.Nlen", Field, 0}, + {"SockaddrDatalink.Slen", Field, 0}, + {"SockaddrDatalink.Type", Field, 0}, + {"SockaddrGen", Type, 0}, + {"SockaddrInet4", Type, 0}, + {"SockaddrInet4.Addr", Field, 0}, + {"SockaddrInet4.Port", Field, 0}, + {"SockaddrInet6", Type, 0}, + {"SockaddrInet6.Addr", Field, 0}, + {"SockaddrInet6.Port", Field, 0}, + {"SockaddrInet6.ZoneId", Field, 0}, + {"SockaddrLinklayer", Type, 0}, + {"SockaddrLinklayer.Addr", Field, 0}, + {"SockaddrLinklayer.Halen", Field, 0}, + {"SockaddrLinklayer.Hatype", Field, 0}, + {"SockaddrLinklayer.Ifindex", Field, 0}, + {"SockaddrLinklayer.Pkttype", Field, 0}, + {"SockaddrLinklayer.Protocol", Field, 0}, + {"SockaddrNetlink", Type, 0}, + {"SockaddrNetlink.Family", Field, 0}, + {"SockaddrNetlink.Groups", Field, 0}, + {"SockaddrNetlink.Pad", Field, 0}, + {"SockaddrNetlink.Pid", Field, 0}, + {"SockaddrUnix", Type, 0}, + {"SockaddrUnix.Name", Field, 0}, + {"Socket", Func, 0}, + {"SocketControlMessage", Type, 0}, + {"SocketControlMessage.Data", Field, 0}, + {"SocketControlMessage.Header", Field, 0}, + {"SocketDisableIPv6", Var, 0}, + {"Socketpair", Func, 0}, + {"Splice", Func, 0}, + {"StartProcess", Func, 0}, + {"StartupInfo", Type, 0}, + {"StartupInfo.Cb", Field, 0}, + {"StartupInfo.Desktop", Field, 0}, + {"StartupInfo.FillAttribute", Field, 0}, + {"StartupInfo.Flags", Field, 0}, + {"StartupInfo.ShowWindow", Field, 0}, + {"StartupInfo.StdErr", Field, 0}, + {"StartupInfo.StdInput", Field, 0}, + {"StartupInfo.StdOutput", Field, 0}, + {"StartupInfo.Title", Field, 0}, + {"StartupInfo.X", Field, 0}, + {"StartupInfo.XCountChars", Field, 0}, + {"StartupInfo.XSize", Field, 0}, + {"StartupInfo.Y", Field, 0}, + {"StartupInfo.YCountChars", Field, 0}, + {"StartupInfo.YSize", Field, 0}, + {"Stat", Func, 0}, + {"Stat_t", Type, 0}, + {"Stat_t.Atim", Field, 0}, + {"Stat_t.Atim_ext", Field, 12}, + {"Stat_t.Atimespec", Field, 0}, + {"Stat_t.Birthtimespec", Field, 0}, + {"Stat_t.Blksize", Field, 0}, + {"Stat_t.Blocks", Field, 0}, + {"Stat_t.Btim_ext", Field, 12}, + {"Stat_t.Ctim", Field, 0}, + {"Stat_t.Ctim_ext", Field, 12}, + {"Stat_t.Ctimespec", Field, 0}, + {"Stat_t.Dev", Field, 0}, + {"Stat_t.Flags", Field, 0}, + {"Stat_t.Gen", Field, 0}, + {"Stat_t.Gid", Field, 0}, + {"Stat_t.Ino", Field, 0}, + {"Stat_t.Lspare", Field, 0}, + {"Stat_t.Lspare0", Field, 2}, + {"Stat_t.Lspare1", Field, 2}, + {"Stat_t.Mode", Field, 0}, + {"Stat_t.Mtim", Field, 0}, + {"Stat_t.Mtim_ext", Field, 12}, + {"Stat_t.Mtimespec", Field, 0}, + {"Stat_t.Nlink", Field, 0}, + {"Stat_t.Pad_cgo_0", Field, 0}, + {"Stat_t.Pad_cgo_1", Field, 0}, + {"Stat_t.Pad_cgo_2", Field, 0}, + {"Stat_t.Padding0", Field, 12}, + {"Stat_t.Padding1", Field, 12}, + {"Stat_t.Qspare", Field, 0}, + {"Stat_t.Rdev", Field, 0}, + {"Stat_t.Size", Field, 0}, + {"Stat_t.Spare", Field, 2}, + {"Stat_t.Uid", Field, 0}, + {"Stat_t.X__pad0", Field, 0}, + {"Stat_t.X__pad1", Field, 0}, + {"Stat_t.X__pad2", Field, 0}, + {"Stat_t.X__st_birthtim", Field, 2}, + {"Stat_t.X__st_ino", Field, 0}, + {"Stat_t.X__unused", Field, 0}, + {"Statfs", Func, 0}, + {"Statfs_t", Type, 0}, + {"Statfs_t.Asyncreads", Field, 0}, + {"Statfs_t.Asyncwrites", Field, 0}, + {"Statfs_t.Bavail", Field, 0}, + {"Statfs_t.Bfree", Field, 0}, + {"Statfs_t.Blocks", Field, 0}, + {"Statfs_t.Bsize", Field, 0}, + {"Statfs_t.Charspare", Field, 0}, + {"Statfs_t.F_asyncreads", Field, 2}, + {"Statfs_t.F_asyncwrites", Field, 2}, + {"Statfs_t.F_bavail", Field, 2}, + {"Statfs_t.F_bfree", Field, 2}, + {"Statfs_t.F_blocks", Field, 2}, + {"Statfs_t.F_bsize", Field, 2}, + {"Statfs_t.F_ctime", Field, 2}, + {"Statfs_t.F_favail", Field, 2}, + {"Statfs_t.F_ffree", Field, 2}, + {"Statfs_t.F_files", Field, 2}, + {"Statfs_t.F_flags", Field, 2}, + {"Statfs_t.F_fsid", Field, 2}, + {"Statfs_t.F_fstypename", Field, 2}, + {"Statfs_t.F_iosize", Field, 2}, + {"Statfs_t.F_mntfromname", Field, 2}, + {"Statfs_t.F_mntfromspec", Field, 3}, + {"Statfs_t.F_mntonname", Field, 2}, + {"Statfs_t.F_namemax", Field, 2}, + {"Statfs_t.F_owner", Field, 2}, + {"Statfs_t.F_spare", Field, 2}, + {"Statfs_t.F_syncreads", Field, 2}, + {"Statfs_t.F_syncwrites", Field, 2}, + {"Statfs_t.Ffree", Field, 0}, + {"Statfs_t.Files", Field, 0}, + {"Statfs_t.Flags", Field, 0}, + {"Statfs_t.Frsize", Field, 0}, + {"Statfs_t.Fsid", Field, 0}, + {"Statfs_t.Fssubtype", Field, 0}, + {"Statfs_t.Fstypename", Field, 0}, + {"Statfs_t.Iosize", Field, 0}, + {"Statfs_t.Mntfromname", Field, 0}, + {"Statfs_t.Mntonname", Field, 0}, + {"Statfs_t.Mount_info", Field, 2}, + {"Statfs_t.Namelen", Field, 0}, + {"Statfs_t.Namemax", Field, 0}, + {"Statfs_t.Owner", Field, 0}, + {"Statfs_t.Pad_cgo_0", Field, 0}, + {"Statfs_t.Pad_cgo_1", Field, 2}, + {"Statfs_t.Reserved", Field, 0}, + {"Statfs_t.Spare", Field, 0}, + {"Statfs_t.Syncreads", Field, 0}, + {"Statfs_t.Syncwrites", Field, 0}, + {"Statfs_t.Type", Field, 0}, + {"Statfs_t.Version", Field, 0}, + {"Stderr", Var, 0}, + {"Stdin", Var, 0}, + {"Stdout", Var, 0}, + {"StringBytePtr", Func, 0}, + {"StringByteSlice", Func, 0}, + {"StringSlicePtr", Func, 0}, + {"StringToSid", Func, 0}, + {"StringToUTF16", Func, 0}, + {"StringToUTF16Ptr", Func, 0}, + {"Symlink", Func, 0}, + {"Sync", Func, 0}, + {"SyncFileRange", Func, 0}, + {"SysProcAttr", Type, 0}, + {"SysProcAttr.AdditionalInheritedHandles", Field, 17}, + {"SysProcAttr.AmbientCaps", Field, 9}, + {"SysProcAttr.CgroupFD", Field, 20}, + {"SysProcAttr.Chroot", Field, 0}, + {"SysProcAttr.Cloneflags", Field, 2}, + {"SysProcAttr.CmdLine", Field, 0}, + {"SysProcAttr.CreationFlags", Field, 1}, + {"SysProcAttr.Credential", Field, 0}, + {"SysProcAttr.Ctty", Field, 1}, + {"SysProcAttr.Foreground", Field, 5}, + {"SysProcAttr.GidMappings", Field, 4}, + {"SysProcAttr.GidMappingsEnableSetgroups", Field, 5}, + {"SysProcAttr.HideWindow", Field, 0}, + {"SysProcAttr.Jail", Field, 21}, + {"SysProcAttr.NoInheritHandles", Field, 16}, + {"SysProcAttr.Noctty", Field, 0}, + {"SysProcAttr.ParentProcess", Field, 17}, + {"SysProcAttr.Pdeathsig", Field, 0}, + {"SysProcAttr.Pgid", Field, 5}, + {"SysProcAttr.PidFD", Field, 22}, + {"SysProcAttr.ProcessAttributes", Field, 13}, + {"SysProcAttr.Ptrace", Field, 0}, + {"SysProcAttr.Setctty", Field, 0}, + {"SysProcAttr.Setpgid", Field, 0}, + {"SysProcAttr.Setsid", Field, 0}, + {"SysProcAttr.ThreadAttributes", Field, 13}, + {"SysProcAttr.Token", Field, 10}, + {"SysProcAttr.UidMappings", Field, 4}, + {"SysProcAttr.Unshareflags", Field, 7}, + {"SysProcAttr.UseCgroupFD", Field, 20}, + {"SysProcIDMap", Type, 4}, + {"SysProcIDMap.ContainerID", Field, 4}, + {"SysProcIDMap.HostID", Field, 4}, + {"SysProcIDMap.Size", Field, 4}, + {"Syscall", Func, 0}, + {"Syscall12", Func, 0}, + {"Syscall15", Func, 0}, + {"Syscall18", Func, 12}, + {"Syscall6", Func, 0}, + {"Syscall9", Func, 0}, + {"SyscallN", Func, 18}, + {"Sysctl", Func, 0}, + {"SysctlUint32", Func, 0}, + {"Sysctlnode", Type, 2}, + {"Sysctlnode.Flags", Field, 2}, + {"Sysctlnode.Name", Field, 2}, + {"Sysctlnode.Num", Field, 2}, + {"Sysctlnode.Un", Field, 2}, + {"Sysctlnode.Ver", Field, 2}, + {"Sysctlnode.X__rsvd", Field, 2}, + {"Sysctlnode.X_sysctl_desc", Field, 2}, + {"Sysctlnode.X_sysctl_func", Field, 2}, + {"Sysctlnode.X_sysctl_parent", Field, 2}, + {"Sysctlnode.X_sysctl_size", Field, 2}, + {"Sysinfo", Func, 0}, + {"Sysinfo_t", Type, 0}, + {"Sysinfo_t.Bufferram", Field, 0}, + {"Sysinfo_t.Freehigh", Field, 0}, + {"Sysinfo_t.Freeram", Field, 0}, + {"Sysinfo_t.Freeswap", Field, 0}, + {"Sysinfo_t.Loads", Field, 0}, + {"Sysinfo_t.Pad", Field, 0}, + {"Sysinfo_t.Pad_cgo_0", Field, 0}, + {"Sysinfo_t.Pad_cgo_1", Field, 0}, + {"Sysinfo_t.Procs", Field, 0}, + {"Sysinfo_t.Sharedram", Field, 0}, + {"Sysinfo_t.Totalhigh", Field, 0}, + {"Sysinfo_t.Totalram", Field, 0}, + {"Sysinfo_t.Totalswap", Field, 0}, + {"Sysinfo_t.Unit", Field, 0}, + {"Sysinfo_t.Uptime", Field, 0}, + {"Sysinfo_t.X_f", Field, 0}, + {"Systemtime", Type, 0}, + {"Systemtime.Day", Field, 0}, + {"Systemtime.DayOfWeek", Field, 0}, + {"Systemtime.Hour", Field, 0}, + {"Systemtime.Milliseconds", Field, 0}, + {"Systemtime.Minute", Field, 0}, + {"Systemtime.Month", Field, 0}, + {"Systemtime.Second", Field, 0}, + {"Systemtime.Year", Field, 0}, + {"TCGETS", Const, 0}, + {"TCIFLUSH", Const, 1}, + {"TCIOFLUSH", Const, 1}, + {"TCOFLUSH", Const, 1}, + {"TCPInfo", Type, 1}, + {"TCPInfo.Advmss", Field, 1}, + {"TCPInfo.Ato", Field, 1}, + {"TCPInfo.Backoff", Field, 1}, + {"TCPInfo.Ca_state", Field, 1}, + {"TCPInfo.Fackets", Field, 1}, + {"TCPInfo.Last_ack_recv", Field, 1}, + {"TCPInfo.Last_ack_sent", Field, 1}, + {"TCPInfo.Last_data_recv", Field, 1}, + {"TCPInfo.Last_data_sent", Field, 1}, + {"TCPInfo.Lost", Field, 1}, + {"TCPInfo.Options", Field, 1}, + {"TCPInfo.Pad_cgo_0", Field, 1}, + {"TCPInfo.Pmtu", Field, 1}, + {"TCPInfo.Probes", Field, 1}, + {"TCPInfo.Rcv_mss", Field, 1}, + {"TCPInfo.Rcv_rtt", Field, 1}, + {"TCPInfo.Rcv_space", Field, 1}, + {"TCPInfo.Rcv_ssthresh", Field, 1}, + {"TCPInfo.Reordering", Field, 1}, + {"TCPInfo.Retrans", Field, 1}, + {"TCPInfo.Retransmits", Field, 1}, + {"TCPInfo.Rto", Field, 1}, + {"TCPInfo.Rtt", Field, 1}, + {"TCPInfo.Rttvar", Field, 1}, + {"TCPInfo.Sacked", Field, 1}, + {"TCPInfo.Snd_cwnd", Field, 1}, + {"TCPInfo.Snd_mss", Field, 1}, + {"TCPInfo.Snd_ssthresh", Field, 1}, + {"TCPInfo.State", Field, 1}, + {"TCPInfo.Total_retrans", Field, 1}, + {"TCPInfo.Unacked", Field, 1}, + {"TCPKeepalive", Type, 3}, + {"TCPKeepalive.Interval", Field, 3}, + {"TCPKeepalive.OnOff", Field, 3}, + {"TCPKeepalive.Time", Field, 3}, + {"TCP_CA_NAME_MAX", Const, 0}, + {"TCP_CONGCTL", Const, 1}, + {"TCP_CONGESTION", Const, 0}, + {"TCP_CONNECTIONTIMEOUT", Const, 0}, + {"TCP_CORK", Const, 0}, + {"TCP_DEFER_ACCEPT", Const, 0}, + {"TCP_ENABLE_ECN", Const, 16}, + {"TCP_INFO", Const, 0}, + {"TCP_KEEPALIVE", Const, 0}, + {"TCP_KEEPCNT", Const, 0}, + {"TCP_KEEPIDLE", Const, 0}, + {"TCP_KEEPINIT", Const, 1}, + {"TCP_KEEPINTVL", Const, 0}, + {"TCP_LINGER2", Const, 0}, + {"TCP_MAXBURST", Const, 0}, + {"TCP_MAXHLEN", Const, 0}, + {"TCP_MAXOLEN", Const, 0}, + {"TCP_MAXSEG", Const, 0}, + {"TCP_MAXWIN", Const, 0}, + {"TCP_MAX_SACK", Const, 0}, + {"TCP_MAX_WINSHIFT", Const, 0}, + {"TCP_MD5SIG", Const, 0}, + {"TCP_MD5SIG_MAXKEYLEN", Const, 0}, + {"TCP_MINMSS", Const, 0}, + {"TCP_MINMSSOVERLOAD", Const, 0}, + {"TCP_MSS", Const, 0}, + {"TCP_NODELAY", Const, 0}, + {"TCP_NOOPT", Const, 0}, + {"TCP_NOPUSH", Const, 0}, + {"TCP_NOTSENT_LOWAT", Const, 16}, + {"TCP_NSTATES", Const, 1}, + {"TCP_QUICKACK", Const, 0}, + {"TCP_RXT_CONNDROPTIME", Const, 0}, + {"TCP_RXT_FINDROP", Const, 0}, + {"TCP_SACK_ENABLE", Const, 1}, + {"TCP_SENDMOREACKS", Const, 16}, + {"TCP_SYNCNT", Const, 0}, + {"TCP_VENDOR", Const, 3}, + {"TCP_WINDOW_CLAMP", Const, 0}, + {"TCSAFLUSH", Const, 1}, + {"TCSETS", Const, 0}, + {"TF_DISCONNECT", Const, 0}, + {"TF_REUSE_SOCKET", Const, 0}, + {"TF_USE_DEFAULT_WORKER", Const, 0}, + {"TF_USE_KERNEL_APC", Const, 0}, + {"TF_USE_SYSTEM_THREAD", Const, 0}, + {"TF_WRITE_BEHIND", Const, 0}, + {"TH32CS_INHERIT", Const, 4}, + {"TH32CS_SNAPALL", Const, 4}, + {"TH32CS_SNAPHEAPLIST", Const, 4}, + {"TH32CS_SNAPMODULE", Const, 4}, + {"TH32CS_SNAPMODULE32", Const, 4}, + {"TH32CS_SNAPPROCESS", Const, 4}, + {"TH32CS_SNAPTHREAD", Const, 4}, + {"TIME_ZONE_ID_DAYLIGHT", Const, 0}, + {"TIME_ZONE_ID_STANDARD", Const, 0}, + {"TIME_ZONE_ID_UNKNOWN", Const, 0}, + {"TIOCCBRK", Const, 0}, + {"TIOCCDTR", Const, 0}, + {"TIOCCONS", Const, 0}, + {"TIOCDCDTIMESTAMP", Const, 0}, + {"TIOCDRAIN", Const, 0}, + {"TIOCDSIMICROCODE", Const, 0}, + {"TIOCEXCL", Const, 0}, + {"TIOCEXT", Const, 0}, + {"TIOCFLAG_CDTRCTS", Const, 1}, + {"TIOCFLAG_CLOCAL", Const, 1}, + {"TIOCFLAG_CRTSCTS", Const, 1}, + {"TIOCFLAG_MDMBUF", Const, 1}, + {"TIOCFLAG_PPS", Const, 1}, + {"TIOCFLAG_SOFTCAR", Const, 1}, + {"TIOCFLUSH", Const, 0}, + {"TIOCGDEV", Const, 0}, + {"TIOCGDRAINWAIT", Const, 0}, + {"TIOCGETA", Const, 0}, + {"TIOCGETD", Const, 0}, + {"TIOCGFLAGS", Const, 1}, + {"TIOCGICOUNT", Const, 0}, + {"TIOCGLCKTRMIOS", Const, 0}, + {"TIOCGLINED", Const, 1}, + {"TIOCGPGRP", Const, 0}, + {"TIOCGPTN", Const, 0}, + {"TIOCGQSIZE", Const, 1}, + {"TIOCGRANTPT", Const, 1}, + {"TIOCGRS485", Const, 0}, + {"TIOCGSERIAL", Const, 0}, + {"TIOCGSID", Const, 0}, + {"TIOCGSIZE", Const, 1}, + {"TIOCGSOFTCAR", Const, 0}, + {"TIOCGTSTAMP", Const, 1}, + {"TIOCGWINSZ", Const, 0}, + {"TIOCINQ", Const, 0}, + {"TIOCIXOFF", Const, 0}, + {"TIOCIXON", Const, 0}, + {"TIOCLINUX", Const, 0}, + {"TIOCMBIC", Const, 0}, + {"TIOCMBIS", Const, 0}, + {"TIOCMGDTRWAIT", Const, 0}, + {"TIOCMGET", Const, 0}, + {"TIOCMIWAIT", Const, 0}, + {"TIOCMODG", Const, 0}, + {"TIOCMODS", Const, 0}, + {"TIOCMSDTRWAIT", Const, 0}, + {"TIOCMSET", Const, 0}, + {"TIOCM_CAR", Const, 0}, + {"TIOCM_CD", Const, 0}, + {"TIOCM_CTS", Const, 0}, + {"TIOCM_DCD", Const, 0}, + {"TIOCM_DSR", Const, 0}, + {"TIOCM_DTR", Const, 0}, + {"TIOCM_LE", Const, 0}, + {"TIOCM_RI", Const, 0}, + {"TIOCM_RNG", Const, 0}, + {"TIOCM_RTS", Const, 0}, + {"TIOCM_SR", Const, 0}, + {"TIOCM_ST", Const, 0}, + {"TIOCNOTTY", Const, 0}, + {"TIOCNXCL", Const, 0}, + {"TIOCOUTQ", Const, 0}, + {"TIOCPKT", Const, 0}, + {"TIOCPKT_DATA", Const, 0}, + {"TIOCPKT_DOSTOP", Const, 0}, + {"TIOCPKT_FLUSHREAD", Const, 0}, + {"TIOCPKT_FLUSHWRITE", Const, 0}, + {"TIOCPKT_IOCTL", Const, 0}, + {"TIOCPKT_NOSTOP", Const, 0}, + {"TIOCPKT_START", Const, 0}, + {"TIOCPKT_STOP", Const, 0}, + {"TIOCPTMASTER", Const, 0}, + {"TIOCPTMGET", Const, 1}, + {"TIOCPTSNAME", Const, 1}, + {"TIOCPTYGNAME", Const, 0}, + {"TIOCPTYGRANT", Const, 0}, + {"TIOCPTYUNLK", Const, 0}, + {"TIOCRCVFRAME", Const, 1}, + {"TIOCREMOTE", Const, 0}, + {"TIOCSBRK", Const, 0}, + {"TIOCSCONS", Const, 0}, + {"TIOCSCTTY", Const, 0}, + {"TIOCSDRAINWAIT", Const, 0}, + {"TIOCSDTR", Const, 0}, + {"TIOCSERCONFIG", Const, 0}, + {"TIOCSERGETLSR", Const, 0}, + {"TIOCSERGETMULTI", Const, 0}, + {"TIOCSERGSTRUCT", Const, 0}, + {"TIOCSERGWILD", Const, 0}, + {"TIOCSERSETMULTI", Const, 0}, + {"TIOCSERSWILD", Const, 0}, + {"TIOCSER_TEMT", Const, 0}, + {"TIOCSETA", Const, 0}, + {"TIOCSETAF", Const, 0}, + {"TIOCSETAW", Const, 0}, + {"TIOCSETD", Const, 0}, + {"TIOCSFLAGS", Const, 1}, + {"TIOCSIG", Const, 0}, + {"TIOCSLCKTRMIOS", Const, 0}, + {"TIOCSLINED", Const, 1}, + {"TIOCSPGRP", Const, 0}, + {"TIOCSPTLCK", Const, 0}, + {"TIOCSQSIZE", Const, 1}, + {"TIOCSRS485", Const, 0}, + {"TIOCSSERIAL", Const, 0}, + {"TIOCSSIZE", Const, 1}, + {"TIOCSSOFTCAR", Const, 0}, + {"TIOCSTART", Const, 0}, + {"TIOCSTAT", Const, 0}, + {"TIOCSTI", Const, 0}, + {"TIOCSTOP", Const, 0}, + {"TIOCSTSTAMP", Const, 1}, + {"TIOCSWINSZ", Const, 0}, + {"TIOCTIMESTAMP", Const, 0}, + {"TIOCUCNTL", Const, 0}, + {"TIOCVHANGUP", Const, 0}, + {"TIOCXMTFRAME", Const, 1}, + {"TOKEN_ADJUST_DEFAULT", Const, 0}, + {"TOKEN_ADJUST_GROUPS", Const, 0}, + {"TOKEN_ADJUST_PRIVILEGES", Const, 0}, + {"TOKEN_ADJUST_SESSIONID", Const, 11}, + {"TOKEN_ALL_ACCESS", Const, 0}, + {"TOKEN_ASSIGN_PRIMARY", Const, 0}, + {"TOKEN_DUPLICATE", Const, 0}, + {"TOKEN_EXECUTE", Const, 0}, + {"TOKEN_IMPERSONATE", Const, 0}, + {"TOKEN_QUERY", Const, 0}, + {"TOKEN_QUERY_SOURCE", Const, 0}, + {"TOKEN_READ", Const, 0}, + {"TOKEN_WRITE", Const, 0}, + {"TOSTOP", Const, 0}, + {"TRUNCATE_EXISTING", Const, 0}, + {"TUNATTACHFILTER", Const, 0}, + {"TUNDETACHFILTER", Const, 0}, + {"TUNGETFEATURES", Const, 0}, + {"TUNGETIFF", Const, 0}, + {"TUNGETSNDBUF", Const, 0}, + {"TUNGETVNETHDRSZ", Const, 0}, + {"TUNSETDEBUG", Const, 0}, + {"TUNSETGROUP", Const, 0}, + {"TUNSETIFF", Const, 0}, + {"TUNSETLINK", Const, 0}, + {"TUNSETNOCSUM", Const, 0}, + {"TUNSETOFFLOAD", Const, 0}, + {"TUNSETOWNER", Const, 0}, + {"TUNSETPERSIST", Const, 0}, + {"TUNSETSNDBUF", Const, 0}, + {"TUNSETTXFILTER", Const, 0}, + {"TUNSETVNETHDRSZ", Const, 0}, + {"Tee", Func, 0}, + {"TerminateProcess", Func, 0}, + {"Termios", Type, 0}, + {"Termios.Cc", Field, 0}, + {"Termios.Cflag", Field, 0}, + {"Termios.Iflag", Field, 0}, + {"Termios.Ispeed", Field, 0}, + {"Termios.Lflag", Field, 0}, + {"Termios.Line", Field, 0}, + {"Termios.Oflag", Field, 0}, + {"Termios.Ospeed", Field, 0}, + {"Termios.Pad_cgo_0", Field, 0}, + {"Tgkill", Func, 0}, + {"Time", Func, 0}, + {"Time_t", Type, 0}, + {"Times", Func, 0}, + {"Timespec", Type, 0}, + {"Timespec.Nsec", Field, 0}, + {"Timespec.Pad_cgo_0", Field, 2}, + {"Timespec.Sec", Field, 0}, + {"TimespecToNsec", Func, 0}, + {"Timeval", Type, 0}, + {"Timeval.Pad_cgo_0", Field, 0}, + {"Timeval.Sec", Field, 0}, + {"Timeval.Usec", Field, 0}, + {"Timeval32", Type, 0}, + {"Timeval32.Sec", Field, 0}, + {"Timeval32.Usec", Field, 0}, + {"TimevalToNsec", Func, 0}, + {"Timex", Type, 0}, + {"Timex.Calcnt", Field, 0}, + {"Timex.Constant", Field, 0}, + {"Timex.Errcnt", Field, 0}, + {"Timex.Esterror", Field, 0}, + {"Timex.Freq", Field, 0}, + {"Timex.Jitcnt", Field, 0}, + {"Timex.Jitter", Field, 0}, + {"Timex.Maxerror", Field, 0}, + {"Timex.Modes", Field, 0}, + {"Timex.Offset", Field, 0}, + {"Timex.Pad_cgo_0", Field, 0}, + {"Timex.Pad_cgo_1", Field, 0}, + {"Timex.Pad_cgo_2", Field, 0}, + {"Timex.Pad_cgo_3", Field, 0}, + {"Timex.Ppsfreq", Field, 0}, + {"Timex.Precision", Field, 0}, + {"Timex.Shift", Field, 0}, + {"Timex.Stabil", Field, 0}, + {"Timex.Status", Field, 0}, + {"Timex.Stbcnt", Field, 0}, + {"Timex.Tai", Field, 0}, + {"Timex.Tick", Field, 0}, + {"Timex.Time", Field, 0}, + {"Timex.Tolerance", Field, 0}, + {"Timezoneinformation", Type, 0}, + {"Timezoneinformation.Bias", Field, 0}, + {"Timezoneinformation.DaylightBias", Field, 0}, + {"Timezoneinformation.DaylightDate", Field, 0}, + {"Timezoneinformation.DaylightName", Field, 0}, + {"Timezoneinformation.StandardBias", Field, 0}, + {"Timezoneinformation.StandardDate", Field, 0}, + {"Timezoneinformation.StandardName", Field, 0}, + {"Tms", Type, 0}, + {"Tms.Cstime", Field, 0}, + {"Tms.Cutime", Field, 0}, + {"Tms.Stime", Field, 0}, + {"Tms.Utime", Field, 0}, + {"Token", Type, 0}, + {"TokenAccessInformation", Const, 0}, + {"TokenAuditPolicy", Const, 0}, + {"TokenDefaultDacl", Const, 0}, + {"TokenElevation", Const, 0}, + {"TokenElevationType", Const, 0}, + {"TokenGroups", Const, 0}, + {"TokenGroupsAndPrivileges", Const, 0}, + {"TokenHasRestrictions", Const, 0}, + {"TokenImpersonationLevel", Const, 0}, + {"TokenIntegrityLevel", Const, 0}, + {"TokenLinkedToken", Const, 0}, + {"TokenLogonSid", Const, 0}, + {"TokenMandatoryPolicy", Const, 0}, + {"TokenOrigin", Const, 0}, + {"TokenOwner", Const, 0}, + {"TokenPrimaryGroup", Const, 0}, + {"TokenPrivileges", Const, 0}, + {"TokenRestrictedSids", Const, 0}, + {"TokenSandBoxInert", Const, 0}, + {"TokenSessionId", Const, 0}, + {"TokenSessionReference", Const, 0}, + {"TokenSource", Const, 0}, + {"TokenStatistics", Const, 0}, + {"TokenType", Const, 0}, + {"TokenUIAccess", Const, 0}, + {"TokenUser", Const, 0}, + {"TokenVirtualizationAllowed", Const, 0}, + {"TokenVirtualizationEnabled", Const, 0}, + {"Tokenprimarygroup", Type, 0}, + {"Tokenprimarygroup.PrimaryGroup", Field, 0}, + {"Tokenuser", Type, 0}, + {"Tokenuser.User", Field, 0}, + {"TranslateAccountName", Func, 0}, + {"TranslateName", Func, 0}, + {"TransmitFile", Func, 0}, + {"TransmitFileBuffers", Type, 0}, + {"TransmitFileBuffers.Head", Field, 0}, + {"TransmitFileBuffers.HeadLength", Field, 0}, + {"TransmitFileBuffers.Tail", Field, 0}, + {"TransmitFileBuffers.TailLength", Field, 0}, + {"Truncate", Func, 0}, + {"UNIX_PATH_MAX", Const, 12}, + {"USAGE_MATCH_TYPE_AND", Const, 0}, + {"USAGE_MATCH_TYPE_OR", Const, 0}, + {"UTF16FromString", Func, 1}, + {"UTF16PtrFromString", Func, 1}, + {"UTF16ToString", Func, 0}, + {"Ucred", Type, 0}, + {"Ucred.Gid", Field, 0}, + {"Ucred.Pid", Field, 0}, + {"Ucred.Uid", Field, 0}, + {"Umask", Func, 0}, + {"Uname", Func, 0}, + {"Undelete", Func, 0}, + {"UnixCredentials", Func, 0}, + {"UnixRights", Func, 0}, + {"Unlink", Func, 0}, + {"Unlinkat", Func, 0}, + {"UnmapViewOfFile", Func, 0}, + {"Unmount", Func, 0}, + {"Unsetenv", Func, 4}, + {"Unshare", Func, 0}, + {"UserInfo10", Type, 0}, + {"UserInfo10.Comment", Field, 0}, + {"UserInfo10.FullName", Field, 0}, + {"UserInfo10.Name", Field, 0}, + {"UserInfo10.UsrComment", Field, 0}, + {"Ustat", Func, 0}, + {"Ustat_t", Type, 0}, + {"Ustat_t.Fname", Field, 0}, + {"Ustat_t.Fpack", Field, 0}, + {"Ustat_t.Pad_cgo_0", Field, 0}, + {"Ustat_t.Pad_cgo_1", Field, 0}, + {"Ustat_t.Tfree", Field, 0}, + {"Ustat_t.Tinode", Field, 0}, + {"Utimbuf", Type, 0}, + {"Utimbuf.Actime", Field, 0}, + {"Utimbuf.Modtime", Field, 0}, + {"Utime", Func, 0}, + {"Utimes", Func, 0}, + {"UtimesNano", Func, 1}, + {"Utsname", Type, 0}, + {"Utsname.Domainname", Field, 0}, + {"Utsname.Machine", Field, 0}, + {"Utsname.Nodename", Field, 0}, + {"Utsname.Release", Field, 0}, + {"Utsname.Sysname", Field, 0}, + {"Utsname.Version", Field, 0}, + {"VDISCARD", Const, 0}, + {"VDSUSP", Const, 1}, + {"VEOF", Const, 0}, + {"VEOL", Const, 0}, + {"VEOL2", Const, 0}, + {"VERASE", Const, 0}, + {"VERASE2", Const, 1}, + {"VINTR", Const, 0}, + {"VKILL", Const, 0}, + {"VLNEXT", Const, 0}, + {"VMIN", Const, 0}, + {"VQUIT", Const, 0}, + {"VREPRINT", Const, 0}, + {"VSTART", Const, 0}, + {"VSTATUS", Const, 1}, + {"VSTOP", Const, 0}, + {"VSUSP", Const, 0}, + {"VSWTC", Const, 0}, + {"VT0", Const, 1}, + {"VT1", Const, 1}, + {"VTDLY", Const, 1}, + {"VTIME", Const, 0}, + {"VWERASE", Const, 0}, + {"VirtualLock", Func, 0}, + {"VirtualUnlock", Func, 0}, + {"WAIT_ABANDONED", Const, 0}, + {"WAIT_FAILED", Const, 0}, + {"WAIT_OBJECT_0", Const, 0}, + {"WAIT_TIMEOUT", Const, 0}, + {"WALL", Const, 0}, + {"WALLSIG", Const, 1}, + {"WALTSIG", Const, 1}, + {"WCLONE", Const, 0}, + {"WCONTINUED", Const, 0}, + {"WCOREFLAG", Const, 0}, + {"WEXITED", Const, 0}, + {"WLINUXCLONE", Const, 0}, + {"WNOHANG", Const, 0}, + {"WNOTHREAD", Const, 0}, + {"WNOWAIT", Const, 0}, + {"WNOZOMBIE", Const, 1}, + {"WOPTSCHECKED", Const, 1}, + {"WORDSIZE", Const, 0}, + {"WSABuf", Type, 0}, + {"WSABuf.Buf", Field, 0}, + {"WSABuf.Len", Field, 0}, + {"WSACleanup", Func, 0}, + {"WSADESCRIPTION_LEN", Const, 0}, + {"WSAData", Type, 0}, + {"WSAData.Description", Field, 0}, + {"WSAData.HighVersion", Field, 0}, + {"WSAData.MaxSockets", Field, 0}, + {"WSAData.MaxUdpDg", Field, 0}, + {"WSAData.SystemStatus", Field, 0}, + {"WSAData.VendorInfo", Field, 0}, + {"WSAData.Version", Field, 0}, + {"WSAEACCES", Const, 2}, + {"WSAECONNABORTED", Const, 9}, + {"WSAECONNRESET", Const, 3}, + {"WSAENOPROTOOPT", Const, 23}, + {"WSAEnumProtocols", Func, 2}, + {"WSAID_CONNECTEX", Var, 1}, + {"WSAIoctl", Func, 0}, + {"WSAPROTOCOL_LEN", Const, 2}, + {"WSAProtocolChain", Type, 2}, + {"WSAProtocolChain.ChainEntries", Field, 2}, + {"WSAProtocolChain.ChainLen", Field, 2}, + {"WSAProtocolInfo", Type, 2}, + {"WSAProtocolInfo.AddressFamily", Field, 2}, + {"WSAProtocolInfo.CatalogEntryId", Field, 2}, + {"WSAProtocolInfo.MaxSockAddr", Field, 2}, + {"WSAProtocolInfo.MessageSize", Field, 2}, + {"WSAProtocolInfo.MinSockAddr", Field, 2}, + {"WSAProtocolInfo.NetworkByteOrder", Field, 2}, + {"WSAProtocolInfo.Protocol", Field, 2}, + {"WSAProtocolInfo.ProtocolChain", Field, 2}, + {"WSAProtocolInfo.ProtocolMaxOffset", Field, 2}, + {"WSAProtocolInfo.ProtocolName", Field, 2}, + {"WSAProtocolInfo.ProviderFlags", Field, 2}, + {"WSAProtocolInfo.ProviderId", Field, 2}, + {"WSAProtocolInfo.ProviderReserved", Field, 2}, + {"WSAProtocolInfo.SecurityScheme", Field, 2}, + {"WSAProtocolInfo.ServiceFlags1", Field, 2}, + {"WSAProtocolInfo.ServiceFlags2", Field, 2}, + {"WSAProtocolInfo.ServiceFlags3", Field, 2}, + {"WSAProtocolInfo.ServiceFlags4", Field, 2}, + {"WSAProtocolInfo.SocketType", Field, 2}, + {"WSAProtocolInfo.Version", Field, 2}, + {"WSARecv", Func, 0}, + {"WSARecvFrom", Func, 0}, + {"WSASYS_STATUS_LEN", Const, 0}, + {"WSASend", Func, 0}, + {"WSASendTo", Func, 0}, + {"WSASendto", Func, 0}, + {"WSAStartup", Func, 0}, + {"WSTOPPED", Const, 0}, + {"WTRAPPED", Const, 1}, + {"WUNTRACED", Const, 0}, + {"Wait4", Func, 0}, + {"WaitForSingleObject", Func, 0}, + {"WaitStatus", Type, 0}, + {"WaitStatus.ExitCode", Field, 0}, + {"Win32FileAttributeData", Type, 0}, + {"Win32FileAttributeData.CreationTime", Field, 0}, + {"Win32FileAttributeData.FileAttributes", Field, 0}, + {"Win32FileAttributeData.FileSizeHigh", Field, 0}, + {"Win32FileAttributeData.FileSizeLow", Field, 0}, + {"Win32FileAttributeData.LastAccessTime", Field, 0}, + {"Win32FileAttributeData.LastWriteTime", Field, 0}, + {"Win32finddata", Type, 0}, + {"Win32finddata.AlternateFileName", Field, 0}, + {"Win32finddata.CreationTime", Field, 0}, + {"Win32finddata.FileAttributes", Field, 0}, + {"Win32finddata.FileName", Field, 0}, + {"Win32finddata.FileSizeHigh", Field, 0}, + {"Win32finddata.FileSizeLow", Field, 0}, + {"Win32finddata.LastAccessTime", Field, 0}, + {"Win32finddata.LastWriteTime", Field, 0}, + {"Win32finddata.Reserved0", Field, 0}, + {"Win32finddata.Reserved1", Field, 0}, + {"Write", Func, 0}, + {"WriteConsole", Func, 1}, + {"WriteFile", Func, 0}, + {"X509_ASN_ENCODING", Const, 0}, + {"XCASE", Const, 0}, + {"XP1_CONNECTIONLESS", Const, 2}, + {"XP1_CONNECT_DATA", Const, 2}, + {"XP1_DISCONNECT_DATA", Const, 2}, + {"XP1_EXPEDITED_DATA", Const, 2}, + {"XP1_GRACEFUL_CLOSE", Const, 2}, + {"XP1_GUARANTEED_DELIVERY", Const, 2}, + {"XP1_GUARANTEED_ORDER", Const, 2}, + {"XP1_IFS_HANDLES", Const, 2}, + {"XP1_MESSAGE_ORIENTED", Const, 2}, + {"XP1_MULTIPOINT_CONTROL_PLANE", Const, 2}, + {"XP1_MULTIPOINT_DATA_PLANE", Const, 2}, + {"XP1_PARTIAL_MESSAGE", Const, 2}, + {"XP1_PSEUDO_STREAM", Const, 2}, + {"XP1_QOS_SUPPORTED", Const, 2}, + {"XP1_SAN_SUPPORT_SDP", Const, 2}, + {"XP1_SUPPORT_BROADCAST", Const, 2}, + {"XP1_SUPPORT_MULTIPOINT", Const, 2}, + {"XP1_UNI_RECV", Const, 2}, + {"XP1_UNI_SEND", Const, 2}, + }, + "syscall/js": { + {"CopyBytesToGo", Func, 0}, + {"CopyBytesToJS", Func, 0}, + {"Error", Type, 0}, + {"Func", Type, 0}, + {"FuncOf", Func, 0}, + {"Global", Func, 0}, + {"Null", Func, 0}, + {"Type", Type, 0}, + {"TypeBoolean", Const, 0}, + {"TypeFunction", Const, 0}, + {"TypeNull", Const, 0}, + {"TypeNumber", Const, 0}, + {"TypeObject", Const, 0}, + {"TypeString", Const, 0}, + {"TypeSymbol", Const, 0}, + {"TypeUndefined", Const, 0}, + {"Undefined", Func, 0}, + {"Value", Type, 0}, + {"ValueError", Type, 0}, + {"ValueOf", Func, 0}, + }, + "testing": { + {"(*B).Cleanup", Method, 14}, + {"(*B).Elapsed", Method, 20}, + {"(*B).Error", Method, 0}, + {"(*B).Errorf", Method, 0}, + {"(*B).Fail", Method, 0}, + {"(*B).FailNow", Method, 0}, + {"(*B).Failed", Method, 0}, + {"(*B).Fatal", Method, 0}, + {"(*B).Fatalf", Method, 0}, + {"(*B).Helper", Method, 9}, + {"(*B).Log", Method, 0}, + {"(*B).Logf", Method, 0}, + {"(*B).Name", Method, 8}, + {"(*B).ReportAllocs", Method, 1}, + {"(*B).ReportMetric", Method, 13}, + {"(*B).ResetTimer", Method, 0}, + {"(*B).Run", Method, 7}, + {"(*B).RunParallel", Method, 3}, + {"(*B).SetBytes", Method, 0}, + {"(*B).SetParallelism", Method, 3}, + {"(*B).Setenv", Method, 17}, + {"(*B).Skip", Method, 1}, + {"(*B).SkipNow", Method, 1}, + {"(*B).Skipf", Method, 1}, + {"(*B).Skipped", Method, 1}, + {"(*B).StartTimer", Method, 0}, + {"(*B).StopTimer", Method, 0}, + {"(*B).TempDir", Method, 15}, + {"(*F).Add", Method, 18}, + {"(*F).Cleanup", Method, 18}, + {"(*F).Error", Method, 18}, + {"(*F).Errorf", Method, 18}, + {"(*F).Fail", Method, 18}, + {"(*F).FailNow", Method, 18}, + {"(*F).Failed", Method, 18}, + {"(*F).Fatal", Method, 18}, + {"(*F).Fatalf", Method, 18}, + {"(*F).Fuzz", Method, 18}, + {"(*F).Helper", Method, 18}, + {"(*F).Log", Method, 18}, + {"(*F).Logf", Method, 18}, + {"(*F).Name", Method, 18}, + {"(*F).Setenv", Method, 18}, + {"(*F).Skip", Method, 18}, + {"(*F).SkipNow", Method, 18}, + {"(*F).Skipf", Method, 18}, + {"(*F).Skipped", Method, 18}, + {"(*F).TempDir", Method, 18}, + {"(*M).Run", Method, 4}, + {"(*PB).Next", Method, 3}, + {"(*T).Cleanup", Method, 14}, + {"(*T).Deadline", Method, 15}, + {"(*T).Error", Method, 0}, + {"(*T).Errorf", Method, 0}, + {"(*T).Fail", Method, 0}, + {"(*T).FailNow", Method, 0}, + {"(*T).Failed", Method, 0}, + {"(*T).Fatal", Method, 0}, + {"(*T).Fatalf", Method, 0}, + {"(*T).Helper", Method, 9}, + {"(*T).Log", Method, 0}, + {"(*T).Logf", Method, 0}, + {"(*T).Name", Method, 8}, + {"(*T).Parallel", Method, 0}, + {"(*T).Run", Method, 7}, + {"(*T).Setenv", Method, 17}, + {"(*T).Skip", Method, 1}, + {"(*T).SkipNow", Method, 1}, + {"(*T).Skipf", Method, 1}, + {"(*T).Skipped", Method, 1}, + {"(*T).TempDir", Method, 15}, + {"(BenchmarkResult).AllocedBytesPerOp", Method, 1}, + {"(BenchmarkResult).AllocsPerOp", Method, 1}, + {"(BenchmarkResult).MemString", Method, 1}, + {"(BenchmarkResult).NsPerOp", Method, 0}, + {"(BenchmarkResult).String", Method, 0}, + {"AllocsPerRun", Func, 1}, + {"B", Type, 0}, + {"B.N", Field, 0}, + {"Benchmark", Func, 0}, + {"BenchmarkResult", Type, 0}, + {"BenchmarkResult.Bytes", Field, 0}, + {"BenchmarkResult.Extra", Field, 13}, + {"BenchmarkResult.MemAllocs", Field, 1}, + {"BenchmarkResult.MemBytes", Field, 1}, + {"BenchmarkResult.N", Field, 0}, + {"BenchmarkResult.T", Field, 0}, + {"Cover", Type, 2}, + {"Cover.Blocks", Field, 2}, + {"Cover.Counters", Field, 2}, + {"Cover.CoveredPackages", Field, 2}, + {"Cover.Mode", Field, 2}, + {"CoverBlock", Type, 2}, + {"CoverBlock.Col0", Field, 2}, + {"CoverBlock.Col1", Field, 2}, + {"CoverBlock.Line0", Field, 2}, + {"CoverBlock.Line1", Field, 2}, + {"CoverBlock.Stmts", Field, 2}, + {"CoverMode", Func, 8}, + {"Coverage", Func, 4}, + {"F", Type, 18}, + {"Init", Func, 13}, + {"InternalBenchmark", Type, 0}, + {"InternalBenchmark.F", Field, 0}, + {"InternalBenchmark.Name", Field, 0}, + {"InternalExample", Type, 0}, + {"InternalExample.F", Field, 0}, + {"InternalExample.Name", Field, 0}, + {"InternalExample.Output", Field, 0}, + {"InternalExample.Unordered", Field, 7}, + {"InternalFuzzTarget", Type, 18}, + {"InternalFuzzTarget.Fn", Field, 18}, + {"InternalFuzzTarget.Name", Field, 18}, + {"InternalTest", Type, 0}, + {"InternalTest.F", Field, 0}, + {"InternalTest.Name", Field, 0}, + {"M", Type, 4}, + {"Main", Func, 0}, + {"MainStart", Func, 4}, + {"PB", Type, 3}, + {"RegisterCover", Func, 2}, + {"RunBenchmarks", Func, 0}, + {"RunExamples", Func, 0}, + {"RunTests", Func, 0}, + {"Short", Func, 0}, + {"T", Type, 0}, + {"TB", Type, 2}, + {"Testing", Func, 21}, + {"Verbose", Func, 1}, + }, + "testing/fstest": { + {"(MapFS).Glob", Method, 16}, + {"(MapFS).Open", Method, 16}, + {"(MapFS).ReadDir", Method, 16}, + {"(MapFS).ReadFile", Method, 16}, + {"(MapFS).Stat", Method, 16}, + {"(MapFS).Sub", Method, 16}, + {"MapFS", Type, 16}, + {"MapFile", Type, 16}, + {"MapFile.Data", Field, 16}, + {"MapFile.ModTime", Field, 16}, + {"MapFile.Mode", Field, 16}, + {"MapFile.Sys", Field, 16}, + {"TestFS", Func, 16}, + }, + "testing/iotest": { + {"DataErrReader", Func, 0}, + {"ErrReader", Func, 16}, + {"ErrTimeout", Var, 0}, + {"HalfReader", Func, 0}, + {"NewReadLogger", Func, 0}, + {"NewWriteLogger", Func, 0}, + {"OneByteReader", Func, 0}, + {"TestReader", Func, 16}, + {"TimeoutReader", Func, 0}, + {"TruncateWriter", Func, 0}, + }, + "testing/quick": { + {"(*CheckEqualError).Error", Method, 0}, + {"(*CheckError).Error", Method, 0}, + {"(SetupError).Error", Method, 0}, + {"Check", Func, 0}, + {"CheckEqual", Func, 0}, + {"CheckEqualError", Type, 0}, + {"CheckEqualError.CheckError", Field, 0}, + {"CheckEqualError.Out1", Field, 0}, + {"CheckEqualError.Out2", Field, 0}, + {"CheckError", Type, 0}, + {"CheckError.Count", Field, 0}, + {"CheckError.In", Field, 0}, + {"Config", Type, 0}, + {"Config.MaxCount", Field, 0}, + {"Config.MaxCountScale", Field, 0}, + {"Config.Rand", Field, 0}, + {"Config.Values", Field, 0}, + {"Generator", Type, 0}, + {"SetupError", Type, 0}, + {"Value", Func, 0}, + }, + "testing/slogtest": { + {"Run", Func, 22}, + {"TestHandler", Func, 21}, + }, + "text/scanner": { + {"(*Position).IsValid", Method, 0}, + {"(*Scanner).Init", Method, 0}, + {"(*Scanner).IsValid", Method, 0}, + {"(*Scanner).Next", Method, 0}, + {"(*Scanner).Peek", Method, 0}, + {"(*Scanner).Pos", Method, 0}, + {"(*Scanner).Scan", Method, 0}, + {"(*Scanner).TokenText", Method, 0}, + {"(Position).String", Method, 0}, + {"(Scanner).String", Method, 0}, + {"Char", Const, 0}, + {"Comment", Const, 0}, + {"EOF", Const, 0}, + {"Float", Const, 0}, + {"GoTokens", Const, 0}, + {"GoWhitespace", Const, 0}, + {"Ident", Const, 0}, + {"Int", Const, 0}, + {"Position", Type, 0}, + {"Position.Column", Field, 0}, + {"Position.Filename", Field, 0}, + {"Position.Line", Field, 0}, + {"Position.Offset", Field, 0}, + {"RawString", Const, 0}, + {"ScanChars", Const, 0}, + {"ScanComments", Const, 0}, + {"ScanFloats", Const, 0}, + {"ScanIdents", Const, 0}, + {"ScanInts", Const, 0}, + {"ScanRawStrings", Const, 0}, + {"ScanStrings", Const, 0}, + {"Scanner", Type, 0}, + {"Scanner.Error", Field, 0}, + {"Scanner.ErrorCount", Field, 0}, + {"Scanner.IsIdentRune", Field, 4}, + {"Scanner.Mode", Field, 0}, + {"Scanner.Position", Field, 0}, + {"Scanner.Whitespace", Field, 0}, + {"SkipComments", Const, 0}, + {"String", Const, 0}, + {"TokenString", Func, 0}, + }, + "text/tabwriter": { + {"(*Writer).Flush", Method, 0}, + {"(*Writer).Init", Method, 0}, + {"(*Writer).Write", Method, 0}, + {"AlignRight", Const, 0}, + {"Debug", Const, 0}, + {"DiscardEmptyColumns", Const, 0}, + {"Escape", Const, 0}, + {"FilterHTML", Const, 0}, + {"NewWriter", Func, 0}, + {"StripEscape", Const, 0}, + {"TabIndent", Const, 0}, + {"Writer", Type, 0}, + }, + "text/template": { + {"(*Template).AddParseTree", Method, 0}, + {"(*Template).Clone", Method, 0}, + {"(*Template).DefinedTemplates", Method, 5}, + {"(*Template).Delims", Method, 0}, + {"(*Template).Execute", Method, 0}, + {"(*Template).ExecuteTemplate", Method, 0}, + {"(*Template).Funcs", Method, 0}, + {"(*Template).Lookup", Method, 0}, + {"(*Template).Name", Method, 0}, + {"(*Template).New", Method, 0}, + {"(*Template).Option", Method, 5}, + {"(*Template).Parse", Method, 0}, + {"(*Template).ParseFS", Method, 16}, + {"(*Template).ParseFiles", Method, 0}, + {"(*Template).ParseGlob", Method, 0}, + {"(*Template).Templates", Method, 0}, + {"(ExecError).Error", Method, 6}, + {"(ExecError).Unwrap", Method, 13}, + {"(Template).Copy", Method, 2}, + {"(Template).ErrorContext", Method, 1}, + {"ExecError", Type, 6}, + {"ExecError.Err", Field, 6}, + {"ExecError.Name", Field, 6}, + {"FuncMap", Type, 0}, + {"HTMLEscape", Func, 0}, + {"HTMLEscapeString", Func, 0}, + {"HTMLEscaper", Func, 0}, + {"IsTrue", Func, 6}, + {"JSEscape", Func, 0}, + {"JSEscapeString", Func, 0}, + {"JSEscaper", Func, 0}, + {"Must", Func, 0}, + {"New", Func, 0}, + {"ParseFS", Func, 16}, + {"ParseFiles", Func, 0}, + {"ParseGlob", Func, 0}, + {"Template", Type, 0}, + {"Template.Tree", Field, 0}, + {"URLQueryEscaper", Func, 0}, + }, + "text/template/parse": { + {"(*ActionNode).Copy", Method, 0}, + {"(*ActionNode).String", Method, 0}, + {"(*BoolNode).Copy", Method, 0}, + {"(*BoolNode).String", Method, 0}, + {"(*BranchNode).Copy", Method, 4}, + {"(*BranchNode).String", Method, 0}, + {"(*BreakNode).Copy", Method, 18}, + {"(*BreakNode).String", Method, 18}, + {"(*ChainNode).Add", Method, 1}, + {"(*ChainNode).Copy", Method, 1}, + {"(*ChainNode).String", Method, 1}, + {"(*CommandNode).Copy", Method, 0}, + {"(*CommandNode).String", Method, 0}, + {"(*CommentNode).Copy", Method, 16}, + {"(*CommentNode).String", Method, 16}, + {"(*ContinueNode).Copy", Method, 18}, + {"(*ContinueNode).String", Method, 18}, + {"(*DotNode).Copy", Method, 0}, + {"(*DotNode).String", Method, 0}, + {"(*DotNode).Type", Method, 0}, + {"(*FieldNode).Copy", Method, 0}, + {"(*FieldNode).String", Method, 0}, + {"(*IdentifierNode).Copy", Method, 0}, + {"(*IdentifierNode).SetPos", Method, 1}, + {"(*IdentifierNode).SetTree", Method, 4}, + {"(*IdentifierNode).String", Method, 0}, + {"(*IfNode).Copy", Method, 0}, + {"(*IfNode).String", Method, 0}, + {"(*ListNode).Copy", Method, 0}, + {"(*ListNode).CopyList", Method, 0}, + {"(*ListNode).String", Method, 0}, + {"(*NilNode).Copy", Method, 1}, + {"(*NilNode).String", Method, 1}, + {"(*NilNode).Type", Method, 1}, + {"(*NumberNode).Copy", Method, 0}, + {"(*NumberNode).String", Method, 0}, + {"(*PipeNode).Copy", Method, 0}, + {"(*PipeNode).CopyPipe", Method, 0}, + {"(*PipeNode).String", Method, 0}, + {"(*RangeNode).Copy", Method, 0}, + {"(*RangeNode).String", Method, 0}, + {"(*StringNode).Copy", Method, 0}, + {"(*StringNode).String", Method, 0}, + {"(*TemplateNode).Copy", Method, 0}, + {"(*TemplateNode).String", Method, 0}, + {"(*TextNode).Copy", Method, 0}, + {"(*TextNode).String", Method, 0}, + {"(*Tree).Copy", Method, 2}, + {"(*Tree).ErrorContext", Method, 1}, + {"(*Tree).Parse", Method, 0}, + {"(*VariableNode).Copy", Method, 0}, + {"(*VariableNode).String", Method, 0}, + {"(*WithNode).Copy", Method, 0}, + {"(*WithNode).String", Method, 0}, + {"(ActionNode).Position", Method, 1}, + {"(ActionNode).Type", Method, 0}, + {"(BoolNode).Position", Method, 1}, + {"(BoolNode).Type", Method, 0}, + {"(BranchNode).Position", Method, 1}, + {"(BranchNode).Type", Method, 0}, + {"(BreakNode).Position", Method, 18}, + {"(BreakNode).Type", Method, 18}, + {"(ChainNode).Position", Method, 1}, + {"(ChainNode).Type", Method, 1}, + {"(CommandNode).Position", Method, 1}, + {"(CommandNode).Type", Method, 0}, + {"(CommentNode).Position", Method, 16}, + {"(CommentNode).Type", Method, 16}, + {"(ContinueNode).Position", Method, 18}, + {"(ContinueNode).Type", Method, 18}, + {"(DotNode).Position", Method, 1}, + {"(FieldNode).Position", Method, 1}, + {"(FieldNode).Type", Method, 0}, + {"(IdentifierNode).Position", Method, 1}, + {"(IdentifierNode).Type", Method, 0}, + {"(IfNode).Position", Method, 1}, + {"(IfNode).Type", Method, 0}, + {"(ListNode).Position", Method, 1}, + {"(ListNode).Type", Method, 0}, + {"(NilNode).Position", Method, 1}, + {"(NodeType).Type", Method, 0}, + {"(NumberNode).Position", Method, 1}, + {"(NumberNode).Type", Method, 0}, + {"(PipeNode).Position", Method, 1}, + {"(PipeNode).Type", Method, 0}, + {"(Pos).Position", Method, 1}, + {"(RangeNode).Position", Method, 1}, + {"(RangeNode).Type", Method, 0}, + {"(StringNode).Position", Method, 1}, + {"(StringNode).Type", Method, 0}, + {"(TemplateNode).Position", Method, 1}, + {"(TemplateNode).Type", Method, 0}, + {"(TextNode).Position", Method, 1}, + {"(TextNode).Type", Method, 0}, + {"(VariableNode).Position", Method, 1}, + {"(VariableNode).Type", Method, 0}, + {"(WithNode).Position", Method, 1}, + {"(WithNode).Type", Method, 0}, + {"ActionNode", Type, 0}, + {"ActionNode.Line", Field, 0}, + {"ActionNode.NodeType", Field, 0}, + {"ActionNode.Pipe", Field, 0}, + {"ActionNode.Pos", Field, 1}, + {"BoolNode", Type, 0}, + {"BoolNode.NodeType", Field, 0}, + {"BoolNode.Pos", Field, 1}, + {"BoolNode.True", Field, 0}, + {"BranchNode", Type, 0}, + {"BranchNode.ElseList", Field, 0}, + {"BranchNode.Line", Field, 0}, + {"BranchNode.List", Field, 0}, + {"BranchNode.NodeType", Field, 0}, + {"BranchNode.Pipe", Field, 0}, + {"BranchNode.Pos", Field, 1}, + {"BreakNode", Type, 18}, + {"BreakNode.Line", Field, 18}, + {"BreakNode.NodeType", Field, 18}, + {"BreakNode.Pos", Field, 18}, + {"ChainNode", Type, 1}, + {"ChainNode.Field", Field, 1}, + {"ChainNode.Node", Field, 1}, + {"ChainNode.NodeType", Field, 1}, + {"ChainNode.Pos", Field, 1}, + {"CommandNode", Type, 0}, + {"CommandNode.Args", Field, 0}, + {"CommandNode.NodeType", Field, 0}, + {"CommandNode.Pos", Field, 1}, + {"CommentNode", Type, 16}, + {"CommentNode.NodeType", Field, 16}, + {"CommentNode.Pos", Field, 16}, + {"CommentNode.Text", Field, 16}, + {"ContinueNode", Type, 18}, + {"ContinueNode.Line", Field, 18}, + {"ContinueNode.NodeType", Field, 18}, + {"ContinueNode.Pos", Field, 18}, + {"DotNode", Type, 0}, + {"DotNode.NodeType", Field, 4}, + {"DotNode.Pos", Field, 1}, + {"FieldNode", Type, 0}, + {"FieldNode.Ident", Field, 0}, + {"FieldNode.NodeType", Field, 0}, + {"FieldNode.Pos", Field, 1}, + {"IdentifierNode", Type, 0}, + {"IdentifierNode.Ident", Field, 0}, + {"IdentifierNode.NodeType", Field, 0}, + {"IdentifierNode.Pos", Field, 1}, + {"IfNode", Type, 0}, + {"IfNode.BranchNode", Field, 0}, + {"IsEmptyTree", Func, 0}, + {"ListNode", Type, 0}, + {"ListNode.NodeType", Field, 0}, + {"ListNode.Nodes", Field, 0}, + {"ListNode.Pos", Field, 1}, + {"Mode", Type, 16}, + {"New", Func, 0}, + {"NewIdentifier", Func, 0}, + {"NilNode", Type, 1}, + {"NilNode.NodeType", Field, 4}, + {"NilNode.Pos", Field, 1}, + {"Node", Type, 0}, + {"NodeAction", Const, 0}, + {"NodeBool", Const, 0}, + {"NodeBreak", Const, 18}, + {"NodeChain", Const, 1}, + {"NodeCommand", Const, 0}, + {"NodeComment", Const, 16}, + {"NodeContinue", Const, 18}, + {"NodeDot", Const, 0}, + {"NodeField", Const, 0}, + {"NodeIdentifier", Const, 0}, + {"NodeIf", Const, 0}, + {"NodeList", Const, 0}, + {"NodeNil", Const, 1}, + {"NodeNumber", Const, 0}, + {"NodePipe", Const, 0}, + {"NodeRange", Const, 0}, + {"NodeString", Const, 0}, + {"NodeTemplate", Const, 0}, + {"NodeText", Const, 0}, + {"NodeType", Type, 0}, + {"NodeVariable", Const, 0}, + {"NodeWith", Const, 0}, + {"NumberNode", Type, 0}, + {"NumberNode.Complex128", Field, 0}, + {"NumberNode.Float64", Field, 0}, + {"NumberNode.Int64", Field, 0}, + {"NumberNode.IsComplex", Field, 0}, + {"NumberNode.IsFloat", Field, 0}, + {"NumberNode.IsInt", Field, 0}, + {"NumberNode.IsUint", Field, 0}, + {"NumberNode.NodeType", Field, 0}, + {"NumberNode.Pos", Field, 1}, + {"NumberNode.Text", Field, 0}, + {"NumberNode.Uint64", Field, 0}, + {"Parse", Func, 0}, + {"ParseComments", Const, 16}, + {"PipeNode", Type, 0}, + {"PipeNode.Cmds", Field, 0}, + {"PipeNode.Decl", Field, 0}, + {"PipeNode.IsAssign", Field, 11}, + {"PipeNode.Line", Field, 0}, + {"PipeNode.NodeType", Field, 0}, + {"PipeNode.Pos", Field, 1}, + {"Pos", Type, 1}, + {"RangeNode", Type, 0}, + {"RangeNode.BranchNode", Field, 0}, + {"SkipFuncCheck", Const, 17}, + {"StringNode", Type, 0}, + {"StringNode.NodeType", Field, 0}, + {"StringNode.Pos", Field, 1}, + {"StringNode.Quoted", Field, 0}, + {"StringNode.Text", Field, 0}, + {"TemplateNode", Type, 0}, + {"TemplateNode.Line", Field, 0}, + {"TemplateNode.Name", Field, 0}, + {"TemplateNode.NodeType", Field, 0}, + {"TemplateNode.Pipe", Field, 0}, + {"TemplateNode.Pos", Field, 1}, + {"TextNode", Type, 0}, + {"TextNode.NodeType", Field, 0}, + {"TextNode.Pos", Field, 1}, + {"TextNode.Text", Field, 0}, + {"Tree", Type, 0}, + {"Tree.Mode", Field, 16}, + {"Tree.Name", Field, 0}, + {"Tree.ParseName", Field, 1}, + {"Tree.Root", Field, 0}, + {"VariableNode", Type, 0}, + {"VariableNode.Ident", Field, 0}, + {"VariableNode.NodeType", Field, 0}, + {"VariableNode.Pos", Field, 1}, + {"WithNode", Type, 0}, + {"WithNode.BranchNode", Field, 0}, + }, + "time": { + {"(*Location).String", Method, 0}, + {"(*ParseError).Error", Method, 0}, + {"(*Ticker).Reset", Method, 15}, + {"(*Ticker).Stop", Method, 0}, + {"(*Time).GobDecode", Method, 0}, + {"(*Time).UnmarshalBinary", Method, 2}, + {"(*Time).UnmarshalJSON", Method, 0}, + {"(*Time).UnmarshalText", Method, 2}, + {"(*Timer).Reset", Method, 1}, + {"(*Timer).Stop", Method, 0}, + {"(Duration).Abs", Method, 19}, + {"(Duration).Hours", Method, 0}, + {"(Duration).Microseconds", Method, 13}, + {"(Duration).Milliseconds", Method, 13}, + {"(Duration).Minutes", Method, 0}, + {"(Duration).Nanoseconds", Method, 0}, + {"(Duration).Round", Method, 9}, + {"(Duration).Seconds", Method, 0}, + {"(Duration).String", Method, 0}, + {"(Duration).Truncate", Method, 9}, + {"(Month).String", Method, 0}, + {"(Time).Add", Method, 0}, + {"(Time).AddDate", Method, 0}, + {"(Time).After", Method, 0}, + {"(Time).AppendFormat", Method, 5}, + {"(Time).Before", Method, 0}, + {"(Time).Clock", Method, 0}, + {"(Time).Compare", Method, 20}, + {"(Time).Date", Method, 0}, + {"(Time).Day", Method, 0}, + {"(Time).Equal", Method, 0}, + {"(Time).Format", Method, 0}, + {"(Time).GoString", Method, 17}, + {"(Time).GobEncode", Method, 0}, + {"(Time).Hour", Method, 0}, + {"(Time).ISOWeek", Method, 0}, + {"(Time).In", Method, 0}, + {"(Time).IsDST", Method, 17}, + {"(Time).IsZero", Method, 0}, + {"(Time).Local", Method, 0}, + {"(Time).Location", Method, 0}, + {"(Time).MarshalBinary", Method, 2}, + {"(Time).MarshalJSON", Method, 0}, + {"(Time).MarshalText", Method, 2}, + {"(Time).Minute", Method, 0}, + {"(Time).Month", Method, 0}, + {"(Time).Nanosecond", Method, 0}, + {"(Time).Round", Method, 1}, + {"(Time).Second", Method, 0}, + {"(Time).String", Method, 0}, + {"(Time).Sub", Method, 0}, + {"(Time).Truncate", Method, 1}, + {"(Time).UTC", Method, 0}, + {"(Time).Unix", Method, 0}, + {"(Time).UnixMicro", Method, 17}, + {"(Time).UnixMilli", Method, 17}, + {"(Time).UnixNano", Method, 0}, + {"(Time).Weekday", Method, 0}, + {"(Time).Year", Method, 0}, + {"(Time).YearDay", Method, 1}, + {"(Time).Zone", Method, 0}, + {"(Time).ZoneBounds", Method, 19}, + {"(Weekday).String", Method, 0}, + {"ANSIC", Const, 0}, + {"After", Func, 0}, + {"AfterFunc", Func, 0}, + {"April", Const, 0}, + {"August", Const, 0}, + {"Date", Func, 0}, + {"DateOnly", Const, 20}, + {"DateTime", Const, 20}, + {"December", Const, 0}, + {"Duration", Type, 0}, + {"February", Const, 0}, + {"FixedZone", Func, 0}, + {"Friday", Const, 0}, + {"Hour", Const, 0}, + {"January", Const, 0}, + {"July", Const, 0}, + {"June", Const, 0}, + {"Kitchen", Const, 0}, + {"Layout", Const, 17}, + {"LoadLocation", Func, 0}, + {"LoadLocationFromTZData", Func, 10}, + {"Local", Var, 0}, + {"Location", Type, 0}, + {"March", Const, 0}, + {"May", Const, 0}, + {"Microsecond", Const, 0}, + {"Millisecond", Const, 0}, + {"Minute", Const, 0}, + {"Monday", Const, 0}, + {"Month", Type, 0}, + {"Nanosecond", Const, 0}, + {"NewTicker", Func, 0}, + {"NewTimer", Func, 0}, + {"November", Const, 0}, + {"Now", Func, 0}, + {"October", Const, 0}, + {"Parse", Func, 0}, + {"ParseDuration", Func, 0}, + {"ParseError", Type, 0}, + {"ParseError.Layout", Field, 0}, + {"ParseError.LayoutElem", Field, 0}, + {"ParseError.Message", Field, 0}, + {"ParseError.Value", Field, 0}, + {"ParseError.ValueElem", Field, 0}, + {"ParseInLocation", Func, 1}, + {"RFC1123", Const, 0}, + {"RFC1123Z", Const, 0}, + {"RFC3339", Const, 0}, + {"RFC3339Nano", Const, 0}, + {"RFC822", Const, 0}, + {"RFC822Z", Const, 0}, + {"RFC850", Const, 0}, + {"RubyDate", Const, 0}, + {"Saturday", Const, 0}, + {"Second", Const, 0}, + {"September", Const, 0}, + {"Since", Func, 0}, + {"Sleep", Func, 0}, + {"Stamp", Const, 0}, + {"StampMicro", Const, 0}, + {"StampMilli", Const, 0}, + {"StampNano", Const, 0}, + {"Sunday", Const, 0}, + {"Thursday", Const, 0}, + {"Tick", Func, 0}, + {"Ticker", Type, 0}, + {"Ticker.C", Field, 0}, + {"Time", Type, 0}, + {"TimeOnly", Const, 20}, + {"Timer", Type, 0}, + {"Timer.C", Field, 0}, + {"Tuesday", Const, 0}, + {"UTC", Var, 0}, + {"Unix", Func, 0}, + {"UnixDate", Const, 0}, + {"UnixMicro", Func, 17}, + {"UnixMilli", Func, 17}, + {"Until", Func, 8}, + {"Wednesday", Const, 0}, + {"Weekday", Type, 0}, + }, + "unicode": { + {"(SpecialCase).ToLower", Method, 0}, + {"(SpecialCase).ToTitle", Method, 0}, + {"(SpecialCase).ToUpper", Method, 0}, + {"ASCII_Hex_Digit", Var, 0}, + {"Adlam", Var, 7}, + {"Ahom", Var, 5}, + {"Anatolian_Hieroglyphs", Var, 5}, + {"Arabic", Var, 0}, + {"Armenian", Var, 0}, + {"Avestan", Var, 0}, + {"AzeriCase", Var, 0}, + {"Balinese", Var, 0}, + {"Bamum", Var, 0}, + {"Bassa_Vah", Var, 4}, + {"Batak", Var, 0}, + {"Bengali", Var, 0}, + {"Bhaiksuki", Var, 7}, + {"Bidi_Control", Var, 0}, + {"Bopomofo", Var, 0}, + {"Brahmi", Var, 0}, + {"Braille", Var, 0}, + {"Buginese", Var, 0}, + {"Buhid", Var, 0}, + {"C", Var, 0}, + {"Canadian_Aboriginal", Var, 0}, + {"Carian", Var, 0}, + {"CaseRange", Type, 0}, + {"CaseRange.Delta", Field, 0}, + {"CaseRange.Hi", Field, 0}, + {"CaseRange.Lo", Field, 0}, + {"CaseRanges", Var, 0}, + {"Categories", Var, 0}, + {"Caucasian_Albanian", Var, 4}, + {"Cc", Var, 0}, + {"Cf", Var, 0}, + {"Chakma", Var, 1}, + {"Cham", Var, 0}, + {"Cherokee", Var, 0}, + {"Chorasmian", Var, 16}, + {"Co", Var, 0}, + {"Common", Var, 0}, + {"Coptic", Var, 0}, + {"Cs", Var, 0}, + {"Cuneiform", Var, 0}, + {"Cypriot", Var, 0}, + {"Cypro_Minoan", Var, 21}, + {"Cyrillic", Var, 0}, + {"Dash", Var, 0}, + {"Deprecated", Var, 0}, + {"Deseret", Var, 0}, + {"Devanagari", Var, 0}, + {"Diacritic", Var, 0}, + {"Digit", Var, 0}, + {"Dives_Akuru", Var, 16}, + {"Dogra", Var, 13}, + {"Duployan", Var, 4}, + {"Egyptian_Hieroglyphs", Var, 0}, + {"Elbasan", Var, 4}, + {"Elymaic", Var, 14}, + {"Ethiopic", Var, 0}, + {"Extender", Var, 0}, + {"FoldCategory", Var, 0}, + {"FoldScript", Var, 0}, + {"Georgian", Var, 0}, + {"Glagolitic", Var, 0}, + {"Gothic", Var, 0}, + {"Grantha", Var, 4}, + {"GraphicRanges", Var, 0}, + {"Greek", Var, 0}, + {"Gujarati", Var, 0}, + {"Gunjala_Gondi", Var, 13}, + {"Gurmukhi", Var, 0}, + {"Han", Var, 0}, + {"Hangul", Var, 0}, + {"Hanifi_Rohingya", Var, 13}, + {"Hanunoo", Var, 0}, + {"Hatran", Var, 5}, + {"Hebrew", Var, 0}, + {"Hex_Digit", Var, 0}, + {"Hiragana", Var, 0}, + {"Hyphen", Var, 0}, + {"IDS_Binary_Operator", Var, 0}, + {"IDS_Trinary_Operator", Var, 0}, + {"Ideographic", Var, 0}, + {"Imperial_Aramaic", Var, 0}, + {"In", Func, 2}, + {"Inherited", Var, 0}, + {"Inscriptional_Pahlavi", Var, 0}, + {"Inscriptional_Parthian", Var, 0}, + {"Is", Func, 0}, + {"IsControl", Func, 0}, + {"IsDigit", Func, 0}, + {"IsGraphic", Func, 0}, + {"IsLetter", Func, 0}, + {"IsLower", Func, 0}, + {"IsMark", Func, 0}, + {"IsNumber", Func, 0}, + {"IsOneOf", Func, 0}, + {"IsPrint", Func, 0}, + {"IsPunct", Func, 0}, + {"IsSpace", Func, 0}, + {"IsSymbol", Func, 0}, + {"IsTitle", Func, 0}, + {"IsUpper", Func, 0}, + {"Javanese", Var, 0}, + {"Join_Control", Var, 0}, + {"Kaithi", Var, 0}, + {"Kannada", Var, 0}, + {"Katakana", Var, 0}, + {"Kawi", Var, 21}, + {"Kayah_Li", Var, 0}, + {"Kharoshthi", Var, 0}, + {"Khitan_Small_Script", Var, 16}, + {"Khmer", Var, 0}, + {"Khojki", Var, 4}, + {"Khudawadi", Var, 4}, + {"L", Var, 0}, + {"Lao", Var, 0}, + {"Latin", Var, 0}, + {"Lepcha", Var, 0}, + {"Letter", Var, 0}, + {"Limbu", Var, 0}, + {"Linear_A", Var, 4}, + {"Linear_B", Var, 0}, + {"Lisu", Var, 0}, + {"Ll", Var, 0}, + {"Lm", Var, 0}, + {"Lo", Var, 0}, + {"Logical_Order_Exception", Var, 0}, + {"Lower", Var, 0}, + {"LowerCase", Const, 0}, + {"Lt", Var, 0}, + {"Lu", Var, 0}, + {"Lycian", Var, 0}, + {"Lydian", Var, 0}, + {"M", Var, 0}, + {"Mahajani", Var, 4}, + {"Makasar", Var, 13}, + {"Malayalam", Var, 0}, + {"Mandaic", Var, 0}, + {"Manichaean", Var, 4}, + {"Marchen", Var, 7}, + {"Mark", Var, 0}, + {"Masaram_Gondi", Var, 10}, + {"MaxASCII", Const, 0}, + {"MaxCase", Const, 0}, + {"MaxLatin1", Const, 0}, + {"MaxRune", Const, 0}, + {"Mc", Var, 0}, + {"Me", Var, 0}, + {"Medefaidrin", Var, 13}, + {"Meetei_Mayek", Var, 0}, + {"Mende_Kikakui", Var, 4}, + {"Meroitic_Cursive", Var, 1}, + {"Meroitic_Hieroglyphs", Var, 1}, + {"Miao", Var, 1}, + {"Mn", Var, 0}, + {"Modi", Var, 4}, + {"Mongolian", Var, 0}, + {"Mro", Var, 4}, + {"Multani", Var, 5}, + {"Myanmar", Var, 0}, + {"N", Var, 0}, + {"Nabataean", Var, 4}, + {"Nag_Mundari", Var, 21}, + {"Nandinagari", Var, 14}, + {"Nd", Var, 0}, + {"New_Tai_Lue", Var, 0}, + {"Newa", Var, 7}, + {"Nko", Var, 0}, + {"Nl", Var, 0}, + {"No", Var, 0}, + {"Noncharacter_Code_Point", Var, 0}, + {"Number", Var, 0}, + {"Nushu", Var, 10}, + {"Nyiakeng_Puachue_Hmong", Var, 14}, + {"Ogham", Var, 0}, + {"Ol_Chiki", Var, 0}, + {"Old_Hungarian", Var, 5}, + {"Old_Italic", Var, 0}, + {"Old_North_Arabian", Var, 4}, + {"Old_Permic", Var, 4}, + {"Old_Persian", Var, 0}, + {"Old_Sogdian", Var, 13}, + {"Old_South_Arabian", Var, 0}, + {"Old_Turkic", Var, 0}, + {"Old_Uyghur", Var, 21}, + {"Oriya", Var, 0}, + {"Osage", Var, 7}, + {"Osmanya", Var, 0}, + {"Other", Var, 0}, + {"Other_Alphabetic", Var, 0}, + {"Other_Default_Ignorable_Code_Point", Var, 0}, + {"Other_Grapheme_Extend", Var, 0}, + {"Other_ID_Continue", Var, 0}, + {"Other_ID_Start", Var, 0}, + {"Other_Lowercase", Var, 0}, + {"Other_Math", Var, 0}, + {"Other_Uppercase", Var, 0}, + {"P", Var, 0}, + {"Pahawh_Hmong", Var, 4}, + {"Palmyrene", Var, 4}, + {"Pattern_Syntax", Var, 0}, + {"Pattern_White_Space", Var, 0}, + {"Pau_Cin_Hau", Var, 4}, + {"Pc", Var, 0}, + {"Pd", Var, 0}, + {"Pe", Var, 0}, + {"Pf", Var, 0}, + {"Phags_Pa", Var, 0}, + {"Phoenician", Var, 0}, + {"Pi", Var, 0}, + {"Po", Var, 0}, + {"Prepended_Concatenation_Mark", Var, 7}, + {"PrintRanges", Var, 0}, + {"Properties", Var, 0}, + {"Ps", Var, 0}, + {"Psalter_Pahlavi", Var, 4}, + {"Punct", Var, 0}, + {"Quotation_Mark", Var, 0}, + {"Radical", Var, 0}, + {"Range16", Type, 0}, + {"Range16.Hi", Field, 0}, + {"Range16.Lo", Field, 0}, + {"Range16.Stride", Field, 0}, + {"Range32", Type, 0}, + {"Range32.Hi", Field, 0}, + {"Range32.Lo", Field, 0}, + {"Range32.Stride", Field, 0}, + {"RangeTable", Type, 0}, + {"RangeTable.LatinOffset", Field, 1}, + {"RangeTable.R16", Field, 0}, + {"RangeTable.R32", Field, 0}, + {"Regional_Indicator", Var, 10}, + {"Rejang", Var, 0}, + {"ReplacementChar", Const, 0}, + {"Runic", Var, 0}, + {"S", Var, 0}, + {"STerm", Var, 0}, + {"Samaritan", Var, 0}, + {"Saurashtra", Var, 0}, + {"Sc", Var, 0}, + {"Scripts", Var, 0}, + {"Sentence_Terminal", Var, 7}, + {"Sharada", Var, 1}, + {"Shavian", Var, 0}, + {"Siddham", Var, 4}, + {"SignWriting", Var, 5}, + {"SimpleFold", Func, 0}, + {"Sinhala", Var, 0}, + {"Sk", Var, 0}, + {"Sm", Var, 0}, + {"So", Var, 0}, + {"Soft_Dotted", Var, 0}, + {"Sogdian", Var, 13}, + {"Sora_Sompeng", Var, 1}, + {"Soyombo", Var, 10}, + {"Space", Var, 0}, + {"SpecialCase", Type, 0}, + {"Sundanese", Var, 0}, + {"Syloti_Nagri", Var, 0}, + {"Symbol", Var, 0}, + {"Syriac", Var, 0}, + {"Tagalog", Var, 0}, + {"Tagbanwa", Var, 0}, + {"Tai_Le", Var, 0}, + {"Tai_Tham", Var, 0}, + {"Tai_Viet", Var, 0}, + {"Takri", Var, 1}, + {"Tamil", Var, 0}, + {"Tangsa", Var, 21}, + {"Tangut", Var, 7}, + {"Telugu", Var, 0}, + {"Terminal_Punctuation", Var, 0}, + {"Thaana", Var, 0}, + {"Thai", Var, 0}, + {"Tibetan", Var, 0}, + {"Tifinagh", Var, 0}, + {"Tirhuta", Var, 4}, + {"Title", Var, 0}, + {"TitleCase", Const, 0}, + {"To", Func, 0}, + {"ToLower", Func, 0}, + {"ToTitle", Func, 0}, + {"ToUpper", Func, 0}, + {"Toto", Var, 21}, + {"TurkishCase", Var, 0}, + {"Ugaritic", Var, 0}, + {"Unified_Ideograph", Var, 0}, + {"Upper", Var, 0}, + {"UpperCase", Const, 0}, + {"UpperLower", Const, 0}, + {"Vai", Var, 0}, + {"Variation_Selector", Var, 0}, + {"Version", Const, 0}, + {"Vithkuqi", Var, 21}, + {"Wancho", Var, 14}, + {"Warang_Citi", Var, 4}, + {"White_Space", Var, 0}, + {"Yezidi", Var, 16}, + {"Yi", Var, 0}, + {"Z", Var, 0}, + {"Zanabazar_Square", Var, 10}, + {"Zl", Var, 0}, + {"Zp", Var, 0}, + {"Zs", Var, 0}, + }, + "unicode/utf16": { + {"AppendRune", Func, 20}, + {"Decode", Func, 0}, + {"DecodeRune", Func, 0}, + {"Encode", Func, 0}, + {"EncodeRune", Func, 0}, + {"IsSurrogate", Func, 0}, + {"RuneLen", Func, 23}, + }, + "unicode/utf8": { + {"AppendRune", Func, 18}, + {"DecodeLastRune", Func, 0}, + {"DecodeLastRuneInString", Func, 0}, + {"DecodeRune", Func, 0}, + {"DecodeRuneInString", Func, 0}, + {"EncodeRune", Func, 0}, + {"FullRune", Func, 0}, + {"FullRuneInString", Func, 0}, + {"MaxRune", Const, 0}, + {"RuneCount", Func, 0}, + {"RuneCountInString", Func, 0}, + {"RuneError", Const, 0}, + {"RuneLen", Func, 0}, + {"RuneSelf", Const, 0}, + {"RuneStart", Func, 0}, + {"UTFMax", Const, 0}, + {"Valid", Func, 0}, + {"ValidRune", Func, 1}, + {"ValidString", Func, 0}, + }, + "unique": { + {"(Handle).Value", Method, 23}, + {"Handle", Type, 23}, + {"Make", Func, 23}, + }, + "unsafe": { + {"Add", Func, 0}, + {"Alignof", Func, 0}, + {"Offsetof", Func, 0}, + {"Pointer", Type, 0}, + {"Sizeof", Func, 0}, + {"Slice", Func, 0}, + {"SliceData", Func, 0}, + {"String", Func, 0}, + {"StringData", Func, 0}, + }, +} diff --git a/contribs/gnopls/internal/stdlib/stdlib.go b/contribs/gnopls/internal/stdlib/stdlib.go new file mode 100644 index 00000000000..98904017f2c --- /dev/null +++ b/contribs/gnopls/internal/stdlib/stdlib.go @@ -0,0 +1,97 @@ +// 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:generate go run generate.go + +// Package stdlib provides a table of all exported symbols in the +// standard library, along with the version at which they first +// appeared. +package stdlib + +import ( + "fmt" + "strings" +) + +type Symbol struct { + Name string + Kind Kind + Version Version // Go version that first included the symbol +} + +// A Kind indicates the kind of a symbol: +// function, variable, constant, type, and so on. +type Kind int8 + +const ( + Invalid Kind = iota // Example name: + Type // "Buffer" + Func // "Println" + Var // "EOF" + Const // "Pi" + Field // "Point.X" + Method // "(*Buffer).Grow" +) + +func (kind Kind) String() string { + return [...]string{ + Invalid: "invalid", + Type: "type", + Func: "func", + Var: "var", + Const: "const", + Field: "field", + Method: "method", + }[kind] +} + +// A Version represents a version of Go of the form "go1.%d". +type Version int8 + +// String returns a version string of the form "go1.23", without allocating. +func (v Version) String() string { return versions[v] } + +var versions [30]string // (increase constant as needed) + +func init() { + for i := range versions { + versions[i] = fmt.Sprintf("go1.%d", i) + } +} + +// HasPackage reports whether the specified package path is part of +// the standard library's public API. +func HasPackage(path string) bool { + _, ok := PackageSymbols[path] + return ok +} + +// SplitField splits the field symbol name into type and field +// components. It must be called only on Field symbols. +// +// Example: "File.Package" -> ("File", "Package") +func (sym *Symbol) SplitField() (typename, name string) { + if sym.Kind != Field { + panic("not a field") + } + typename, name, _ = strings.Cut(sym.Name, ".") + return +} + +// SplitMethod splits the method symbol name into pointer, receiver, +// and method components. It must be called only on Method symbols. +// +// Example: "(*Buffer).Grow" -> (true, "Buffer", "Grow") +func (sym *Symbol) SplitMethod() (ptr bool, recv, name string) { + if sym.Kind != Method { + panic("not a method") + } + recv, name, _ = strings.Cut(sym.Name, ".") + recv = recv[len("(") : len(recv)-len(")")] + ptr = recv[0] == '*' + if ptr { + recv = recv[len("*"):] + } + return +} diff --git a/contribs/gnopls/internal/telemetry/cmd/stacks/stacks.go b/contribs/gnopls/internal/telemetry/cmd/stacks/stacks.go index 746b0ff68ef..b741add93cd 100644 --- a/contribs/gnopls/internal/telemetry/cmd/stacks/stacks.go +++ b/contribs/gnopls/internal/telemetry/cmd/stacks/stacks.go @@ -84,8 +84,8 @@ import ( "golang.org/x/sys/unix" "golang.org/x/telemetry" - "golang.org/x/tools/gopls/internal/util/browser" - "golang.org/x/tools/gopls/internal/util/moremaps" + "github.com/gnolang/gno/contribs/gnopls/internal/util/browser" + "github.com/gnolang/gno/contribs/gnopls/internal/util/moremaps" ) // flags @@ -159,7 +159,7 @@ func main() { log.Fatal(err) } for _, prog := range report.Programs { - if prog.Program == "golang.org/x/tools/gopls" && len(prog.Stacks) > 0 { + if prog.Program == "github.com/gnolang/gno/contribs/gnopls" && len(prog.Stacks) > 0 { // Include applicable client names (e.g. vscode, eglot). var clients []string var clientSuffix string @@ -430,7 +430,7 @@ func main() { // Info is used as a key for de-duping and aggregating. // Do not add detail about particular records (e.g. data, telemetry URL). type Info struct { - Program string // "golang.org/x/tools/gopls" + Program string // "github.com/gnolang/gno/contribs/gnopls" Version, GoVersion string // e.g. "gopls/v0.16.1", "go1.23" GOOS, GOARCH string Client string // e.g. "vscode" @@ -484,7 +484,7 @@ func newIssue(stack, id string, jsonURL string, counts map[Info]int64) string { if strings.Contains(line, "internal/util/bug.") { continue // not interesting } - if _, rest, ok := strings.Cut(line, "golang.org/x/tools/gopls/"); ok { + if _, rest, ok := strings.Cut(line, "github.com/gnolang/gno/contribs/gnopls/"); ok { if i := strings.IndexByte(rest, '.'); i >= 0 { rest = rest[i+1:] rest = strings.TrimPrefix(rest, "(*") @@ -507,7 +507,7 @@ func newIssue(stack, id string, jsonURL string, counts map[Info]int64) string { #!stacks "<insert predicate here>" ` + "```\n") - fmt.Fprintf(body, "Issue created by [stacks](https://pkg.go.dev/golang.org/x/tools/gopls/internal/telemetry/cmd/stacks).\n\n") + fmt.Fprintf(body, "Issue created by [stacks](https://pkg.go.dev/github.com/gnolang/gno/contribs/gnopls/internal/telemetry/cmd/stacks).\n\n") writeStackComment(body, stack, id, jsonURL, counts) @@ -566,7 +566,7 @@ func writeStackComment(body *bytes.Buffer, stack, id string, jsonURL string, cou // frameURL returns the CodeSearch URL for the stack frame, if known. func frameURL(pclntab map[string]FileLine, info Info, frame string) string { - // e.g. "golang.org/x/tools/gopls/foo.(*Type).Method.inlined.func3:+5" + // e.g. "github.com/gnolang/gno/contribs/gnopls/foo.(*Type).Method.inlined.func3:+5" symbol, offset, ok := strings.Cut(frame, ":") if !ok { // Not a symbol (perhaps stack counter title: "gopls/bug"?) @@ -621,7 +621,7 @@ func frameURL(pclntab map[string]FileLine, info Info, frame string) string { // x/tools repo (tools or gopls module)? if rest, ok := strings.CutPrefix(fileline.file, "golang.org/x/tools"); ok { if rest[0] == '/' { - // "golang.org/x/tools/gopls" -> "gopls" + // "github.com/gnolang/gno/contribs/gnopls" -> "gopls" rest = rest[1:] } else if rest[0] == '@' { // "golang.org/x/tools@version/dir/file.go" -> "dir/file.go" diff --git a/contribs/gnopls/internal/telemetry/telemetry_test.go b/contribs/gnopls/internal/telemetry/telemetry_test.go index 7aaca41ab55..51b41fb4b65 100644 --- a/contribs/gnopls/internal/telemetry/telemetry_test.go +++ b/contribs/gnopls/internal/telemetry/telemetry_test.go @@ -18,11 +18,11 @@ import ( "golang.org/x/telemetry/counter" "golang.org/x/telemetry/counter/countertest" // requires go1.21+ - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/telemetry" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/telemetry" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) func TestMain(m *testing.M) { @@ -82,10 +82,10 @@ func TestTelemetry(t *testing.T) { // This will increment a counter named something like: // // `gopls/bug - // golang.org/x/tools/gopls/internal/util/bug.report:+35 - // golang.org/x/tools/gopls/internal/util/bug.Report:=68 - // golang.org/x/tools/gopls/internal/telemetry_test.TestTelemetry.func2:+4 - // golang.org/x/tools/gopls/internal/test/integration.(*Runner).Run.func1:+87 + // github.com/gnolang/gno/contribs/gnopls/internal/util/bug.report:+35 + // github.com/gnolang/gno/contribs/gnopls/internal/util/bug.Report:=68 + // github.com/gnolang/gno/contribs/gnopls/internal/telemetry_test.TestTelemetry.func2:+4 + // github.com/gnolang/gno/contribs/gnopls/internal/test/integration.(*Runner).Run.func1:+87 // testing.tRunner:+150 // runtime.goexit:+0` // diff --git a/contribs/gnopls/internal/template/completion.go b/contribs/gnopls/internal/template/completion.go index dfacefc938e..1625658306f 100644 --- a/contribs/gnopls/internal/template/completion.go +++ b/contribs/gnopls/internal/template/completion.go @@ -12,9 +12,9 @@ import ( "go/token" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // information needed for completion diff --git a/contribs/gnopls/internal/template/completion_test.go b/contribs/gnopls/internal/template/completion_test.go index 8e1bdbf0535..f3fae9cb9e9 100644 --- a/contribs/gnopls/internal/template/completion_test.go +++ b/contribs/gnopls/internal/template/completion_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func init() { diff --git a/contribs/gnopls/internal/template/highlight.go b/contribs/gnopls/internal/template/highlight.go index 39812cfd0ba..c48d5e24d8b 100644 --- a/contribs/gnopls/internal/template/highlight.go +++ b/contribs/gnopls/internal/template/highlight.go @@ -9,9 +9,9 @@ import ( "fmt" "regexp" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, loc protocol.Position) ([]protocol.DocumentHighlight, error) { diff --git a/contribs/gnopls/internal/template/implementations.go b/contribs/gnopls/internal/template/implementations.go index 19a27620b57..a015fc440ff 100644 --- a/contribs/gnopls/internal/template/implementations.go +++ b/contribs/gnopls/internal/template/implementations.go @@ -11,10 +11,10 @@ import ( "strconv" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/semtok" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/semtok" ) // line number (1-based) and message diff --git a/contribs/gnopls/internal/template/parse.go b/contribs/gnopls/internal/template/parse.go index 448a5ab51e8..78b5cbb19bf 100644 --- a/contribs/gnopls/internal/template/parse.go +++ b/contribs/gnopls/internal/template/parse.go @@ -21,9 +21,9 @@ import ( "text/template/parse" "unicode/utf8" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) var ( diff --git a/contribs/gnopls/internal/template/symbols.go b/contribs/gnopls/internal/template/symbols.go index fcbaec43c54..2dee9612725 100644 --- a/contribs/gnopls/internal/template/symbols.go +++ b/contribs/gnopls/internal/template/symbols.go @@ -11,10 +11,10 @@ import ( "text/template/parse" "unicode/utf8" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) // in local coordinates, to be translated to protocol.DocumentSymbol diff --git a/contribs/gnopls/internal/test/compare/text.go b/contribs/gnopls/internal/test/compare/text.go index 4ce2f8c6b28..fa6df1531dd 100644 --- a/contribs/gnopls/internal/test/compare/text.go +++ b/contribs/gnopls/internal/test/compare/text.go @@ -7,7 +7,7 @@ package compare import ( "bytes" - "golang.org/x/tools/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" ) // Text returns a formatted unified diff of the edits to go from want to diff --git a/contribs/gnopls/internal/test/compare/text_test.go b/contribs/gnopls/internal/test/compare/text_test.go index 66bdf0996e2..53906e241dd 100644 --- a/contribs/gnopls/internal/test/compare/text_test.go +++ b/contribs/gnopls/internal/test/compare/text_test.go @@ -7,7 +7,7 @@ package compare_test import ( "testing" - "golang.org/x/tools/gopls/internal/test/compare" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" ) func TestText(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/bench/bench_test.go b/contribs/gnopls/internal/test/integration/bench/bench_test.go index 5de6804c03b..f694c586846 100644 --- a/contribs/gnopls/internal/test/integration/bench/bench_test.go +++ b/contribs/gnopls/internal/test/integration/bench/bench_test.go @@ -20,17 +20,17 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/cmd" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/event" - "golang.org/x/tools/internal/fakenet" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/jsonrpc2/servertest" - "golang.org/x/tools/internal/pprof" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/cmd" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/fakenet" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2/servertest" + "github.com/gnolang/gno/contribs/gnopls/internal/pprof" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) var ( diff --git a/contribs/gnopls/internal/test/integration/bench/codeaction_test.go b/contribs/gnopls/internal/test/integration/bench/codeaction_test.go index 679f2d4cf3d..f6b064d52d9 100644 --- a/contribs/gnopls/internal/test/integration/bench/codeaction_test.go +++ b/contribs/gnopls/internal/test/integration/bench/codeaction_test.go @@ -9,7 +9,7 @@ import ( "sync/atomic" "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func BenchmarkCodeAction(b *testing.B) { diff --git a/contribs/gnopls/internal/test/integration/bench/completion_test.go b/contribs/gnopls/internal/test/integration/bench/completion_test.go index bbbba0e3fd1..dd06c2232fb 100644 --- a/contribs/gnopls/internal/test/integration/bench/completion_test.go +++ b/contribs/gnopls/internal/test/integration/bench/completion_test.go @@ -10,9 +10,9 @@ import ( "sync/atomic" "testing" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" ) var completionGOPATH = flag.String("completion_gopath", "", "if set, use this GOPATH for BenchmarkCompletion") diff --git a/contribs/gnopls/internal/test/integration/bench/didchange_test.go b/contribs/gnopls/internal/test/integration/bench/didchange_test.go index 22e7ca2a11b..227b69d7eb4 100644 --- a/contribs/gnopls/internal/test/integration/bench/didchange_test.go +++ b/contribs/gnopls/internal/test/integration/bench/didchange_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" ) // Use a global edit counter as bench function may execute multiple times, and diff --git a/contribs/gnopls/internal/test/integration/bench/imports_test.go b/contribs/gnopls/internal/test/integration/bench/imports_test.go index 97419cb10c5..0f75fe5e414 100644 --- a/contribs/gnopls/internal/test/integration/bench/imports_test.go +++ b/contribs/gnopls/internal/test/integration/bench/imports_test.go @@ -10,10 +10,10 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" ) var gopath = flag.String("gopath", "", "if set, run goimports scan with this GOPATH value") diff --git a/contribs/gnopls/internal/test/integration/bench/iwl_test.go b/contribs/gnopls/internal/test/integration/bench/iwl_test.go index ecf26f95463..780b91de987 100644 --- a/contribs/gnopls/internal/test/integration/bench/iwl_test.go +++ b/contribs/gnopls/internal/test/integration/bench/iwl_test.go @@ -7,10 +7,10 @@ package bench import ( "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" ) // BenchmarkInitialWorkspaceLoad benchmarks the initial workspace load time for diff --git a/contribs/gnopls/internal/test/integration/bench/reload_test.go b/contribs/gnopls/internal/test/integration/bench/reload_test.go index 332809ee1eb..d54bd3e9c26 100644 --- a/contribs/gnopls/internal/test/integration/bench/reload_test.go +++ b/contribs/gnopls/internal/test/integration/bench/reload_test.go @@ -6,7 +6,7 @@ package bench import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // BenchmarkReload benchmarks reloading a file metadata after a change to an import. diff --git a/contribs/gnopls/internal/test/integration/bench/repo_test.go b/contribs/gnopls/internal/test/integration/bench/repo_test.go index 0e86f3e1da7..3bdb566e317 100644 --- a/contribs/gnopls/internal/test/integration/bench/repo_test.go +++ b/contribs/gnopls/internal/test/integration/bench/repo_test.go @@ -17,8 +17,8 @@ import ( "testing" "time" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" ) // repos holds shared repositories for use in benchmarks. diff --git a/contribs/gnopls/internal/test/integration/bench/stress_test.go b/contribs/gnopls/internal/test/integration/bench/stress_test.go index 1b63e3aff9e..367ae4bfd77 100644 --- a/contribs/gnopls/internal/test/integration/bench/stress_test.go +++ b/contribs/gnopls/internal/test/integration/bench/stress_test.go @@ -11,11 +11,11 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/lsprpc" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/jsonrpc2/servertest" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2/servertest" ) // github.com/pilosa/pilosa is a repository that has historically caused diff --git a/contribs/gnopls/internal/test/integration/bench/tests_test.go b/contribs/gnopls/internal/test/integration/bench/tests_test.go index 3bc69ef95e1..f5fd9d5780a 100644 --- a/contribs/gnopls/internal/test/integration/bench/tests_test.go +++ b/contribs/gnopls/internal/test/integration/bench/tests_test.go @@ -7,9 +7,9 @@ import ( "encoding/json" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func BenchmarkPackagesCommand(b *testing.B) { diff --git a/contribs/gnopls/internal/test/integration/bench/typing_test.go b/contribs/gnopls/internal/test/integration/bench/typing_test.go index 78bd16cef5b..2053f83e364 100644 --- a/contribs/gnopls/internal/test/integration/bench/typing_test.go +++ b/contribs/gnopls/internal/test/integration/bench/typing_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // BenchmarkTyping simulates typing steadily in a single file at different diff --git a/contribs/gnopls/internal/test/integration/codelens/codelens_test.go b/contribs/gnopls/internal/test/integration/codelens/codelens_test.go index 75b9fda1fbf..7a41eb61dc0 100644 --- a/contribs/gnopls/internal/test/integration/codelens/codelens_test.go +++ b/contribs/gnopls/internal/test/integration/codelens/codelens_test.go @@ -9,15 +9,15 @@ import ( "os" "testing" - "golang.org/x/tools/gopls/internal/server" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/util/bug" - - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/codelens/gcdetails_test.go b/contribs/gnopls/internal/test/integration/codelens/gcdetails_test.go index 359a7804ec4..f855f591877 100644 --- a/contribs/gnopls/internal/test/integration/codelens/gcdetails_test.go +++ b/contribs/gnopls/internal/test/integration/codelens/gcdetails_test.go @@ -8,13 +8,13 @@ import ( "runtime" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/server" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestGCDetails_Toggle(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/completion/completion18_test.go b/contribs/gnopls/internal/test/integration/completion/completion18_test.go index a35061d693b..91c388a6053 100644 --- a/contribs/gnopls/internal/test/integration/completion/completion18_test.go +++ b/contribs/gnopls/internal/test/integration/completion/completion18_test.go @@ -7,8 +7,8 @@ package completion import ( "testing" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // test generic receivers diff --git a/contribs/gnopls/internal/test/integration/completion/completion_test.go b/contribs/gnopls/internal/test/integration/completion/completion_test.go index c96e569f1ad..5ffa631bb16 100644 --- a/contribs/gnopls/internal/test/integration/completion/completion_test.go +++ b/contribs/gnopls/internal/test/integration/completion/completion_test.go @@ -15,12 +15,12 @@ import ( "github.com/google/go-cmp/cmp" "golang.org/x/telemetry/counter" "golang.org/x/telemetry/counter/countertest" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/server" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/completion/fixedbugs_test.go b/contribs/gnopls/internal/test/integration/completion/fixedbugs_test.go index faa5324e138..0efa3171bf1 100644 --- a/contribs/gnopls/internal/test/integration/completion/fixedbugs_test.go +++ b/contribs/gnopls/internal/test/integration/completion/fixedbugs_test.go @@ -7,7 +7,7 @@ package completion import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestPackageCompletionCrash_Issue68169(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/completion/postfix_snippet_test.go b/contribs/gnopls/internal/test/integration/completion/postfix_snippet_test.go index 884be420835..1fd17c7cad6 100644 --- a/contribs/gnopls/internal/test/integration/completion/postfix_snippet_test.go +++ b/contribs/gnopls/internal/test/integration/completion/postfix_snippet_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestPostfixSnippetCompletion(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/debug/debug_test.go b/contribs/gnopls/internal/test/integration/debug/debug_test.go index 1dccea43062..997aa9b347c 100644 --- a/contribs/gnopls/internal/test/integration/debug/debug_test.go +++ b/contribs/gnopls/internal/test/integration/debug/debug_test.go @@ -13,10 +13,10 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/diagnostics/analysis_test.go b/contribs/gnopls/internal/test/integration/diagnostics/analysis_test.go index 8cb86f8f735..aa188d52bb5 100644 --- a/contribs/gnopls/internal/test/integration/diagnostics/analysis_test.go +++ b/contribs/gnopls/internal/test/integration/diagnostics/analysis_test.go @@ -8,9 +8,9 @@ import ( "fmt" "testing" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // Test for the timeformat analyzer, following golang/vscode-go#2406. diff --git a/contribs/gnopls/internal/test/integration/diagnostics/builtin_test.go b/contribs/gnopls/internal/test/integration/diagnostics/builtin_test.go index d6828a0df5c..c9276d2f8cf 100644 --- a/contribs/gnopls/internal/test/integration/diagnostics/builtin_test.go +++ b/contribs/gnopls/internal/test/integration/diagnostics/builtin_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestIssue44866(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/diagnostics/diagnostics_test.go b/contribs/gnopls/internal/test/integration/diagnostics/diagnostics_test.go index 195089ffce3..ce7ab3a7852 100644 --- a/contribs/gnopls/internal/test/integration/diagnostics/diagnostics_test.go +++ b/contribs/gnopls/internal/test/integration/diagnostics/diagnostics_test.go @@ -11,12 +11,12 @@ import ( "os/exec" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/server" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/diagnostics/golist_test.go b/contribs/gnopls/internal/test/integration/diagnostics/golist_test.go index 8c11246d3e1..feb334fb799 100644 --- a/contribs/gnopls/internal/test/integration/diagnostics/golist_test.go +++ b/contribs/gnopls/internal/test/integration/diagnostics/golist_test.go @@ -7,9 +7,9 @@ package diagnostics import ( "testing" - "golang.org/x/tools/gopls/internal/cache" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestGoListErrors(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/diagnostics/gopackagesdriver_test.go b/contribs/gnopls/internal/test/integration/diagnostics/gopackagesdriver_test.go index 65700b69795..e8ecbce9913 100644 --- a/contribs/gnopls/internal/test/integration/diagnostics/gopackagesdriver_test.go +++ b/contribs/gnopls/internal/test/integration/diagnostics/gopackagesdriver_test.go @@ -7,7 +7,7 @@ package diagnostics import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // Test that the import error does not mention GOPATH when building with diff --git a/contribs/gnopls/internal/test/integration/diagnostics/invalidation_test.go b/contribs/gnopls/internal/test/integration/diagnostics/invalidation_test.go index e8d39c3c38a..42accf5383a 100644 --- a/contribs/gnopls/internal/test/integration/diagnostics/invalidation_test.go +++ b/contribs/gnopls/internal/test/integration/diagnostics/invalidation_test.go @@ -8,8 +8,8 @@ import ( "fmt" "testing" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // Test for golang/go#50267: diagnostics should be re-sent after a file is diff --git a/contribs/gnopls/internal/test/integration/diagnostics/undeclared_test.go b/contribs/gnopls/internal/test/integration/diagnostics/undeclared_test.go index 5579c0752d7..4d9d635a64f 100644 --- a/contribs/gnopls/internal/test/integration/diagnostics/undeclared_test.go +++ b/contribs/gnopls/internal/test/integration/diagnostics/undeclared_test.go @@ -7,8 +7,8 @@ package diagnostics import ( "testing" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestUndeclaredDiagnostics(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/doc.go b/contribs/gnopls/internal/test/integration/doc.go index a1c5856c261..69920bf8aeb 100644 --- a/contribs/gnopls/internal/test/integration/doc.go +++ b/contribs/gnopls/internal/test/integration/doc.go @@ -28,8 +28,8 @@ // "fmt" // "testing" // -// "golang.org/x/tools/gopls/internal/hooks" -// . "golang.org/x/tools/gopls/internal/test/integration" +// "github.com/gnolang/gno/contribs/gnopls/internal/hooks" +// . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" // ) // // func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/env.go b/contribs/gnopls/internal/test/integration/env.go index 1a7ea70c89b..a4d56f23dfa 100644 --- a/contribs/gnopls/internal/test/integration/env.go +++ b/contribs/gnopls/internal/test/integration/env.go @@ -11,9 +11,9 @@ import ( "sync" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/internal/jsonrpc2/servertest" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2/servertest" ) // Env holds the building blocks of an editor testing environment, providing diff --git a/contribs/gnopls/internal/test/integration/env_test.go b/contribs/gnopls/internal/test/integration/env_test.go index 32203f7cb83..5645e825b46 100644 --- a/contribs/gnopls/internal/test/integration/env_test.go +++ b/contribs/gnopls/internal/test/integration/env_test.go @@ -9,7 +9,7 @@ import ( "encoding/json" "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func TestProgressUpdating(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/expectation.go b/contribs/gnopls/internal/test/integration/expectation.go index 858daeee18a..0d3ddbc59a7 100644 --- a/contribs/gnopls/internal/test/integration/expectation.go +++ b/contribs/gnopls/internal/test/integration/expectation.go @@ -11,8 +11,8 @@ import ( "strings" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/server" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/server" ) var ( diff --git a/contribs/gnopls/internal/test/integration/fake/client.go b/contribs/gnopls/internal/test/integration/fake/client.go index 8fdddd92574..60ff88b5389 100644 --- a/contribs/gnopls/internal/test/integration/fake/client.go +++ b/contribs/gnopls/internal/test/integration/fake/client.go @@ -12,8 +12,8 @@ import ( "path/filepath" "sync/atomic" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/integration/fake/glob" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake/glob" ) // ClientHooks are a set of optional hooks called during handling of diff --git a/contribs/gnopls/internal/test/integration/fake/edit.go b/contribs/gnopls/internal/test/integration/fake/edit.go index b06984b3dbc..33af5c94925 100644 --- a/contribs/gnopls/internal/test/integration/fake/edit.go +++ b/contribs/gnopls/internal/test/integration/fake/edit.go @@ -5,8 +5,8 @@ package fake import ( - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" ) // NewEdit creates an edit replacing all content between the 0-based diff --git a/contribs/gnopls/internal/test/integration/fake/edit_test.go b/contribs/gnopls/internal/test/integration/fake/edit_test.go index 0d7ac18c414..520fdc73a7d 100644 --- a/contribs/gnopls/internal/test/integration/fake/edit_test.go +++ b/contribs/gnopls/internal/test/integration/fake/edit_test.go @@ -7,7 +7,7 @@ package fake import ( "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func TestApplyEdits(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/fake/editor.go b/contribs/gnopls/internal/test/integration/fake/editor.go index 09cdbaf8c66..5037c1bc63d 100644 --- a/contribs/gnopls/internal/test/integration/fake/editor.go +++ b/contribs/gnopls/internal/test/integration/fake/editor.go @@ -18,14 +18,14 @@ import ( "strings" "sync" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/test/integration/fake/glob" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/pathutil" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/jsonrpc2/servertest" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake/glob" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/pathutil" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2/servertest" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) // Editor is a fake client editor. It keeps track of client state and can be diff --git a/contribs/gnopls/internal/test/integration/fake/editor_test.go b/contribs/gnopls/internal/test/integration/fake/editor_test.go index 68983bda50c..f792e287102 100644 --- a/contribs/gnopls/internal/test/integration/fake/editor_test.go +++ b/contribs/gnopls/internal/test/integration/fake/editor_test.go @@ -8,7 +8,7 @@ import ( "context" "testing" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) const exampleProgram = ` diff --git a/contribs/gnopls/internal/test/integration/fake/glob/glob_test.go b/contribs/gnopls/internal/test/integration/fake/glob/glob_test.go index 8accd908e7a..4f8c5d54345 100644 --- a/contribs/gnopls/internal/test/integration/fake/glob/glob_test.go +++ b/contribs/gnopls/internal/test/integration/fake/glob/glob_test.go @@ -7,7 +7,7 @@ package glob_test import ( "testing" - "golang.org/x/tools/gopls/internal/test/integration/fake/glob" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake/glob" ) func TestParseErrors(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/fake/proxy.go b/contribs/gnopls/internal/test/integration/fake/proxy.go index 9e56efeb17f..00b39570f56 100644 --- a/contribs/gnopls/internal/test/integration/fake/proxy.go +++ b/contribs/gnopls/internal/test/integration/fake/proxy.go @@ -7,7 +7,7 @@ package fake import ( "fmt" - "golang.org/x/tools/internal/proxydir" + "github.com/gnolang/gno/contribs/gnopls/internal/proxydir" ) // WriteProxy creates a new proxy file tree using the txtar-encoded content, diff --git a/contribs/gnopls/internal/test/integration/fake/sandbox.go b/contribs/gnopls/internal/test/integration/fake/sandbox.go index fcaa50f0a76..3118e1bc0d3 100644 --- a/contribs/gnopls/internal/test/integration/fake/sandbox.go +++ b/contribs/gnopls/internal/test/integration/fake/sandbox.go @@ -12,9 +12,9 @@ import ( "path/filepath" "strings" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/robustio" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/robustio" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" "golang.org/x/tools/txtar" ) diff --git a/contribs/gnopls/internal/test/integration/fake/workdir.go b/contribs/gnopls/internal/test/integration/fake/workdir.go index be3cb3bcf15..3dd91f920f9 100644 --- a/contribs/gnopls/internal/test/integration/fake/workdir.go +++ b/contribs/gnopls/internal/test/integration/fake/workdir.go @@ -19,8 +19,8 @@ import ( "sync" "time" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/robustio" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/robustio" ) // RelativeTo is a helper for operations relative to a given directory. diff --git a/contribs/gnopls/internal/test/integration/fake/workdir_test.go b/contribs/gnopls/internal/test/integration/fake/workdir_test.go index 153a3576b4e..87f8f33649c 100644 --- a/contribs/gnopls/internal/test/integration/fake/workdir_test.go +++ b/contribs/gnopls/internal/test/integration/fake/workdir_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) const sharedData = ` diff --git a/contribs/gnopls/internal/test/integration/inlayhints/inlayhints_test.go b/contribs/gnopls/internal/test/integration/inlayhints/inlayhints_test.go index 6c55ee7601c..57d1f5eeccc 100644 --- a/contribs/gnopls/internal/test/integration/inlayhints/inlayhints_test.go +++ b/contribs/gnopls/internal/test/integration/inlayhints/inlayhints_test.go @@ -7,9 +7,9 @@ import ( "os" "testing" - "golang.org/x/tools/gopls/internal/settings" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/misc/call_hierarchy_test.go b/contribs/gnopls/internal/test/integration/misc/call_hierarchy_test.go index 4d16dba2b3c..263d3618e0d 100644 --- a/contribs/gnopls/internal/test/integration/misc/call_hierarchy_test.go +++ b/contribs/gnopls/internal/test/integration/misc/call_hierarchy_test.go @@ -7,8 +7,8 @@ package misc import ( "testing" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // Test for golang/go#49125 diff --git a/contribs/gnopls/internal/test/integration/misc/codeactions_test.go b/contribs/gnopls/internal/test/integration/misc/codeactions_test.go index 354921afc01..9e05b79bac9 100644 --- a/contribs/gnopls/internal/test/integration/misc/codeactions_test.go +++ b/contribs/gnopls/internal/test/integration/misc/codeactions_test.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/settings" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // This test exercises the filtering of code actions in generated files. diff --git a/contribs/gnopls/internal/test/integration/misc/configuration_test.go b/contribs/gnopls/internal/test/integration/misc/configuration_test.go index e96fe5dd806..4dd3dc6c510 100644 --- a/contribs/gnopls/internal/test/integration/misc/configuration_test.go +++ b/contribs/gnopls/internal/test/integration/misc/configuration_test.go @@ -7,9 +7,9 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) // Test that enabling and disabling produces the expected results of showing diff --git a/contribs/gnopls/internal/test/integration/misc/debugserver_test.go b/contribs/gnopls/internal/test/integration/misc/debugserver_test.go index 87f892f7443..4aea8ace047 100644 --- a/contribs/gnopls/internal/test/integration/misc/debugserver_test.go +++ b/contribs/gnopls/internal/test/integration/misc/debugserver_test.go @@ -8,10 +8,10 @@ import ( "net/http" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestStartDebugging(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/definition_test.go b/contribs/gnopls/internal/test/integration/misc/definition_test.go index 71f255b52e2..5e11b209620 100644 --- a/contribs/gnopls/internal/test/integration/misc/definition_test.go +++ b/contribs/gnopls/internal/test/integration/misc/definition_test.go @@ -13,9 +13,9 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) const internalDefinition = ` diff --git a/contribs/gnopls/internal/test/integration/misc/embed_test.go b/contribs/gnopls/internal/test/integration/misc/embed_test.go index 894cff9f5a3..d0e5ee7e08a 100644 --- a/contribs/gnopls/internal/test/integration/misc/embed_test.go +++ b/contribs/gnopls/internal/test/integration/misc/embed_test.go @@ -7,7 +7,7 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestMissingPatternDiagnostic(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/extract_test.go b/contribs/gnopls/internal/test/integration/misc/extract_test.go index 569d53e8bba..c323ac47363 100644 --- a/contribs/gnopls/internal/test/integration/misc/extract_test.go +++ b/contribs/gnopls/internal/test/integration/misc/extract_test.go @@ -7,11 +7,11 @@ package misc import ( "testing" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func TestExtractFunction(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/failures_test.go b/contribs/gnopls/internal/test/integration/misc/failures_test.go index 81fa17deb9b..05e1a5811d6 100644 --- a/contribs/gnopls/internal/test/integration/misc/failures_test.go +++ b/contribs/gnopls/internal/test/integration/misc/failures_test.go @@ -7,8 +7,8 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" ) // This is a slight variant of TestHoverOnError in definition_test.go diff --git a/contribs/gnopls/internal/test/integration/misc/fix_test.go b/contribs/gnopls/internal/test/integration/misc/fix_test.go index 5a01afe2400..80131323298 100644 --- a/contribs/gnopls/internal/test/integration/misc/fix_test.go +++ b/contribs/gnopls/internal/test/integration/misc/fix_test.go @@ -7,11 +7,11 @@ package misc import ( "testing" - "golang.org/x/tools/gopls/internal/settings" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // A basic test for fillstruct, now that it uses a command and supports resolve edits. diff --git a/contribs/gnopls/internal/test/integration/misc/formatting_test.go b/contribs/gnopls/internal/test/integration/misc/formatting_test.go index 1808dbc8791..4eb16d12876 100644 --- a/contribs/gnopls/internal/test/integration/misc/formatting_test.go +++ b/contribs/gnopls/internal/test/integration/misc/formatting_test.go @@ -8,9 +8,9 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) const unformattedProgram = ` diff --git a/contribs/gnopls/internal/test/integration/misc/generate_test.go b/contribs/gnopls/internal/test/integration/misc/generate_test.go index 548f3bd5f5e..ba8b74a7567 100644 --- a/contribs/gnopls/internal/test/integration/misc/generate_test.go +++ b/contribs/gnopls/internal/test/integration/misc/generate_test.go @@ -12,7 +12,7 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestGenerateProgress(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/highlight_test.go b/contribs/gnopls/internal/test/integration/misc/highlight_test.go index 9e3dd980464..1e8c7b12103 100644 --- a/contribs/gnopls/internal/test/integration/misc/highlight_test.go +++ b/contribs/gnopls/internal/test/integration/misc/highlight_test.go @@ -8,8 +8,8 @@ import ( "sort" "testing" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestWorkspacePackageHighlight(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/hover_test.go b/contribs/gnopls/internal/test/integration/misc/hover_test.go index 9c679f02d53..c9808840464 100644 --- a/contribs/gnopls/internal/test/integration/misc/hover_test.go +++ b/contribs/gnopls/internal/test/integration/misc/hover_test.go @@ -11,10 +11,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestHoverUnexported(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/import_test.go b/contribs/gnopls/internal/test/integration/misc/import_test.go index 671d72d27b6..e72d6b4c309 100644 --- a/contribs/gnopls/internal/test/integration/misc/import_test.go +++ b/contribs/gnopls/internal/test/integration/misc/import_test.go @@ -8,10 +8,10 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestAddImport(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/imports_test.go b/contribs/gnopls/internal/test/integration/misc/imports_test.go index 15fbd87e0fd..305c5f8faf1 100644 --- a/contribs/gnopls/internal/test/integration/misc/imports_test.go +++ b/contribs/gnopls/internal/test/integration/misc/imports_test.go @@ -11,10 +11,10 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) // Tests golang/go#38815. diff --git a/contribs/gnopls/internal/test/integration/misc/link_test.go b/contribs/gnopls/internal/test/integration/misc/link_test.go index 53b0f0818f3..1e02364d401 100644 --- a/contribs/gnopls/internal/test/integration/misc/link_test.go +++ b/contribs/gnopls/internal/test/integration/misc/link_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestHoverAndDocumentLink(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/misc_test.go b/contribs/gnopls/internal/test/integration/misc/misc_test.go index ca0125894c8..bbb674a7062 100644 --- a/contribs/gnopls/internal/test/integration/misc/misc_test.go +++ b/contribs/gnopls/internal/test/integration/misc/misc_test.go @@ -10,9 +10,9 @@ import ( "testing" "golang.org/x/telemetry/counter/countertest" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/misc/multiple_adhoc_test.go b/contribs/gnopls/internal/test/integration/misc/multiple_adhoc_test.go index aba7e987968..50a1ae099c5 100644 --- a/contribs/gnopls/internal/test/integration/misc/multiple_adhoc_test.go +++ b/contribs/gnopls/internal/test/integration/misc/multiple_adhoc_test.go @@ -7,7 +7,7 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestMultipleAdHocPackages(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/prompt_test.go b/contribs/gnopls/internal/test/integration/misc/prompt_test.go index 9e87bd9ba36..a7bcee7c64c 100644 --- a/contribs/gnopls/internal/test/integration/misc/prompt_test.go +++ b/contribs/gnopls/internal/test/integration/misc/prompt_test.go @@ -16,10 +16,10 @@ import ( "github.com/google/go-cmp/cmp" "golang.org/x/telemetry/counter" "golang.org/x/telemetry/counter/countertest" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/server" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // Test prompt file in old and new formats are handled as expected. diff --git a/contribs/gnopls/internal/test/integration/misc/references_test.go b/contribs/gnopls/internal/test/integration/misc/references_test.go index 73e4fffe3b8..451e7b34ff1 100644 --- a/contribs/gnopls/internal/test/integration/misc/references_test.go +++ b/contribs/gnopls/internal/test/integration/misc/references_test.go @@ -14,9 +14,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/integration" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestStdlibReferences(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/rename_test.go b/contribs/gnopls/internal/test/integration/misc/rename_test.go index e3116e1dd2a..cec415de619 100644 --- a/contribs/gnopls/internal/test/integration/misc/rename_test.go +++ b/contribs/gnopls/internal/test/integration/misc/rename_test.go @@ -9,9 +9,9 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestPrepareRenameMainPackage(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/semantictokens_test.go b/contribs/gnopls/internal/test/integration/misc/semantictokens_test.go index b8d8729c63a..3f128ef91d2 100644 --- a/contribs/gnopls/internal/test/integration/misc/semantictokens_test.go +++ b/contribs/gnopls/internal/test/integration/misc/semantictokens_test.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" ) func TestBadURICrash_VSCodeIssue1498(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/settings_test.go b/contribs/gnopls/internal/test/integration/misc/settings_test.go index c367f9fc357..694c51e4165 100644 --- a/contribs/gnopls/internal/test/integration/misc/settings_test.go +++ b/contribs/gnopls/internal/test/integration/misc/settings_test.go @@ -7,7 +7,7 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestEmptyDirectoryFilters_Issue51843(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/shared_test.go b/contribs/gnopls/internal/test/integration/misc/shared_test.go index b0bbcaa030a..ce32155f9b9 100644 --- a/contribs/gnopls/internal/test/integration/misc/shared_test.go +++ b/contribs/gnopls/internal/test/integration/misc/shared_test.go @@ -7,7 +7,7 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // Smoke test that simultaneous editing sessions in the same workspace works. diff --git a/contribs/gnopls/internal/test/integration/misc/signature_help_test.go b/contribs/gnopls/internal/test/integration/misc/signature_help_test.go index 8dffedf48e0..f56acccccb4 100644 --- a/contribs/gnopls/internal/test/integration/misc/signature_help_test.go +++ b/contribs/gnopls/internal/test/integration/misc/signature_help_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestSignatureHelpInNonWorkspacePackage(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/staticcheck_test.go b/contribs/gnopls/internal/test/integration/misc/staticcheck_test.go index 4970064c5f6..26d31487f36 100644 --- a/contribs/gnopls/internal/test/integration/misc/staticcheck_test.go +++ b/contribs/gnopls/internal/test/integration/misc/staticcheck_test.go @@ -7,10 +7,10 @@ package misc import ( "testing" - "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestStaticcheckGenerics(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/vendor_test.go b/contribs/gnopls/internal/test/integration/misc/vendor_test.go index f3bed9082b7..e39dfd71dd3 100644 --- a/contribs/gnopls/internal/test/integration/misc/vendor_test.go +++ b/contribs/gnopls/internal/test/integration/misc/vendor_test.go @@ -7,9 +7,9 @@ package misc import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) const basicProxy = ` diff --git a/contribs/gnopls/internal/test/integration/misc/vuln_test.go b/contribs/gnopls/internal/test/integration/misc/vuln_test.go index 7be02b3ceb3..ae606899618 100644 --- a/contribs/gnopls/internal/test/integration/misc/vuln_test.go +++ b/contribs/gnopls/internal/test/integration/misc/vuln_test.go @@ -14,13 +14,13 @@ import ( "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/vulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/vulntest" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/vulntest" ) func TestRunGovulncheckError(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/misc/webserver_test.go b/contribs/gnopls/internal/test/integration/misc/webserver_test.go index f8038f5721d..bed7b0918b8 100644 --- a/contribs/gnopls/internal/test/integration/misc/webserver_test.go +++ b/contribs/gnopls/internal/test/integration/misc/webserver_test.go @@ -14,11 +14,11 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/settings" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) // TestWebServer exercises the web server created on demand diff --git a/contribs/gnopls/internal/test/integration/misc/workspace_symbol_test.go b/contribs/gnopls/internal/test/integration/misc/workspace_symbol_test.go index 9420b146d85..04bf0a83c6a 100644 --- a/contribs/gnopls/internal/test/integration/misc/workspace_symbol_test.go +++ b/contribs/gnopls/internal/test/integration/misc/workspace_symbol_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/settings" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/settings" ) func TestWorkspaceSymbolMissingMetadata(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/modfile/modfile_test.go b/contribs/gnopls/internal/test/integration/modfile/modfile_test.go index 243bb04e960..9f10b3ed2b9 100644 --- a/contribs/gnopls/internal/test/integration/modfile/modfile_test.go +++ b/contribs/gnopls/internal/test/integration/modfile/modfile_test.go @@ -11,11 +11,11 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/modfile/tempmodfile_test.go b/contribs/gnopls/internal/test/integration/modfile/tempmodfile_test.go index 9f8972dc13f..8d32e06e9aa 100644 --- a/contribs/gnopls/internal/test/integration/modfile/tempmodfile_test.go +++ b/contribs/gnopls/internal/test/integration/modfile/tempmodfile_test.go @@ -7,7 +7,7 @@ package modfile import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // This test replaces an older, problematic test (golang/go#57784). But it has diff --git a/contribs/gnopls/internal/test/integration/options.go b/contribs/gnopls/internal/test/integration/options.go index 87be2114eaa..646ccb3950d 100644 --- a/contribs/gnopls/internal/test/integration/options.go +++ b/contribs/gnopls/internal/test/integration/options.go @@ -8,9 +8,9 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/internal/drivertest" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/drivertest" ) type runConfig struct { diff --git a/contribs/gnopls/internal/test/integration/regtest.go b/contribs/gnopls/internal/test/integration/regtest.go index b676fd4c500..6fdfcd65dd3 100644 --- a/contribs/gnopls/internal/test/integration/regtest.go +++ b/contribs/gnopls/internal/test/integration/regtest.go @@ -13,13 +13,13 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/cmd" - "golang.org/x/tools/internal/drivertest" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/memoize" - "golang.org/x/tools/internal/testenv" - "golang.org/x/tools/internal/tool" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/cmd" + "github.com/gnolang/gno/contribs/gnopls/internal/drivertest" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) var ( diff --git a/contribs/gnopls/internal/test/integration/runner.go b/contribs/gnopls/internal/test/integration/runner.go index 7b3b757536f..75ce7398394 100644 --- a/contribs/gnopls/internal/test/integration/runner.go +++ b/contribs/gnopls/internal/test/integration/runner.go @@ -20,16 +20,16 @@ import ( "testing" "time" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/lsprpc" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/jsonrpc2/servertest" - "golang.org/x/tools/internal/memoize" - "golang.org/x/tools/internal/testenv" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2/servertest" + "github.com/gnolang/gno/contribs/gnopls/internal/memoize" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) // Mode is a bitmask that defines for which execution modes a test should run. diff --git a/contribs/gnopls/internal/test/integration/template/template_test.go b/contribs/gnopls/internal/test/integration/template/template_test.go index 47398f5a3a2..edabb84dbe6 100644 --- a/contribs/gnopls/internal/test/integration/template/template_test.go +++ b/contribs/gnopls/internal/test/integration/template/template_test.go @@ -9,9 +9,9 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/watch/setting_test.go b/contribs/gnopls/internal/test/integration/watch/setting_test.go index abd9799c584..df09b71c9a7 100644 --- a/contribs/gnopls/internal/test/integration/watch/setting_test.go +++ b/contribs/gnopls/internal/test/integration/watch/setting_test.go @@ -8,7 +8,7 @@ import ( "fmt" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestSubdirWatchPatterns(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/watch/watch_test.go b/contribs/gnopls/internal/test/integration/watch/watch_test.go index 7f41511d140..0c6b12e29d1 100644 --- a/contribs/gnopls/internal/test/integration/watch/watch_test.go +++ b/contribs/gnopls/internal/test/integration/watch/watch_test.go @@ -8,11 +8,11 @@ import ( "os" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/util/bug" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/workspace/adhoc_test.go b/contribs/gnopls/internal/test/integration/workspace/adhoc_test.go index 3d451dd5f08..bf02cde23cb 100644 --- a/contribs/gnopls/internal/test/integration/workspace/adhoc_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/adhoc_test.go @@ -7,7 +7,7 @@ package workspace import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // Test for golang/go#57209: editing a file in an ad-hoc package should not diff --git a/contribs/gnopls/internal/test/integration/workspace/broken_test.go b/contribs/gnopls/internal/test/integration/workspace/broken_test.go index 8f00be775e4..68187d29f78 100644 --- a/contribs/gnopls/internal/test/integration/workspace/broken_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/broken_test.go @@ -8,9 +8,9 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/server" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/server" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) // This file holds various tests for UX with respect to broken workspaces. diff --git a/contribs/gnopls/internal/test/integration/workspace/directoryfilters_test.go b/contribs/gnopls/internal/test/integration/workspace/directoryfilters_test.go index 6eec8377233..df806611df1 100644 --- a/contribs/gnopls/internal/test/integration/workspace/directoryfilters_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/directoryfilters_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // This file contains regression tests for the directoryFilters setting. diff --git a/contribs/gnopls/internal/test/integration/workspace/fromenv_test.go b/contribs/gnopls/internal/test/integration/workspace/fromenv_test.go index bc909c7deca..c5b1f54a39a 100644 --- a/contribs/gnopls/internal/test/integration/workspace/fromenv_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/fromenv_test.go @@ -9,7 +9,7 @@ import ( "path/filepath" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // Test that setting go.work via environment variables or settings works. diff --git a/contribs/gnopls/internal/test/integration/workspace/goversion_test.go b/contribs/gnopls/internal/test/integration/workspace/goversion_test.go index 0a2f91505c2..26f429f9410 100644 --- a/contribs/gnopls/internal/test/integration/workspace/goversion_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/goversion_test.go @@ -12,8 +12,8 @@ import ( "strings" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/internal/testenv" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) var go121bin = flag.String("go121bin", "", "bin directory containing go 1.21 or later") diff --git a/contribs/gnopls/internal/test/integration/workspace/metadata_test.go b/contribs/gnopls/internal/test/integration/workspace/metadata_test.go index 59dfec3ad97..eb94103e9b0 100644 --- a/contribs/gnopls/internal/test/integration/workspace/metadata_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/metadata_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // TODO(rfindley): move workspace tests related to metadata bugs into this diff --git a/contribs/gnopls/internal/test/integration/workspace/misspelling_test.go b/contribs/gnopls/internal/test/integration/workspace/misspelling_test.go index ddca05c860e..67682e5a66b 100644 --- a/contribs/gnopls/internal/test/integration/workspace/misspelling_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/misspelling_test.go @@ -8,8 +8,8 @@ import ( "runtime" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/compare" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" ) // Test for golang/go#57081. diff --git a/contribs/gnopls/internal/test/integration/workspace/modules_test.go b/contribs/gnopls/internal/test/integration/workspace/modules_test.go index 7eedcff688a..ed90b449522 100644 --- a/contribs/gnopls/internal/test/integration/workspace/modules_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/modules_test.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestModulesCmd(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/workspace/multi_folder_test.go b/contribs/gnopls/internal/test/integration/workspace/multi_folder_test.go index 6adc1f8d5ce..e1eff8e0990 100644 --- a/contribs/gnopls/internal/test/integration/workspace/multi_folder_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/multi_folder_test.go @@ -7,7 +7,7 @@ package workspace import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) // TODO(rfindley): update the marker tests to support the concept of multiple diff --git a/contribs/gnopls/internal/test/integration/workspace/packages_test.go b/contribs/gnopls/internal/test/integration/workspace/packages_test.go index 106734a1864..433b733af0b 100644 --- a/contribs/gnopls/internal/test/integration/workspace/packages_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/packages_test.go @@ -10,9 +10,9 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestPackages(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/workspace/quickfix_test.go b/contribs/gnopls/internal/test/integration/workspace/quickfix_test.go index 6f7c8e854d0..c08d0c6e2fe 100644 --- a/contribs/gnopls/internal/test/integration/workspace/quickfix_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/quickfix_test.go @@ -8,10 +8,10 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/compare" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestQuickFix_UseModule(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/workspace/standalone_test.go b/contribs/gnopls/internal/test/integration/workspace/standalone_test.go index d837899f7fb..2ebd765e05a 100644 --- a/contribs/gnopls/internal/test/integration/workspace/standalone_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/standalone_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestStandaloneFiles(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/workspace/std_test.go b/contribs/gnopls/internal/test/integration/workspace/std_test.go index 9c021fef4f3..8f5b9ed6b16 100644 --- a/contribs/gnopls/internal/test/integration/workspace/std_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/std_test.go @@ -11,7 +11,7 @@ import ( "strings" "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestStdWorkspace(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/workspace/vendor_test.go b/contribs/gnopls/internal/test/integration/workspace/vendor_test.go index f14cf539de0..46aa24abb54 100644 --- a/contribs/gnopls/internal/test/integration/workspace/vendor_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/vendor_test.go @@ -7,7 +7,7 @@ package workspace import ( "testing" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestWorkspacePackagesExcludesVendor(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/workspace/workspace_test.go b/contribs/gnopls/internal/test/integration/workspace/workspace_test.go index ac74e6deed5..74584041bd0 100644 --- a/contribs/gnopls/internal/test/integration/workspace/workspace_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/workspace_test.go @@ -14,16 +14,16 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/goversion" - "golang.org/x/tools/internal/gocommand" - "golang.org/x/tools/internal/testenv" - - . "golang.org/x/tools/gopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/goversion" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestMain(m *testing.M) { diff --git a/contribs/gnopls/internal/test/integration/workspace/zero_config_test.go b/contribs/gnopls/internal/test/integration/workspace/zero_config_test.go index 95906274b93..0bd2709870e 100644 --- a/contribs/gnopls/internal/test/integration/workspace/zero_config_test.go +++ b/contribs/gnopls/internal/test/integration/workspace/zero_config_test.go @@ -10,10 +10,10 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" - . "golang.org/x/tools/gopls/internal/test/integration" + . "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" ) func TestAddAndRemoveGoWork(t *testing.T) { diff --git a/contribs/gnopls/internal/test/integration/wrappers.go b/contribs/gnopls/internal/test/integration/wrappers.go index ddff4da979b..e590a6b07e6 100644 --- a/contribs/gnopls/internal/test/integration/wrappers.go +++ b/contribs/gnopls/internal/test/integration/wrappers.go @@ -10,10 +10,10 @@ import ( "os" "path" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/protocol/command" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/internal/xcontext" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" ) // RemoveWorkspaceFile deletes a file on disk but does nothing in the diff --git a/contribs/gnopls/internal/test/marker/marker_test.go b/contribs/gnopls/internal/test/marker/marker_test.go index 1896d8fb19f..bd14227c120 100644 --- a/contribs/gnopls/internal/test/marker/marker_test.go +++ b/contribs/gnopls/internal/test/marker/marker_test.go @@ -32,20 +32,20 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "golang.org/x/tools/go/expect" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/debug" - "golang.org/x/tools/gopls/internal/lsprpc" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/test/compare" - "golang.org/x/tools/gopls/internal/test/integration" - "golang.org/x/tools/gopls/internal/test/integration/fake" - "golang.org/x/tools/gopls/internal/util/bug" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/diff/myers" - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/jsonrpc2/servertest" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/test/compare" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration" + "github.com/gnolang/gno/contribs/gnopls/internal/test/integration/fake" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/diff/myers" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2/servertest" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" "golang.org/x/tools/txtar" ) diff --git a/contribs/gnopls/internal/testenv/exec.go b/contribs/gnopls/internal/testenv/exec.go new file mode 100644 index 00000000000..f2ab5f5eb8d --- /dev/null +++ b/contribs/gnopls/internal/testenv/exec.go @@ -0,0 +1,192 @@ +// Copyright 2015 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 testenv + +import ( + "context" + "flag" + "os" + "os/exec" + "reflect" + "runtime" + "strconv" + "sync" + "testing" + "time" +) + +// HasExec reports whether the current system can start new processes +// using os.StartProcess or (more commonly) exec.Command. +func HasExec() bool { + switch runtime.GOOS { + case "aix", + "android", + "darwin", + "dragonfly", + "freebsd", + "illumos", + "linux", + "netbsd", + "openbsd", + "plan9", + "solaris", + "windows": + // Known OS that isn't ios or wasm; assume that exec works. + return true + + case "ios", "js", "wasip1": + // ios has an exec syscall but on real iOS devices it might return a + // permission error. In an emulated environment (such as a Corellium host) + // it might succeed, so try it and find out. + // + // As of 2023-04-19 wasip1 and js don't have exec syscalls at all, but we + // may as well use the same path so that this branch can be tested without + // an ios environment. + fallthrough + + default: + tryExecOnce.Do(func() { + exe, err := os.Executable() + if err != nil { + return + } + if flag.Lookup("test.list") == nil { + // We found the executable, but we don't know how to run it in a way + // that should succeed without side-effects. Just forget it. + return + } + // We know that a test executable exists and can run, because we're + // running it now. Use it to check for overall exec support, but be sure + // to remove any environment variables that might trigger non-default + // behavior in a custom TestMain. + cmd := exec.Command(exe, "-test.list=^$") + cmd.Env = []string{} + if err := cmd.Run(); err == nil { + tryExecOk = true + } + }) + return tryExecOk + } +} + +var ( + tryExecOnce sync.Once + tryExecOk bool +) + +// NeedsExec checks that the current system can start new processes +// using os.StartProcess or (more commonly) exec.Command. +// If not, NeedsExec calls t.Skip with an explanation. +func NeedsExec(t testing.TB) { + if !HasExec() { + t.Skipf("skipping test: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH) + } +} + +// CommandContext is like exec.CommandContext, but: +// - skips t if the platform does not support os/exec, +// - if supported, sends SIGQUIT instead of SIGKILL in its Cancel function +// - if the test has a deadline, adds a Context timeout and (if supported) WaitDelay +// for an arbitrary grace period before the test's deadline expires, +// - if Cmd has the Cancel field, fails the test if the command is canceled +// due to the test's deadline, and +// - sets a Cleanup function that verifies that the test did not leak a subprocess. +func CommandContext(t testing.TB, ctx context.Context, name string, args ...string) *exec.Cmd { + t.Helper() + NeedsExec(t) + + var ( + cancelCtx context.CancelFunc + gracePeriod time.Duration // unlimited unless the test has a deadline (to allow for interactive debugging) + ) + + if td, ok := Deadline(t); ok { + // Start with a minimum grace period, just long enough to consume the + // output of a reasonable program after it terminates. + gracePeriod = 100 * time.Millisecond + if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { + scale, err := strconv.Atoi(s) + if err != nil { + t.Fatalf("invalid GO_TEST_TIMEOUT_SCALE: %v", err) + } + gracePeriod *= time.Duration(scale) + } + + // If time allows, increase the termination grace period to 5% of the + // test's remaining time. + testTimeout := time.Until(td) + if gp := testTimeout / 20; gp > gracePeriod { + gracePeriod = gp + } + + // When we run commands that execute subprocesses, we want to reserve two + // grace periods to clean up: one for the delay between the first + // termination signal being sent (via the Cancel callback when the Context + // expires) and the process being forcibly terminated (via the WaitDelay + // field), and a second one for the delay between the process being + // terminated and the test logging its output for debugging. + // + // (We want to ensure that the test process itself has enough time to + // log the output before it is also terminated.) + cmdTimeout := testTimeout - 2*gracePeriod + + if cd, ok := ctx.Deadline(); !ok || time.Until(cd) > cmdTimeout { + // Either ctx doesn't have a deadline, or its deadline would expire + // after (or too close before) the test has already timed out. + // Add a shorter timeout so that the test will produce useful output. + ctx, cancelCtx = context.WithTimeout(ctx, cmdTimeout) + } + } + + cmd := exec.CommandContext(ctx, name, args...) + + // Use reflection to set the Cancel and WaitDelay fields, if present. + // TODO(bcmills): When we no longer support Go versions below 1.20, + // remove the use of reflect and assume that the fields are always present. + rc := reflect.ValueOf(cmd).Elem() + + if rCancel := rc.FieldByName("Cancel"); rCancel.IsValid() { + rCancel.Set(reflect.ValueOf(func() error { + if cancelCtx != nil && ctx.Err() == context.DeadlineExceeded { + // The command timed out due to running too close to the test's deadline + // (because we specifically set a shorter Context deadline for that + // above). There is no way the test did that intentionally — it's too + // close to the wire! — so mark it as a test failure. That way, if the + // test expects the command to fail for some other reason, it doesn't + // have to distinguish between that reason and a timeout. + t.Errorf("test timed out while running command: %v", cmd) + } else { + // The command is being terminated due to ctx being canceled, but + // apparently not due to an explicit test deadline that we added. + // Log that information in case it is useful for diagnosing a failure, + // but don't actually fail the test because of it. + t.Logf("%v: terminating command: %v", ctx.Err(), cmd) + } + return cmd.Process.Signal(Sigquit) + })) + } + + if rWaitDelay := rc.FieldByName("WaitDelay"); rWaitDelay.IsValid() { + rWaitDelay.Set(reflect.ValueOf(gracePeriod)) + } + + t.Cleanup(func() { + if cancelCtx != nil { + cancelCtx() + } + if cmd.Process != nil && cmd.ProcessState == nil { + t.Errorf("command was started, but test did not wait for it to complete: %v", cmd) + } + }) + + return cmd +} + +// Command is like exec.Command, but applies the same changes as +// testenv.CommandContext (with a default Context). +func Command(t testing.TB, name string, args ...string) *exec.Cmd { + t.Helper() + return CommandContext(t, context.Background(), name, args...) +} diff --git a/contribs/gnopls/internal/testenv/testenv.go b/contribs/gnopls/internal/testenv/testenv.go new file mode 100644 index 00000000000..2b7542e7b85 --- /dev/null +++ b/contribs/gnopls/internal/testenv/testenv.go @@ -0,0 +1,492 @@ +// 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. + +// Package testenv contains helper functions for skipping tests +// based on which tools are present in the environment. +package testenv + +import ( + "bytes" + "fmt" + "go/build" + "os" + "os/exec" + "path/filepath" + "runtime" + "runtime/debug" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/mod/modfile" + "github.com/gnolang/gno/contribs/gnopls/internal/goroot" +) + +// packageMainIsDevel reports whether the module containing package main +// is a development version (if module information is available). +func packageMainIsDevel() bool { + info, ok := debug.ReadBuildInfo() + if !ok { + // Most test binaries currently lack build info, but this should become more + // permissive once https://golang.org/issue/33976 is fixed. + return true + } + + // Note: info.Main.Version describes the version of the module containing + // package main, not the version of “the main module”. + // See https://golang.org/issue/33975. + return info.Main.Version == "(devel)" +} + +var checkGoBuild struct { + once sync.Once + err error +} + +// HasTool reports an error if the required tool is not available in PATH. +// +// For certain tools, it checks that the tool executable is correct. +func HasTool(tool string) error { + if tool == "cgo" { + enabled, err := cgoEnabled(false) + if err != nil { + return fmt.Errorf("checking cgo: %v", err) + } + if !enabled { + return fmt.Errorf("cgo not enabled") + } + return nil + } + + _, err := exec.LookPath(tool) + if err != nil { + return err + } + + switch tool { + case "patch": + // check that the patch tools supports the -o argument + temp, err := os.CreateTemp("", "patch-test") + if err != nil { + return err + } + temp.Close() + defer os.Remove(temp.Name()) + cmd := exec.Command(tool, "-o", temp.Name()) + if err := cmd.Run(); err != nil { + return err + } + + case "go": + checkGoBuild.once.Do(func() { + if runtime.GOROOT() != "" { + // Ensure that the 'go' command found by exec.LookPath is from the correct + // GOROOT. Otherwise, 'some/path/go test ./...' will test against some + // version of the 'go' binary other than 'some/path/go', which is almost + // certainly not what the user intended. + out, err := exec.Command(tool, "env", "GOROOT").Output() + if err != nil { + if exit, ok := err.(*exec.ExitError); ok && len(exit.Stderr) > 0 { + err = fmt.Errorf("%w\nstderr:\n%s)", err, exit.Stderr) + } + checkGoBuild.err = err + return + } + GOROOT := strings.TrimSpace(string(out)) + if GOROOT != runtime.GOROOT() { + checkGoBuild.err = fmt.Errorf("'go env GOROOT' does not match runtime.GOROOT:\n\tgo env: %s\n\tGOROOT: %s", GOROOT, runtime.GOROOT()) + return + } + } + + dir, err := os.MkdirTemp("", "testenv-*") + if err != nil { + checkGoBuild.err = err + return + } + defer os.RemoveAll(dir) + + mainGo := filepath.Join(dir, "main.go") + if err := os.WriteFile(mainGo, []byte("package main\nfunc main() {}\n"), 0644); err != nil { + checkGoBuild.err = err + return + } + cmd := exec.Command("go", "build", "-o", os.DevNull, mainGo) + cmd.Dir = dir + if out, err := cmd.CombinedOutput(); err != nil { + if len(out) > 0 { + checkGoBuild.err = fmt.Errorf("%v: %v\n%s", cmd, err, out) + } else { + checkGoBuild.err = fmt.Errorf("%v: %v", cmd, err) + } + } + }) + if checkGoBuild.err != nil { + return checkGoBuild.err + } + + case "diff": + // Check that diff is the GNU version, needed for the -u argument and + // to report missing newlines at the end of files. + out, err := exec.Command(tool, "-version").Output() + if err != nil { + return err + } + if !bytes.Contains(out, []byte("GNU diffutils")) { + return fmt.Errorf("diff is not the GNU version") + } + } + + return nil +} + +func cgoEnabled(bypassEnvironment bool) (bool, error) { + cmd := exec.Command("go", "env", "CGO_ENABLED") + if bypassEnvironment { + cmd.Env = append(append([]string(nil), os.Environ()...), "CGO_ENABLED=") + } + out, err := cmd.Output() + if err != nil { + if exit, ok := err.(*exec.ExitError); ok && len(exit.Stderr) > 0 { + err = fmt.Errorf("%w\nstderr:\n%s", err, exit.Stderr) + } + return false, err + } + enabled := strings.TrimSpace(string(out)) + return enabled == "1", nil +} + +func allowMissingTool(tool string) bool { + switch runtime.GOOS { + case "aix", "darwin", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "plan9", "solaris", "windows": + // Known non-mobile OS. Expect a reasonably complete environment. + default: + return true + } + + switch tool { + case "cgo": + if strings.HasSuffix(os.Getenv("GO_BUILDER_NAME"), "-nocgo") { + // Explicitly disabled on -nocgo builders. + return true + } + if enabled, err := cgoEnabled(true); err == nil && !enabled { + // No platform support. + return true + } + case "go": + if os.Getenv("GO_BUILDER_NAME") == "illumos-amd64-joyent" { + // Work around a misconfigured builder (see https://golang.org/issue/33950). + return true + } + case "diff": + if os.Getenv("GO_BUILDER_NAME") != "" { + return true + } + case "patch": + if os.Getenv("GO_BUILDER_NAME") != "" { + return true + } + } + + // If a developer is actively working on this test, we expect them to have all + // of its dependencies installed. However, if it's just a dependency of some + // other module (for example, being run via 'go test all'), we should be more + // tolerant of unusual environments. + return !packageMainIsDevel() +} + +// NeedsTool skips t if the named tool is not present in the path. +// As a special case, "cgo" means "go" is present and can compile cgo programs. +func NeedsTool(t testing.TB, tool string) { + err := HasTool(tool) + if err == nil { + return + } + + t.Helper() + if allowMissingTool(tool) { + // TODO(adonovan): if we skip because of (e.g.) + // mismatched go env GOROOT and runtime.GOROOT, don't + // we risk some users not getting the coverage they expect? + // bcmills notes: this shouldn't be a concern as of CL 404134 (Go 1.19). + // We could probably safely get rid of that GOPATH consistency + // check entirely at this point. + t.Skipf("skipping because %s tool not available: %v", tool, err) + } else { + t.Fatalf("%s tool not available: %v", tool, err) + } +} + +// NeedsGoPackages skips t if the go/packages driver (or 'go' tool) implied by +// the current process environment is not present in the path. +func NeedsGoPackages(t testing.TB) { + t.Helper() + + tool := os.Getenv("GOPACKAGESDRIVER") + switch tool { + case "off": + // "off" forces go/packages to use the go command. + tool = "go" + case "": + if _, err := exec.LookPath("gopackagesdriver"); err == nil { + tool = "gopackagesdriver" + } else { + tool = "go" + } + } + + NeedsTool(t, tool) +} + +// NeedsGoPackagesEnv skips t if the go/packages driver (or 'go' tool) implied +// by env is not present in the path. +func NeedsGoPackagesEnv(t testing.TB, env []string) { + t.Helper() + + for _, v := range env { + if strings.HasPrefix(v, "GOPACKAGESDRIVER=") { + tool := strings.TrimPrefix(v, "GOPACKAGESDRIVER=") + if tool == "off" { + NeedsTool(t, "go") + } else { + NeedsTool(t, tool) + } + return + } + } + + NeedsGoPackages(t) +} + +// NeedsGoBuild skips t if the current system can't build programs with “go build” +// and then run them with os.StartProcess or exec.Command. +// Android doesn't have the userspace go build needs to run, +// and js/wasm doesn't support running subprocesses. +func NeedsGoBuild(t testing.TB) { + t.Helper() + + // This logic was derived from internal/testing.HasGoBuild and + // may need to be updated as that function evolves. + + NeedsTool(t, "go") +} + +// ExitIfSmallMachine emits a helpful diagnostic and calls os.Exit(0) if the +// current machine is a builder known to have scarce resources. +// +// It should be called from within a TestMain function. +func ExitIfSmallMachine() { + switch b := os.Getenv("GO_BUILDER_NAME"); b { + case "linux-arm-scaleway": + // "linux-arm" was renamed to "linux-arm-scaleway" in CL 303230. + fmt.Fprintln(os.Stderr, "skipping test: linux-arm-scaleway builder lacks sufficient memory (https://golang.org/issue/32834)") + case "plan9-arm": + fmt.Fprintln(os.Stderr, "skipping test: plan9-arm builder lacks sufficient memory (https://golang.org/issue/38772)") + case "netbsd-arm-bsiegert", "netbsd-arm64-bsiegert": + // As of 2021-06-02, these builders are running with GO_TEST_TIMEOUT_SCALE=10, + // and there is only one of each. We shouldn't waste those scarce resources + // running very slow tests. + fmt.Fprintf(os.Stderr, "skipping test: %s builder is very slow\n", b) + case "dragonfly-amd64": + // As of 2021-11-02, this builder is running with GO_TEST_TIMEOUT_SCALE=2, + // and seems to have unusually slow disk performance. + fmt.Fprintln(os.Stderr, "skipping test: dragonfly-amd64 has slow disk (https://golang.org/issue/45216)") + case "linux-riscv64-unmatched": + // As of 2021-11-03, this builder is empirically not fast enough to run + // gopls tests. Ideally we should make the tests faster in short mode + // and/or fix them to not assume arbitrary deadlines. + // For now, we'll skip them instead. + fmt.Fprintf(os.Stderr, "skipping test: %s builder is too slow (https://golang.org/issue/49321)\n", b) + default: + switch runtime.GOOS { + case "android", "ios": + fmt.Fprintf(os.Stderr, "skipping test: assuming that %s is resource-constrained\n", runtime.GOOS) + default: + return + } + } + os.Exit(0) +} + +// Go1Point returns the x in Go 1.x. +func Go1Point() int { + for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- { + var version int + if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil { + continue + } + return version + } + panic("bad release tags") +} + +// NeedsGo1Point skips t if the Go version used to run the test is older than +// 1.x. +func NeedsGo1Point(t testing.TB, x int) { + if Go1Point() < x { + t.Helper() + t.Skipf("running Go version %q is version 1.%d, older than required 1.%d", runtime.Version(), Go1Point(), x) + } +} + +// SkipAfterGo1Point skips t if the Go version used to run the test is newer than +// 1.x. +func SkipAfterGo1Point(t testing.TB, x int) { + if Go1Point() > x { + t.Helper() + t.Skipf("running Go version %q is version 1.%d, newer than maximum 1.%d", runtime.Version(), Go1Point(), x) + } +} + +// NeedsLocalhostNet skips t if networking does not work for ports opened +// with "localhost". +func NeedsLocalhostNet(t testing.TB) { + switch runtime.GOOS { + case "js", "wasip1": + t.Skipf(`Listening on "localhost" fails on %s; see https://go.dev/issue/59718`, runtime.GOOS) + } +} + +// Deadline returns the deadline of t, if known, +// using the Deadline method added in Go 1.15. +func Deadline(t testing.TB) (time.Time, bool) { + td, ok := t.(interface { + Deadline() (time.Time, bool) + }) + if !ok { + return time.Time{}, false + } + return td.Deadline() +} + +// WriteImportcfg writes an importcfg file used by the compiler or linker to +// dstPath containing entries for the packages in std and cmd in addition +// to the package to package file mappings in additionalPackageFiles. +func WriteImportcfg(t testing.TB, dstPath string, additionalPackageFiles map[string]string) { + importcfg, err := goroot.Importcfg() + for k, v := range additionalPackageFiles { + importcfg += fmt.Sprintf("\npackagefile %s=%s", k, v) + } + if err != nil { + t.Fatalf("preparing the importcfg failed: %s", err) + } + os.WriteFile(dstPath, []byte(importcfg), 0655) + if err != nil { + t.Fatalf("writing the importcfg failed: %s", err) + } +} + +var ( + gorootOnce sync.Once + gorootPath string + gorootErr error +) + +func findGOROOT() (string, error) { + gorootOnce.Do(func() { + gorootPath = runtime.GOROOT() + if gorootPath != "" { + // If runtime.GOROOT() is non-empty, assume that it is valid. (It might + // not be: for example, the user may have explicitly set GOROOT + // to the wrong directory.) + return + } + + cmd := exec.Command("go", "env", "GOROOT") + out, err := cmd.Output() + if err != nil { + gorootErr = fmt.Errorf("%v: %v", cmd, err) + } + gorootPath = strings.TrimSpace(string(out)) + }) + + return gorootPath, gorootErr +} + +// GOROOT reports the path to the directory containing the root of the Go +// project source tree. This is normally equivalent to runtime.GOROOT, but +// works even if the test binary was built with -trimpath. +// +// If GOROOT cannot be found, GOROOT skips t if t is non-nil, +// or panics otherwise. +func GOROOT(t testing.TB) string { + path, err := findGOROOT() + if err != nil { + if t == nil { + panic(err) + } + t.Helper() + t.Skip(err) + } + return path +} + +// NeedsLocalXTools skips t if the golang.org/x/tools module is replaced and +// its replacement directory does not exist (or does not contain the module). +func NeedsLocalXTools(t testing.TB) { + t.Helper() + + NeedsTool(t, "go") + + cmd := Command(t, "go", "list", "-f", "{{with .Replace}}{{.Dir}}{{end}}", "-m", "golang.org/x/tools") + out, err := cmd.Output() + if err != nil { + if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 { + t.Skipf("skipping test: %v: %v\n%s", cmd, err, ee.Stderr) + } + t.Skipf("skipping test: %v: %v", cmd, err) + } + + dir := string(bytes.TrimSpace(out)) + if dir == "" { + // No replacement directory, and (since we didn't set -e) no error either. + // Maybe x/tools isn't replaced at all (as in a gopls release, or when + // using a go.work file that includes the x/tools module). + return + } + + // We found the directory where x/tools would exist if we're in a clone of the + // repo. Is it there? (If not, we're probably in the module cache instead.) + modFilePath := filepath.Join(dir, "go.mod") + b, err := os.ReadFile(modFilePath) + if err != nil { + t.Skipf("skipping test: x/tools replacement not found: %v", err) + } + modulePath := modfile.ModulePath(b) + + if want := "golang.org/x/tools"; modulePath != want { + t.Skipf("skipping test: %s module path is %q, not %q", modFilePath, modulePath, want) + } +} + +// NeedsGoExperiment skips t if the current process environment does not +// have a GOEXPERIMENT flag set. +func NeedsGoExperiment(t testing.TB, flag string) { + t.Helper() + + goexp := os.Getenv("GOEXPERIMENT") + set := false + for _, f := range strings.Split(goexp, ",") { + if f == "" { + continue + } + if f == "none" { + // GOEXPERIMENT=none disables all experiment flags. + set = false + break + } + val := true + if strings.HasPrefix(f, "no") { + f, val = f[2:], false + } + if f == flag { + set = val + } + } + if !set { + t.Skipf("skipping test: flag %q is not set in GOEXPERIMENT=%q", flag, goexp) + } +} diff --git a/contribs/gnopls/internal/testenv/testenv_notunix.go b/contribs/gnopls/internal/testenv/testenv_notunix.go new file mode 100644 index 00000000000..e9ce0d3649d --- /dev/null +++ b/contribs/gnopls/internal/testenv/testenv_notunix.go @@ -0,0 +1,14 @@ +// 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. + +//go:build !(unix || aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris) +// +build !unix,!aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris + +package testenv + +import "os" + +// Sigquit is the signal to send to kill a hanging subprocess. +// On Unix we send SIGQUIT, but on non-Unix we only have os.Kill. +var Sigquit = os.Kill diff --git a/contribs/gnopls/internal/testenv/testenv_unix.go b/contribs/gnopls/internal/testenv/testenv_unix.go new file mode 100644 index 00000000000..bc6af1ff81d --- /dev/null +++ b/contribs/gnopls/internal/testenv/testenv_unix.go @@ -0,0 +1,14 @@ +// 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. + +//go:build unix || aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build unix aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package testenv + +import "syscall" + +// Sigquit is the signal to send to kill a hanging subprocess. +// Send SIGQUIT to get a stack trace. +var Sigquit = syscall.SIGQUIT diff --git a/contribs/gnopls/internal/testfiles/testdata/somefile.txt b/contribs/gnopls/internal/testfiles/testdata/somefile.txt new file mode 100644 index 00000000000..8d9c108d86c --- /dev/null +++ b/contribs/gnopls/internal/testfiles/testdata/somefile.txt @@ -0,0 +1 @@ +A file to try to load. \ No newline at end of file diff --git a/contribs/gnopls/internal/testfiles/testdata/versions/go.mod.test b/contribs/gnopls/internal/testfiles/testdata/versions/go.mod.test new file mode 100644 index 00000000000..0dfc6f15a11 --- /dev/null +++ b/contribs/gnopls/internal/testfiles/testdata/versions/go.mod.test @@ -0,0 +1,5 @@ +// File is versions/go.mod after expansion with TestDir() + +module golang.org/fake/versions + +go 1.22 diff --git a/contribs/gnopls/internal/testfiles/testdata/versions/mod.go b/contribs/gnopls/internal/testfiles/testdata/versions/mod.go new file mode 100644 index 00000000000..bd0bc18ac65 --- /dev/null +++ b/contribs/gnopls/internal/testfiles/testdata/versions/mod.go @@ -0,0 +1,3 @@ +// The file will be go1.22 from the go.mod. + +package versions // want "mod.go@go1.22" diff --git a/contribs/gnopls/internal/testfiles/testdata/versions/post.go b/contribs/gnopls/internal/testfiles/testdata/versions/post.go new file mode 100644 index 00000000000..c7eef6eeaa9 --- /dev/null +++ b/contribs/gnopls/internal/testfiles/testdata/versions/post.go @@ -0,0 +1,3 @@ +//go:build go1.23 + +package versions // want "post.go@go1.23" diff --git a/contribs/gnopls/internal/testfiles/testdata/versions/pre.go b/contribs/gnopls/internal/testfiles/testdata/versions/pre.go new file mode 100644 index 00000000000..809f8b793f3 --- /dev/null +++ b/contribs/gnopls/internal/testfiles/testdata/versions/pre.go @@ -0,0 +1,3 @@ +//go:build go1.21 + +package versions // want "pre.go@go1.21" diff --git a/contribs/gnopls/internal/testfiles/testdata/versions/sub.test/sub.go.test b/contribs/gnopls/internal/testfiles/testdata/versions/sub.test/sub.go.test new file mode 100644 index 00000000000..f573fdd782d --- /dev/null +++ b/contribs/gnopls/internal/testfiles/testdata/versions/sub.test/sub.go.test @@ -0,0 +1 @@ +package sub // want "sub.go@go1.22" diff --git a/contribs/gnopls/internal/testfiles/testfiles.go b/contribs/gnopls/internal/testfiles/testfiles.go new file mode 100644 index 00000000000..ec5fc7f6920 --- /dev/null +++ b/contribs/gnopls/internal/testfiles/testfiles.go @@ -0,0 +1,106 @@ +// 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 testfiles provides utilities for writing Go tests with files +// in testdata. +package testfiles + +import ( + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "testing" + + "golang.org/x/tools/txtar" +) + +// CopyToTmp copies the files and directories in src to a new temporary testing +// directory dst, and returns dst on success. +// +// After copying the files, it processes each of the 'old,new,' rename +// directives in order. Each rename directive moves the relative path "old" +// to the relative path "new" within the directory. +// +// Renaming allows tests to hide files whose names have +// special meaning, such as "go.mod" files or "testdata" directories +// from the go command, or ill-formed Go source files from gofmt. +// +// For example if we copy the directory testdata: +// +// testdata/ +// go.mod.test +// a/a.go +// b/b.go +// +// with the rename "go.mod.test,go.mod", the resulting files will be: +// +// dst/ +// go.mod +// a/a.go +// b/b.go +func CopyToTmp(t testing.TB, src fs.FS, rename ...string) string { + dstdir := t.TempDir() + + if err := copyFS(dstdir, src); err != nil { + t.Fatal(err) + } + for _, r := range rename { + old, new, found := strings.Cut(r, ",") + if !found { + t.Fatalf("rename directive %q does not contain delimiter %q", r, ",") + } + oldpath := filepath.Join(dstdir, old) + newpath := filepath.Join(dstdir, new) + if err := os.Rename(oldpath, newpath); err != nil { + t.Fatal(err) + } + } + + return dstdir +} + +// Copy the files in src to dst. +// Use os.CopyFS when 1.23 can be used in x/tools. +func copyFS(dstdir string, src fs.FS) error { + return fs.WalkDir(src, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + newpath := filepath.Join(dstdir, path) + if d.IsDir() { + return os.MkdirAll(newpath, 0777) + } + r, err := src.Open(path) + if err != nil { + return err + } + defer r.Close() + + w, err := os.Create(newpath) + if err != nil { + return err + } + defer w.Close() + _, err = io.Copy(w, r) + return err + }) +} + +// ExtractTxtarFileToTmp read a txtar archive on a given path, +// extracts it to a temporary directory, and returns the +// temporary directory. +func ExtractTxtarFileToTmp(t testing.TB, file string) string { + ar, err := txtar.ParseFile(file) + if err != nil { + t.Fatal(err) + } + + fs, err := txtar.FS(ar) + if err != nil { + t.Fatal(err) + } + return CopyToTmp(t, fs) +} diff --git a/contribs/gnopls/internal/testfiles/testfiles_test.go b/contribs/gnopls/internal/testfiles/testfiles_test.go new file mode 100644 index 00000000000..789344601e4 --- /dev/null +++ b/contribs/gnopls/internal/testfiles/testfiles_test.go @@ -0,0 +1,95 @@ +// 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 testfiles_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/internal/testenv" + "golang.org/x/tools/internal/testfiles" + "golang.org/x/tools/internal/versions" + "golang.org/x/tools/txtar" +) + +func TestTestDir(t *testing.T) { + testenv.NeedsGo1Point(t, 23) + + // Files are initially {go.mod.test,sub.test/sub.go.test}. + fs := os.DirFS(filepath.Join(analysistest.TestData(), "versions")) + tmpdir := testfiles.CopyToTmp(t, fs, + "go.mod.test,go.mod", // After: {go.mod,sub.test/sub.go.test} + "sub.test/sub.go.test,sub.test/abc", // After: {go.mod,sub.test/abc} + "sub.test,sub", // After: {go.mod,sub/abc} + "sub/abc,sub/sub.go", // After: {go.mod,sub/sub.go} + ) + + filever := &analysis.Analyzer{ + Name: "filever", + Doc: "reports file go versions", + Run: func(pass *analysis.Pass) (any, error) { + for _, file := range pass.Files { + ver := versions.FileVersion(pass.TypesInfo, file) + name := filepath.Base(pass.Fset.Position(file.Package).Filename) + pass.Reportf(file.Package, "%s@%s", name, ver) + } + return nil, nil + }, + } + res := analysistest.Run(t, tmpdir, filever, "golang.org/fake/versions", "golang.org/fake/versions/sub") + got := 0 + for _, r := range res { + got += len(r.Diagnostics) + } + + if want := 4; got != want { + t.Errorf("Got %d diagnostics. wanted %d", got, want) + } +} + +func TestTestDirErrors(t *testing.T) { + const input = ` +-- one.txt -- +one +` + // Files are initially {go.mod.test,sub.test/sub.go.test}. + fs, err := txtar.FS(txtar.Parse([]byte(input))) + if err != nil { + t.Fatal(err) + } + + directive := "no comma to split on" + intercept := &fatalIntercept{t, nil} + func() { + defer func() { // swallow panics from fatalIntercept.Fatal + if r := recover(); r != intercept { + panic(r) + } + }() + testfiles.CopyToTmp(intercept, fs, directive) + }() + + got := fmt.Sprint(intercept.fatalfs) + want := `[rename directive "no comma to split on" does not contain delimiter ","]` + if got != want { + t.Errorf("CopyToTmp(%q) had the Fatal messages %q. wanted %q", directive, got, want) + } +} + +// helper for TestTestDirErrors +type fatalIntercept struct { + testing.TB + fatalfs []string +} + +func (i *fatalIntercept) Fatalf(format string, args ...any) { + i.fatalfs = append(i.fatalfs, fmt.Sprintf(format, args...)) + // Do not mark the test as failing, but fail early. + panic(i) +} diff --git a/contribs/gnopls/internal/tokeninternal/tokeninternal.go b/contribs/gnopls/internal/tokeninternal/tokeninternal.go new file mode 100644 index 00000000000..ff9437a36cd --- /dev/null +++ b/contribs/gnopls/internal/tokeninternal/tokeninternal.go @@ -0,0 +1,137 @@ +// 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 tokeninternal provides access to some internal features of the token +// package. +package tokeninternal + +import ( + "fmt" + "go/token" + "sort" + "sync" + "unsafe" +) + +// GetLines returns the table of line-start offsets from a token.File. +func GetLines(file *token.File) []int { + // token.File has a Lines method on Go 1.21 and later. + if file, ok := (interface{})(file).(interface{ Lines() []int }); ok { + return file.Lines() + } + + // This declaration must match that of token.File. + // This creates a risk of dependency skew. + // For now we check that the size of the two + // declarations is the same, on the (fragile) assumption + // that future changes would add fields. + type tokenFile119 struct { + _ string + _ int + _ int + mu sync.Mutex // we're not complete monsters + lines []int + _ []struct{} + } + + if unsafe.Sizeof(*file) != unsafe.Sizeof(tokenFile119{}) { + panic("unexpected token.File size") + } + var ptr *tokenFile119 + type uP = unsafe.Pointer + *(*uP)(uP(&ptr)) = uP(file) + ptr.mu.Lock() + defer ptr.mu.Unlock() + return ptr.lines +} + +// AddExistingFiles adds the specified files to the FileSet if they +// are not already present. It panics if any pair of files in the +// resulting FileSet would overlap. +func AddExistingFiles(fset *token.FileSet, files []*token.File) { + // Punch through the FileSet encapsulation. + type tokenFileSet struct { + // This type remained essentially consistent from go1.16 to go1.21. + mutex sync.RWMutex + base int + files []*token.File + _ *token.File // changed to atomic.Pointer[token.File] in go1.19 + } + + // If the size of token.FileSet changes, this will fail to compile. + const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) + var _ [-delta * delta]int + + type uP = unsafe.Pointer + var ptr *tokenFileSet + *(*uP)(uP(&ptr)) = uP(fset) + ptr.mutex.Lock() + defer ptr.mutex.Unlock() + + // Merge and sort. + newFiles := append(ptr.files, files...) + sort.Slice(newFiles, func(i, j int) bool { + return newFiles[i].Base() < newFiles[j].Base() + }) + + // Reject overlapping files. + // Discard adjacent identical files. + out := newFiles[:0] + for i, file := range newFiles { + if i > 0 { + prev := newFiles[i-1] + if file == prev { + continue + } + if prev.Base()+prev.Size()+1 > file.Base() { + panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)", + prev.Name(), prev.Base(), prev.Base()+prev.Size(), + file.Name(), file.Base(), file.Base()+file.Size())) + } + } + out = append(out, file) + } + newFiles = out + + ptr.files = newFiles + + // Advance FileSet.Base(). + if len(newFiles) > 0 { + last := newFiles[len(newFiles)-1] + newBase := last.Base() + last.Size() + 1 + if ptr.base < newBase { + ptr.base = newBase + } + } +} + +// FileSetFor returns a new FileSet containing a sequence of new Files with +// the same base, size, and line as the input files, for use in APIs that +// require a FileSet. +// +// Precondition: the input files must be non-overlapping, and sorted in order +// of their Base. +func FileSetFor(files ...*token.File) *token.FileSet { + fset := token.NewFileSet() + for _, f := range files { + f2 := fset.AddFile(f.Name(), f.Base(), f.Size()) + lines := GetLines(f) + f2.SetLines(lines) + } + return fset +} + +// CloneFileSet creates a new FileSet holding all files in fset. It does not +// create copies of the token.Files in fset: they are added to the resulting +// FileSet unmodified. +func CloneFileSet(fset *token.FileSet) *token.FileSet { + var files []*token.File + fset.Iterate(func(f *token.File) bool { + files = append(files, f) + return true + }) + newFileSet := token.NewFileSet() + AddExistingFiles(newFileSet, files) + return newFileSet +} diff --git a/contribs/gnopls/internal/tokeninternal/tokeninternal_test.go b/contribs/gnopls/internal/tokeninternal/tokeninternal_test.go new file mode 100644 index 00000000000..86f4f463b23 --- /dev/null +++ b/contribs/gnopls/internal/tokeninternal/tokeninternal_test.go @@ -0,0 +1,55 @@ +// 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 tokeninternal_test + +import ( + "fmt" + "go/token" + "strings" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" +) + +func TestAddExistingFiles(t *testing.T) { + fset := token.NewFileSet() + + check := func(descr, want string) { + t.Helper() + if got := fsetString(fset); got != want { + t.Errorf("%s: got %s, want %s", descr, got, want) + } + } + + fileA := fset.AddFile("A", -1, 3) + fileB := fset.AddFile("B", -1, 5) + _ = fileB + check("after AddFile [AB]", "{A:1-4 B:5-10}") + + tokeninternal.AddExistingFiles(fset, nil) + check("after AddExistingFiles []", "{A:1-4 B:5-10}") + + fileC := token.NewFileSet().AddFile("C", 100, 5) + fileD := token.NewFileSet().AddFile("D", 200, 5) + tokeninternal.AddExistingFiles(fset, []*token.File{fileC, fileA, fileD, fileC}) + check("after AddExistingFiles [CADC]", "{A:1-4 B:5-10 C:100-105 D:200-205}") + + fileE := fset.AddFile("E", -1, 3) + _ = fileE + check("after AddFile [E]", "{A:1-4 B:5-10 C:100-105 D:200-205 E:206-209}") +} + +func fsetString(fset *token.FileSet) string { + var buf strings.Builder + buf.WriteRune('{') + sep := "" + fset.Iterate(func(f *token.File) bool { + fmt.Fprintf(&buf, "%s%s:%d-%d", sep, f.Name(), f.Base(), f.Base()+f.Size()) + sep = " " + return true + }) + buf.WriteRune('}') + return buf.String() +} diff --git a/contribs/gnopls/internal/tool/tool.go b/contribs/gnopls/internal/tool/tool.go new file mode 100644 index 00000000000..e5e07927f94 --- /dev/null +++ b/contribs/gnopls/internal/tool/tool.go @@ -0,0 +1,277 @@ +// Copyright 2018 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 tool is a harness for writing Go tools. +package tool + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "reflect" + "runtime" + "runtime/pprof" + "runtime/trace" + "strings" + "time" +) + +// This file is a harness for writing your main function. +// The original version of the file is in github.com/gnolang/gno/contribs/gnopls/internal/tool. +// +// It adds a method to the Application type +// Main(name, usage string, args []string) +// which should normally be invoked from a true main as follows: +// func main() { +// (&Application{}).Main("myapp", "non-flag-command-line-arg-help", os.Args[1:]) +// } +// It recursively scans the application object for fields with a tag containing +// `flag:"flagnames" help:"short help text"` +// uses all those fields to build command line flags. It will split flagnames on +// commas and add a flag per name. +// It expects the Application type to have a method +// Run(context.Context, args...string) error +// which it invokes only after all command line flag processing has been finished. +// If Run returns an error, the error will be printed to stderr and the +// application will quit with a non zero exit status. + +// Profile can be embedded in your application struct to automatically +// add command line arguments and handling for the common profiling methods. +type Profile struct { + CPU string `flag:"profile.cpu" help:"write CPU profile to this file"` + Memory string `flag:"profile.mem" help:"write memory profile to this file"` + Alloc string `flag:"profile.alloc" help:"write alloc profile to this file"` + Trace string `flag:"profile.trace" help:"write trace log to this file"` +} + +// Application is the interface that must be satisfied by an object passed to Main. +type Application interface { + // Name returns the application's name. It is used in help and error messages. + Name() string + // Most of the help usage is automatically generated, this string should only + // describe the contents of non flag arguments. + Usage() string + // ShortHelp returns the one line overview of the command. + ShortHelp() string + // DetailedHelp should print a detailed help message. It will only ever be shown + // when the ShortHelp is also printed, so there is no need to duplicate + // anything from there. + // It is passed the flag set so it can print the default values of the flags. + // It should use the flag sets configured Output to write the help to. + DetailedHelp(*flag.FlagSet) + // Run is invoked after all flag processing, and inside the profiling and + // error handling harness. + Run(ctx context.Context, args ...string) error +} + +type SubCommand interface { + Parent() string +} + +// This is the type returned by CommandLineErrorf, which causes the outer main +// to trigger printing of the command line help. +type commandLineError string + +func (e commandLineError) Error() string { return string(e) } + +// CommandLineErrorf is like fmt.Errorf except that it returns a value that +// triggers printing of the command line help. +// In general you should use this when generating command line validation errors. +func CommandLineErrorf(message string, args ...interface{}) error { + return commandLineError(fmt.Sprintf(message, args...)) +} + +// Main should be invoked directly by main function. +// It will only return if there was no error. If an error +// was encountered it is printed to standard error and the +// application exits with an exit code of 2. +func Main(ctx context.Context, app Application, args []string) { + s := flag.NewFlagSet(app.Name(), flag.ExitOnError) + if err := Run(ctx, s, app, args); err != nil { + fmt.Fprintf(s.Output(), "%s: %v\n", app.Name(), err) + if _, printHelp := err.(commandLineError); printHelp { + // TODO(adonovan): refine this. It causes + // any command-line error to result in the full + // usage message, which typically obscures + // the actual error. + s.Usage() + } + os.Exit(2) + } +} + +// Run is the inner loop for Main; invoked by Main, recursively by +// Run, and by various tests. It runs the application and returns an +// error. +func Run(ctx context.Context, s *flag.FlagSet, app Application, args []string) (resultErr error) { + s.Usage = func() { + if app.ShortHelp() != "" { + fmt.Fprintf(s.Output(), "%s\n\nUsage:\n ", app.ShortHelp()) + if sub, ok := app.(SubCommand); ok && sub.Parent() != "" { + fmt.Fprintf(s.Output(), "%s [flags] %s", sub.Parent(), app.Name()) + } else { + fmt.Fprintf(s.Output(), "%s [flags]", app.Name()) + } + if usage := app.Usage(); usage != "" { + fmt.Fprintf(s.Output(), " %s", usage) + } + fmt.Fprint(s.Output(), "\n") + } + app.DetailedHelp(s) + } + p := addFlags(s, reflect.StructField{}, reflect.ValueOf(app)) + if err := s.Parse(args); err != nil { + return err + } + + if p != nil && p.CPU != "" { + f, err := os.Create(p.CPU) + if err != nil { + return err + } + if err := pprof.StartCPUProfile(f); err != nil { + f.Close() // ignore error + return err + } + defer func() { + pprof.StopCPUProfile() + if closeErr := f.Close(); resultErr == nil { + resultErr = closeErr + } + }() + } + + if p != nil && p.Trace != "" { + f, err := os.Create(p.Trace) + if err != nil { + return err + } + if err := trace.Start(f); err != nil { + f.Close() // ignore error + return err + } + defer func() { + trace.Stop() + if closeErr := f.Close(); resultErr == nil { + resultErr = closeErr + } + log.Printf("To view the trace, run:\n$ go tool trace view %s", p.Trace) + }() + } + + if p != nil && p.Memory != "" { + f, err := os.Create(p.Memory) + if err != nil { + return err + } + defer func() { + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + log.Printf("Writing memory profile: %v", err) + } + f.Close() + }() + } + + if p != nil && p.Alloc != "" { + f, err := os.Create(p.Alloc) + if err != nil { + return err + } + defer func() { + if err := pprof.Lookup("allocs").WriteTo(f, 0); err != nil { + log.Printf("Writing alloc profile: %v", err) + } + f.Close() + }() + } + + return app.Run(ctx, s.Args()...) +} + +// addFlags scans fields of structs recursively to find things with flag tags +// and add them to the flag set. +func addFlags(f *flag.FlagSet, field reflect.StructField, value reflect.Value) *Profile { + // is it a field we are allowed to reflect on? + if field.PkgPath != "" { + return nil + } + // now see if is actually a flag + flagNames, isFlag := field.Tag.Lookup("flag") + help := field.Tag.Get("help") + if isFlag { + nameList := strings.Split(flagNames, ",") + // add the main flag + addFlag(f, value, nameList[0], help) + if len(nameList) > 1 { + // and now add any aliases using the same flag value + fv := f.Lookup(nameList[0]).Value + for _, flagName := range nameList[1:] { + f.Var(fv, flagName, help) + } + } + return nil + } + // not a flag, but it might be a struct with flags in it + value = resolve(value.Elem()) + if value.Kind() != reflect.Struct { + return nil + } + + // TODO(adonovan): there's no need for this special treatment of Profile: + // The caller can use f.Lookup("profile.cpu") etc instead. + p, _ := value.Addr().Interface().(*Profile) + // go through all the fields of the struct + for i := 0; i < value.Type().NumField(); i++ { + child := value.Type().Field(i) + v := value.Field(i) + // make sure we have a pointer + if v.Kind() != reflect.Ptr { + v = v.Addr() + } + // check if that field is a flag or contains flags + if fp := addFlags(f, child, v); fp != nil { + p = fp + } + } + return p +} + +func addFlag(f *flag.FlagSet, value reflect.Value, flagName string, help string) { + switch v := value.Interface().(type) { + case flag.Value: + f.Var(v, flagName, help) + case *bool: + f.BoolVar(v, flagName, *v, help) + case *time.Duration: + f.DurationVar(v, flagName, *v, help) + case *float64: + f.Float64Var(v, flagName, *v, help) + case *int64: + f.Int64Var(v, flagName, *v, help) + case *int: + f.IntVar(v, flagName, *v, help) + case *string: + f.StringVar(v, flagName, *v, help) + case *uint: + f.UintVar(v, flagName, *v, help) + case *uint64: + f.Uint64Var(v, flagName, *v, help) + default: + log.Fatalf("field %q of type %T is not assignable to flag.Value", flagName, v) + } +} + +func resolve(v reflect.Value) reflect.Value { + for { + switch v.Kind() { + case reflect.Interface, reflect.Ptr: + v = v.Elem() + default: + return v + } + } +} diff --git a/contribs/gnopls/internal/typeparams/common.go b/contribs/gnopls/internal/typeparams/common.go new file mode 100644 index 00000000000..ac3a0955161 --- /dev/null +++ b/contribs/gnopls/internal/typeparams/common.go @@ -0,0 +1,142 @@ +// 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 typeparams contains common utilities for writing tools that +// interact with generic Go code, as introduced with Go 1.18. It +// supplements the standard library APIs. Notably, the StructuralTerms +// API computes a minimal representation of the structural +// restrictions on a type parameter. +// +// An external version of these APIs is available in the +// golang.org/x/exp/typeparams module. +package typeparams + +import ( + "go/ast" + "go/token" + "go/types" + + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" +) + +// UnpackIndexExpr extracts data from AST nodes that represent index +// expressions. +// +// For an ast.IndexExpr, the resulting indices slice will contain exactly one +// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable +// number of index expressions. +// +// For nodes that don't represent index expressions, the first return value of +// UnpackIndexExpr will be nil. +func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) { + switch e := n.(type) { + case *ast.IndexExpr: + return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack + case *ast.IndexListExpr: + return e.X, e.Lbrack, e.Indices, e.Rbrack + } + return nil, token.NoPos, nil, token.NoPos +} + +// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on +// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0 +// will panic. +func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr { + switch len(indices) { + case 0: + panic("empty indices") + case 1: + return &ast.IndexExpr{ + X: x, + Lbrack: lbrack, + Index: indices[0], + Rbrack: rbrack, + } + default: + return &ast.IndexListExpr{ + X: x, + Lbrack: lbrack, + Indices: indices, + Rbrack: rbrack, + } + } +} + +// IsTypeParam reports whether t is a type parameter (or an alias of one). +func IsTypeParam(t types.Type) bool { + _, ok := aliases.Unalias(t).(*types.TypeParam) + return ok +} + +// GenericAssignableTo is a generalization of types.AssignableTo that +// implements the following rule for uninstantiated generic types: +// +// If V and T are generic named types, then V is considered assignable to T if, +// for every possible instantiation of V[A_1, ..., A_N], the instantiation +// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N]. +// +// If T has structural constraints, they must be satisfied by V. +// +// For example, consider the following type declarations: +// +// type Interface[T any] interface { +// Accept(T) +// } +// +// type Container[T any] struct { +// Element T +// } +// +// func (c Container[T]) Accept(t T) { c.Element = t } +// +// In this case, GenericAssignableTo reports that instantiations of Container +// are assignable to the corresponding instantiation of Interface. +func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool { + V = aliases.Unalias(V) + T = aliases.Unalias(T) + + // If V and T are not both named, or do not have matching non-empty type + // parameter lists, fall back on types.AssignableTo. + + VN, Vnamed := V.(*types.Named) + TN, Tnamed := T.(*types.Named) + if !Vnamed || !Tnamed { + return types.AssignableTo(V, T) + } + + vtparams := VN.TypeParams() + ttparams := TN.TypeParams() + if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || VN.TypeArgs().Len() != 0 || TN.TypeArgs().Len() != 0 { + return types.AssignableTo(V, T) + } + + // V and T have the same (non-zero) number of type params. Instantiate both + // with the type parameters of V. This must always succeed for V, and will + // succeed for T if and only if the type set of each type parameter of V is a + // subset of the type set of the corresponding type parameter of T, meaning + // that every instantiation of V corresponds to a valid instantiation of T. + + // Minor optimization: ensure we share a context across the two + // instantiations below. + if ctxt == nil { + ctxt = types.NewContext() + } + + var targs []types.Type + for i := 0; i < vtparams.Len(); i++ { + targs = append(targs, vtparams.At(i)) + } + + vinst, err := types.Instantiate(ctxt, V, targs, true) + if err != nil { + panic("type parameters should satisfy their own constraints") + } + + tinst, err := types.Instantiate(ctxt, T, targs, true) + if err != nil { + return false + } + + return types.AssignableTo(vinst, tinst) +} diff --git a/contribs/gnopls/internal/typeparams/common_test.go b/contribs/gnopls/internal/typeparams/common_test.go new file mode 100644 index 00000000000..5e16465c04d --- /dev/null +++ b/contribs/gnopls/internal/typeparams/common_test.go @@ -0,0 +1,277 @@ +// 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 typeparams_test + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "testing" + + . "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" +) + +func TestGetIndexExprData(t *testing.T) { + x := &ast.Ident{} + i := &ast.Ident{} + + want := &ast.IndexListExpr{X: x, Lbrack: 1, Indices: []ast.Expr{i}, Rbrack: 2} + tests := map[ast.Node]bool{ + &ast.IndexExpr{X: x, Lbrack: 1, Index: i, Rbrack: 2}: true, + want: true, + &ast.Ident{}: false, + } + + for n, isIndexExpr := range tests { + X, lbrack, indices, rbrack := UnpackIndexExpr(n) + if got := X != nil; got != isIndexExpr { + t.Errorf("UnpackIndexExpr(%v) = %v, _, _, _; want nil: %t", n, x, !isIndexExpr) + } + if X == nil { + continue + } + if X != x || lbrack != 1 || indices[0] != i || rbrack != 2 { + t.Errorf("UnpackIndexExprData(%v) = %v, %v, %v, %v; want %+v", n, x, lbrack, indices, rbrack, want) + } + } +} + +func TestFuncOriginRecursive(t *testing.T) { + src := `package p + +type N[A any] int + +func (r N[B]) m() { r.m(); r.n() } + +func (r *N[C]) n() { } +` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "p.go", src, 0) + if err != nil { + t.Fatal(err) + } + info := types.Info{ + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + } + var conf types.Config + if _, err := conf.Check("p", fset, []*ast.File{f}, &info); err != nil { + t.Fatal(err) + } + + // Collect objects from types.Info. + var m, n *types.Func // the 'origin' methods in Info.Defs + var mm, mn *types.Func // the methods used in the body of m + + for _, decl := range f.Decls { + fdecl, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + def := info.Defs[fdecl.Name].(*types.Func) + switch fdecl.Name.Name { + case "m": + m = def + ast.Inspect(fdecl.Body, func(n ast.Node) bool { + if call, ok := n.(*ast.CallExpr); ok { + sel := call.Fun.(*ast.SelectorExpr) + use := info.Uses[sel.Sel].(*types.Func) + switch sel.Sel.Name { + case "m": + mm = use + case "n": + mn = use + } + } + return true + }) + case "n": + n = def + } + } + + tests := []struct { + name string + input, want *types.Func + }{ + {"declared m", m, m}, + {"declared n", n, n}, + {"used m", mm, m}, + {"used n", mn, n}, + } + + for _, test := range tests { + if got := test.input.Origin(); got != test.want { + t.Errorf("Origin(%q) = %v, want %v", test.name, test.input, test.want) + } + } +} + +func TestFuncOriginUses(t *testing.T) { + + tests := []string{ + `type T interface { m() }; func _(t T) { t.m() }`, + `type T[P any] interface { m() P }; func _[A any](t T[A]) { t.m() }`, + `type T[P any] interface { m() P }; func _(t T[int]) { t.m() }`, + `type T[P any] int; func (r T[A]) m() { r.m() }`, + `type T[P any] int; func (r *T[A]) m() { r.m() }`, + `type T[P any] int; func (r *T[A]) m() {}; func _(t T[int]) { t.m() }`, + `type T[P any] int; func (r *T[A]) m() {}; func _[A any](t T[A]) { t.m() }`, + } + + for _, src := range tests { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "p.go", "package p; "+src, 0) + if err != nil { + t.Fatal(err) + } + info := types.Info{ + Uses: make(map[*ast.Ident]types.Object), + } + var conf types.Config + pkg, err := conf.Check("p", fset, []*ast.File{f}, &info) + if err != nil { + t.Fatal(err) + } + + // Look up func T.m. + T := pkg.Scope().Lookup("T").Type() + obj, _, _ := types.LookupFieldOrMethod(T, true, pkg, "m") + m := obj.(*types.Func) + + // Assert that the origin of each t.m() call is p.T.m. + ast.Inspect(f, func(n ast.Node) bool { + if call, ok := n.(*ast.CallExpr); ok { + sel := call.Fun.(*ast.SelectorExpr) + use := info.Uses[sel.Sel].(*types.Func) + orig := use.Origin() + if orig != m { + t.Errorf("%s:\nUses[%v] = %v, want %v", src, types.ExprString(sel), use, m) + } + } + return true + }) + } +} + +// Issue #60628 was a crash in gopls caused by inconsistency (#60634) between +// LookupFieldOrMethod and NewFileSet for methods with an illegal +// *T receiver type, where T itself is a pointer. +// This is a regression test for the workaround in the (now deleted) OriginMethod. +func TestFuncOrigin60628(t *testing.T) { + const src = `package p; type T[P any] *int; func (r *T[A]) f() {}` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "p.go", src, 0) + if err != nil { + t.Fatal(err) + } + + // Expect type error: "invalid receiver type T[A] (pointer or interface type)". + info := types.Info{ + Uses: make(map[*ast.Ident]types.Object), + } + var conf types.Config + pkg, _ := conf.Check("p", fset, []*ast.File{f}, &info) // error expected + if pkg == nil { + t.Fatal("no package") + } + + // Look up methodset of *T. + T := pkg.Scope().Lookup("T").Type() + mset := types.NewMethodSet(types.NewPointer(T)) + if mset.Len() == 0 { + t.Errorf("NewMethodSet(*T) is empty") + } + for i := 0; i < mset.Len(); i++ { + sel := mset.At(i) + m := sel.Obj().(*types.Func) + + // TODO(adonovan): check the consistency property required to fix #60634. + if false { + m2, _, _ := types.LookupFieldOrMethod(T, true, m.Pkg(), m.Name()) + if m2 != m { + t.Errorf("LookupFieldOrMethod(%v, indirect=true, %v) = %v, want %v", + T, m, m2, m) + } + } + + // Check the workaround. + if m.Origin() == nil { + t.Errorf("Origin(%v) = nil", m) + } + } +} + +func TestGenericAssignableTo(t *testing.T) { + tests := []struct { + src string + want bool + }{ + // The inciting issue: golang/go#50887. + {` + type T[P any] interface { + Accept(P) + } + + type V[Q any] struct { + Element Q + } + + func (c V[Q]) Accept(q Q) { c.Element = q } + `, true}, + + // Various permutations on constraints and signatures. + {`type T[P ~int] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true}, + {`type T[P int] interface{ A(P) }; type V[Q ~int] int; func (V[Q]) A(Q) {}`, false}, + {`type T[P int|string] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true}, + {`type T[P any] interface{ A(P) }; type V[Q any] int; func (V[Q]) A(Q, Q) {}`, false}, + {`type T[P any] interface{ int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, false}, + + // Various structural restrictions on T. + {`type T[P any] interface{ ~int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true}, + {`type T[P any] interface{ ~int|string; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true}, + {`type T[P any] interface{ int; A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, false}, + + // Various recursive constraints. + {`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ f *Q }] int; func (V[Q]) A(Q) {}`, true}, + {`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ g *Q }] int; func (V[Q]) A(Q) {}`, false}, + {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true}, + {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~**Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false}, + {`type T[P, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true}, + {`type T[P ~*X, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false}, + {`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true}, + + // In this test case, we reverse the type parameters in the signature of V.A + {`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false}, + // It would be nice to return true here: V can only be instantiated with + // [int, int], so the identity of the type parameters should not matter. + {`type T[P, X any] interface{ A(P) X }; type V[Q, Y int] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false}, + } + + for _, test := range tests { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "p.go", "package p; "+test.src, 0) + if err != nil { + t.Fatalf("%s:\n%v", test.src, err) + } + var conf types.Config + pkg, err := conf.Check("p", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatalf("%s:\n%v", test.src, err) + } + + V := pkg.Scope().Lookup("V").Type() + T := pkg.Scope().Lookup("T").Type() + + if types.AssignableTo(V, T) { + t.Fatal("AssignableTo") + } + + if got := GenericAssignableTo(nil, V, T); got != test.want { + t.Fatalf("%s:\nGenericAssignableTo(%v, %v) = %v, want %v", test.src, V, T, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/typeparams/copytermlist.go b/contribs/gnopls/internal/typeparams/copytermlist.go new file mode 100644 index 00000000000..5357f9d2fd7 --- /dev/null +++ b/contribs/gnopls/internal/typeparams/copytermlist.go @@ -0,0 +1,98 @@ +// 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. + +//go:build ignore +// +build ignore + +// copytermlist.go copies the term list algorithm from GOROOT/src/go/types. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + + "golang.org/x/tools/go/ast/astutil" +) + +func main() { + if err := doCopy(); err != nil { + fmt.Fprintf(os.Stderr, "error copying from go/types: %v", err) + os.Exit(1) + } +} + +func doCopy() error { + dir := filepath.Join(runtime.GOROOT(), "src", "go", "types") + for _, name := range []string{"typeterm.go", "termlist.go"} { + path := filepath.Join(dir, name) + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + return err + } + file.Name.Name = "typeparams" + file.Doc = &ast.CommentGroup{List: []*ast.Comment{{Text: "DO NOT MODIFY"}}} + var needImport bool + selectorType := reflect.TypeOf((*ast.SelectorExpr)(nil)) + astutil.Apply(file, func(c *astutil.Cursor) bool { + if id, _ := c.Node().(*ast.Ident); id != nil { + // Check if this ident should be qualified with types. For simplicity, + // assume the copied files do not themselves contain any exported + // symbols. + + // As a simple heuristic, just verify that the ident may be replaced by + // a selector. + if !token.IsExported(id.Name) { + return false + } + v := reflect.TypeOf(c.Parent()).Elem() // ast nodes are all pointers + field, ok := v.FieldByName(c.Name()) + if !ok { + panic("missing field") + } + t := field.Type + if c.Index() > 0 { // => t is a slice + t = t.Elem() + } + if !selectorType.AssignableTo(t) { + return false + } + needImport = true + c.Replace(&ast.SelectorExpr{ + X: &ast.Ident{NamePos: id.NamePos, Name: "types"}, + Sel: &ast.Ident{NamePos: id.NamePos, Name: id.Name, Obj: id.Obj}, + }) + } + return true + }, nil) + if needImport { + astutil.AddImport(fset, file, "go/types") + } + + var b bytes.Buffer + if err := format.Node(&b, fset, file); err != nil { + return err + } + + // Hack in the 'generated' byline. + content := b.String() + header := "// Code generated by copytermlist.go DO NOT EDIT.\n\npackage typeparams" + content = strings.Replace(content, "package typeparams", header, 1) + + if err := os.WriteFile(name, []byte(content), 0644); err != nil { + return err + } + } + return nil +} diff --git a/contribs/gnopls/internal/typeparams/coretype.go b/contribs/gnopls/internal/typeparams/coretype.go new file mode 100644 index 00000000000..6e83c6fb1a2 --- /dev/null +++ b/contribs/gnopls/internal/typeparams/coretype.go @@ -0,0 +1,150 @@ +// 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 typeparams + +import ( + "fmt" + "go/types" +) + +// CoreType returns the core type of T or nil if T does not have a core type. +// +// See https://go.dev/ref/spec#Core_types for the definition of a core type. +func CoreType(T types.Type) types.Type { + U := T.Underlying() + if _, ok := U.(*types.Interface); !ok { + return U // for non-interface types, + } + + terms, err := NormalTerms(U) + if len(terms) == 0 || err != nil { + // len(terms) -> empty type set of interface. + // err != nil => U is invalid, exceeds complexity bounds, or has an empty type set. + return nil // no core type. + } + + U = terms[0].Type().Underlying() + var identical int // i in [0,identical) => Identical(U, terms[i].Type().Underlying()) + for identical = 1; identical < len(terms); identical++ { + if !types.Identical(U, terms[identical].Type().Underlying()) { + break + } + } + + if identical == len(terms) { + // https://go.dev/ref/spec#Core_types + // "There is a single type U which is the underlying type of all types in the type set of T" + return U + } + ch, ok := U.(*types.Chan) + if !ok { + return nil // no core type as identical < len(terms) and U is not a channel. + } + // https://go.dev/ref/spec#Core_types + // "the type chan E if T contains only bidirectional channels, or the type chan<- E or + // <-chan E depending on the direction of the directional channels present." + for chans := identical; chans < len(terms); chans++ { + curr, ok := terms[chans].Type().Underlying().(*types.Chan) + if !ok { + return nil + } + if !types.Identical(ch.Elem(), curr.Elem()) { + return nil // channel elements are not identical. + } + if ch.Dir() == types.SendRecv { + // ch is bidirectional. We can safely always use curr's direction. + ch = curr + } else if curr.Dir() != types.SendRecv && ch.Dir() != curr.Dir() { + // ch and curr are not bidirectional and not the same direction. + return nil + } + } + return ch +} + +// NormalTerms returns a slice of terms representing the normalized structural +// type restrictions of a type, if any. +// +// For all types other than *types.TypeParam, *types.Interface, and +// *types.Union, this is just a single term with Tilde() == false and +// Type() == typ. For *types.TypeParam, *types.Interface, and *types.Union, see +// below. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration type +// T[P interface{~int; m()}] int the structural restriction of the type +// parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// NormalTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, NormalTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the type is +// invalid, exceeds complexity bounds, or has an empty type set. In the latter +// case, NormalTerms returns ErrEmptyTypeSet. +// +// NormalTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func NormalTerms(typ types.Type) ([]*types.Term, error) { + switch typ := typ.Underlying().(type) { + case *types.TypeParam: + return StructuralTerms(typ) + case *types.Union: + return UnionTermSet(typ) + case *types.Interface: + return InterfaceTermSet(typ) + default: + return []*types.Term{types.NewTerm(false, typ)}, nil + } +} + +// Deref returns the type of the variable pointed to by t, +// if t's core type is a pointer; otherwise it returns t. +// +// Do not assume that Deref(T)==T implies T is not a pointer: +// consider "type T *T", for example. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func Deref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + return t +} + +// MustDeref returns the type of the variable pointed to by t. +// It panics if t's core type is not a pointer. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func MustDeref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + panic(fmt.Sprintf("%v is not a pointer", t)) +} diff --git a/contribs/gnopls/internal/typeparams/coretype_test.go b/contribs/gnopls/internal/typeparams/coretype_test.go new file mode 100644 index 00000000000..238f9faa0da --- /dev/null +++ b/contribs/gnopls/internal/typeparams/coretype_test.go @@ -0,0 +1,101 @@ +// 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 typeparams_test + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" +) + +func TestCoreType(t *testing.T) { + const source = ` + package P + + type Named int + + type A any + type B interface{~int} + type C interface{int} + type D interface{Named} + type E interface{~int|interface{Named}} + type F interface{~int|~float32} + type G interface{chan int|interface{chan int}} + type H interface{chan int|chan float32} + type I interface{chan<- int|chan int} + type J interface{chan int|chan<- int} + type K interface{<-chan int|chan int} + type L interface{chan int|<-chan int} + type M interface{chan int|chan Named} + type N interface{<-chan int|chan<- int} + type O interface{chan int|bool} + type P struct{ Named } + type Q interface{ Foo() } + type R interface{ Foo() ; Named } + type S interface{ Foo() ; ~int } + + type T interface{chan int|interface{chan int}|<-chan int} +` + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hello.go", source, 0) + if err != nil { + t.Fatal(err) + } + + var conf types.Config + pkg, err := conf.Check("P", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + for _, test := range []struct { + expr string // type expression of Named type + want string // expected core type (or "<nil>" if none) + }{ + {"Named", "int"}, // Underlying type is not interface. + {"A", "<nil>"}, // Interface has no terms. + {"B", "int"}, // Tilde term. + {"C", "int"}, // Non-tilde term. + {"D", "int"}, // Named term. + {"E", "int"}, // Identical underlying types. + {"F", "<nil>"}, // Differing underlying types. + {"G", "chan int"}, // Identical Element types. + {"H", "<nil>"}, // Element type int has differing underlying type to float32. + {"I", "chan<- int"}, // SendRecv followed by SendOnly + {"J", "chan<- int"}, // SendOnly followed by SendRecv + {"K", "<-chan int"}, // RecvOnly followed by SendRecv + {"L", "<-chan int"}, // SendRecv followed by RecvOnly + {"M", "<nil>"}, // Element type int is not *identical* to Named. + {"N", "<nil>"}, // Differing channel directions + {"O", "<nil>"}, // A channel followed by a non-channel. + {"P", "struct{P.Named}"}, // Embedded type. + {"Q", "<nil>"}, // interface type with no terms and functions + {"R", "int"}, // interface type with both terms and functions. + {"S", "int"}, // interface type with a tilde term + {"T", "<-chan int"}, // Prefix of 2 terms that are identical before switching to channel. + } { + // Eval() expr for its type. + tv, err := types.Eval(fset, pkg, 0, test.expr) + if err != nil { + t.Fatalf("Eval(%s) failed: %v", test.expr, err) + } + + ct := typeparams.CoreType(tv.Type) + var got string + if ct == nil { + got = "<nil>" + } else { + got = ct.String() + } + if got != test.want { + t.Errorf("coreType(%s) = %v, want %v", test.expr, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/typeparams/free.go b/contribs/gnopls/internal/typeparams/free.go new file mode 100644 index 00000000000..e5d4ee266de --- /dev/null +++ b/contribs/gnopls/internal/typeparams/free.go @@ -0,0 +1,120 @@ +// 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 typeparams + +import ( + "go/types" + + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" +) + +// Free is a memoization of the set of free type parameters within a +// type. It makes a sequence of calls to [Free.Has] for overlapping +// types more efficient. The zero value is ready for use. +// +// NOTE: Adapted from go/types/infer.go. If it is later exported, factor. +type Free struct { + seen map[types.Type]bool +} + +// Has reports whether the specified type has a free type parameter. +func (w *Free) Has(typ types.Type) (res bool) { + // detect cycles + if x, ok := w.seen[typ]; ok { + return x + } + if w.seen == nil { + w.seen = make(map[types.Type]bool) + } + w.seen[typ] = false + defer func() { + w.seen[typ] = res + }() + + switch t := typ.(type) { + case nil, *types.Basic: // TODO(gri) should nil be handled here? + break + + case *aliases.Alias: + return w.Has(aliases.Unalias(t)) + + case *types.Array: + return w.Has(t.Elem()) + + case *types.Slice: + return w.Has(t.Elem()) + + case *types.Struct: + for i, n := 0, t.NumFields(); i < n; i++ { + if w.Has(t.Field(i).Type()) { + return true + } + } + + case *types.Pointer: + return w.Has(t.Elem()) + + case *types.Tuple: + n := t.Len() + for i := 0; i < n; i++ { + if w.Has(t.At(i).Type()) { + return true + } + } + + case *types.Signature: + // t.tparams may not be nil if we are looking at a signature + // of a generic function type (or an interface method) that is + // part of the type we're testing. We don't care about these type + // parameters. + // Similarly, the receiver of a method may declare (rather than + // use) type parameters, we don't care about those either. + // Thus, we only need to look at the input and result parameters. + return w.Has(t.Params()) || w.Has(t.Results()) + + case *types.Interface: + for i, n := 0, t.NumMethods(); i < n; i++ { + if w.Has(t.Method(i).Type()) { + return true + } + } + terms, err := InterfaceTermSet(t) + if err != nil { + return false // ill typed + } + for _, term := range terms { + if w.Has(term.Type()) { + return true + } + } + + case *types.Map: + return w.Has(t.Key()) || w.Has(t.Elem()) + + case *types.Chan: + return w.Has(t.Elem()) + + case *types.Named: + args := t.TypeArgs() + // TODO(taking): this does not match go/types/infer.go. Check with rfindley. + if params := t.TypeParams(); params.Len() > args.Len() { + return true + } + for i, n := 0, args.Len(); i < n; i++ { + if w.Has(args.At(i)) { + return true + } + } + return w.Has(t.Underlying()) // recurse for types local to parameterized functions + + case *types.TypeParam: + return true + + default: + panic(t) // unreachable + } + + return false +} diff --git a/contribs/gnopls/internal/typeparams/free_test.go b/contribs/gnopls/internal/typeparams/free_test.go new file mode 100644 index 00000000000..b73a8238be3 --- /dev/null +++ b/contribs/gnopls/internal/typeparams/free_test.go @@ -0,0 +1,73 @@ +// 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 typeparams + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "testing" +) + +func TestFree(t *testing.T) { + const source = ` +package P +type A int +func (A) f() +func (*A) g() + +type fer interface { f() } + +func Apply[T fer](x T) T { + x.f() + return x +} + +type V[T any] []T +func (v *V[T]) Push(x T) { *v = append(*v, x) } +` + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hello.go", source, 0) + if err != nil { + t.Fatal(err) + } + + var conf types.Config + pkg, err := conf.Check("P", fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } + + for _, test := range []struct { + expr string // type expression + want bool // expected value + }{ + {"A", false}, + {"*A", false}, + {"error", false}, + {"*error", false}, + {"struct{A}", false}, + {"*struct{A}", false}, + {"fer", false}, + {"Apply", true}, + {"Apply[A]", false}, + {"V", true}, + {"V[A]", false}, + {"*V[A]", false}, + {"(*V[A]).Push", false}, + } { + tv, err := types.Eval(fset, pkg, 0, test.expr) + if err != nil { + t.Errorf("Eval(%s) failed: %v", test.expr, err) + } + + if got := new(Free).Has(tv.Type); got != test.want { + t.Logf("Eval(%s) returned the type %s", test.expr, tv.Type) + t.Errorf("isParameterized(%s) = %v, want %v", test.expr, got, test.want) + } + } +} diff --git a/contribs/gnopls/internal/typeparams/genericfeatures/features.go b/contribs/gnopls/internal/typeparams/genericfeatures/features.go new file mode 100644 index 00000000000..841e7eed8ef --- /dev/null +++ b/contribs/gnopls/internal/typeparams/genericfeatures/features.go @@ -0,0 +1,104 @@ +// 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. + +// The genericfeatures package provides utilities for detecting usage of +// generic programming in Go packages. +package genericfeatures + +import ( + "go/ast" + "go/types" + "strings" + + "golang.org/x/tools/go/ast/inspector" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" +) + +// Features is a set of flags reporting which features of generic Go code a +// package uses, or 0. +type Features int + +const ( + // GenericTypeDecls indicates whether the package declares types with type + // parameters. + GenericTypeDecls Features = 1 << iota + + // GenericFuncDecls indicates whether the package declares functions with + // type parameters. + GenericFuncDecls + + // EmbeddedTypeSets indicates whether the package declares interfaces that + // contain structural type restrictions, i.e. are not fully described by + // their method sets. + EmbeddedTypeSets + + // TypeInstantiation indicates whether the package instantiates any generic + // types. + TypeInstantiation + + // FuncInstantiation indicates whether the package instantiates any generic + // functions. + FuncInstantiation +) + +func (f Features) String() string { + var feats []string + if f&GenericTypeDecls != 0 { + feats = append(feats, "typeDecl") + } + if f&GenericFuncDecls != 0 { + feats = append(feats, "funcDecl") + } + if f&EmbeddedTypeSets != 0 { + feats = append(feats, "typeSet") + } + if f&TypeInstantiation != 0 { + feats = append(feats, "typeInstance") + } + if f&FuncInstantiation != 0 { + feats = append(feats, "funcInstance") + } + return "features{" + strings.Join(feats, ",") + "}" +} + +// ForPackage computes which generic features are used directly by the +// package being analyzed. +func ForPackage(inspect *inspector.Inspector, info *types.Info) Features { + nodeFilter := []ast.Node{ + (*ast.FuncType)(nil), + (*ast.InterfaceType)(nil), + (*ast.ImportSpec)(nil), + (*ast.TypeSpec)(nil), + } + + var direct Features + + inspect.Preorder(nodeFilter, func(node ast.Node) { + switch n := node.(type) { + case *ast.FuncType: + if tparams := n.TypeParams; tparams != nil { + direct |= GenericFuncDecls + } + case *ast.InterfaceType: + tv := info.Types[n] + if iface, _ := tv.Type.(*types.Interface); iface != nil && !iface.IsMethodSet() { + direct |= EmbeddedTypeSets + } + case *ast.TypeSpec: + if tparams := n.TypeParams; tparams != nil { + direct |= GenericTypeDecls + } + } + }) + + for _, inst := range info.Instances { + switch aliases.Unalias(inst.Type).(type) { + case *types.Named: + direct |= TypeInstantiation + case *types.Signature: + direct |= FuncInstantiation + } + } + return direct +} diff --git a/contribs/gnopls/internal/typeparams/normalize.go b/contribs/gnopls/internal/typeparams/normalize.go new file mode 100644 index 00000000000..93c80fdc96c --- /dev/null +++ b/contribs/gnopls/internal/typeparams/normalize.go @@ -0,0 +1,218 @@ +// 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 typeparams + +import ( + "errors" + "fmt" + "go/types" + "os" + "strings" +) + +//go:generate go run copytermlist.go + +const debug = false + +var ErrEmptyTypeSet = errors.New("empty type set") + +// StructuralTerms returns a slice of terms representing the normalized +// structural type restrictions of a type parameter, if any. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration +// +// type T[P interface{~int; m()}] int +// +// the structural restriction of the type parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// StructuralTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, StructuralTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the +// constraint interface is invalid, exceeds complexity bounds, or has an empty +// type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet. +// +// StructuralTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func StructuralTerms(tparam *types.TypeParam) ([]*types.Term, error) { + constraint := tparam.Constraint() + if constraint == nil { + return nil, fmt.Errorf("%s has nil constraint", tparam) + } + iface, _ := constraint.Underlying().(*types.Interface) + if iface == nil { + return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying()) + } + return InterfaceTermSet(iface) +} + +// InterfaceTermSet computes the normalized terms for a constraint interface, +// returning an error if the term set cannot be computed or is empty. In the +// latter case, the error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func InterfaceTermSet(iface *types.Interface) ([]*types.Term, error) { + return computeTermSet(iface) +} + +// UnionTermSet computes the normalized terms for a union, returning an error +// if the term set cannot be computed or is empty. In the latter case, the +// error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func UnionTermSet(union *types.Union) ([]*types.Term, error) { + return computeTermSet(union) +} + +func computeTermSet(typ types.Type) ([]*types.Term, error) { + tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0) + if err != nil { + return nil, err + } + if tset.terms.isEmpty() { + return nil, ErrEmptyTypeSet + } + if tset.terms.isAll() { + return nil, nil + } + var terms []*types.Term + for _, term := range tset.terms { + terms = append(terms, types.NewTerm(term.tilde, term.typ)) + } + return terms, nil +} + +// A termSet holds the normalized set of terms for a given type. +// +// The name termSet is intentionally distinct from 'type set': a type set is +// all types that implement a type (and includes method restrictions), whereas +// a term set just represents the structural restrictions on a type. +type termSet struct { + complete bool + terms termlist +} + +func indentf(depth int, format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) +} + +func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) { + if t == nil { + panic("nil type") + } + + if debug { + indentf(depth, "%s", t.String()) + defer func() { + if err != nil { + indentf(depth, "=> %s", err) + } else { + indentf(depth, "=> %s", res.terms.String()) + } + }() + } + + const maxTermCount = 100 + if tset, ok := seen[t]; ok { + if !tset.complete { + return nil, fmt.Errorf("cycle detected in the declaration of %s", t) + } + return tset, nil + } + + // Mark the current type as seen to avoid infinite recursion. + tset := new(termSet) + defer func() { + tset.complete = true + }() + seen[t] = tset + + switch u := t.Underlying().(type) { + case *types.Interface: + // The term set of an interface is the intersection of the term sets of its + // embedded types. + tset.terms = allTermlist + for i := 0; i < u.NumEmbeddeds(); i++ { + embedded := u.EmbeddedType(i) + if _, ok := embedded.Underlying().(*types.TypeParam); ok { + return nil, fmt.Errorf("invalid embedded type %T", embedded) + } + tset2, err := computeTermSetInternal(embedded, seen, depth+1) + if err != nil { + return nil, err + } + tset.terms = tset.terms.intersect(tset2.terms) + } + case *types.Union: + // The term set of a union is the union of term sets of its terms. + tset.terms = nil + for i := 0; i < u.Len(); i++ { + t := u.Term(i) + var terms termlist + switch t.Type().Underlying().(type) { + case *types.Interface: + tset2, err := computeTermSetInternal(t.Type(), seen, depth+1) + if err != nil { + return nil, err + } + terms = tset2.terms + case *types.TypeParam, *types.Union: + // A stand-alone type parameter or union is not permitted as union + // term. + return nil, fmt.Errorf("invalid union term %T", t) + default: + if t.Type() == types.Typ[types.Invalid] { + continue + } + terms = termlist{{t.Tilde(), t.Type()}} + } + tset.terms = tset.terms.union(terms) + if len(tset.terms) > maxTermCount { + return nil, fmt.Errorf("exceeded max term count %d", maxTermCount) + } + } + case *types.TypeParam: + panic("unreachable") + default: + // For all other types, the term set is just a single non-tilde term + // holding the type itself. + if u != types.Typ[types.Invalid] { + tset.terms = termlist{{false, t}} + } + } + return tset, nil +} + +// under is a facade for the go/types internal function of the same name. It is +// used by typeterm.go. +func under(t types.Type) types.Type { + return t.Underlying() +} diff --git a/contribs/gnopls/internal/typeparams/normalize_test.go b/contribs/gnopls/internal/typeparams/normalize_test.go new file mode 100644 index 00000000000..66d5061fd17 --- /dev/null +++ b/contribs/gnopls/internal/typeparams/normalize_test.go @@ -0,0 +1,101 @@ +// 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 typeparams_test + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "regexp" + "strings" + "testing" + + . "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" +) + +func TestStructuralTerms(t *testing.T) { + // In the following tests, src must define a type T with (at least) one type + // parameter. We will compute the structural terms of the first type + // parameter. + tests := []struct { + src string + want string + wantError string + }{ + {"package emptyinterface0; type T[P interface{}] int", "all", ""}, + {"package emptyinterface1; type T[P interface{ int | interface{} }] int", "all", ""}, + {"package singleton; type T[P interface{ int }] int", "int", ""}, + {"package under; type T[P interface{~int}] int", "~int", ""}, + {"package superset; type T[P interface{ ~int | int }] int", "~int", ""}, + {"package overlap; type T[P interface{ ~int; int }] int", "int", ""}, + {"package emptyintersection; type T[P interface{ ~int; string }] int", "", "empty type set"}, + + {"package embedded0; type T[P interface{ I }] int; type I interface { int }", "int", ""}, + {"package embedded1; type T[P interface{ I | string }] int; type I interface{ int | ~string }", "int ?\\| ?~string", ""}, + {"package embedded2; type T[P interface{ I; string }] int; type I interface{ int | ~string }", "string", ""}, + + {"package named; type T[P C] int; type C interface{ ~int|int }", "~int", ""}, + {`// package example is taken from the docstring for StructuralTerms +package example + +type A interface{ ~string|~[]byte } + +type B interface{ int|string } + +type C interface { ~string|~int } + +type T[P interface{ A|B; C }] int +`, "~string ?\\| ?int", ""}, + } + + for _, test := range tests { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "p.go", test.src, 0) + if err != nil { + t.Fatal(err) + } + t.Run(f.Name.Name, func(t *testing.T) { + conf := types.Config{ + Error: func(error) {}, // keep going on errors + } + pkg, err := conf.Check("", fset, []*ast.File{f}, nil) + if err != nil { + t.Logf("types.Config.Check: %v", err) + // keep going on type checker errors: we want to assert on behavior of + // invalid code as well. + } + obj := pkg.Scope().Lookup("T") + if obj == nil { + t.Fatal("type T not found") + } + T := obj.Type().(*types.Named).TypeParams().At(0) + terms, err := StructuralTerms(T) + if test.wantError != "" { + if err == nil { + t.Fatalf("StructuralTerms(%s): nil error, want %q", T, test.wantError) + } + if !strings.Contains(err.Error(), test.wantError) { + t.Errorf("StructuralTerms(%s): err = %q, want %q", T, err, test.wantError) + } + return + } + if err != nil { + t.Fatal(err) + } + var got string + if len(terms) == 0 { + got = "all" + } else { + qf := types.RelativeTo(pkg) + got = types.TypeString(types.NewUnion(terms), qf) + } + want := regexp.MustCompile(test.want) + if !want.MatchString(got) { + t.Errorf("StructuralTerms(%s) = %q, want %q", T, got, test.want) + } + }) + } +} diff --git a/contribs/gnopls/internal/typeparams/termlist.go b/contribs/gnopls/internal/typeparams/termlist.go new file mode 100644 index 00000000000..cbd12f80131 --- /dev/null +++ b/contribs/gnopls/internal/typeparams/termlist.go @@ -0,0 +1,163 @@ +// 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. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import ( + "bytes" + "go/types" +) + +// A termlist represents the type set represented by the union +// t1 ∪ y2 ∪ ... tn of the type sets of the terms t1 to tn. +// A termlist is in normal form if all terms are disjoint. +// termlist operations don't require the operands to be in +// normal form. +type termlist []*term + +// allTermlist represents the set of all types. +// It is in normal form. +var allTermlist = termlist{new(term)} + +// String prints the termlist exactly (without normalization). +func (xl termlist) String() string { + if len(xl) == 0 { + return "∅" + } + var buf bytes.Buffer + for i, x := range xl { + if i > 0 { + buf.WriteString(" | ") + } + buf.WriteString(x.String()) + } + return buf.String() +} + +// isEmpty reports whether the termlist xl represents the empty set of types. +func (xl termlist) isEmpty() bool { + // If there's a non-nil term, the entire list is not empty. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil { + return false + } + } + return true +} + +// isAll reports whether the termlist xl represents the set of all types. +func (xl termlist) isAll() bool { + // If there's a 𝓤 term, the entire list is 𝓤. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil && x.typ == nil { + return true + } + } + return false +} + +// norm returns the normal form of xl. +func (xl termlist) norm() termlist { + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + used := make([]bool, len(xl)) + var rl termlist + for i, xi := range xl { + if xi == nil || used[i] { + continue + } + for j := i + 1; j < len(xl); j++ { + xj := xl[j] + if xj == nil || used[j] { + continue + } + if u1, u2 := xi.union(xj); u2 == nil { + // If we encounter a 𝓤 term, the entire list is 𝓤. + // Exit early. + // (Note that this is not just an optimization; + // if we continue, we may end up with a 𝓤 term + // and other terms and the result would not be + // in normal form.) + if u1.typ == nil { + return allTermlist + } + xi = u1 + used[j] = true // xj is now unioned into xi - ignore it in future iterations + } + } + rl = append(rl, xi) + } + return rl +} + +// union returns the union xl ∪ yl. +func (xl termlist) union(yl termlist) termlist { + return append(xl, yl...).norm() +} + +// intersect returns the intersection xl ∩ yl. +func (xl termlist) intersect(yl termlist) termlist { + if xl.isEmpty() || yl.isEmpty() { + return nil + } + + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + var rl termlist + for _, x := range xl { + for _, y := range yl { + if r := x.intersect(y); r != nil { + rl = append(rl, r) + } + } + } + return rl.norm() +} + +// equal reports whether xl and yl represent the same type set. +func (xl termlist) equal(yl termlist) bool { + // TODO(gri) this should be more efficient + return xl.subsetOf(yl) && yl.subsetOf(xl) +} + +// includes reports whether t ∈ xl. +func (xl termlist) includes(t types.Type) bool { + for _, x := range xl { + if x.includes(t) { + return true + } + } + return false +} + +// supersetOf reports whether y ⊆ xl. +func (xl termlist) supersetOf(y *term) bool { + for _, x := range xl { + if y.subsetOf(x) { + return true + } + } + return false +} + +// subsetOf reports whether xl ⊆ yl. +func (xl termlist) subsetOf(yl termlist) bool { + if yl.isEmpty() { + return xl.isEmpty() + } + + // each term x of xl must be a subset of yl + for _, x := range xl { + if !yl.supersetOf(x) { + return false // x is not a subset yl + } + } + return true +} diff --git a/contribs/gnopls/internal/typeparams/typeterm.go b/contribs/gnopls/internal/typeparams/typeterm.go new file mode 100644 index 00000000000..7350bb702a1 --- /dev/null +++ b/contribs/gnopls/internal/typeparams/typeterm.go @@ -0,0 +1,169 @@ +// 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. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import "go/types" + +// A term describes elementary type sets: +// +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t +type term struct { + tilde bool // valid if typ != nil + typ types.Type +} + +func (x *term) String() string { + switch { + case x == nil: + return "∅" + case x.typ == nil: + return "𝓤" + case x.tilde: + return "~" + x.typ.String() + default: + return x.typ.String() + } +} + +// equal reports whether x and y represent the same type set. +func (x *term) equal(y *term) bool { + // easy cases + switch { + case x == nil || y == nil: + return x == y + case x.typ == nil || y.typ == nil: + return x.typ == y.typ + } + // ∅ ⊂ x, y ⊂ 𝓤 + + return x.tilde == y.tilde && types.Identical(x.typ, y.typ) +} + +// union returns the union x ∪ y: zero, one, or two non-nil terms. +func (x *term) union(y *term) (_, _ *term) { + // easy cases + switch { + case x == nil && y == nil: + return nil, nil // ∅ ∪ ∅ == ∅ + case x == nil: + return y, nil // ∅ ∪ y == y + case y == nil: + return x, nil // x ∪ ∅ == x + case x.typ == nil: + return x, nil // 𝓤 ∪ y == 𝓤 + case y.typ == nil: + return y, nil // x ∪ 𝓤 == 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return x, y // x ∪ y == (x, y) if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∪ ~t == ~t + // ~t ∪ T == ~t + // T ∪ ~t == ~t + // T ∪ T == T + if x.tilde || !y.tilde { + return x, nil + } + return y, nil +} + +// intersect returns the intersection x ∩ y. +func (x *term) intersect(y *term) *term { + // easy cases + switch { + case x == nil || y == nil: + return nil // ∅ ∩ y == ∅ and ∩ ∅ == ∅ + case x.typ == nil: + return y // 𝓤 ∩ y == y + case y.typ == nil: + return x // x ∩ 𝓤 == x + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return nil // x ∩ y == ∅ if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∩ ~t == ~t + // ~t ∩ T == T + // T ∩ ~t == T + // T ∩ T == T + if !x.tilde || y.tilde { + return x + } + return y +} + +// includes reports whether t ∈ x. +func (x *term) includes(t types.Type) bool { + // easy cases + switch { + case x == nil: + return false // t ∈ ∅ == false + case x.typ == nil: + return true // t ∈ 𝓤 == true + } + // ∅ ⊂ x ⊂ 𝓤 + + u := t + if x.tilde { + u = under(u) + } + return types.Identical(x.typ, u) +} + +// subsetOf reports whether x ⊆ y. +func (x *term) subsetOf(y *term) bool { + // easy cases + switch { + case x == nil: + return true // ∅ ⊆ y == true + case y == nil: + return false // x ⊆ ∅ == false since x != ∅ + case y.typ == nil: + return true // x ⊆ 𝓤 == true + case x.typ == nil: + return false // 𝓤 ⊆ y == false since y != 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return false // x ⊆ y == false if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ⊆ ~t == true + // ~t ⊆ T == false + // T ⊆ ~t == true + // T ⊆ T == true + return !x.tilde || y.tilde +} + +// disjoint reports whether x ∩ y == ∅. +// x.typ and y.typ must not be nil. +func (x *term) disjoint(y *term) bool { + if debug && (x.typ == nil || y.typ == nil) { + panic("invalid argument(s)") + } + ux := x.typ + if y.tilde { + ux = under(ux) + } + uy := y.typ + if x.tilde { + uy = under(uy) + } + return !types.Identical(ux, uy) +} diff --git a/contribs/gnopls/internal/typesinternal/element.go b/contribs/gnopls/internal/typesinternal/element.go new file mode 100644 index 00000000000..144e4d9004a --- /dev/null +++ b/contribs/gnopls/internal/typesinternal/element.go @@ -0,0 +1,134 @@ +// 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 typesinternal + +import ( + "fmt" + "go/types" + + "golang.org/x/tools/go/types/typeutil" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" +) + +// ForEachElement calls f for type T and each type reachable from its +// type through reflection. It does this by recursively stripping off +// type constructors; in addition, for each named type N, the type *N +// is added to the result as it may have additional methods. +// +// The caller must provide an initially empty set used to de-duplicate +// identical types, potentially across multiple calls to ForEachElement. +// (Its final value holds all the elements seen, matching the arguments +// passed to f.) +// +// TODO(adonovan): share/harmonize with go/callgraph/rta. +func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T types.Type, f func(types.Type)) { + var visit func(T types.Type, skip bool) + visit = func(T types.Type, skip bool) { + if !skip { + if seen, _ := rtypes.Set(T, true).(bool); seen { + return // de-dup + } + + f(T) // notify caller of new element type + } + + // Recursion over signatures of each method. + tmset := msets.MethodSet(T) + for i := 0; i < tmset.Len(); i++ { + sig := tmset.At(i).Type().(*types.Signature) + // It is tempting to call visit(sig, false) + // but, as noted in golang.org/cl/65450043, + // the Signature.Recv field is ignored by + // types.Identical and typeutil.Map, which + // is confusing at best. + // + // More importantly, the true signature rtype + // reachable from a method using reflection + // has no receiver but an extra ordinary parameter. + // For the Read method of io.Reader we want: + // func(Reader, []byte) (int, error) + // but here sig is: + // func([]byte) (int, error) + // with .Recv = Reader (though it is hard to + // notice because it doesn't affect Signature.String + // or types.Identical). + // + // TODO(adonovan): construct and visit the correct + // non-method signature with an extra parameter + // (though since unnamed func types have no methods + // there is essentially no actual demand for this). + // + // TODO(adonovan): document whether or not it is + // safe to skip non-exported methods (as RTA does). + visit(sig.Params(), true) // skip the Tuple + visit(sig.Results(), true) // skip the Tuple + } + + switch T := T.(type) { + case *aliases.Alias: + visit(aliases.Unalias(T), skip) // emulates the pre-Alias behavior + + case *types.Basic: + // nop + + case *types.Interface: + // nop---handled by recursion over method set. + + case *types.Pointer: + visit(T.Elem(), false) + + case *types.Slice: + visit(T.Elem(), false) + + case *types.Chan: + visit(T.Elem(), false) + + case *types.Map: + visit(T.Key(), false) + visit(T.Elem(), false) + + case *types.Signature: + if T.Recv() != nil { + panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv())) + } + visit(T.Params(), true) // skip the Tuple + visit(T.Results(), true) // skip the Tuple + + case *types.Named: + // A pointer-to-named type can be derived from a named + // type via reflection. It may have methods too. + visit(types.NewPointer(T), false) + + // Consider 'type T struct{S}' where S has methods. + // Reflection provides no way to get from T to struct{S}, + // only to S, so the method set of struct{S} is unwanted, + // so set 'skip' flag during recursion. + visit(T.Underlying(), true) // skip the unnamed type + + case *types.Array: + visit(T.Elem(), false) + + case *types.Struct: + for i, n := 0, T.NumFields(); i < n; i++ { + // TODO(adonovan): document whether or not + // it is safe to skip non-exported fields. + visit(T.Field(i).Type(), false) + } + + case *types.Tuple: + for i, n := 0, T.Len(); i < n; i++ { + visit(T.At(i).Type(), false) + } + + case *types.TypeParam, *types.Union: + // forEachReachable must not be called on parameterized types. + panic(T) + + default: + panic(T) + } + } + visit(T, false) +} diff --git a/contribs/gnopls/internal/typesinternal/element_test.go b/contribs/gnopls/internal/typesinternal/element_test.go new file mode 100644 index 00000000000..f60b24e180e --- /dev/null +++ b/contribs/gnopls/internal/typesinternal/element_test.go @@ -0,0 +1,154 @@ +// 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 typesinternal_test + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "strings" + "testing" + + "golang.org/x/tools/go/types/typeutil" + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" + "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" +) + +const elementSrc = ` +package p + +type A = int + +type B = *map[chan int][]func() [2]bool + +type C = T + +type T struct{ x int } +func (T) method() uint +func (*T) ptrmethod() complex128 + +type D = A + +type E = struct{ x int } + +type F = func(int8, int16) (int32, int64) + +type G = struct { U } + +type U struct{} +func (U) method() uint32 + +` + +func TestForEachElement(t *testing.T) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "a.go", elementSrc, 0) + if err != nil { + t.Fatal(err) // parse error + } + var config types.Config + pkg, err := config.Check(f.Name.Name, fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) // type error + } + + tests := []struct { + name string // name of a type alias whose RHS type's elements to compute + want []string // strings of types that are/are not elements (! => not) + }{ + // simple type + {"A", []string{"int"}}, + + // compound type + {"B", []string{ + "*map[chan int][]func() [2]bool", + "map[chan int][]func() [2]bool", + "chan int", + "int", + "[]func() [2]bool", + "func() [2]bool", + "[2]bool", + "bool", + }}, + + // defined struct type with methods, incl. pointer methods. + // Observe that it descends into the field type, but + // the result does not include the struct type itself. + // (This follows the Go toolchain behavior , and finesses the need + // to create wrapper methods for that struct type.) + {"C", []string{"T", "*T", "int", "uint", "complex128", "!struct{x int}"}}, + + // alias type + {"D", []string{"int"}}, + + // struct type not beneath a defined type + {"E", []string{"struct{x int}", "int"}}, + + // signature types: the params/results tuples + // are traversed but not included. + {"F", []string{"func(int8, int16) (int32, int64)", + "int8", "int16", "int32", "int64"}}, + + // struct with embedded field that has methods + {"G", []string{"*U", "struct{U}", "uint32", "U"}}, + } + var msets typeutil.MethodSetCache + for _, test := range tests { + tname, ok := pkg.Scope().Lookup(test.name).(*types.TypeName) + if !ok { + t.Errorf("no such type %q", test.name) + continue + } + T := aliases.Unalias(tname.Type()) + + toStr := func(T types.Type) string { + return types.TypeString(T, func(*types.Package) string { return "" }) + } + + got := make(map[string]bool) + set := new(typeutil.Map) // for de-duping + set2 := new(typeutil.Map) // for consistency check + typesinternal.ForEachElement(set, &msets, T, func(elem types.Type) { + got[toStr(elem)] = true + set2.Set(elem, true) + }) + + // Assert that set==set2, meaning f(x) was + // called for each x in the de-duping map. + if set.Len() != set2.Len() { + t.Errorf("ForEachElement called f %d times yet de-dup set has %d elements", + set2.Len(), set.Len()) + } else { + set.Iterate(func(key types.Type, _ any) { + if set2.At(key) == nil { + t.Errorf("ForEachElement did not call f(%v)", key) + } + }) + } + + // Assert than all expected (and no unexpected) elements were found. + fail := false + for _, typstr := range test.want { + found := got[typstr] + typstr, unwanted := strings.CutPrefix(typstr, "!") + if found && unwanted { + fail = true + t.Errorf("ForEachElement(%s): unwanted element %q", T, typstr) + } else if !found && !unwanted { + fail = true + t.Errorf("ForEachElement(%s): element %q not found", T, typstr) + } + } + if fail { + for k := range got { + t.Logf("got element: %s", k) + } + // TODO(adonovan): use this when go1.23 is assured: + // t.Logf("got elements:\n%s", + // strings.Join(slices.Sorted(maps.Keys(got)), "\n")) + } + } +} diff --git a/contribs/gnopls/internal/typesinternal/errorcode.go b/contribs/gnopls/internal/typesinternal/errorcode.go new file mode 100644 index 00000000000..131caab2847 --- /dev/null +++ b/contribs/gnopls/internal/typesinternal/errorcode.go @@ -0,0 +1,1560 @@ +// 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 typesinternal + +//go:generate stringer -type=ErrorCode + +type ErrorCode int + +// This file defines the error codes that can be produced during type-checking. +// Collectively, these codes provide an identifier that may be used to +// implement special handling for certain types of errors. +// +// Error codes should be fine-grained enough that the exact nature of the error +// can be easily determined, but coarse enough that they are not an +// implementation detail of the type checking algorithm. As a rule-of-thumb, +// errors should be considered equivalent if there is a theoretical refactoring +// of the type checker in which they are emitted in exactly one place. For +// example, the type checker emits different error messages for "too many +// arguments" and "too few arguments", but one can imagine an alternative type +// checker where this check instead just emits a single "wrong number of +// arguments", so these errors should have the same code. +// +// Error code names should be as brief as possible while retaining accuracy and +// distinctiveness. In most cases names should start with an adjective +// describing the nature of the error (e.g. "invalid", "unused", "misplaced"), +// and end with a noun identifying the relevant language object. For example, +// "DuplicateDecl" or "InvalidSliceExpr". For brevity, naming follows the +// convention that "bad" implies a problem with syntax, and "invalid" implies a +// problem with types. + +const ( + // InvalidSyntaxTree occurs if an invalid syntax tree is provided + // to the type checker. It should never happen. + InvalidSyntaxTree ErrorCode = -1 +) + +const ( + _ ErrorCode = iota + + // Test is reserved for errors that only apply while in self-test mode. + Test + + /* package names */ + + // BlankPkgName occurs when a package name is the blank identifier "_". + // + // Per the spec: + // "The PackageName must not be the blank identifier." + BlankPkgName + + // MismatchedPkgName occurs when a file's package name doesn't match the + // package name already established by other files. + MismatchedPkgName + + // InvalidPkgUse occurs when a package identifier is used outside of a + // selector expression. + // + // Example: + // import "fmt" + // + // var _ = fmt + InvalidPkgUse + + /* imports */ + + // BadImportPath occurs when an import path is not valid. + BadImportPath + + // BrokenImport occurs when importing a package fails. + // + // Example: + // import "amissingpackage" + BrokenImport + + // ImportCRenamed occurs when the special import "C" is renamed. "C" is a + // pseudo-package, and must not be renamed. + // + // Example: + // import _ "C" + ImportCRenamed + + // UnusedImport occurs when an import is unused. + // + // Example: + // import "fmt" + // + // func main() {} + UnusedImport + + /* initialization */ + + // InvalidInitCycle occurs when an invalid cycle is detected within the + // initialization graph. + // + // Example: + // var x int = f() + // + // func f() int { return x } + InvalidInitCycle + + /* decls */ + + // DuplicateDecl occurs when an identifier is declared multiple times. + // + // Example: + // var x = 1 + // var x = 2 + DuplicateDecl + + // InvalidDeclCycle occurs when a declaration cycle is not valid. + // + // Example: + // import "unsafe" + // + // type T struct { + // a [n]int + // } + // + // var n = unsafe.Sizeof(T{}) + InvalidDeclCycle + + // InvalidTypeCycle occurs when a cycle in type definitions results in a + // type that is not well-defined. + // + // Example: + // import "unsafe" + // + // type T [unsafe.Sizeof(T{})]int + InvalidTypeCycle + + /* decls > const */ + + // InvalidConstInit occurs when a const declaration has a non-constant + // initializer. + // + // Example: + // var x int + // const _ = x + InvalidConstInit + + // InvalidConstVal occurs when a const value cannot be converted to its + // target type. + // + // TODO(findleyr): this error code and example are not very clear. Consider + // removing it. + // + // Example: + // const _ = 1 << "hello" + InvalidConstVal + + // InvalidConstType occurs when the underlying type in a const declaration + // is not a valid constant type. + // + // Example: + // const c *int = 4 + InvalidConstType + + /* decls > var (+ other variable assignment codes) */ + + // UntypedNilUse occurs when the predeclared (untyped) value nil is used to + // initialize a variable declared without an explicit type. + // + // Example: + // var x = nil + UntypedNilUse + + // WrongAssignCount occurs when the number of values on the right-hand side + // of an assignment or initialization expression does not match the number + // of variables on the left-hand side. + // + // Example: + // var x = 1, 2 + WrongAssignCount + + // UnassignableOperand occurs when the left-hand side of an assignment is + // not assignable. + // + // Example: + // func f() { + // const c = 1 + // c = 2 + // } + UnassignableOperand + + // NoNewVar occurs when a short variable declaration (':=') does not declare + // new variables. + // + // Example: + // func f() { + // x := 1 + // x := 2 + // } + NoNewVar + + // MultiValAssignOp occurs when an assignment operation (+=, *=, etc) does + // not have single-valued left-hand or right-hand side. + // + // Per the spec: + // "In assignment operations, both the left- and right-hand expression lists + // must contain exactly one single-valued expression" + // + // Example: + // func f() int { + // x, y := 1, 2 + // x, y += 1 + // return x + y + // } + MultiValAssignOp + + // InvalidIfaceAssign occurs when a value of type T is used as an + // interface, but T does not implement a method of the expected interface. + // + // Example: + // type I interface { + // f() + // } + // + // type T int + // + // var x I = T(1) + InvalidIfaceAssign + + // InvalidChanAssign occurs when a chan assignment is invalid. + // + // Per the spec, a value x is assignable to a channel type T if: + // "x is a bidirectional channel value, T is a channel type, x's type V and + // T have identical element types, and at least one of V or T is not a + // defined type." + // + // Example: + // type T1 chan int + // type T2 chan int + // + // var x T1 + // // Invalid assignment because both types are named + // var _ T2 = x + InvalidChanAssign + + // IncompatibleAssign occurs when the type of the right-hand side expression + // in an assignment cannot be assigned to the type of the variable being + // assigned. + // + // Example: + // var x []int + // var _ int = x + IncompatibleAssign + + // UnaddressableFieldAssign occurs when trying to assign to a struct field + // in a map value. + // + // Example: + // func f() { + // m := make(map[string]struct{i int}) + // m["foo"].i = 42 + // } + UnaddressableFieldAssign + + /* decls > type (+ other type expression codes) */ + + // NotAType occurs when the identifier used as the underlying type in a type + // declaration or the right-hand side of a type alias does not denote a type. + // + // Example: + // var S = 2 + // + // type T S + NotAType + + // InvalidArrayLen occurs when an array length is not a constant value. + // + // Example: + // var n = 3 + // var _ = [n]int{} + InvalidArrayLen + + // BlankIfaceMethod occurs when a method name is '_'. + // + // Per the spec: + // "The name of each explicitly specified method must be unique and not + // blank." + // + // Example: + // type T interface { + // _(int) + // } + BlankIfaceMethod + + // IncomparableMapKey occurs when a map key type does not support the == and + // != operators. + // + // Per the spec: + // "The comparison operators == and != must be fully defined for operands of + // the key type; thus the key type must not be a function, map, or slice." + // + // Example: + // var x map[T]int + // + // type T []int + IncomparableMapKey + + // InvalidIfaceEmbed occurs when a non-interface type is embedded in an + // interface. + // + // Example: + // type T struct {} + // + // func (T) m() + // + // type I interface { + // T + // } + InvalidIfaceEmbed + + // InvalidPtrEmbed occurs when an embedded field is of the pointer form *T, + // and T itself is itself a pointer, an unsafe.Pointer, or an interface. + // + // Per the spec: + // "An embedded field must be specified as a type name T or as a pointer to + // a non-interface type name *T, and T itself may not be a pointer type." + // + // Example: + // type T *int + // + // type S struct { + // *T + // } + InvalidPtrEmbed + + /* decls > func and method */ + + // BadRecv occurs when a method declaration does not have exactly one + // receiver parameter. + // + // Example: + // func () _() {} + BadRecv + + // InvalidRecv occurs when a receiver type expression is not of the form T + // or *T, or T is a pointer type. + // + // Example: + // type T struct {} + // + // func (**T) m() {} + InvalidRecv + + // DuplicateFieldAndMethod occurs when an identifier appears as both a field + // and method name. + // + // Example: + // type T struct { + // m int + // } + // + // func (T) m() {} + DuplicateFieldAndMethod + + // DuplicateMethod occurs when two methods on the same receiver type have + // the same name. + // + // Example: + // type T struct {} + // func (T) m() {} + // func (T) m(i int) int { return i } + DuplicateMethod + + /* decls > special */ + + // InvalidBlank occurs when a blank identifier is used as a value or type. + // + // Per the spec: + // "The blank identifier may appear as an operand only on the left-hand side + // of an assignment." + // + // Example: + // var x = _ + InvalidBlank + + // InvalidIota occurs when the predeclared identifier iota is used outside + // of a constant declaration. + // + // Example: + // var x = iota + InvalidIota + + // MissingInitBody occurs when an init function is missing its body. + // + // Example: + // func init() + MissingInitBody + + // InvalidInitSig occurs when an init function declares parameters or + // results. + // + // Example: + // func init() int { return 1 } + InvalidInitSig + + // InvalidInitDecl occurs when init is declared as anything other than a + // function. + // + // Example: + // var init = 1 + InvalidInitDecl + + // InvalidMainDecl occurs when main is declared as anything other than a + // function, in a main package. + InvalidMainDecl + + /* exprs */ + + // TooManyValues occurs when a function returns too many values for the + // expression context in which it is used. + // + // Example: + // func ReturnTwo() (int, int) { + // return 1, 2 + // } + // + // var x = ReturnTwo() + TooManyValues + + // NotAnExpr occurs when a type expression is used where a value expression + // is expected. + // + // Example: + // type T struct {} + // + // func f() { + // T + // } + NotAnExpr + + /* exprs > const */ + + // TruncatedFloat occurs when a float constant is truncated to an integer + // value. + // + // Example: + // var _ int = 98.6 + TruncatedFloat + + // NumericOverflow occurs when a numeric constant overflows its target type. + // + // Example: + // var x int8 = 1000 + NumericOverflow + + /* exprs > operation */ + + // UndefinedOp occurs when an operator is not defined for the type(s) used + // in an operation. + // + // Example: + // var c = "a" - "b" + UndefinedOp + + // MismatchedTypes occurs when operand types are incompatible in a binary + // operation. + // + // Example: + // var a = "hello" + // var b = 1 + // var c = a - b + MismatchedTypes + + // DivByZero occurs when a division operation is provable at compile + // time to be a division by zero. + // + // Example: + // const divisor = 0 + // var x int = 1/divisor + DivByZero + + // NonNumericIncDec occurs when an increment or decrement operator is + // applied to a non-numeric value. + // + // Example: + // func f() { + // var c = "c" + // c++ + // } + NonNumericIncDec + + /* exprs > ptr */ + + // UnaddressableOperand occurs when the & operator is applied to an + // unaddressable expression. + // + // Example: + // var x = &1 + UnaddressableOperand + + // InvalidIndirection occurs when a non-pointer value is indirected via the + // '*' operator. + // + // Example: + // var x int + // var y = *x + InvalidIndirection + + /* exprs > [] */ + + // NonIndexableOperand occurs when an index operation is applied to a value + // that cannot be indexed. + // + // Example: + // var x = 1 + // var y = x[1] + NonIndexableOperand + + // InvalidIndex occurs when an index argument is not of integer type, + // negative, or out-of-bounds. + // + // Example: + // var s = [...]int{1,2,3} + // var x = s[5] + // + // Example: + // var s = []int{1,2,3} + // var _ = s[-1] + // + // Example: + // var s = []int{1,2,3} + // var i string + // var _ = s[i] + InvalidIndex + + // SwappedSliceIndices occurs when constant indices in a slice expression + // are decreasing in value. + // + // Example: + // var _ = []int{1,2,3}[2:1] + SwappedSliceIndices + + /* operators > slice */ + + // NonSliceableOperand occurs when a slice operation is applied to a value + // whose type is not sliceable, or is unaddressable. + // + // Example: + // var x = [...]int{1, 2, 3}[:1] + // + // Example: + // var x = 1 + // var y = 1[:1] + NonSliceableOperand + + // InvalidSliceExpr occurs when a three-index slice expression (a[x:y:z]) is + // applied to a string. + // + // Example: + // var s = "hello" + // var x = s[1:2:3] + InvalidSliceExpr + + /* exprs > shift */ + + // InvalidShiftCount occurs when the right-hand side of a shift operation is + // either non-integer, negative, or too large. + // + // Example: + // var ( + // x string + // y int = 1 << x + // ) + InvalidShiftCount + + // InvalidShiftOperand occurs when the shifted operand is not an integer. + // + // Example: + // var s = "hello" + // var x = s << 2 + InvalidShiftOperand + + /* exprs > chan */ + + // InvalidReceive occurs when there is a channel receive from a value that + // is either not a channel, or is a send-only channel. + // + // Example: + // func f() { + // var x = 1 + // <-x + // } + InvalidReceive + + // InvalidSend occurs when there is a channel send to a value that is not a + // channel, or is a receive-only channel. + // + // Example: + // func f() { + // var x = 1 + // x <- "hello!" + // } + InvalidSend + + /* exprs > literal */ + + // DuplicateLitKey occurs when an index is duplicated in a slice, array, or + // map literal. + // + // Example: + // var _ = []int{0:1, 0:2} + // + // Example: + // var _ = map[string]int{"a": 1, "a": 2} + DuplicateLitKey + + // MissingLitKey occurs when a map literal is missing a key expression. + // + // Example: + // var _ = map[string]int{1} + MissingLitKey + + // InvalidLitIndex occurs when the key in a key-value element of a slice or + // array literal is not an integer constant. + // + // Example: + // var i = 0 + // var x = []string{i: "world"} + InvalidLitIndex + + // OversizeArrayLit occurs when an array literal exceeds its length. + // + // Example: + // var _ = [2]int{1,2,3} + OversizeArrayLit + + // MixedStructLit occurs when a struct literal contains a mix of positional + // and named elements. + // + // Example: + // var _ = struct{i, j int}{i: 1, 2} + MixedStructLit + + // InvalidStructLit occurs when a positional struct literal has an incorrect + // number of values. + // + // Example: + // var _ = struct{i, j int}{1,2,3} + InvalidStructLit + + // MissingLitField occurs when a struct literal refers to a field that does + // not exist on the struct type. + // + // Example: + // var _ = struct{i int}{j: 2} + MissingLitField + + // DuplicateLitField occurs when a struct literal contains duplicated + // fields. + // + // Example: + // var _ = struct{i int}{i: 1, i: 2} + DuplicateLitField + + // UnexportedLitField occurs when a positional struct literal implicitly + // assigns an unexported field of an imported type. + UnexportedLitField + + // InvalidLitField occurs when a field name is not a valid identifier. + // + // Example: + // var _ = struct{i int}{1: 1} + InvalidLitField + + // UntypedLit occurs when a composite literal omits a required type + // identifier. + // + // Example: + // type outer struct{ + // inner struct { i int } + // } + // + // var _ = outer{inner: {1}} + UntypedLit + + // InvalidLit occurs when a composite literal expression does not match its + // type. + // + // Example: + // type P *struct{ + // x int + // } + // var _ = P {} + InvalidLit + + /* exprs > selector */ + + // AmbiguousSelector occurs when a selector is ambiguous. + // + // Example: + // type E1 struct { i int } + // type E2 struct { i int } + // type T struct { E1; E2 } + // + // var x T + // var _ = x.i + AmbiguousSelector + + // UndeclaredImportedName occurs when a package-qualified identifier is + // undeclared by the imported package. + // + // Example: + // import "go/types" + // + // var _ = types.NotAnActualIdentifier + UndeclaredImportedName + + // UnexportedName occurs when a selector refers to an unexported identifier + // of an imported package. + // + // Example: + // import "reflect" + // + // type _ reflect.flag + UnexportedName + + // UndeclaredName occurs when an identifier is not declared in the current + // scope. + // + // Example: + // var x T + UndeclaredName + + // MissingFieldOrMethod occurs when a selector references a field or method + // that does not exist. + // + // Example: + // type T struct {} + // + // var x = T{}.f + MissingFieldOrMethod + + /* exprs > ... */ + + // BadDotDotDotSyntax occurs when a "..." occurs in a context where it is + // not valid. + // + // Example: + // var _ = map[int][...]int{0: {}} + BadDotDotDotSyntax + + // NonVariadicDotDotDot occurs when a "..." is used on the final argument to + // a non-variadic function. + // + // Example: + // func printArgs(s []string) { + // for _, a := range s { + // println(a) + // } + // } + // + // func f() { + // s := []string{"a", "b", "c"} + // printArgs(s...) + // } + NonVariadicDotDotDot + + // MisplacedDotDotDot occurs when a "..." is used somewhere other than the + // final argument to a function call. + // + // Example: + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func f() { + // a := []int{1,2,3} + // printArgs(0, a...) + // } + MisplacedDotDotDot + + // InvalidDotDotDotOperand occurs when a "..." operator is applied to a + // single-valued operand. + // + // Example: + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func f() { + // a := 1 + // printArgs(a...) + // } + // + // Example: + // func args() (int, int) { + // return 1, 2 + // } + // + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func g() { + // printArgs(args()...) + // } + InvalidDotDotDotOperand + + // InvalidDotDotDot occurs when a "..." is used in a non-variadic built-in + // function. + // + // Example: + // var s = []int{1, 2, 3} + // var l = len(s...) + InvalidDotDotDot + + /* exprs > built-in */ + + // UncalledBuiltin occurs when a built-in function is used as a + // function-valued expression, instead of being called. + // + // Per the spec: + // "The built-in functions do not have standard Go types, so they can only + // appear in call expressions; they cannot be used as function values." + // + // Example: + // var _ = copy + UncalledBuiltin + + // InvalidAppend occurs when append is called with a first argument that is + // not a slice. + // + // Example: + // var _ = append(1, 2) + InvalidAppend + + // InvalidCap occurs when an argument to the cap built-in function is not of + // supported type. + // + // See https://golang.org/ref/spec#Length_and_capacity for information on + // which underlying types are supported as arguments to cap and len. + // + // Example: + // var s = 2 + // var x = cap(s) + InvalidCap + + // InvalidClose occurs when close(...) is called with an argument that is + // not of channel type, or that is a receive-only channel. + // + // Example: + // func f() { + // var x int + // close(x) + // } + InvalidClose + + // InvalidCopy occurs when the arguments are not of slice type or do not + // have compatible type. + // + // See https://golang.org/ref/spec#Appending_and_copying_slices for more + // information on the type requirements for the copy built-in. + // + // Example: + // func f() { + // var x []int + // y := []int64{1,2,3} + // copy(x, y) + // } + InvalidCopy + + // InvalidComplex occurs when the complex built-in function is called with + // arguments with incompatible types. + // + // Example: + // var _ = complex(float32(1), float64(2)) + InvalidComplex + + // InvalidDelete occurs when the delete built-in function is called with a + // first argument that is not a map. + // + // Example: + // func f() { + // m := "hello" + // delete(m, "e") + // } + InvalidDelete + + // InvalidImag occurs when the imag built-in function is called with an + // argument that does not have complex type. + // + // Example: + // var _ = imag(int(1)) + InvalidImag + + // InvalidLen occurs when an argument to the len built-in function is not of + // supported type. + // + // See https://golang.org/ref/spec#Length_and_capacity for information on + // which underlying types are supported as arguments to cap and len. + // + // Example: + // var s = 2 + // var x = len(s) + InvalidLen + + // SwappedMakeArgs occurs when make is called with three arguments, and its + // length argument is larger than its capacity argument. + // + // Example: + // var x = make([]int, 3, 2) + SwappedMakeArgs + + // InvalidMake occurs when make is called with an unsupported type argument. + // + // See https://golang.org/ref/spec#Making_slices_maps_and_channels for + // information on the types that may be created using make. + // + // Example: + // var x = make(int) + InvalidMake + + // InvalidReal occurs when the real built-in function is called with an + // argument that does not have complex type. + // + // Example: + // var _ = real(int(1)) + InvalidReal + + /* exprs > assertion */ + + // InvalidAssert occurs when a type assertion is applied to a + // value that is not of interface type. + // + // Example: + // var x = 1 + // var _ = x.(float64) + InvalidAssert + + // ImpossibleAssert occurs for a type assertion x.(T) when the value x of + // interface cannot have dynamic type T, due to a missing or mismatching + // method on T. + // + // Example: + // type T int + // + // func (t *T) m() int { return int(*t) } + // + // type I interface { m() int } + // + // var x I + // var _ = x.(T) + ImpossibleAssert + + /* exprs > conversion */ + + // InvalidConversion occurs when the argument type cannot be converted to the + // target. + // + // See https://golang.org/ref/spec#Conversions for the rules of + // convertibility. + // + // Example: + // var x float64 + // var _ = string(x) + InvalidConversion + + // InvalidUntypedConversion occurs when an there is no valid implicit + // conversion from an untyped value satisfying the type constraints of the + // context in which it is used. + // + // Example: + // var _ = 1 + "" + InvalidUntypedConversion + + /* offsetof */ + + // BadOffsetofSyntax occurs when unsafe.Offsetof is called with an argument + // that is not a selector expression. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Offsetof(x) + BadOffsetofSyntax + + // InvalidOffsetof occurs when unsafe.Offsetof is called with a method + // selector, rather than a field selector, or when the field is embedded via + // a pointer. + // + // Per the spec: + // + // "If f is an embedded field, it must be reachable without pointer + // indirections through fields of the struct. " + // + // Example: + // import "unsafe" + // + // type T struct { f int } + // type S struct { *T } + // var s S + // var _ = unsafe.Offsetof(s.f) + // + // Example: + // import "unsafe" + // + // type S struct{} + // + // func (S) m() {} + // + // var s S + // var _ = unsafe.Offsetof(s.m) + InvalidOffsetof + + /* control flow > scope */ + + // UnusedExpr occurs when a side-effect free expression is used as a + // statement. Such a statement has no effect. + // + // Example: + // func f(i int) { + // i*i + // } + UnusedExpr + + // UnusedVar occurs when a variable is declared but unused. + // + // Example: + // func f() { + // x := 1 + // } + UnusedVar + + // MissingReturn occurs when a function with results is missing a return + // statement. + // + // Example: + // func f() int {} + MissingReturn + + // WrongResultCount occurs when a return statement returns an incorrect + // number of values. + // + // Example: + // func ReturnOne() int { + // return 1, 2 + // } + WrongResultCount + + // OutOfScopeResult occurs when the name of a value implicitly returned by + // an empty return statement is shadowed in a nested scope. + // + // Example: + // func factor(n int) (i int) { + // for i := 2; i < n; i++ { + // if n%i == 0 { + // return + // } + // } + // return 0 + // } + OutOfScopeResult + + /* control flow > if */ + + // InvalidCond occurs when an if condition is not a boolean expression. + // + // Example: + // func checkReturn(i int) { + // if i { + // panic("non-zero return") + // } + // } + InvalidCond + + /* control flow > for */ + + // InvalidPostDecl occurs when there is a declaration in a for-loop post + // statement. + // + // Example: + // func f() { + // for i := 0; i < 10; j := 0 {} + // } + InvalidPostDecl + + // InvalidChanRange occurs when a send-only channel used in a range + // expression. + // + // Example: + // func sum(c chan<- int) { + // s := 0 + // for i := range c { + // s += i + // } + // } + InvalidChanRange + + // InvalidIterVar occurs when two iteration variables are used while ranging + // over a channel. + // + // Example: + // func f(c chan int) { + // for k, v := range c { + // println(k, v) + // } + // } + InvalidIterVar + + // InvalidRangeExpr occurs when the type of a range expression is not array, + // slice, string, map, or channel. + // + // Example: + // func f(i int) { + // for j := range i { + // println(j) + // } + // } + InvalidRangeExpr + + /* control flow > switch */ + + // MisplacedBreak occurs when a break statement is not within a for, switch, + // or select statement of the innermost function definition. + // + // Example: + // func f() { + // break + // } + MisplacedBreak + + // MisplacedContinue occurs when a continue statement is not within a for + // loop of the innermost function definition. + // + // Example: + // func sumeven(n int) int { + // proceed := func() { + // continue + // } + // sum := 0 + // for i := 1; i <= n; i++ { + // if i % 2 != 0 { + // proceed() + // } + // sum += i + // } + // return sum + // } + MisplacedContinue + + // MisplacedFallthrough occurs when a fallthrough statement is not within an + // expression switch. + // + // Example: + // func typename(i interface{}) string { + // switch i.(type) { + // case int64: + // fallthrough + // case int: + // return "int" + // } + // return "unsupported" + // } + MisplacedFallthrough + + // DuplicateCase occurs when a type or expression switch has duplicate + // cases. + // + // Example: + // func printInt(i int) { + // switch i { + // case 1: + // println("one") + // case 1: + // println("One") + // } + // } + DuplicateCase + + // DuplicateDefault occurs when a type or expression switch has multiple + // default clauses. + // + // Example: + // func printInt(i int) { + // switch i { + // case 1: + // println("one") + // default: + // println("One") + // default: + // println("1") + // } + // } + DuplicateDefault + + // BadTypeKeyword occurs when a .(type) expression is used anywhere other + // than a type switch. + // + // Example: + // type I interface { + // m() + // } + // var t I + // var _ = t.(type) + BadTypeKeyword + + // InvalidTypeSwitch occurs when .(type) is used on an expression that is + // not of interface type. + // + // Example: + // func f(i int) { + // switch x := i.(type) {} + // } + InvalidTypeSwitch + + // InvalidExprSwitch occurs when a switch expression is not comparable. + // + // Example: + // func _() { + // var a struct{ _ func() } + // switch a /* ERROR cannot switch on a */ { + // } + // } + InvalidExprSwitch + + /* control flow > select */ + + // InvalidSelectCase occurs when a select case is not a channel send or + // receive. + // + // Example: + // func checkChan(c <-chan int) bool { + // select { + // case c: + // return true + // default: + // return false + // } + // } + InvalidSelectCase + + /* control flow > labels and jumps */ + + // UndeclaredLabel occurs when an undeclared label is jumped to. + // + // Example: + // func f() { + // goto L + // } + UndeclaredLabel + + // DuplicateLabel occurs when a label is declared more than once. + // + // Example: + // func f() int { + // L: + // L: + // return 1 + // } + DuplicateLabel + + // MisplacedLabel occurs when a break or continue label is not on a for, + // switch, or select statement. + // + // Example: + // func f() { + // L: + // a := []int{1,2,3} + // for _, e := range a { + // if e > 10 { + // break L + // } + // println(a) + // } + // } + MisplacedLabel + + // UnusedLabel occurs when a label is declared but not used. + // + // Example: + // func f() { + // L: + // } + UnusedLabel + + // JumpOverDecl occurs when a label jumps over a variable declaration. + // + // Example: + // func f() int { + // goto L + // x := 2 + // L: + // x++ + // return x + // } + JumpOverDecl + + // JumpIntoBlock occurs when a forward jump goes to a label inside a nested + // block. + // + // Example: + // func f(x int) { + // goto L + // if x > 0 { + // L: + // print("inside block") + // } + // } + JumpIntoBlock + + /* control flow > calls */ + + // InvalidMethodExpr occurs when a pointer method is called but the argument + // is not addressable. + // + // Example: + // type T struct {} + // + // func (*T) m() int { return 1 } + // + // var _ = T.m(T{}) + InvalidMethodExpr + + // WrongArgCount occurs when too few or too many arguments are passed by a + // function call. + // + // Example: + // func f(i int) {} + // var x = f() + WrongArgCount + + // InvalidCall occurs when an expression is called that is not of function + // type. + // + // Example: + // var x = "x" + // var y = x() + InvalidCall + + /* control flow > suspended */ + + // UnusedResults occurs when a restricted expression-only built-in function + // is suspended via go or defer. Such a suspension discards the results of + // these side-effect free built-in functions, and therefore is ineffectual. + // + // Example: + // func f(a []int) int { + // defer len(a) + // return i + // } + UnusedResults + + // InvalidDefer occurs when a deferred expression is not a function call, + // for example if the expression is a type conversion. + // + // Example: + // func f(i int) int { + // defer int32(i) + // return i + // } + InvalidDefer + + // InvalidGo occurs when a go expression is not a function call, for example + // if the expression is a type conversion. + // + // Example: + // func f(i int) int { + // go int32(i) + // return i + // } + InvalidGo + + // All codes below were added in Go 1.17. + + /* decl */ + + // BadDecl occurs when a declaration has invalid syntax. + BadDecl + + // RepeatedDecl occurs when an identifier occurs more than once on the left + // hand side of a short variable declaration. + // + // Example: + // func _() { + // x, y, y := 1, 2, 3 + // } + RepeatedDecl + + /* unsafe */ + + // InvalidUnsafeAdd occurs when unsafe.Add is called with a + // length argument that is not of integer type. + // + // Example: + // import "unsafe" + // + // var p unsafe.Pointer + // var _ = unsafe.Add(p, float64(1)) + InvalidUnsafeAdd + + // InvalidUnsafeSlice occurs when unsafe.Slice is called with a + // pointer argument that is not of pointer type or a length argument + // that is not of integer type, negative, or out of bounds. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(x, 1) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, float64(1)) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, -1) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, uint64(1) << 63) + InvalidUnsafeSlice + + // All codes below were added in Go 1.18. + + /* features */ + + // UnsupportedFeature occurs when a language feature is used that is not + // supported at this Go version. + UnsupportedFeature + + /* type params */ + + // NotAGenericType occurs when a non-generic type is used where a generic + // type is expected: in type or function instantiation. + // + // Example: + // type T int + // + // var _ T[int] + NotAGenericType + + // WrongTypeArgCount occurs when a type or function is instantiated with an + // incorrect number of type arguments, including when a generic type or + // function is used without instantiation. + // + // Errors involving failed type inference are assigned other error codes. + // + // Example: + // type T[p any] int + // + // var _ T[int, string] + // + // Example: + // func f[T any]() {} + // + // var x = f + WrongTypeArgCount + + // CannotInferTypeArgs occurs when type or function type argument inference + // fails to infer all type arguments. + // + // Example: + // func f[T any]() {} + // + // func _() { + // f() + // } + // + // Example: + // type N[P, Q any] struct{} + // + // var _ N[int] + CannotInferTypeArgs + + // InvalidTypeArg occurs when a type argument does not satisfy its + // corresponding type parameter constraints. + // + // Example: + // type T[P ~int] struct{} + // + // var _ T[string] + InvalidTypeArg // arguments? InferenceFailed + + // InvalidInstanceCycle occurs when an invalid cycle is detected + // within the instantiation graph. + // + // Example: + // func f[T any]() { f[*T]() } + InvalidInstanceCycle + + // InvalidUnion occurs when an embedded union or approximation element is + // not valid. + // + // Example: + // type _ interface { + // ~int | interface{ m() } + // } + InvalidUnion + + // MisplacedConstraintIface occurs when a constraint-type interface is used + // outside of constraint position. + // + // Example: + // type I interface { ~int } + // + // var _ I + MisplacedConstraintIface + + // InvalidMethodTypeParams occurs when methods have type parameters. + // + // It cannot be encountered with an AST parsed using go/parser. + InvalidMethodTypeParams + + // MisplacedTypeParam occurs when a type parameter is used in a place where + // it is not permitted. + // + // Example: + // type T[P any] P + // + // Example: + // type T[P any] struct{ *P } + MisplacedTypeParam + + // InvalidUnsafeSliceData occurs when unsafe.SliceData is called with + // an argument that is not of slice type. It also occurs if it is used + // in a package compiled for a language version before go1.20. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.SliceData(x) + InvalidUnsafeSliceData + + // InvalidUnsafeString occurs when unsafe.String is called with + // a length argument that is not of integer type, negative, or + // out of bounds. It also occurs if it is used in a package + // compiled for a language version before go1.20. + // + // Example: + // import "unsafe" + // + // var b [10]byte + // var _ = unsafe.String(&b[0], -1) + InvalidUnsafeString + + // InvalidUnsafeStringData occurs if it is used in a package + // compiled for a language version before go1.20. + _ // not used anymore + +) diff --git a/contribs/gnopls/internal/typesinternal/errorcode_string.go b/contribs/gnopls/internal/typesinternal/errorcode_string.go new file mode 100644 index 00000000000..15ecf7c5ded --- /dev/null +++ b/contribs/gnopls/internal/typesinternal/errorcode_string.go @@ -0,0 +1,179 @@ +// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT. + +package typesinternal + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[InvalidSyntaxTree - -1] + _ = x[Test-1] + _ = x[BlankPkgName-2] + _ = x[MismatchedPkgName-3] + _ = x[InvalidPkgUse-4] + _ = x[BadImportPath-5] + _ = x[BrokenImport-6] + _ = x[ImportCRenamed-7] + _ = x[UnusedImport-8] + _ = x[InvalidInitCycle-9] + _ = x[DuplicateDecl-10] + _ = x[InvalidDeclCycle-11] + _ = x[InvalidTypeCycle-12] + _ = x[InvalidConstInit-13] + _ = x[InvalidConstVal-14] + _ = x[InvalidConstType-15] + _ = x[UntypedNilUse-16] + _ = x[WrongAssignCount-17] + _ = x[UnassignableOperand-18] + _ = x[NoNewVar-19] + _ = x[MultiValAssignOp-20] + _ = x[InvalidIfaceAssign-21] + _ = x[InvalidChanAssign-22] + _ = x[IncompatibleAssign-23] + _ = x[UnaddressableFieldAssign-24] + _ = x[NotAType-25] + _ = x[InvalidArrayLen-26] + _ = x[BlankIfaceMethod-27] + _ = x[IncomparableMapKey-28] + _ = x[InvalidIfaceEmbed-29] + _ = x[InvalidPtrEmbed-30] + _ = x[BadRecv-31] + _ = x[InvalidRecv-32] + _ = x[DuplicateFieldAndMethod-33] + _ = x[DuplicateMethod-34] + _ = x[InvalidBlank-35] + _ = x[InvalidIota-36] + _ = x[MissingInitBody-37] + _ = x[InvalidInitSig-38] + _ = x[InvalidInitDecl-39] + _ = x[InvalidMainDecl-40] + _ = x[TooManyValues-41] + _ = x[NotAnExpr-42] + _ = x[TruncatedFloat-43] + _ = x[NumericOverflow-44] + _ = x[UndefinedOp-45] + _ = x[MismatchedTypes-46] + _ = x[DivByZero-47] + _ = x[NonNumericIncDec-48] + _ = x[UnaddressableOperand-49] + _ = x[InvalidIndirection-50] + _ = x[NonIndexableOperand-51] + _ = x[InvalidIndex-52] + _ = x[SwappedSliceIndices-53] + _ = x[NonSliceableOperand-54] + _ = x[InvalidSliceExpr-55] + _ = x[InvalidShiftCount-56] + _ = x[InvalidShiftOperand-57] + _ = x[InvalidReceive-58] + _ = x[InvalidSend-59] + _ = x[DuplicateLitKey-60] + _ = x[MissingLitKey-61] + _ = x[InvalidLitIndex-62] + _ = x[OversizeArrayLit-63] + _ = x[MixedStructLit-64] + _ = x[InvalidStructLit-65] + _ = x[MissingLitField-66] + _ = x[DuplicateLitField-67] + _ = x[UnexportedLitField-68] + _ = x[InvalidLitField-69] + _ = x[UntypedLit-70] + _ = x[InvalidLit-71] + _ = x[AmbiguousSelector-72] + _ = x[UndeclaredImportedName-73] + _ = x[UnexportedName-74] + _ = x[UndeclaredName-75] + _ = x[MissingFieldOrMethod-76] + _ = x[BadDotDotDotSyntax-77] + _ = x[NonVariadicDotDotDot-78] + _ = x[MisplacedDotDotDot-79] + _ = x[InvalidDotDotDotOperand-80] + _ = x[InvalidDotDotDot-81] + _ = x[UncalledBuiltin-82] + _ = x[InvalidAppend-83] + _ = x[InvalidCap-84] + _ = x[InvalidClose-85] + _ = x[InvalidCopy-86] + _ = x[InvalidComplex-87] + _ = x[InvalidDelete-88] + _ = x[InvalidImag-89] + _ = x[InvalidLen-90] + _ = x[SwappedMakeArgs-91] + _ = x[InvalidMake-92] + _ = x[InvalidReal-93] + _ = x[InvalidAssert-94] + _ = x[ImpossibleAssert-95] + _ = x[InvalidConversion-96] + _ = x[InvalidUntypedConversion-97] + _ = x[BadOffsetofSyntax-98] + _ = x[InvalidOffsetof-99] + _ = x[UnusedExpr-100] + _ = x[UnusedVar-101] + _ = x[MissingReturn-102] + _ = x[WrongResultCount-103] + _ = x[OutOfScopeResult-104] + _ = x[InvalidCond-105] + _ = x[InvalidPostDecl-106] + _ = x[InvalidChanRange-107] + _ = x[InvalidIterVar-108] + _ = x[InvalidRangeExpr-109] + _ = x[MisplacedBreak-110] + _ = x[MisplacedContinue-111] + _ = x[MisplacedFallthrough-112] + _ = x[DuplicateCase-113] + _ = x[DuplicateDefault-114] + _ = x[BadTypeKeyword-115] + _ = x[InvalidTypeSwitch-116] + _ = x[InvalidExprSwitch-117] + _ = x[InvalidSelectCase-118] + _ = x[UndeclaredLabel-119] + _ = x[DuplicateLabel-120] + _ = x[MisplacedLabel-121] + _ = x[UnusedLabel-122] + _ = x[JumpOverDecl-123] + _ = x[JumpIntoBlock-124] + _ = x[InvalidMethodExpr-125] + _ = x[WrongArgCount-126] + _ = x[InvalidCall-127] + _ = x[UnusedResults-128] + _ = x[InvalidDefer-129] + _ = x[InvalidGo-130] + _ = x[BadDecl-131] + _ = x[RepeatedDecl-132] + _ = x[InvalidUnsafeAdd-133] + _ = x[InvalidUnsafeSlice-134] + _ = x[UnsupportedFeature-135] + _ = x[NotAGenericType-136] + _ = x[WrongTypeArgCount-137] + _ = x[CannotInferTypeArgs-138] + _ = x[InvalidTypeArg-139] + _ = x[InvalidInstanceCycle-140] + _ = x[InvalidUnion-141] + _ = x[MisplacedConstraintIface-142] + _ = x[InvalidMethodTypeParams-143] + _ = x[MisplacedTypeParam-144] + _ = x[InvalidUnsafeSliceData-145] + _ = x[InvalidUnsafeString-146] +} + +const ( + _ErrorCode_name_0 = "InvalidSyntaxTree" + _ErrorCode_name_1 = "TestBlankPkgNameMismatchedPkgNameInvalidPkgUseBadImportPathBrokenImportImportCRenamedUnusedImportInvalidInitCycleDuplicateDeclInvalidDeclCycleInvalidTypeCycleInvalidConstInitInvalidConstValInvalidConstTypeUntypedNilUseWrongAssignCountUnassignableOperandNoNewVarMultiValAssignOpInvalidIfaceAssignInvalidChanAssignIncompatibleAssignUnaddressableFieldAssignNotATypeInvalidArrayLenBlankIfaceMethodIncomparableMapKeyInvalidIfaceEmbedInvalidPtrEmbedBadRecvInvalidRecvDuplicateFieldAndMethodDuplicateMethodInvalidBlankInvalidIotaMissingInitBodyInvalidInitSigInvalidInitDeclInvalidMainDeclTooManyValuesNotAnExprTruncatedFloatNumericOverflowUndefinedOpMismatchedTypesDivByZeroNonNumericIncDecUnaddressableOperandInvalidIndirectionNonIndexableOperandInvalidIndexSwappedSliceIndicesNonSliceableOperandInvalidSliceExprInvalidShiftCountInvalidShiftOperandInvalidReceiveInvalidSendDuplicateLitKeyMissingLitKeyInvalidLitIndexOversizeArrayLitMixedStructLitInvalidStructLitMissingLitFieldDuplicateLitFieldUnexportedLitFieldInvalidLitFieldUntypedLitInvalidLitAmbiguousSelectorUndeclaredImportedNameUnexportedNameUndeclaredNameMissingFieldOrMethodBadDotDotDotSyntaxNonVariadicDotDotDotMisplacedDotDotDotInvalidDotDotDotOperandInvalidDotDotDotUncalledBuiltinInvalidAppendInvalidCapInvalidCloseInvalidCopyInvalidComplexInvalidDeleteInvalidImagInvalidLenSwappedMakeArgsInvalidMakeInvalidRealInvalidAssertImpossibleAssertInvalidConversionInvalidUntypedConversionBadOffsetofSyntaxInvalidOffsetofUnusedExprUnusedVarMissingReturnWrongResultCountOutOfScopeResultInvalidCondInvalidPostDeclInvalidChanRangeInvalidIterVarInvalidRangeExprMisplacedBreakMisplacedContinueMisplacedFallthroughDuplicateCaseDuplicateDefaultBadTypeKeywordInvalidTypeSwitchInvalidExprSwitchInvalidSelectCaseUndeclaredLabelDuplicateLabelMisplacedLabelUnusedLabelJumpOverDeclJumpIntoBlockInvalidMethodExprWrongArgCountInvalidCallUnusedResultsInvalidDeferInvalidGoBadDeclRepeatedDeclInvalidUnsafeAddInvalidUnsafeSliceUnsupportedFeatureNotAGenericTypeWrongTypeArgCountCannotInferTypeArgsInvalidTypeArgInvalidInstanceCycleInvalidUnionMisplacedConstraintIfaceInvalidMethodTypeParamsMisplacedTypeParamInvalidUnsafeSliceDataInvalidUnsafeString" +) + +var ( + _ErrorCode_index_1 = [...]uint16{0, 4, 16, 33, 46, 59, 71, 85, 97, 113, 126, 142, 158, 174, 189, 205, 218, 234, 253, 261, 277, 295, 312, 330, 354, 362, 377, 393, 411, 428, 443, 450, 461, 484, 499, 511, 522, 537, 551, 566, 581, 594, 603, 617, 632, 643, 658, 667, 683, 703, 721, 740, 752, 771, 790, 806, 823, 842, 856, 867, 882, 895, 910, 926, 940, 956, 971, 988, 1006, 1021, 1031, 1041, 1058, 1080, 1094, 1108, 1128, 1146, 1166, 1184, 1207, 1223, 1238, 1251, 1261, 1273, 1284, 1298, 1311, 1322, 1332, 1347, 1358, 1369, 1382, 1398, 1415, 1439, 1456, 1471, 1481, 1490, 1503, 1519, 1535, 1546, 1561, 1577, 1591, 1607, 1621, 1638, 1658, 1671, 1687, 1701, 1718, 1735, 1752, 1767, 1781, 1795, 1806, 1818, 1831, 1848, 1861, 1872, 1885, 1897, 1906, 1913, 1925, 1941, 1959, 1977, 1992, 2009, 2028, 2042, 2062, 2074, 2098, 2121, 2139, 2161, 2180} +) + +func (i ErrorCode) String() string { + switch { + case i == -1: + return _ErrorCode_name_0 + case 1 <= i && i <= 146: + i -= 1 + return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]] + default: + return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/contribs/gnopls/internal/typesinternal/errorcode_test.go b/contribs/gnopls/internal/typesinternal/errorcode_test.go new file mode 100644 index 00000000000..63d13f19eae --- /dev/null +++ b/contribs/gnopls/internal/typesinternal/errorcode_test.go @@ -0,0 +1,105 @@ +// 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 typesinternal_test + +import ( + "fmt" + "go/ast" + "go/constant" + "go/parser" + "go/token" + "go/types" + "path/filepath" + "runtime" + "sort" + "strings" + "testing" +) + +func TestErrorCodes(t *testing.T) { + t.Skip("unskip this test to verify the correctness of errorcode.go for the current Go version") + + // For older go versions, this file was src/go/types/errorcodes.go. + stdPath := filepath.Join(runtime.GOROOT(), "src", "internal", "types", "errors", "codes.go") + stdCodes, err := loadCodes(stdPath) + if err != nil { + t.Fatalf("loading std codes: %v", err) + } + + localPath := "errorcode.go" + localCodes, err := loadCodes(localPath) + if err != nil { + t.Fatalf("loading local codes: %v", err) + } + + // Verify that all std codes are present, with the correct value. + type codeVal struct { + Name string + Value int64 + } + var byValue []codeVal + for k, v := range stdCodes { + byValue = append(byValue, codeVal{k, v}) + } + sort.Slice(byValue, func(i, j int) bool { + return byValue[i].Value < byValue[j].Value + }) + + localLookup := make(map[int64]string) + for k, v := range localCodes { + if _, ok := localLookup[v]; ok { + t.Errorf("duplicate error code value %d", v) + } + localLookup[v] = k + } + + for _, std := range byValue { + local, ok := localCodes[std.Name] + if !ok { + if v, ok := localLookup[std.Value]; ok { + t.Errorf("Missing code for %s (code %d is %s)", std.Name, std.Value, v) + } else { + t.Errorf("Missing code for %s", std.Name) + } + } + if local != std.Value { + t.Errorf("Mismatching value for %s: got %d, but stdlib has %d", std.Name, local, std.Value) + } + } +} + +// loadCodes loads all constant values found in filepath. +// +// The given file must type-check cleanly as a standalone file. +func loadCodes(filepath string) (map[string]int64, error) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, filepath, nil, 0) + if err != nil { + return nil, err + } + var config types.Config + pkg, err := config.Check("p", fset, []*ast.File{f}, nil) + if err != nil { + return nil, err + } + + codes := make(map[string]int64) + for _, name := range pkg.Scope().Names() { + obj := pkg.Scope().Lookup(name) + c, ok := obj.(*types.Const) + if !ok { + continue + } + name := strings.TrimPrefix(name, "_") // compatibility with earlier go versions + codes[name], ok = constant.Int64Val(c.Val()) + if !ok { + return nil, fmt.Errorf("non integral value %v for %s", c.Val(), name) + } + } + if len(codes) < 100 { + return nil, fmt.Errorf("sanity check: got %d codes but expected at least 100", len(codes)) + } + return codes, nil +} diff --git a/contribs/gnopls/internal/typesinternal/recv.go b/contribs/gnopls/internal/typesinternal/recv.go new file mode 100644 index 00000000000..75d3f4af6c0 --- /dev/null +++ b/contribs/gnopls/internal/typesinternal/recv.go @@ -0,0 +1,43 @@ +// 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 typesinternal + +import ( + "go/types" + + "github.com/gnolang/gno/contribs/gnopls/internal/aliases" +) + +// ReceiverNamed returns the named type (if any) associated with the +// type of recv, which may be of the form N or *N, or aliases thereof. +// It also reports whether a Pointer was present. +func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) { + t := recv.Type() + if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok { + isPtr = true + t = ptr.Elem() + } + named, _ = aliases.Unalias(t).(*types.Named) + return +} + +// Unpointer returns T given *T or an alias thereof. +// For all other types it is the identity function. +// It does not look at underlying types. +// The result may be an alias. +// +// Use this function to strip off the optional pointer on a receiver +// in a field or method selection, without losing the named type +// (which is needed to compute the method set). +// +// See also [typeparams.MustDeref], which removes one level of +// indirection from the type, regardless of named types (analogous to +// a LOAD instruction). +func Unpointer(t types.Type) types.Type { + if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok { + return ptr.Elem() + } + return t +} diff --git a/contribs/gnopls/internal/typesinternal/toonew.go b/contribs/gnopls/internal/typesinternal/toonew.go new file mode 100644 index 00000000000..9a7d31db14c --- /dev/null +++ b/contribs/gnopls/internal/typesinternal/toonew.go @@ -0,0 +1,89 @@ +// 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 typesinternal + +import ( + "go/types" + + "github.com/gnolang/gno/contribs/gnopls/internal/stdlib" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" +) + +// TooNewStdSymbols computes the set of package-level symbols +// exported by pkg that are not available at the specified version. +// The result maps each symbol to its minimum version. +// +// The pkg is allowed to contain type errors. +func TooNewStdSymbols(pkg *types.Package, version string) map[types.Object]string { + disallowed := make(map[types.Object]string) + + // Pass 1: package-level symbols. + symbols := stdlib.PackageSymbols[pkg.Path()] + for _, sym := range symbols { + symver := sym.Version.String() + if versions.Before(version, symver) { + switch sym.Kind { + case stdlib.Func, stdlib.Var, stdlib.Const, stdlib.Type: + disallowed[pkg.Scope().Lookup(sym.Name)] = symver + } + } + } + + // Pass 2: fields and methods. + // + // We allow fields and methods if their associated type is + // disallowed, as otherwise we would report false positives + // for compatibility shims. Consider: + // + // //go:build go1.22 + // type T struct { F std.Real } // correct new API + // + // //go:build !go1.22 + // type T struct { F fake } // shim + // type fake struct { ... } + // func (fake) M () {} + // + // These alternative declarations of T use either the std.Real + // type, introduced in go1.22, or a fake type, for the field + // F. (The fakery could be arbitrarily deep, involving more + // nested fields and methods than are shown here.) Clients + // that use the compatibility shim T will compile with any + // version of go, whether older or newer than go1.22, but only + // the newer version will use the std.Real implementation. + // + // Now consider a reference to method M in new(T).F.M() in a + // module that requires a minimum of go1.21. The analysis may + // occur using a version of Go higher than 1.21, selecting the + // first version of T, so the method M is Real.M. This would + // spuriously cause the analyzer to report a reference to a + // too-new symbol even though this expression compiles just + // fine (with the fake implementation) using go1.21. + for _, sym := range symbols { + symVersion := sym.Version.String() + if !versions.Before(version, symVersion) { + continue // allowed + } + + var obj types.Object + switch sym.Kind { + case stdlib.Field: + typename, name := sym.SplitField() + if t := pkg.Scope().Lookup(typename); t != nil && disallowed[t] == "" { + obj, _, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name) + } + + case stdlib.Method: + ptr, recvname, name := sym.SplitMethod() + if t := pkg.Scope().Lookup(recvname); t != nil && disallowed[t] == "" { + obj, _, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name) + } + } + if obj != nil { + disallowed[obj] = symVersion + } + } + + return disallowed +} diff --git a/contribs/gnopls/internal/typesinternal/types.go b/contribs/gnopls/internal/typesinternal/types.go new file mode 100644 index 00000000000..83923286120 --- /dev/null +++ b/contribs/gnopls/internal/typesinternal/types.go @@ -0,0 +1,65 @@ +// 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 typesinternal provides access to internal go/types APIs that are not +// yet exported. +package typesinternal + +import ( + "go/token" + "go/types" + "reflect" + "unsafe" +) + +func SetUsesCgo(conf *types.Config) bool { + v := reflect.ValueOf(conf).Elem() + + f := v.FieldByName("go115UsesCgo") + if !f.IsValid() { + f = v.FieldByName("UsesCgo") + if !f.IsValid() { + return false + } + } + + addr := unsafe.Pointer(f.UnsafeAddr()) + *(*bool)(addr) = true + + return true +} + +// ReadGo116ErrorData extracts additional information from types.Error values +// generated by Go version 1.16 and later: the error code, start position, and +// end position. If all positions are valid, start <= err.Pos <= end. +// +// If the data could not be read, the final result parameter will be false. +func ReadGo116ErrorData(err types.Error) (code ErrorCode, start, end token.Pos, ok bool) { + var data [3]int + // By coincidence all of these fields are ints, which simplifies things. + v := reflect.ValueOf(err) + for i, name := range []string{"go116code", "go116start", "go116end"} { + f := v.FieldByName(name) + if !f.IsValid() { + return 0, 0, 0, false + } + data[i] = int(f.Int()) + } + return ErrorCode(data[0]), token.Pos(data[1]), token.Pos(data[2]), true +} + +// NameRelativeTo returns a types.Qualifier that qualifies members of +// all packages other than pkg, using only the package name. +// (By contrast, [types.RelativeTo] uses the complete package path, +// which is often excessive.) +// +// If pkg is nil, it is equivalent to [*types.Package.Name]. +func NameRelativeTo(pkg *types.Package) types.Qualifier { + return func(other *types.Package) string { + if pkg != nil && pkg == other { + return "" // same package; unqualified + } + return other.Name() + } +} diff --git a/contribs/gnopls/internal/util/astutil/purge.go b/contribs/gnopls/internal/util/astutil/purge.go index 95117c568ba..db2ebc8b48c 100644 --- a/contribs/gnopls/internal/util/astutil/purge.go +++ b/contribs/gnopls/internal/util/astutil/purge.go @@ -10,7 +10,7 @@ import ( "go/scanner" "go/token" - "golang.org/x/tools/gopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" ) // PurgeFuncBodies returns a copy of src in which the contents of each diff --git a/contribs/gnopls/internal/util/astutil/purge_test.go b/contribs/gnopls/internal/util/astutil/purge_test.go index c67f9039adc..8f827e742d3 100644 --- a/contribs/gnopls/internal/util/astutil/purge_test.go +++ b/contribs/gnopls/internal/util/astutil/purge_test.go @@ -13,8 +13,8 @@ import ( "testing" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/util/astutil" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/util/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) // TestPurgeFuncBodies tests PurgeFuncBodies by comparing it against a diff --git a/contribs/gnopls/internal/util/astutil/util.go b/contribs/gnopls/internal/util/astutil/util.go index ac7515d1daf..2b871235739 100644 --- a/contribs/gnopls/internal/util/astutil/util.go +++ b/contribs/gnopls/internal/util/astutil/util.go @@ -8,7 +8,7 @@ import ( "go/ast" "go/token" - "golang.org/x/tools/internal/typeparams" + "github.com/gnolang/gno/contribs/gnopls/internal/typeparams" ) // UnpackRecv unpacks a receiver type expression, reporting whether it is a diff --git a/contribs/gnopls/internal/util/frob/frob_test.go b/contribs/gnopls/internal/util/frob/frob_test.go index 5765c9642ef..f0681d77b53 100644 --- a/contribs/gnopls/internal/util/frob/frob_test.go +++ b/contribs/gnopls/internal/util/frob/frob_test.go @@ -9,7 +9,7 @@ import ( "reflect" "testing" - "golang.org/x/tools/gopls/internal/util/frob" + "github.com/gnolang/gno/contribs/gnopls/internal/util/frob" ) func TestBasics(t *testing.T) { diff --git a/contribs/gnopls/internal/util/goversion/goversion_test.go b/contribs/gnopls/internal/util/goversion/goversion_test.go index e2df9f23118..662fef0cc20 100644 --- a/contribs/gnopls/internal/util/goversion/goversion_test.go +++ b/contribs/gnopls/internal/util/goversion/goversion_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/util/goversion" + "github.com/gnolang/gno/contribs/gnopls/internal/util/goversion" ) func TestMessage(t *testing.T) { diff --git a/contribs/gnopls/internal/util/lru/lru_fuzz_test.go b/contribs/gnopls/internal/util/lru/lru_fuzz_test.go index 2f5f43cb9f5..44b5cc8ea72 100644 --- a/contribs/gnopls/internal/util/lru/lru_fuzz_test.go +++ b/contribs/gnopls/internal/util/lru/lru_fuzz_test.go @@ -7,7 +7,7 @@ package lru_test import ( "testing" - "golang.org/x/tools/gopls/internal/util/lru" + "github.com/gnolang/gno/contribs/gnopls/internal/util/lru" ) // Simple fuzzing test for consistency. diff --git a/contribs/gnopls/internal/util/lru/lru_nil_test.go b/contribs/gnopls/internal/util/lru/lru_nil_test.go index 443d2a67818..3d170227fb6 100644 --- a/contribs/gnopls/internal/util/lru/lru_nil_test.go +++ b/contribs/gnopls/internal/util/lru/lru_nil_test.go @@ -7,7 +7,7 @@ package lru_test import ( "testing" - "golang.org/x/tools/gopls/internal/util/lru" + "github.com/gnolang/gno/contribs/gnopls/internal/util/lru" ) func TestSetUntypedNil(t *testing.T) { diff --git a/contribs/gnopls/internal/util/lru/lru_test.go b/contribs/gnopls/internal/util/lru/lru_test.go index bf96e8d31b7..cff908897d1 100644 --- a/contribs/gnopls/internal/util/lru/lru_test.go +++ b/contribs/gnopls/internal/util/lru/lru_test.go @@ -14,7 +14,7 @@ import ( "testing" "golang.org/x/sync/errgroup" - "golang.org/x/tools/gopls/internal/util/lru" + "github.com/gnolang/gno/contribs/gnopls/internal/util/lru" ) func TestCache(t *testing.T) { diff --git a/contribs/gnopls/internal/util/persistent/map.go b/contribs/gnopls/internal/util/persistent/map.go index b0e49f27d42..06ec65e1d47 100644 --- a/contribs/gnopls/internal/util/persistent/map.go +++ b/contribs/gnopls/internal/util/persistent/map.go @@ -13,7 +13,7 @@ import ( "strings" "sync/atomic" - "golang.org/x/tools/gopls/internal/util/constraints" + "github.com/gnolang/gno/contribs/gnopls/internal/util/constraints" ) // Implementation details: diff --git a/contribs/gnopls/internal/util/persistent/set.go b/contribs/gnopls/internal/util/persistent/set.go index 2d5f4edac96..8560a837cd4 100644 --- a/contribs/gnopls/internal/util/persistent/set.go +++ b/contribs/gnopls/internal/util/persistent/set.go @@ -4,7 +4,7 @@ package persistent -import "golang.org/x/tools/gopls/internal/util/constraints" +import "github.com/gnolang/gno/contribs/gnopls/internal/util/constraints" // Set is a collection of elements of type K. // diff --git a/contribs/gnopls/internal/util/persistent/set_test.go b/contribs/gnopls/internal/util/persistent/set_test.go index 31911b451b3..65a514488ca 100644 --- a/contribs/gnopls/internal/util/persistent/set_test.go +++ b/contribs/gnopls/internal/util/persistent/set_test.go @@ -9,8 +9,8 @@ import ( "strings" "testing" - "golang.org/x/tools/gopls/internal/util/constraints" - "golang.org/x/tools/gopls/internal/util/persistent" + "github.com/gnolang/gno/contribs/gnopls/internal/util/constraints" + "github.com/gnolang/gno/contribs/gnopls/internal/util/persistent" ) func TestSet(t *testing.T) { diff --git a/contribs/gnopls/internal/util/safetoken/safetoken_test.go b/contribs/gnopls/internal/util/safetoken/safetoken_test.go index 4cdce7a97b9..d58a6baa8f6 100644 --- a/contribs/gnopls/internal/util/safetoken/safetoken_test.go +++ b/contribs/gnopls/internal/util/safetoken/safetoken_test.go @@ -13,8 +13,8 @@ import ( "testing" "golang.org/x/tools/go/packages" - "golang.org/x/tools/gopls/internal/util/safetoken" - "golang.org/x/tools/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" ) func TestWorkaroundIssue57490(t *testing.T) { @@ -84,7 +84,7 @@ func TestGoplsSourceDoesNotCallTokenFileMethods(t *testing.T) { "GOFLAGS=-mod=mod", ) - pkgs, err := packages.Load(cfg, "go/token", "golang.org/x/tools/gopls/...") + pkgs, err := packages.Load(cfg, "go/token", "github.com/gnolang/gno/contribs/gnopls/...") if err != nil { t.Fatal(err) } @@ -116,7 +116,7 @@ func TestGoplsSourceDoesNotCallTokenFileMethods(t *testing.T) { for _, pkg := range pkgs { switch pkg.PkgPath { - case "go/token", "golang.org/x/tools/gopls/internal/util/safetoken": + case "go/token", "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken": continue // allow calls within these packages } diff --git a/contribs/gnopls/internal/versions/constraint.go b/contribs/gnopls/internal/versions/constraint.go new file mode 100644 index 00000000000..179063d4848 --- /dev/null +++ b/contribs/gnopls/internal/versions/constraint.go @@ -0,0 +1,13 @@ +// 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 versions + +import "go/build/constraint" + +// ConstraintGoVersion is constraint.GoVersion (if built with go1.21+). +// Otherwise nil. +// +// Deprecate once x/tools is after go1.21. +var ConstraintGoVersion func(x constraint.Expr) string diff --git a/contribs/gnopls/internal/versions/constraint_go121.go b/contribs/gnopls/internal/versions/constraint_go121.go new file mode 100644 index 00000000000..38011407d5f --- /dev/null +++ b/contribs/gnopls/internal/versions/constraint_go121.go @@ -0,0 +1,14 @@ +// 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. + +//go:build go1.21 +// +build go1.21 + +package versions + +import "go/build/constraint" + +func init() { + ConstraintGoVersion = constraint.GoVersion +} diff --git a/contribs/gnopls/internal/versions/features.go b/contribs/gnopls/internal/versions/features.go new file mode 100644 index 00000000000..b53f1786161 --- /dev/null +++ b/contribs/gnopls/internal/versions/features.go @@ -0,0 +1,43 @@ +// 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 versions + +// This file contains predicates for working with file versions to +// decide when a tool should consider a language feature enabled. + +// GoVersions that features in x/tools can be gated to. +const ( + Go1_18 = "go1.18" + Go1_19 = "go1.19" + Go1_20 = "go1.20" + Go1_21 = "go1.21" + Go1_22 = "go1.22" +) + +// Future is an invalid unknown Go version sometime in the future. +// Do not use directly with Compare. +const Future = "" + +// AtLeast reports whether the file version v comes after a Go release. +// +// Use this predicate to enable a behavior once a certain Go release +// has happened (and stays enabled in the future). +func AtLeast(v, release string) bool { + if v == Future { + return true // an unknown future version is always after y. + } + return Compare(Lang(v), Lang(release)) >= 0 +} + +// Before reports whether the file version v is strictly before a Go release. +// +// Use this predicate to disable a behavior once a certain Go release +// has happened (and stays enabled in the future). +func Before(v, release string) bool { + if v == Future { + return false // an unknown future version happens after y. + } + return Compare(Lang(v), Lang(release)) < 0 +} diff --git a/contribs/gnopls/internal/versions/gover.go b/contribs/gnopls/internal/versions/gover.go new file mode 100644 index 00000000000..bbabcd22e94 --- /dev/null +++ b/contribs/gnopls/internal/versions/gover.go @@ -0,0 +1,172 @@ +// 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. + +// This is a fork of internal/gover for use by x/tools until +// go1.21 and earlier are no longer supported by x/tools. + +package versions + +import "strings" + +// A gover is a parsed Go gover: major[.Minor[.Patch]][kind[pre]] +// The numbers are the original decimal strings to avoid integer overflows +// and since there is very little actual math. (Probably overflow doesn't matter in practice, +// but at the time this code was written, there was an existing test that used +// go1.99999999999, which does not fit in an int on 32-bit platforms. +// The "big decimal" representation avoids the problem entirely.) +type gover struct { + major string // decimal + minor string // decimal or "" + patch string // decimal or "" + kind string // "", "alpha", "beta", "rc" + pre string // decimal or "" +} + +// compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as toolchain versions. +// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21". +// Malformed versions compare less than well-formed versions and equal to each other. +// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0". +func compare(x, y string) int { + vx := parse(x) + vy := parse(y) + + if c := cmpInt(vx.major, vy.major); c != 0 { + return c + } + if c := cmpInt(vx.minor, vy.minor); c != 0 { + return c + } + if c := cmpInt(vx.patch, vy.patch); c != 0 { + return c + } + if c := strings.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc + return c + } + if c := cmpInt(vx.pre, vy.pre); c != 0 { + return c + } + return 0 +} + +// lang returns the Go language version. For example, lang("1.2.3") == "1.2". +func lang(x string) string { + v := parse(x) + if v.minor == "" || v.major == "1" && v.minor == "0" { + return v.major + } + return v.major + "." + v.minor +} + +// isValid reports whether the version x is valid. +func isValid(x string) bool { + return parse(x) != gover{} +} + +// parse parses the Go version string x into a version. +// It returns the zero version if x is malformed. +func parse(x string) gover { + var v gover + + // Parse major version. + var ok bool + v.major, x, ok = cutInt(x) + if !ok { + return gover{} + } + if x == "" { + // Interpret "1" as "1.0.0". + v.minor = "0" + v.patch = "0" + return v + } + + // Parse . before minor version. + if x[0] != '.' { + return gover{} + } + + // Parse minor version. + v.minor, x, ok = cutInt(x[1:]) + if !ok { + return gover{} + } + if x == "" { + // Patch missing is same as "0" for older versions. + // Starting in Go 1.21, patch missing is different from explicit .0. + if cmpInt(v.minor, "21") < 0 { + v.patch = "0" + } + return v + } + + // Parse patch if present. + if x[0] == '.' { + v.patch, x, ok = cutInt(x[1:]) + if !ok || x != "" { + // Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != ""). + // Allowing them would be a bit confusing because we already have: + // 1.21 < 1.21rc1 + // But a prerelease of a patch would have the opposite effect: + // 1.21.3rc1 < 1.21.3 + // We've never needed them before, so let's not start now. + return gover{} + } + return v + } + + // Parse prerelease. + i := 0 + for i < len(x) && (x[i] < '0' || '9' < x[i]) { + if x[i] < 'a' || 'z' < x[i] { + return gover{} + } + i++ + } + if i == 0 { + return gover{} + } + v.kind, x = x[:i], x[i:] + if x == "" { + return v + } + v.pre, x, ok = cutInt(x) + if !ok || x != "" { + return gover{} + } + + return v +} + +// cutInt scans the leading decimal number at the start of x to an integer +// and returns that value and the rest of the string. +func cutInt(x string) (n, rest string, ok bool) { + i := 0 + for i < len(x) && '0' <= x[i] && x[i] <= '9' { + i++ + } + if i == 0 || x[0] == '0' && i != 1 { // no digits or unnecessary leading zero + return "", "", false + } + return x[:i], x[i:], true +} + +// cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers. +// (Copied from golang.org/x/mod/semver's compareInt.) +func cmpInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} diff --git a/contribs/gnopls/internal/versions/toolchain.go b/contribs/gnopls/internal/versions/toolchain.go new file mode 100644 index 00000000000..377bf7a53b4 --- /dev/null +++ b/contribs/gnopls/internal/versions/toolchain.go @@ -0,0 +1,14 @@ +// 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 versions + +// toolchain is maximum version (<1.22) that the go toolchain used +// to build the current tool is known to support. +// +// When a tool is built with >=1.22, the value of toolchain is unused. +// +// x/tools does not support building with go <1.18. So we take this +// as the minimum possible maximum. +var toolchain string = Go1_18 diff --git a/contribs/gnopls/internal/versions/toolchain_go119.go b/contribs/gnopls/internal/versions/toolchain_go119.go new file mode 100644 index 00000000000..f65beed9d83 --- /dev/null +++ b/contribs/gnopls/internal/versions/toolchain_go119.go @@ -0,0 +1,14 @@ +// 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. + +//go:build go1.19 +// +build go1.19 + +package versions + +func init() { + if Compare(toolchain, Go1_19) < 0 { + toolchain = Go1_19 + } +} diff --git a/contribs/gnopls/internal/versions/toolchain_go120.go b/contribs/gnopls/internal/versions/toolchain_go120.go new file mode 100644 index 00000000000..1a9efa126cd --- /dev/null +++ b/contribs/gnopls/internal/versions/toolchain_go120.go @@ -0,0 +1,14 @@ +// 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. + +//go:build go1.20 +// +build go1.20 + +package versions + +func init() { + if Compare(toolchain, Go1_20) < 0 { + toolchain = Go1_20 + } +} diff --git a/contribs/gnopls/internal/versions/toolchain_go121.go b/contribs/gnopls/internal/versions/toolchain_go121.go new file mode 100644 index 00000000000..b7ef216dfec --- /dev/null +++ b/contribs/gnopls/internal/versions/toolchain_go121.go @@ -0,0 +1,14 @@ +// 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. + +//go:build go1.21 +// +build go1.21 + +package versions + +func init() { + if Compare(toolchain, Go1_21) < 0 { + toolchain = Go1_21 + } +} diff --git a/contribs/gnopls/internal/versions/types.go b/contribs/gnopls/internal/versions/types.go new file mode 100644 index 00000000000..562eef21fa2 --- /dev/null +++ b/contribs/gnopls/internal/versions/types.go @@ -0,0 +1,19 @@ +// 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 versions + +import ( + "go/types" +) + +// GoVersion returns the Go version of the type package. +// It returns zero if no version can be determined. +func GoVersion(pkg *types.Package) string { + // TODO(taking): x/tools can call GoVersion() [from 1.21] after 1.25. + if pkg, ok := any(pkg).(interface{ GoVersion() string }); ok { + return pkg.GoVersion() + } + return "" +} diff --git a/contribs/gnopls/internal/versions/types_go121.go b/contribs/gnopls/internal/versions/types_go121.go new file mode 100644 index 00000000000..b4345d3349e --- /dev/null +++ b/contribs/gnopls/internal/versions/types_go121.go @@ -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. + +//go:build !go1.22 +// +build !go1.22 + +package versions + +import ( + "go/ast" + "go/types" +) + +// FileVersion returns a language version (<=1.21) derived from runtime.Version() +// or an unknown future version. +func FileVersion(info *types.Info, file *ast.File) string { + // In x/tools built with Go <= 1.21, we do not have Info.FileVersions + // available. We use a go version derived from the toolchain used to + // compile the tool by default. + // This will be <= go1.21. We take this as the maximum version that + // this tool can support. + // + // There are no features currently in x/tools that need to tell fine grained + // differences for versions <1.22. + return toolchain +} + +// InitFileVersions is a noop when compiled with this Go version. +func InitFileVersions(*types.Info) {} diff --git a/contribs/gnopls/internal/versions/types_go122.go b/contribs/gnopls/internal/versions/types_go122.go new file mode 100644 index 00000000000..aac5db62c98 --- /dev/null +++ b/contribs/gnopls/internal/versions/types_go122.go @@ -0,0 +1,41 @@ +// 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. + +//go:build go1.22 +// +build go1.22 + +package versions + +import ( + "go/ast" + "go/types" +) + +// FileVersion returns a file's Go version. +// The reported version is an unknown Future version if a +// version cannot be determined. +func FileVersion(info *types.Info, file *ast.File) string { + // In tools built with Go >= 1.22, the Go version of a file + // follow a cascades of sources: + // 1) types.Info.FileVersion, which follows the cascade: + // 1.a) file version (ast.File.GoVersion), + // 1.b) the package version (types.Config.GoVersion), or + // 2) is some unknown Future version. + // + // File versions require a valid package version to be provided to types + // in Config.GoVersion. Config.GoVersion is either from the package's module + // or the toolchain (go run). This value should be provided by go/packages + // or unitchecker.Config.GoVersion. + if v := info.FileVersions[file]; IsValid(v) { + return v + } + // Note: we could instead return runtime.Version() [if valid]. + // This would act as a max version on what a tool can support. + return Future +} + +// InitFileVersions initializes info to record Go versions for Go files. +func InitFileVersions(info *types.Info) { + info.FileVersions = make(map[*ast.File]string) +} diff --git a/contribs/gnopls/internal/versions/types_test.go b/contribs/gnopls/internal/versions/types_test.go new file mode 100644 index 00000000000..f13734fe1bf --- /dev/null +++ b/contribs/gnopls/internal/versions/types_test.go @@ -0,0 +1,87 @@ +// 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 versions_test + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" +) + +func Test(t *testing.T) { + testenv.NeedsGo1Point(t, 22) + + var contents = map[string]string{ + "gobuild.go": ` + //go:build go1.23 + package p + `, + "noversion.go": ` + package p + `, + } + type fileTest struct { + fname string + want string + } + for _, item := range []struct { + goversion string + pversion string + tests []fileTest + }{ + // {"", "", []fileTest{{"noversion.go", ""}, {"gobuild.go", ""}}}, // TODO(matloob): re-enable this test (with modifications) once CL 607955 has been submitted + {"go1.22", "go1.22", []fileTest{{"noversion.go", "go1.22"}, {"gobuild.go", "go1.23"}}}, + } { + name := fmt.Sprintf("types.Config{GoVersion:%q}", item.goversion) + t.Run(name, func(t *testing.T) { + fset := token.NewFileSet() + files := make([]*ast.File, len(item.tests)) + for i, test := range item.tests { + files[i] = parse(t, fset, test.fname, contents[test.fname]) + } + pkg, info := typeCheck(t, fset, files, item.goversion) + if got, want := versions.GoVersion(pkg), item.pversion; versions.Compare(got, want) != 0 { + t.Errorf("GoVersion()=%q. expected %q", got, want) + } + if got := versions.FileVersion(info, nil); got != "" { + t.Errorf(`FileVersions(nil)=%q. expected ""`, got) + } + for i, test := range item.tests { + if got, want := versions.FileVersion(info, files[i]), test.want; got != want { + t.Errorf("FileVersions(%s)=%q. expected %q", test.fname, got, want) + } + } + }) + } +} + +func parse(t *testing.T, fset *token.FileSet, name, src string) *ast.File { + file, err := parser.ParseFile(fset, name, src, 0) + if err != nil { + t.Fatal(err) + } + return file +} + +func typeCheck(t *testing.T, fset *token.FileSet, files []*ast.File, goversion string) (*types.Package, *types.Info) { + conf := types.Config{ + Importer: importer.Default(), + GoVersion: goversion, + } + info := types.Info{} + versions.InitFileVersions(&info) + pkg, err := conf.Check("", fset, files, &info) + if err != nil { + t.Fatal(err) + } + return pkg, &info +} diff --git a/contribs/gnopls/internal/versions/versions.go b/contribs/gnopls/internal/versions/versions.go new file mode 100644 index 00000000000..8d1f7453dbf --- /dev/null +++ b/contribs/gnopls/internal/versions/versions.go @@ -0,0 +1,57 @@ +// 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 versions + +import ( + "strings" +) + +// Note: If we use build tags to use go/versions when go >=1.22, +// we run into go.dev/issue/53737. Under some operations users would see an +// import of "go/versions" even if they would not compile the file. +// For example, during `go get -u ./...` (go.dev/issue/64490) we do not try to include +// For this reason, this library just a clone of go/versions for the moment. + +// Lang returns the Go language version for version x. +// If x is not a valid version, Lang returns the empty string. +// For example: +// +// Lang("go1.21rc2") = "go1.21" +// Lang("go1.21.2") = "go1.21" +// Lang("go1.21") = "go1.21" +// Lang("go1") = "go1" +// Lang("bad") = "" +// Lang("1.21") = "" +func Lang(x string) string { + v := lang(stripGo(x)) + if v == "" { + return "" + } + return x[:2+len(v)] // "go"+v without allocation +} + +// Compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as Go versions. +// The versions x and y must begin with a "go" prefix: "go1.21" not "1.21". +// Invalid versions, including the empty string, compare less than +// valid versions and equal to each other. +// The language version "go1.21" compares less than the +// release candidate and eventual releases "go1.21rc1" and "go1.21.0". +// Custom toolchain suffixes are ignored during comparison: +// "go1.21.0" and "go1.21.0-bigcorp" are equal. +func Compare(x, y string) int { return compare(stripGo(x), stripGo(y)) } + +// IsValid reports whether the version x is valid. +func IsValid(x string) bool { return isValid(stripGo(x)) } + +// stripGo converts from a "go1.21" version to a "1.21" version. +// If v does not start with "go", stripGo returns the empty string (a known invalid version). +func stripGo(v string) string { + v, _, _ = strings.Cut(v, "-") // strip -bigcorp suffix. + if len(v) < 2 || v[:2] != "go" { + return "" + } + return v[2:] +} diff --git a/contribs/gnopls/internal/versions/versions_test.go b/contribs/gnopls/internal/versions/versions_test.go new file mode 100644 index 00000000000..d69e58b274d --- /dev/null +++ b/contribs/gnopls/internal/versions/versions_test.go @@ -0,0 +1,256 @@ +// 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 versions_test + +import ( + "go/ast" + "go/parser" + "go/token" + "go/types" + "testing" + + "github.com/gnolang/gno/contribs/gnopls/internal/testenv" + "github.com/gnolang/gno/contribs/gnopls/internal/versions" +) + +func TestIsValid(t *testing.T) { + // valid versions + for _, x := range []string{ + "go1.21", + "go1.21.2", + "go1.21rc", + "go1.21rc2", + "go0.0", // ?? + "go1", + "go2", + "go1.20.0-bigcorp", + } { + if !versions.IsValid(x) { + t.Errorf("expected versions.IsValid(%q) to hold", x) + } + } + + // invalid versions + for _, x := range []string{ + "", + "bad", + "1.21", + "v1.21", + "go", + "goAA", + "go2_3", + "go1.BB", + "go1.21.", + "go1.21.2_2", + "go1.21rc_2", + "go1.21rc2_", + "go1.600+auto", + } { + if versions.IsValid(x) { + t.Errorf("expected versions.IsValid(%q) to not hold", x) + } + } +} + +func TestVersionComparisons(t *testing.T) { + for _, item := range []struct { + x, y string + want int + }{ + // All comparisons of go2, go1.21.2, go1.21rc2, go1.21rc2, go1, go0.0, "", bad + {"go2", "go2", 0}, + {"go2", "go1.21.2", +1}, + {"go2", "go1.21rc2", +1}, + {"go2", "go1.21rc", +1}, + {"go2", "go1.21", +1}, + {"go2", "go1", +1}, + {"go2", "go0.0", +1}, + {"go2", "", +1}, + {"go2", "bad", +1}, + {"go1.21.2", "go1.21.2", 0}, + {"go1.21.2", "go1.21rc2", +1}, + {"go1.21.2", "go1.21rc", +1}, + {"go1.21.2", "go1.21", +1}, + {"go1.21.2", "go1", +1}, + {"go1.21.2", "go0.0", +1}, + {"go1.21.2", "", +1}, + {"go1.21.2", "bad", +1}, + {"go1.21rc2", "go1.21rc2", 0}, + {"go1.21rc2", "go1.21rc", +1}, + {"go1.21rc2", "go1.21", +1}, + {"go1.21rc2", "go1", +1}, + {"go1.21rc2", "go0.0", +1}, + {"go1.21rc2", "", +1}, + {"go1.21rc2", "bad", +1}, + {"go1.21rc", "go1.21rc", 0}, + {"go1.21rc", "go1.21", +1}, + {"go1.21rc", "go1", +1}, + {"go1.21rc", "go0.0", +1}, + {"go1.21rc", "", +1}, + {"go1.21rc", "bad", +1}, + {"go1.21", "go1.21", 0}, + {"go1.21", "go1", +1}, + {"go1.21", "go0.0", +1}, + {"go1.21", "", +1}, + {"go1.21", "bad", +1}, + {"go1", "go1", 0}, + {"go1", "go0.0", +1}, + {"go1", "", +1}, + {"go1", "bad", +1}, + {"go0.0", "go0.0", 0}, + {"go0.0", "", +1}, + {"go0.0", "bad", +1}, + {"", "", 0}, + {"", "bad", 0}, + {"bad", "bad", 0}, + // Other tests. + {"go1.20", "go1.20.0-bigcorp", 0}, + {"go1.21", "go1.21.0-bigcorp", -1}, // Starting in Go 1.21, patch missing is different from explicit .0. + {"go1.21.0", "go1.21.0-bigcorp", 0}, // Starting in Go 1.21, patch missing is different from explicit .0. + {"go1.19rc1", "go1.19", -1}, + } { + got := versions.Compare(item.x, item.y) + if got != item.want { + t.Errorf("versions.Compare(%q, %q)=%d. expected %d", item.x, item.y, got, item.want) + } + reverse := versions.Compare(item.y, item.x) + if reverse != -got { + t.Errorf("versions.Compare(%q, %q)=%d. expected %d", item.y, item.x, reverse, -got) + } + } +} + +func TestLang(t *testing.T) { + for _, item := range []struct { + x string + want string + }{ + // valid + {"go1.21rc2", "go1.21"}, + {"go1.21.2", "go1.21"}, + {"go1.21", "go1.21"}, + {"go1", "go1"}, + // invalid + {"bad", ""}, + {"1.21", ""}, + } { + if got := versions.Lang(item.x); got != item.want { + t.Errorf("versions.Lang(%q)=%q. expected %q", item.x, got, item.want) + } + } + +} + +func TestKnown(t *testing.T) { + for _, v := range [...]string{ + versions.Go1_18, + versions.Go1_19, + versions.Go1_20, + versions.Go1_21, + versions.Go1_22, + } { + if !versions.IsValid(v) { + t.Errorf("Expected known version %q to be valid.", v) + } + if v != versions.Lang(v) { + t.Errorf("Expected known version %q == Lang(%q).", v, versions.Lang(v)) + } + } +} + +func TestAtLeast(t *testing.T) { + for _, item := range [...]struct { + v, release string + want bool + }{ + {versions.Future, versions.Go1_22, true}, + {versions.Go1_22, versions.Go1_22, true}, + {"go1.21", versions.Go1_22, false}, + {"invalid", versions.Go1_22, false}, + } { + if got := versions.AtLeast(item.v, item.release); got != item.want { + t.Errorf("AtLeast(%q, %q)=%v. wanted %v", item.v, item.release, got, item.want) + } + } +} + +func TestBefore(t *testing.T) { + for _, item := range [...]struct { + v, release string + want bool + }{ + {versions.Future, versions.Go1_22, false}, + {versions.Go1_22, versions.Go1_22, false}, + {"go1.21", versions.Go1_22, true}, + {"invalid", versions.Go1_22, true}, // invalid < Go1_22 + } { + if got := versions.Before(item.v, item.release); got != item.want { + t.Errorf("Before(%q, %q)=%v. wanted %v", item.v, item.release, got, item.want) + } + } +} + +func TestFileVersions122(t *testing.T) { + testenv.NeedsGo1Point(t, 22) + + const source = ` + package P + ` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hello.go", source, 0) + if err != nil { + t.Fatal(err) + } + + for _, conf := range []types.Config{ + {GoVersion: versions.Go1_22}, + {}, // GoVersion is unset. + } { + info := &types.Info{} + versions.InitFileVersions(info) + + _, err = conf.Check("P", fset, []*ast.File{f}, info) + if err != nil { + t.Fatal(err) + } + + v := versions.FileVersion(info, f) + if !versions.AtLeast(v, versions.Go1_22) { + t.Errorf("versions.AtLeast(%q, %q) expected to hold", v, versions.Go1_22) + } + + if versions.Before(v, versions.Go1_22) { + t.Errorf("versions.AtLeast(%q, %q) expected to be false", v, versions.Go1_22) + } + + if conf.GoVersion == "" && v != versions.Future { + t.Error("Expected the FileVersion to be the Future when conf.GoVersion is unset") + } + } +} + +func TestFileVersions121(t *testing.T) { + testenv.SkipAfterGo1Point(t, 21) + + // If <1.22, info and file are ignored. + v := versions.FileVersion(nil, nil) + oneof := map[string]bool{ + versions.Go1_18: true, + versions.Go1_19: true, + versions.Go1_20: true, + versions.Go1_21: true, + } + if !oneof[v] { + t.Errorf("FileVersion(...)=%q expected to be a known go version <1.22", v) + } + + if versions.AtLeast(v, versions.Go1_22) { + t.Errorf("versions.AtLeast(%q, %q) expected to be false", v, versions.Go1_22) + } + + if !versions.Before(v, versions.Go1_22) { + t.Errorf("versions.Before(%q, %q) expected to hold", v, versions.Go1_22) + } +} diff --git a/contribs/gnopls/internal/vulncheck/copier.go b/contribs/gnopls/internal/vulncheck/copier.go index ade5a5f6be2..412a1d4ee10 100644 --- a/contribs/gnopls/internal/vulncheck/copier.go +++ b/contribs/gnopls/internal/vulncheck/copier.go @@ -26,7 +26,7 @@ import ( "strconv" "strings" - "golang.org/x/tools/internal/edit" + "github.com/gnolang/gno/contribs/gnopls/internal/edit" ) func main() { diff --git a/contribs/gnopls/internal/vulncheck/govulncheck/govulncheck.go b/contribs/gnopls/internal/vulncheck/govulncheck/govulncheck.go index fd0390703ae..20b9d38536d 100644 --- a/contribs/gnopls/internal/vulncheck/govulncheck/govulncheck.go +++ b/contribs/gnopls/internal/vulncheck/govulncheck/govulncheck.go @@ -10,7 +10,7 @@ package govulncheck import ( "time" - "golang.org/x/tools/gopls/internal/vulncheck/osv" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" ) const ( diff --git a/contribs/gnopls/internal/vulncheck/govulncheck/handler.go b/contribs/gnopls/internal/vulncheck/govulncheck/handler.go index 4100910a3c3..0ecb8ccd5e2 100644 --- a/contribs/gnopls/internal/vulncheck/govulncheck/handler.go +++ b/contribs/gnopls/internal/vulncheck/govulncheck/handler.go @@ -10,7 +10,7 @@ import ( "encoding/json" "io" - "golang.org/x/tools/gopls/internal/vulncheck/osv" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" ) // Handler handles messages to be presented in a vulnerability scan output diff --git a/contribs/gnopls/internal/vulncheck/govulncheck/jsonhandler.go b/contribs/gnopls/internal/vulncheck/govulncheck/jsonhandler.go index eb110a2aee9..aee321c43c3 100644 --- a/contribs/gnopls/internal/vulncheck/govulncheck/jsonhandler.go +++ b/contribs/gnopls/internal/vulncheck/govulncheck/jsonhandler.go @@ -11,7 +11,7 @@ import ( "io" - "golang.org/x/tools/gopls/internal/vulncheck/osv" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" ) type jsonHandler struct { diff --git a/contribs/gnopls/internal/vulncheck/scan/command.go b/contribs/gnopls/internal/vulncheck/scan/command.go index 4ef005010c9..6a488193fb3 100644 --- a/contribs/gnopls/internal/vulncheck/scan/command.go +++ b/contribs/gnopls/internal/vulncheck/scan/command.go @@ -15,10 +15,10 @@ import ( "time" "golang.org/x/sync/errgroup" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/vulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/osv" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/govulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" "golang.org/x/vuln/scan" ) diff --git a/contribs/gnopls/internal/vulncheck/types.go b/contribs/gnopls/internal/vulncheck/types.go index 450cd961797..070ae2380b1 100644 --- a/contribs/gnopls/internal/vulncheck/types.go +++ b/contribs/gnopls/internal/vulncheck/types.go @@ -9,8 +9,8 @@ package vulncheck import ( "time" - gvc "golang.org/x/tools/gopls/internal/vulncheck/govulncheck" - "golang.org/x/tools/gopls/internal/vulncheck/osv" + gvc "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/govulncheck" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" ) // Result is the result of vulnerability scanning. diff --git a/contribs/gnopls/internal/vulncheck/vulntest/db.go b/contribs/gnopls/internal/vulncheck/vulntest/db.go index ee2a6923264..1dbb0f70e77 100644 --- a/contribs/gnopls/internal/vulncheck/vulntest/db.go +++ b/contribs/gnopls/internal/vulncheck/vulntest/db.go @@ -16,8 +16,8 @@ import ( "strings" "time" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/vulncheck/osv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" "golang.org/x/tools/txtar" ) diff --git a/contribs/gnopls/internal/vulncheck/vulntest/db_test.go b/contribs/gnopls/internal/vulncheck/vulntest/db_test.go index 3c3407105ac..dd69d30fbc0 100644 --- a/contribs/gnopls/internal/vulncheck/vulntest/db_test.go +++ b/contribs/gnopls/internal/vulncheck/vulntest/db_test.go @@ -14,8 +14,8 @@ import ( "time" "github.com/google/go-cmp/cmp" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/gopls/internal/vulncheck/osv" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" ) var update = flag.Bool("update", false, "update golden files in testdata/") diff --git a/contribs/gnopls/internal/vulncheck/vulntest/report.go b/contribs/gnopls/internal/vulncheck/vulntest/report.go index b67986cf8c2..1724d2250ca 100644 --- a/contribs/gnopls/internal/vulncheck/vulntest/report.go +++ b/contribs/gnopls/internal/vulncheck/vulntest/report.go @@ -12,7 +12,7 @@ import ( "time" "golang.org/x/mod/semver" - "golang.org/x/tools/gopls/internal/vulncheck/osv" + "github.com/gnolang/gno/contribs/gnopls/internal/vulncheck/osv" "gopkg.in/yaml.v3" ) diff --git a/contribs/gnopls/internal/work/completion.go b/contribs/gnopls/internal/work/completion.go index 194721ef36d..fb1df9092bd 100644 --- a/contribs/gnopls/internal/work/completion.go +++ b/contribs/gnopls/internal/work/completion.go @@ -14,10 +14,10 @@ import ( "sort" "strings" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func Completion(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.CompletionList, error) { diff --git a/contribs/gnopls/internal/work/diagnostics.go b/contribs/gnopls/internal/work/diagnostics.go index f1acd4d27c7..5b311ee0c9e 100644 --- a/contribs/gnopls/internal/work/diagnostics.go +++ b/contribs/gnopls/internal/work/diagnostics.go @@ -11,10 +11,10 @@ import ( "path/filepath" "golang.org/x/mod/modfile" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func Diagnostics(ctx context.Context, snapshot *cache.Snapshot) (map[protocol.DocumentURI][]*cache.Diagnostic, error) { diff --git a/contribs/gnopls/internal/work/format.go b/contribs/gnopls/internal/work/format.go index 162bc8c0004..401e6d50589 100644 --- a/contribs/gnopls/internal/work/format.go +++ b/contribs/gnopls/internal/work/format.go @@ -8,11 +8,11 @@ import ( "context" "golang.org/x/mod/modfile" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/diff" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func Format(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]protocol.TextEdit, error) { diff --git a/contribs/gnopls/internal/work/hover.go b/contribs/gnopls/internal/work/hover.go index c59c14789be..505c6bbbc64 100644 --- a/contribs/gnopls/internal/work/hover.go +++ b/contribs/gnopls/internal/work/hover.go @@ -10,10 +10,10 @@ import ( "fmt" "golang.org/x/mod/modfile" - "golang.org/x/tools/gopls/internal/cache" - "golang.org/x/tools/gopls/internal/file" - "golang.org/x/tools/gopls/internal/protocol" - "golang.org/x/tools/internal/event" + "github.com/gnolang/gno/contribs/gnopls/internal/cache" + "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" + "github.com/gnolang/gno/contribs/gnopls/internal/event" ) func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) { diff --git a/contribs/gnopls/internal/xcontext/xcontext.go b/contribs/gnopls/internal/xcontext/xcontext.go new file mode 100644 index 00000000000..ff8ed4ebb95 --- /dev/null +++ b/contribs/gnopls/internal/xcontext/xcontext.go @@ -0,0 +1,23 @@ +// 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. + +// Package xcontext is a package to offer the extra functionality we need +// from contexts that is not available from the standard context package. +package xcontext + +import ( + "context" + "time" +) + +// Detach returns a context that keeps all the values of its parent context +// but detaches from the cancellation and error handling. +func Detach(ctx context.Context) context.Context { return detachedContext{ctx} } + +type detachedContext struct{ parent context.Context } + +func (v detachedContext) Deadline() (time.Time, bool) { return time.Time{}, false } +func (v detachedContext) Done() <-chan struct{} { return nil } +func (v detachedContext) Err() error { return nil } +func (v detachedContext) Value(key interface{}) interface{} { return v.parent.Value(key) } diff --git a/contribs/gnopls/main.go b/contribs/gnopls/main.go index 083c4efd8de..8aafd632386 100644 --- a/contribs/gnopls/main.go +++ b/contribs/gnopls/main.go @@ -9,16 +9,16 @@ // // See https://github.com/golang/tools/blob/master/gopls/README.md // for the most up-to-date documentation. -package main // import "golang.org/x/tools/gopls" +package main // import "golang.org/x/tools/gnopls" import ( "context" "os" + "github.com/gnolang/gno/contribs/gnopls/internal/cmd" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" + versionpkg "github.com/gnolang/gno/contribs/gnopls/internal/version" "golang.org/x/telemetry" - "golang.org/x/tools/gopls/internal/cmd" - versionpkg "golang.org/x/tools/gopls/internal/version" - "golang.org/x/tools/internal/tool" ) var version = "" // if set by the linker, overrides the gopls version diff --git a/contribs/gnopls/release/release.go b/contribs/gnopls/release/release.go index 26ce5f7870a..cc71f7febc2 100644 --- a/contribs/gnopls/release/release.go +++ b/contribs/gnopls/release/release.go @@ -7,7 +7,7 @@ // // To run: // -// $ cd $GOPATH/src/golang.org/x/tools/gopls +// $ cd $GOPATH/src/github.com/gnolang/gno/contribs/gnopls // $ go run release/release.go -version=<version> package main From e025f48328becaa555ae687ba898ff7ce51a809a Mon Sep 17 00:00:00 2001 From: gfanton <8671905+gfanton@users.noreply.github.com> Date: Tue, 24 Sep 2024 12:19:14 +0200 Subject: [PATCH 3/5] wip: gnopls Signed-off-by: gfanton <8671905+gfanton@users.noreply.github.com> --- contribs/gnopls/go.mod | 36 +++- contribs/gnopls/go.sum | 184 ++++++++++++++++++- contribs/gnopls/internal/cache/check.go | 18 +- contribs/gnopls/internal/cache/load.go | 16 +- contribs/gnopls/internal/cache/snapshot.go | 2 +- contribs/gnopls/internal/cmd/cmd.go | 38 ++-- contribs/gnopls/internal/settings/default.go | 6 +- 7 files changed, 261 insertions(+), 39 deletions(-) diff --git a/contribs/gnopls/go.mod b/contribs/gnopls/go.mod index 54db08d4f35..68cf3b30a94 100644 --- a/contribs/gnopls/go.mod +++ b/contribs/gnopls/go.mod @@ -4,7 +4,10 @@ module github.com/gnolang/gno/contribs/gnopls // (golang/go#68894 and golang/go#68905). go 1.23.1 +replace github.com/gnolang/gno => ../.. + require ( + github.com/gnolang/gno v0.0.0-00010101000000-000000000000 github.com/google/go-cmp v0.6.0 github.com/jba/templatecheck v0.7.0 golang.org/x/mod v0.21.0 @@ -22,8 +25,39 @@ require ( require ( github.com/BurntSushi/toml v1.2.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/btcsuite/btcd/btcutil v1.1.6 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/google/safehtml v0.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/exp/typeparams v0.0.0-20221212164502-fae10dda9338 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + golang.org/x/net v0.29.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect ) diff --git a/contribs/gnopls/go.sum b/contribs/gnopls/go.sum index 2d9a767b85f..7cb98fcd18e 100644 --- a/contribs/gnopls/go.sum +++ b/contribs/gnopls/go.sum @@ -1,29 +1,187 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= +github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk= +github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8= github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0 h1:k6fQVDQexDE+3jG2SfCQjnHS7OamcP73YMoxEVq5B6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0/go.mod h1:t4BrYLHU450Zo9fnydWlIuswB1bm7rM8havDpWOJeDo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0 h1:xvhQxJ/C9+RTnAj5DpTg7LSM1vbbMTiXt7e9hsfqHNw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.29.0/go.mod h1:Fcvs2Bz1jkDM+Wf5/ozBGmi3tQ/c9zPKLnsipnfhGAo= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= +golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= @@ -32,9 +190,31 @@ golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/vuln v1.0.4 h1:SP0mPeg2PmGCu03V+61EcQiOjmpri2XijexKdzv8Z1I= golang.org/x/vuln v1.0.4/go.mod h1:NbJdUQhX8jY++FtuhrXs2Eyx0yePo9pF7nPlIjo9aaQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= +google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= diff --git a/contribs/gnopls/internal/cache/check.go b/contribs/gnopls/internal/cache/check.go index b6a334a131e..d5164717e6c 100644 --- a/contribs/gnopls/internal/cache/check.go +++ b/contribs/gnopls/internal/cache/check.go @@ -21,25 +21,25 @@ import ( "sync" "sync/atomic" - "golang.org/x/mod/module" - "golang.org/x/sync/errgroup" - "golang.org/x/tools/go/ast/astutil" + "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" "github.com/gnolang/gno/contribs/gnopls/internal/cache/parsego" "github.com/gnolang/gno/contribs/gnopls/internal/cache/typerefs" + "github.com/gnolang/gno/contribs/gnopls/internal/event" "github.com/gnolang/gno/contribs/gnopls/internal/file" "github.com/gnolang/gno/contribs/gnopls/internal/filecache" - "github.com/gnolang/gno/contribs/gnopls/internal/label" - "github.com/gnolang/gno/contribs/gnopls/internal/protocol" - "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" - "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" - "github.com/gnolang/gno/contribs/gnopls/internal/analysisinternal" - "github.com/gnolang/gno/contribs/gnopls/internal/event" "github.com/gnolang/gno/contribs/gnopls/internal/gcimporter" + "github.com/gnolang/gno/contribs/gnopls/internal/label" "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol" "github.com/gnolang/gno/contribs/gnopls/internal/tokeninternal" "github.com/gnolang/gno/contribs/gnopls/internal/typesinternal" + "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" + "github.com/gnolang/gno/contribs/gnopls/internal/util/safetoken" "github.com/gnolang/gno/contribs/gnopls/internal/versions" + "golang.org/x/mod/module" + "golang.org/x/sync/errgroup" + "golang.org/x/tools/go/ast/astutil" ) // Various optimizations that should not affect correctness. diff --git a/contribs/gnopls/internal/cache/load.go b/contribs/gnopls/internal/cache/load.go index 7952ad4bbab..0af993b64af 100644 --- a/contribs/gnopls/internal/cache/load.go +++ b/contribs/gnopls/internal/cache/load.go @@ -16,18 +16,18 @@ import ( "sync/atomic" "time" - "golang.org/x/tools/go/packages" "github.com/gnolang/gno/contribs/gnopls/internal/cache/metadata" + "github.com/gnolang/gno/contribs/gnopls/internal/event" "github.com/gnolang/gno/contribs/gnopls/internal/file" + "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" "github.com/gnolang/gno/contribs/gnopls/internal/label" + "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" "github.com/gnolang/gno/contribs/gnopls/internal/protocol" "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" "github.com/gnolang/gno/contribs/gnopls/internal/util/immutable" "github.com/gnolang/gno/contribs/gnopls/internal/util/pathutil" - "github.com/gnolang/gno/contribs/gnopls/internal/event" - "github.com/gnolang/gno/contribs/gnopls/internal/gocommand" - "github.com/gnolang/gno/contribs/gnopls/internal/packagesinternal" "github.com/gnolang/gno/contribs/gnopls/internal/xcontext" + "golang.org/x/tools/go/packages" ) var loadID uint64 // atomic identifier for loads @@ -119,6 +119,9 @@ func (s *Snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc startTime := time.Now() inv, cleanupInvocation, err := s.GoCommandInvocation(allowNetwork, &gocommand.Invocation{ + Env: []string{ + "GOPACKAGESDRIVER=hello", + }, WorkingDir: s.view.root.Path(), }) if err != nil { @@ -132,8 +135,13 @@ func (s *Snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) defer cancel() + event.Error(ctx, "DEBUG:inv", fmt.Errorf("debug query: %+v", query)) + cfg := s.config(ctx, inv) pkgs, err := packages.Load(cfg, query...) + if len(pkgs) == 0 { + return fmt.Errorf("unable to load packages [%v]: %w", query, err) + } // If the context was canceled, return early. Otherwise, we might be // type-checking an incomplete result. Check the context directly, diff --git a/contribs/gnopls/internal/cache/snapshot.go b/contribs/gnopls/internal/cache/snapshot.go index 4a5020dafdb..e12e26bab39 100644 --- a/contribs/gnopls/internal/cache/snapshot.go +++ b/contribs/gnopls/internal/cache/snapshot.go @@ -899,7 +899,7 @@ func (s *Snapshot) fileWatchingGlobPatterns() map[protocol.RelativePattern]unit patterns[workPattern] = unit{} } - extensions := "go,mod,sum,work" + extensions := "gno,mod,sum,work" for _, ext := range s.Options().TemplateExtensions { extensions += "," + ext } diff --git a/contribs/gnopls/internal/cmd/cmd.go b/contribs/gnopls/internal/cmd/cmd.go index 3ba50681ef1..9c8772a5c6f 100644 --- a/contribs/gnopls/internal/cmd/cmd.go +++ b/contribs/gnopls/internal/cmd/cmd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package cmd handles the gopls command line. +// Package cmd handles the gnopls command line. // It contains a handler for each of the modes, along with all the flag handling // and the command line output format. package cmd @@ -24,17 +24,17 @@ import ( "github.com/gnolang/gno/contribs/gnopls/internal/cache" "github.com/gnolang/gno/contribs/gnopls/internal/debug" + "github.com/gnolang/gno/contribs/gnopls/internal/diff" "github.com/gnolang/gno/contribs/gnopls/internal/filecache" + "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" "github.com/gnolang/gno/contribs/gnopls/internal/lsprpc" "github.com/gnolang/gno/contribs/gnopls/internal/protocol" "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command" "github.com/gnolang/gno/contribs/gnopls/internal/server" "github.com/gnolang/gno/contribs/gnopls/internal/settings" + "github.com/gnolang/gno/contribs/gnopls/internal/tool" "github.com/gnolang/gno/contribs/gnopls/internal/util/browser" bugpkg "github.com/gnolang/gno/contribs/gnopls/internal/util/bug" - "github.com/gnolang/gno/contribs/gnopls/internal/diff" - "github.com/gnolang/gno/contribs/gnopls/internal/jsonrpc2" - "github.com/gnolang/gno/contribs/gnopls/internal/tool" ) // Application is the main application as passed to tool.Main @@ -66,7 +66,7 @@ type Application struct { OCAgent string `flag:"ocagent" help:"the address of the ocagent (e.g. http://localhost:55678), or off"` // PrepareOptions is called to update the options when a new view is built. - // It is primarily to allow the behavior of gopls to be modified by hooks. + // It is primarily to allow the behavior of gnopls to be modified by hooks. PrepareOptions func(*settings.Options) // editFlags holds flags that control how file edit operations @@ -108,7 +108,7 @@ func New() *Application { } // Name implements tool.Application returning the binary name. -func (app *Application) Name() string { return "gopls" } +func (app *Application) Name() string { return "gnopls" } // Usage implements tool.Application returning empty extra argument usage. func (app *Application) Usage() string { return "" } @@ -125,18 +125,18 @@ func (app *Application) DetailedHelp(f *flag.FlagSet) { defer w.Flush() fmt.Fprint(w, ` -gopls is a Go language server. +gnopls is a Go language server. It is typically used with an editor to provide language features. When no -command is specified, gopls will default to the 'serve' command. The language -features can also be accessed via the gopls command-line interface. +command is specified, gnopls will default to the 'serve' command. The language +features can also be accessed via the gnopls command-line interface. For documentation of all its features, see: - https://github.com/golang/tools/blob/master/gopls/doc/features + https://github.com/golang/tools/blob/master/gnopls/doc/features Usage: - gopls help [<subject>] + gnopls help [<subject>] Command: `) @@ -233,7 +233,7 @@ func isZeroValue(f *flag.Flag, value string) bool { // temporary measure for compatibility. func (app *Application) Run(ctx context.Context, args ...string) error { // In the category of "things we can do while waiting for the Go command": - // Pre-initialize the filecache, which takes ~50ms to hash the gopls + // Pre-initialize the filecache, which takes ~50ms to hash the gnopls // executable, and immediately runs a gc. filecache.Start() @@ -252,7 +252,7 @@ func (app *Application) Run(ctx context.Context, args ...string) error { return tool.CommandLineErrorf("Unknown command %v", command) } -// Commands returns the set of commands supported by the gopls tool on the +// Commands returns the set of commands supported by the gnopls tool on the // command line. // The command is specified by the first non flag argument. func (app *Application) Commands() []tool.Application { @@ -314,7 +314,7 @@ var ( internalConnections = make(map[string]*connection) ) -// connect creates and initializes a new in-process gopls session. +// connect creates and initializes a new in-process gnopls session. func (app *Application) connect(ctx context.Context) (*connection, error) { client := newClient(app) var svr protocol.Server @@ -405,7 +405,7 @@ func (cli *cmdClient) registerProgressHandler(handler func(*protocol.ProgressPar return token, unregister } -// cmdClient defines the protocol.Client interface behavior of the gopls CLI tool. +// cmdClient defines the protocol.Client interface behavior of the gnopls CLI tool. type cmdClient struct { app *Application @@ -498,7 +498,7 @@ func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceF func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) { results := make([]interface{}, len(p.Items)) for i, item := range p.Items { - if item.Section != "gopls" { + if item.Section != "gnopls" { continue } m := map[string]interface{}{ @@ -668,7 +668,7 @@ func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishD file.diagnostics = append(file.diagnostics, p.Diagnostics...) // Perform a crude in-place deduplication. - // TODO(golang/go#60122): replace the gopls.diagnose_files + // TODO(golang/go#60122): replace the gnopls.diagnose_files // command with support for textDocument/diagnostic, // so that we don't need to do this de-duplication. type key [6]interface{} @@ -942,8 +942,8 @@ func (cmd *fix) Parent() string { return cmd.app.Name() } func (*fix) Usage() string { return "" } func (*fix) ShortHelp() string { return "apply suggested fixes (obsolete)" } func (*fix) DetailedHelp(flags *flag.FlagSet) { - fmt.Fprintf(flags.Output(), `No longer supported; use "gopls codeaction" instead.`) + fmt.Fprintf(flags.Output(), `No longer supported; use "gnopls codeaction" instead.`) } func (*fix) Run(ctx context.Context, args ...string) error { - return tool.CommandLineErrorf(`no longer supported; use "gopls codeaction" instead`) + return tool.CommandLineErrorf(`no longer supported; use "gnopls codeaction" instead`) } diff --git a/contribs/gnopls/internal/settings/default.go b/contribs/gnopls/internal/settings/default.go index b773e0d67c4..dea2e2c2820 100644 --- a/contribs/gnopls/internal/settings/default.go +++ b/contribs/gnopls/internal/settings/default.go @@ -48,10 +48,10 @@ func DefaultOptions(overrides ...func(*Options)) *Options { // (e.g. refactor.inline or refactor). protocol.SourceFixAll: true, protocol.SourceOrganizeImports: true, - protocol.QuickFix: true, - GoAssembly: true, + protocol.QuickFix: false, + GoAssembly: false, GoDoc: true, - GoFreeSymbols: true, + GoFreeSymbols: false, GoplsDocFeatures: true, RefactorRewriteChangeQuote: true, RefactorRewriteFillStruct: true, From 1e4628fb5017dd932ca5b558d367766f9cec0912 Mon Sep 17 00:00:00 2001 From: Norman Meier <norman@samourai.coop> Date: Wed, 25 Sep 2024 15:46:09 +0200 Subject: [PATCH 4/5] fix: rename conflicting commands Signed-off-by: Norman Meier <norman@samourai.coop> --- .../internal/protocol/command/command_gen.go | 82 +++++++++---------- .../internal/protocol/command/gen/gen.go | 4 +- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/contribs/gnopls/internal/protocol/command/command_gen.go b/contribs/gnopls/internal/protocol/command/command_gen.go index df4a8a1c562..72017722a0a 100644 --- a/contribs/gnopls/internal/protocol/command/command_gen.go +++ b/contribs/gnopls/internal/protocol/command/command_gen.go @@ -24,47 +24,47 @@ import ( // These commands may be obtained from a CodeLens or CodeAction request // and executed by an ExecuteCommand request. const ( - AddDependency Command = "gopls.add_dependency" - AddImport Command = "gopls.add_import" - AddTelemetryCounters Command = "gopls.add_telemetry_counters" - ApplyFix Command = "gopls.apply_fix" - Assembly Command = "gopls.assembly" - ChangeSignature Command = "gopls.change_signature" - CheckUpgrades Command = "gopls.check_upgrades" - ClientOpenURL Command = "gopls.client_open_url" - DiagnoseFiles Command = "gopls.diagnose_files" - Doc Command = "gopls.doc" - EditGoDirective Command = "gopls.edit_go_directive" - ExtractToNewFile Command = "gopls.extract_to_new_file" - FetchVulncheckResult Command = "gopls.fetch_vulncheck_result" - FreeSymbols Command = "gopls.free_symbols" - GCDetails Command = "gopls.gc_details" - Generate Command = "gopls.generate" - GoGetPackage Command = "gopls.go_get_package" - ListImports Command = "gopls.list_imports" - ListKnownPackages Command = "gopls.list_known_packages" - MaybePromptForTelemetry Command = "gopls.maybe_prompt_for_telemetry" - MemStats Command = "gopls.mem_stats" - Modules Command = "gopls.modules" - Packages Command = "gopls.packages" - RegenerateCgo Command = "gopls.regenerate_cgo" - RemoveDependency Command = "gopls.remove_dependency" - ResetGoModDiagnostics Command = "gopls.reset_go_mod_diagnostics" - RunGoWorkCommand Command = "gopls.run_go_work_command" - RunGovulncheck Command = "gopls.run_govulncheck" - RunTests Command = "gopls.run_tests" - ScanImports Command = "gopls.scan_imports" - StartDebugging Command = "gopls.start_debugging" - StartProfile Command = "gopls.start_profile" - StopProfile Command = "gopls.stop_profile" - Test Command = "gopls.test" - Tidy Command = "gopls.tidy" - ToggleGCDetails Command = "gopls.toggle_gc_details" - UpdateGoSum Command = "gopls.update_go_sum" - UpgradeDependency Command = "gopls.upgrade_dependency" - Vendor Command = "gopls.vendor" - Views Command = "gopls.views" - WorkspaceStats Command = "gopls.workspace_stats" + AddDependency Command = "gnopls.add_dependency" + AddImport Command = "gnopls.add_import" + AddTelemetryCounters Command = "gnopls.add_telemetry_counters" + ApplyFix Command = "gnopls.apply_fix" + Assembly Command = "gnopls.assembly" + ChangeSignature Command = "gnopls.change_signature" + CheckUpgrades Command = "gnopls.check_upgrades" + ClientOpenURL Command = "gnopls.client_open_url" + DiagnoseFiles Command = "gnopls.diagnose_files" + Doc Command = "gnopls.doc" + EditGoDirective Command = "gnopls.edit_go_directive" + ExtractToNewFile Command = "gnopls.extract_to_new_file" + FetchVulncheckResult Command = "gnopls.fetch_vulncheck_result" + FreeSymbols Command = "gnopls.free_symbols" + GCDetails Command = "gnopls.gc_details" + Generate Command = "gnopls.generate" + GoGetPackage Command = "gnopls.go_get_package" + ListImports Command = "gnopls.list_imports" + ListKnownPackages Command = "gnopls.list_known_packages" + MaybePromptForTelemetry Command = "gnopls.maybe_prompt_for_telemetry" + MemStats Command = "gnopls.mem_stats" + Modules Command = "gnopls.modules" + Packages Command = "gnopls.packages" + RegenerateCgo Command = "gnopls.regenerate_cgo" + RemoveDependency Command = "gnopls.remove_dependency" + ResetGoModDiagnostics Command = "gnopls.reset_go_mod_diagnostics" + RunGoWorkCommand Command = "gnopls.run_go_work_command" + RunGovulncheck Command = "gnopls.run_govulncheck" + RunTests Command = "gnopls.run_tests" + ScanImports Command = "gnopls.scan_imports" + StartDebugging Command = "gnopls.start_debugging" + StartProfile Command = "gnopls.start_profile" + StopProfile Command = "gnopls.stop_profile" + Test Command = "gnopls.test" + Tidy Command = "gnopls.tidy" + ToggleGCDetails Command = "gnopls.toggle_gc_details" + UpdateGoSum Command = "gnopls.update_go_sum" + UpgradeDependency Command = "gnopls.upgrade_dependency" + Vendor Command = "gnopls.vendor" + Views Command = "gnopls.views" + WorkspaceStats Command = "gnopls.workspace_stats" ) var Commands = []Command{ diff --git a/contribs/gnopls/internal/protocol/command/gen/gen.go b/contribs/gnopls/internal/protocol/command/gen/gen.go index 29b18892897..7e7d94f86a2 100644 --- a/contribs/gnopls/internal/protocol/command/gen/gen.go +++ b/contribs/gnopls/internal/protocol/command/gen/gen.go @@ -13,8 +13,8 @@ import ( "log" "text/template" - "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command/commandmeta" "github.com/gnolang/gno/contribs/gnopls/internal/imports" + "github.com/gnolang/gno/contribs/gnopls/internal/protocol/command/commandmeta" ) const src = `// Copyright 2024 The Go Authors. All rights reserved. @@ -102,6 +102,8 @@ type data struct { Commands []*commandmeta.Command } +// TOOD: modify codegen to generate gnopls.command instead of gopls.command + // Generate computes the new contents of ../command_gen.go from a // static analysis of the command.Interface type. func Generate() ([]byte, error) { From 47b504bce37fe85c1fd64874ce22d05337b50185 Mon Sep 17 00:00:00 2001 From: Norman Meier <norman@samourai.coop> Date: Wed, 25 Sep 2024 15:46:21 +0200 Subject: [PATCH 5/5] feat: add gnopackagesdriver Signed-off-by: Norman Meier <norman@samourai.coop> --- contribs/gnopackagesdriver/main.go | 354 +++++++++++++++++++++++++ contribs/gnopls/internal/cache/load.go | 2 +- 2 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 contribs/gnopackagesdriver/main.go diff --git a/contribs/gnopackagesdriver/main.go b/contribs/gnopackagesdriver/main.go new file mode 100644 index 00000000000..bcbd5830471 --- /dev/null +++ b/contribs/gnopackagesdriver/main.go @@ -0,0 +1,354 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "go/parser" + "go/token" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnomod" + "go.uber.org/zap" + "golang.org/x/tools/go/packages" +) + +func main() { + // init logger + + conf := zap.NewDevelopmentConfig() + conf.OutputPaths = []string{"/tmp/gnopackagesdriver.log"} + logger, err := conf.Build() + if err != nil { + panic(err) + } + + // read request + + flag.Parse() + args := flag.Args() + + logger.Info("started gnopackagesdriver", zap.Strings("args", args)) + + reqBytes, err := io.ReadAll(os.Stdin) + if err != nil { + logger.Error("failed to read request", zap.Error(err)) + panic(err) + } + + req := packages.DriverRequest{} + if err := json.Unmarshal(reqBytes, &req); err != nil { + logger.Error("failed to unmarshal request", zap.Error(err)) + panic(err) + } + + logger.Info("unmarshalled request", zap.String("mode", req.Mode.String()), zap.Bool("tests", req.Tests), zap.Strings("build-flags", req.BuildFlags), zap.Reflect("overlay", req.Overlay)) + + // inject examples + + gnoRoot, err := gnoenv.GuessRootDir() + if err != nil { + logger.Warn("can't find gno root, examples and std packages are ignored", zap.Error(err)) + } + + targets := args + + if gnoRoot != "" { + targets = append(args, filepath.Join(gnoRoot, "examples", "...")) + } + + // inject stdlibs + + pkgsCache := map[string]*packages.Package{} + res := packages.DriverResponse{} + + if gnoRoot != "" { + libsRoot := filepath.Join(gnoRoot, "gnovm", "stdlibs") + if err := fs.WalkDir(os.DirFS(libsRoot), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return nil + } + + if !d.IsDir() { + return nil + } + + pkgDir := filepath.Join(libsRoot, path) + entries, err := os.ReadDir(pkgDir) + if err != nil { + return fmt.Errorf("failed to read dir %q: %w", path, err) + } + + gnoFiles := []string{} + for _, e := range entries { + if e.IsDir() || !strings.HasSuffix(e.Name(), ".gno") { + continue + } + if strings.HasSuffix(e.Name(), "_test.gno") || strings.HasSuffix(e.Name(), "_filetest.gno") { + continue + } + gnoFiles = append(gnoFiles, filepath.Join(pkgDir, e.Name())) + } + + if len(gnoFiles) == 0 { + return nil + } + + name, imports, err := resolveNameAndImports(gnoFiles, logger) + if err != nil { + return fmt.Errorf("failed to resolve name and imports for %q: %w", path, err) + } + + logger.Info("injecting stdlib", zap.String("path", path), zap.String("name", name)) + + pkg := &packages.Package{ + ID: path, + Name: name, + PkgPath: path, + Imports: imports, + GoFiles: gnoFiles, + CompiledGoFiles: gnoFiles, + } + pkgsCache[path] = pkg + res.Packages = append(res.Packages, pkg) + + return nil + }); err != nil { + logger.Warn("failed to inject all stdlibs", zap.Error(err)) + } + } + + // discover packages + + pkgs := gnomod.PkgList{} + for _, target := range targets { + // TODO: handle std libs and queries + dir, file := filepath.Split(target) + if file == "..." { + pkgQueryRes, err := gnomod.ListPkgs(dir) + if err != nil { + logger.Error("failed to get pkg list", zap.Error(err)) + panic(err) + } + pkgs = append(pkgs, pkgQueryRes...) + } else if strings.HasPrefix(target, "file=") { + dir = strings.TrimPrefix(dir, "file=") + pkgQueryRes, err := gnomod.ListPkgs(dir) + if err != nil { + logger.Error("failed to get pkg", zap.Error(err)) + panic(err) + } + if len(pkgQueryRes) != 1 { + logger.Warn("unexpected number of packages", zap.String("arg", target), zap.Int("count", len(pkgQueryRes))) + } + pkgs = append(pkgs, pkgQueryRes...) + } else { + logger.Warn("unknown arg shape", zap.String("value", target)) + } + } + logger.Info("discovered packages", zap.Int("count", len(pkgs))) + + // convert packages + + for _, pkg := range pkgs { + pkg, err := gnoPkgToGo(&pkg, logger) + if err != nil { + logger.Error("failed to convert gno pkg to go pkg", zap.Error(err)) + panic(err) + } + pkgsCache[pkg.PkgPath] = pkg + res.Packages = append(res.Packages, pkg) + res.Roots = append(res.Roots, pkg.ID) + } + + // resolve imports + + for _, pkg := range res.Packages { + toDelete := []string{} + for importPath := range pkg.Imports { + imp, ok := pkgsCache[importPath] + if ok { + pkg.Imports[importPath] = imp + logger.Info("found import", zap.String("path", importPath)) + } else { + // TODO: load additional package + logger.Info("missed import", zap.String("path", importPath)) + toDelete = append(toDelete, importPath) + } + } + for _, toDel := range toDelete { + delete(pkg.Imports, toDel) + } + logger.Info("converted package", zap.Reflect("pkg", pkg)) + } + + // respond + + out, err := json.Marshal(res) + if err != nil { + logger.Error("failed to marshall response", zap.Error(err)) + panic(err) + } + if _, err := os.Stdout.Write(out); err != nil { + logger.Error("failed to write response", zap.Error(err)) + panic(err) + } + logger.Info("success") +} + +func gnoPkgToGo(gnoPkg *gnomod.Pkg, logger *zap.Logger) (*packages.Package, error) { + // TODO: support subpkgs + + gnomodFile, err := gnomod.ParseAt(gnoPkg.Dir) + if err != nil { + return nil, fmt.Errorf("failed to parse gno module at %q: %w", gnoPkg.Dir, err) + } + + pkgDir := filepath.Clean(gnoPkg.Dir) + + gnoFiles := []string{} + otherFiles := []string{} + dirEntries, err := os.ReadDir(pkgDir) + if err != nil { + return nil, fmt.Errorf("failed to read pkg dir %q: %w", pkgDir, err) + } + for _, entry := range dirEntries { + if entry.IsDir() { + continue + } + fpath := filepath.Join(pkgDir, entry.Name()) + if strings.HasSuffix(fpath, ".gno") { + if !strings.HasSuffix(fpath, "_test.gno") && !strings.HasSuffix(fpath, "_filetest.gno") { + gnoFiles = append(gnoFiles, fpath) + } + } else { + // TODO: should we really include all other files? + otherFiles = append(otherFiles, fpath) + } + } + + // find package name and imports + bestName, imports, err := resolveNameAndImports(gnoFiles, logger) + if err != nil { + return nil, fmt.Errorf("failed to resove name and imports: %w", err) + } + + return &packages.Package{ + // Always required + ID: pkgDir, + Errors: nil, // TODO + + // NeedName + Name: bestName, + PkgPath: gnomodFile.Module.Mod.Path, + + // NeedFiles + GoFiles: gnoFiles, + OtherFiles: otherFiles, + + // NeedCompiledGoFiles + CompiledGoFiles: gnoFiles, // TODO: check if enough + + // NeedImports + // if not NeedDeps, only ID filled + Imports: imports, + }, nil +} + +func resolveNameAndImports(gnoFiles []string, logger *zap.Logger) (string, map[string]*packages.Package, error) { + names := map[string]int{} + imports := map[string]*packages.Package{} + bestName := "" + bestNameCount := 0 + for _, srcPath := range gnoFiles { + src, err := os.ReadFile(srcPath) + if err != nil { + return "", nil, fmt.Errorf("failed to read file %q: %w", srcPath, err) + } + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, srcPath, src, + // SkipObjectResolution -- unused here. + // ParseComments -- so that they show up when re-building the AST. + parser.SkipObjectResolution|parser.ParseComments) + if err != nil { + return "", nil, fmt.Errorf("parse: %w", err) + } + + name := f.Name.String() + names[name] += 1 + count := names[name] + if count > bestNameCount { + bestName = name + bestNameCount = count + } + + for _, imp := range f.Imports { + importPath := imp.Path.Value + // trim quotes + if len(importPath) >= 2 { + importPath = importPath[1 : len(importPath)-1] + } + imports[importPath] = nil + } + } + logger.Info("analyzed sources", zap.String("name", bestName), zap.Reflect("imports", imports)) + + return bestName, imports, nil +} + +// fork of gno/gnovm/pkg/gnomod/pkg.go that ignores malformed gnomods instead of error out +// ListPkgs lists all gno packages in the given root directory. +func ListPkgs(root string) (gnomod.PkgList, error) { + var pkgs []gnomod.Pkg + + err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() { + return nil + } + gnoModPath := filepath.Join(path, "gno.mod") + data, err := os.ReadFile(gnoModPath) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return err + } + + gnoMod, err := gnomod.Parse(gnoModPath, data) + if err != nil { + return nil + } + gnoMod.Sanitize() + if err := gnoMod.Validate(); err != nil { + return nil + } + + pkgs = append(pkgs, gnomod.Pkg{ + Dir: path, + Name: gnoMod.Module.Mod.Path, + Draft: gnoMod.Draft, + Requires: func() []string { + var reqs []string + for _, req := range gnoMod.Require { + reqs = append(reqs, req.Mod.Path) + } + return reqs + }(), + }) + return nil + }) + if err != nil { + return nil, err + } + + return pkgs, nil +} diff --git a/contribs/gnopls/internal/cache/load.go b/contribs/gnopls/internal/cache/load.go index 0af993b64af..f2107e7a7c4 100644 --- a/contribs/gnopls/internal/cache/load.go +++ b/contribs/gnopls/internal/cache/load.go @@ -120,7 +120,7 @@ func (s *Snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc inv, cleanupInvocation, err := s.GoCommandInvocation(allowNetwork, &gocommand.Invocation{ Env: []string{ - "GOPACKAGESDRIVER=hello", + "GOPACKAGESDRIVER=gnopackagesdriver", }, WorkingDir: s.view.root.Path(), })
  • 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/extract-function-after.png b/contribs/gnopls/doc/assets/extract-function-after.png new file mode 100644 index 0000000000000000000000000000000000000000..4599a827a0e86f726478f0222eda5758bc0d284f GIT binary patch literal 37975 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 literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/extract-to-new-file-after.png b/contribs/gnopls/doc/assets/extract-to-new-file-after.png new file mode 100644 index 0000000000000000000000000000000000000000..3f0aa8560918b32affb03c0f1b05f7448f1aeaa3 GIT binary patch literal 28575 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 Nd1)1?DhcD@{|62L7a0Hm literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/extract-to-new-file-before.png b/contribs/gnopls/doc/assets/extract-to-new-file-before.png new file mode 100644 index 0000000000000000000000000000000000000000..9c05ceb9db142f3acf4671f1092a01aacf2f20fb GIT binary patch literal 34603 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 R_X+fomiQ)KDQXb#e*kOdPu~Cl literal 0 HcmV?d00001 diff --git a/contribs/gnopls/doc/assets/extract-var-after.png b/contribs/gnopls/doc/assets/extract-var-after.png new file mode 100644 index 0000000000000000000000000000000000000000..db558d6736a56c3f03e72b2cfb13b3a3934de658 GIT binary patch literal 12035 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 z zP4Fs0Yzu?a0z(qJo3;g_l>Np9f_L9qf5Y-S`q1dJBOgEkdM= z674hkJO5nEJVrO78{-8Jr<+UD!e(eSn9vvV-uN7{mcmi^ov~XrpTtqiJX+E%Xgt|< zVS|}K9loAm_i?b6kocYu1+iHE$PNpPOuiaJ#>#_nDuN{R_9{7u%y(jncbIZ()ij{D6H_D=DVcQI)VZRp!OZKFDqvbFyidIc8Vmz5|?>w|kX!H4}=7n=1fy-I{$ zt&p>*NcGBKvl?ARqzGKVTFC!drzb{Orj-sJ1rVJrI-BU!Uf{co7;)iq#_=Mc*nCsv z>!YY`=cnL8)PAz0vNN!K5D2E@JAIpiMw%~sg`1u`4!{QUpNUX>hVK>%pUOZs#$kK8 zY(Vc}rb!v?8FF%LXYC~WldAkKQg8kWY4V+kK6S`s&mw%>%@cN1grAanymS4_LBWwa zMZw(M0Zw$r_1u@m`v<5k3u!UZ1XAF^j<}3LvE*;DlRZ1qYuygl|m+-d-X30yvF$Q9Xz?a0Sy~E zzEX6YL8z1OMmpUW9lAnL4}#B}T$jf5g;W)^6yt4jAA(Tu*t2sS#SloK;3f3yZCDA|=^pjLzMZ{m;yFS#>s@ z(7*Vy*de*G(jRj|bVX}B8$Z4KI;VUsY*vfFgB+jrQ89TnBPV#Nhc{vg3q-4~yQP$F zn*o%>Iy~qN^3gcCu^@hWqaUIh3Hx}$!XbvUX#r72G?x>*y8}5eh2iKosn5eOml3c$ zQ27c(Z%|n=u|~z{&Xwq^j_jOr>`a_${MCjULe{vmsmEv3+#;!>8)P z0H5fxT8CNd-?y#YQIyi8C-G=-)4V)$<0 zKNhNroN+WsmXE#7VS=|%3n7sB=ryeb4fn)C^*fdbfw~rX_V}@uJsJ$Z>6t;QEnN|^ z47iCqMoC!kT9fW2t@3;$Q1icuwqphm^SIxi4h>0aMut zgbwdWZH*iqTeq@NpsT*k5Ug>zdqRt~I#>|tCg)mTQ5KbJ^geo~2!5jyIr7v~rK?Yl zi`xDf27Nvg=VmY8dpSb*PB$_ngiN8+R2q%iB>&q92eie}-)F@Hk~I9GIM>xQKCMSy z{4CGuWIhtz!i;qKr)EQXis<+%X!)H?hN$AQg>Z3JI3BkmmDk&}IpvUI^v>gBuqTKi z2el(c(Mq>HB+08?4eoLtH)~!nl2xIE3=*nCN}5=f`X39ZkvsN;(R$0Jk+j}x!$x(J zO!Z@fvt{t)vZA3Q%LCFsw3_uZX=_1KQX11sG5e*`?U!M*T#ZPCSQr&Xyv8hKWDJ7l zgsdS*GsR!2oqr>2(KNtVjj}v2kn23H)S2ro*L%NN7rYHd<4O)(Hy1j&nv5o3F55#_ zWx4QvH~uNA1u!kMR~g3)e($$K=8Pg>RE@&m{p~5rceb2phAueMlukNjHV})RtkVjF4AwK87nMG8M+V6CeU&Bepwjf~%G!O75& zTO~!77?YW}M8fU`V;IYUA^;RX>%34X!O|t*ro{sFb<#9eNK7vh1nf$ckzNF^sN^6n zucB2EQ~)h&&43c>ki;a{p3S<@B_1`GlCFT2#G)=c?$~@aH7{XSM_CR%MS7A4<|0YK z9Kd{n9`qEnorrGui^~lmS;;=?Dyo<_VjVP~t;e!~|oK z(Nxr0`#Q+13O9p}Kc`O5>?|lvsrC>-k3As(oWZ5F zkPM?`6t5pf)7NxbguNKWzEV+W7`C|O-u|x814-E5#dBpOR}pgZvo1Ne*eqbfC^g?; z@#@bwvI6C}@Ir`ldDPF8|1aWDHOn_mum$xp|YWrfj$ z4)Z676y#ScpUNDGW~0l>og($Eqv|?cFJvEaH+?s51y2%*%r?f8dd@O&Z8xKwB}HAN z$g!xs_WXEg!S8KQx_)#KhwrE}AHy_~F=gGjZjsoPL+1He*-`xb6Cx}#P22f`r)#x6 zqgevo-%*a?5g21>hh=FD1$M#+@omT8}#rc>hO>oP!m`!dDXs-jObw&ti_ zW&LRv@0m_n36J{J5-28l*GNv&vmIX~mo4?l37Oph?IT z3Qa-qD+hGdKS!Nz!bn1xeK8Zod{UzAPs->oj9QGp__$zvc`9%D zrG%w8%cLY970cqZoWW?9zThPeqkjwp_-Sam@sz8C1Akymc1NN{Ay;69DmqastUr+6 z#M?4Raz#f*vXmF4COfPTmDpTq*|eW{jlEZOpb;w16L~PGC+g>B9}tmis>RBoBAl&l z%~}^?&3BXfNq505sb@MKiK~BoE5*~ZW%vyP0Ec~ERA#4=2Z3H6TNv4nQ#SgjLinBN za&l@T<+mXSV&2g%F1IsHZPeIBl(+xbkmxvjzSZ(wSu5}~l7+4$&b02X;Cl+~qGB#A ztTGI=ED#P7k?hpPE6=yTj_udNxj9f{xEGqor*2m!Pih&kqA19Zq?gu~cmg+N#b<#4 zQU3eUmAy)X|8m^SM-z6|2^Jc@@Xcbssa=YC9-^F=cLwfcgf*g);k!q+gqL|(f8r{@ z9uAjAou32K`1HwL3}orx54ocFQ{}Dug1`!ql%EA9A^+rWGL7+FJ%wm=q;jI%Hf26a z!ZOX82aQu1yGM`vQ^qcoy8@SbjAYP|V&6{wxPo-Y3Ks!5QHy95W0_gd?mTmIhgnou zapuYLocMUe`dm%kqw=%hqE32B!aVVn84F3jd5eVP%}VpVtSL}`cGlAicR7%YGs&ziw6#*d-5Pz0HA+qt!njyu`e^73_QtofJ((-o={?hPo_gZa5{4zS>?kjF9 zj8c9=@}E1ih@g$=HDT30377tJXO?X%kNJw66v!w2E$2%5B0%V|q{xrR=~XVtB%sc$ ztddxBa7+wALpa-4VzS~XO_lqWMO`gR?TdtDrX%FU%$F7XErvLw4rgNuGS*N%WrpuX zn~zctB+r{iY$c*V7=+Pg%o=?MgVL4R!Owr6#VletVC+@7Hqqu{y^TC)m@D0ZPt$3_ZkbJqo6x=Ci85{oT~qF_l58IFl_(N95XPs-NO&!WF;|5j2zSh1G`(_ zsZ)%JY($vNa^29W{<(De%>5s(lD07??E}2xs*UAim4!Qn3|!x(KR+=3#v+d`J{ti+ zh}$7Ib^mHhsmB`Flb1Z^p{2kNzX$^1MX&xFyt@7QTdx_}-AN`}72n;gQk1#qs4gL) z$ws4{;qAr7#v}YLdu*EcCJF{dh(G`&)|laFDkCHZIA)5ra~entea9=x)YR1HpQWfW zSW8PwYPBW^^)?&P$+Vj3OyDR1P+$5NYYd~3^IkfhkE!d-XCMp2V%VHugbfT0Wq^N1 z$ff+!KTSwbrqt*Lw*k$z&>opzu_IoZalP!*T{R)FKNEO=y_vu%dv89eVvaZpQ;%Q_ z2@1-~%>{r#A+q=~nU3Q|67a2yKXcKWv|BDQRqC{1v#;8MeLCqzm@U3y5LdX^%9204s#4)`VvpDt7rEH~K1w466rbnou1S--3(<}!Vn zr*d&9Z#`P+r@d)2ZabeXZyXj1k)H_^6PRPD>Uyzm@9e~-rkY7hep8AzOX9X!=aEij z=wfg;$GaBW-QUM9rjHUNB(xITBt%Uao*b?!@4;F8<}`t?0jvCXoFJ%9zexF&QxWpV z!iYv2GHQl24YQCYj6ZDn*Z`TfZV#t=c9Yx=2MBz6xBDW-O#36Tf*I+xo6#=XU+BiU zPAbFg(P=dT7j!(v7_P!lDX?=oyMy4aZYKC}&Nio|=ve{NEiN`auPPV82soFWU|&)p zbv@4h6zim=&NL@k{u;L%lO0O$n=$q%BYIlF1G2V}4Z<=0(Zw)Id$t2)y;5n;kcFR`rlaEV z&j%AZHm?8-9ao~>!z5e;GA5>JGt_d;<-mBraX}2wA0YfxcMy=EtKlDi{Ch|-m;CQ8 zP}FZrJOiu{xcl_vuk)HVSQ5JFeu_8yV`I-|fPg`UsCzE*neKI0JqZH!_ww};~%a3GBd8D~FJik$)O1~rhUG0SXqBVf~ zyb;XdV4QU!N$#NU@=g<3OmdoTocBRXI)e=XL(3LqchNb%Q*fwkgwv*j76MTeq=E=H zlIgy+2tY+>QXW(2BXTwSE+%B43YMy9JG9r;S)Z{5=t-DUA?OdOmg&E-~v;4w=PMaZaP z$g_ti7##Mc3$h%~SC;^O%f!BMhMEfE!_*YuWx&CVceM>9Q;1?^_g28+=c-4mNblWsE zX;Q>g#88CWiP{19{~R6bJr*amGSLu2_#D+_0BGcf+LCoF!xonH#bziK==;GJ*?^Ip+#b(`=5i>8$|%r$oo`E5<`LV-Bk-t@f%F zBPv@4ShasRCT>!G_OPK{-&4w)&wT?PypnF*n6HknRodm;D!5d&vDO#o@Tn<-qE zUG_^i$)NwFyX?>TfBZ&Lk5~84{n=Q5AMKH{Z2#Pz&EHny3lZr45o!fR3qkWq7NVbz z!YZsR%c^HV5Y+WDBssZMQ?H}svydYGJG4z#cEgTmtxqSYB&JPcf^yTI*`TzNU&%?m4HteFAK zF+=|v=rxOmm{d3mr7%+p%TpM%n{r$&vyK)kvYHb318q~^*aME2d&KSF>YWz*q^VCn z18&Dba?62=P*2rZeyXY8&zq9`@Pbd4g%=Kby`b;?^T|jA(VLzD^+dFF^7c4zYxj8K zgc=u@7WAcEEzlZoWpg+0t%iaMOMG+ypX>Hju#gj%%lH7@o=PeCoHtCIjqOG7YNso~g5 zZQ>a50s#bRH>iSyPC|e7qVq9<<7DYv7oou5N0N;Q*=AKjk_6rE1&^k%dcJNCVH}O} zjyp~jvdGzk_vLnVSX^Y--q@pa*}-1Uh5Mc}cAa3(8}xjc2y(T{w{N#c>oC=$@9?Ub z5A=ki1Q_r~{(AQD$GaYk=?4D%)sMx83q@qj4L`#1&oc74`FOe6#7gF)%mTOH$LH#F z^pIpXM6lD=q9M3r6Ep*n@Ksf9So`J<JZR-yt3B^6ZwkI{*CHW zJ{zrwGX$-Gr7{TUiejyU_2zdg*1+a>aI2ROBv?!s&mSno>kN1m^VZ#csIq-`AFkjJ zYt9JhZ@+L;vR-WJN1$qR8X*IqZu>P0tcB4`d-iTJaVOTP(&1U%C)GYHbVB+jC3zba<^yL zF>)R1(e(A_LX>0j1}=;sG$F-b(j@)e=%TQN(Xj~JNU`M$p!GOGV=A|qjFhD8)Cx7I z_g7*cUYO)y(rn#p24;yNGm=$8xzDkq4hNxmZqJDyZP8s}t34=^=0f)|)>?8FJl!gr zaUJA9PNa$o>0wxttzh;c<}yYcj`j{9fOeJvxc6nJVsMi88A>;oCHS?y$Uvi_tiVIDHaq?5wc*Qg%ZdS z4gqR#7;3-4!a3=x_nIa&7P@6!AVV_FkavdmpqeSu%JyWq%G{9=NS-VoaOIKDAJicF z@+dVw;_iX5x?pvcVbm zHyMquYv!RgE07KAmqa6!{JXKDgG9B;OeT2m>$Epg3H-wSV#E=v+We&CHp5offrs_T z!G08$;{fPblWg%2ai$Y!QZzJ@^v*^blIk;R(b+J@MV=fla_aX)+}C=jY{N|66KWbk zJu||tfs{xdII3?_r_Cam8ogoyutCpiz1jynVHDyePP&uIVxjg3p+XMn4@_f_i?g+#|B&O4_ zL@<|H5MU{t&eyBkqQEW>9c&Dab*sBI30gsQnX37a;om(63>$Ag==e*%8_mNsL5Ie? ztBeeZ4HjS)YjpF*JvrR% zx(naVj!nHHyvq)KLyTZWKjR?9jmJlelNysLT)u~xlD`X)u`VJKW@y=$?6Ad9`9h=J z&33tDVsk@m>|5(d>Jn)o!iQ+|gsk^n^~DZ)teA1_Fhc(zH6xIhxo^anDH9xUL*{9& z`?X&~q2UNA6N%|22^PjMLwa}Nc|MjTMJ<5f9$H`Y0( zniDXNx11k&C&zJP6v^p^iT|=W!SVFE-uU)Gz8yIun*Sq&tr(wxsX4~9s(a!`4u1^Y znR+sXwucJ-``xbesO#Ae^oxKR3g=E#7u}HipBbF$3{Tf+S`G(>YHbtvp5^?!*7tJ0j~Q!5}`&->YB`qTj4~FFY`mG zwVT{LACpjz_EV^soS*Lyydo?gtz54{T5p{zox!|~`@dhN+O>Jc`CgQ757BJyP;MPV`-z|KQZoI#dAYGR zmD)$>a6>XKykKo6biy`43Y!wJUkqoKl~*};q^GY?xbg!ssW}Wskusf!3e0c`Oh$FS zHB^zqK|9?IEjbf7z=o<*gV%Ve5wcBd*>&9sO_dU{H$Tj@f@x^%`%G`_V(S_8Z59u!UI2!Jz6g(2L{K+yXH^_^gkzz>(1K5ug#Xd&S;vR zS7qap$M5^E#bYa1yCqVW(<|(l45=?;UmkaFg3Oj$8H=@;k(H`|Uzkli1>|A73S6X_ z^e(p!Ha2VNJA@{x&g{Go!SekbCL=C1{6 ziu@VdB~MKp1W2GeMPYs^4AK15c*&`6_Rn>YfJ@pmM^tlhnW` zpeIlG?tUgL&GXVgPOkW-x8I&$^Mwi?WHg{{J`jZAXjuk02 z!8upAla{hBfIsH3P|LRi6O0k_f?UnVHj=u1%L8?rZ+@}!^tiub%wc>R#<8=TmwMk& zadlstfM9kU<9AeO8JWHa=OWWI?RzFP(HdUpMb-G~&un`O|9d3OX6;xk()TGew6j)z+js*<$6`G zPuGA5#Be>o$aSae!8NJgzH)#vc+~l9+RsU3d!m3l-{Ix^qxTZF1-TDNK}mA#V_xOi z;oMKK;QEN3(~s2{j}QB@DWqcFM&Hupn9}o_uhB;|%L`NWL^>ajIAG8eS&J#-E|RZ8 z%Sc*Wsh6wl+3$!u)yEM(tUZuE(imgTa;f0BCj&MefUH@yP%b?rIUUeGT$xE#9S>Ne z?#J5wi}m2k1DM6M2toC_1ODgH((#wVu5Byu1f;UZt(67B6b%T+Jae*3Rf zeTlBbXWsYFxgTto?c$CX$RU~0Q;qNE>}I9(!Y>L@Zr+$O%q*+SthqDIl+4i_&rKfC4@5c=h)vvUaw9Qx-;i3aN6)hP&it?LPRBx9iNSS!Trjxmtpxad zBOLF{>=1JwnH0s%m#`V%)-e}bsJs``ApA-J`tlF;t{(G}h8j#*aenABJPx?lt=pEX zu1~HGGUNBLoBTQvLa6EB5Bpz<5_9*iAl=R@rF>`!XNyXVSKLRbHhl%S)5Fl_wtc%W zSf->o&aCSP2yW12g>vw-uDX9*CVV_p01&FBR#bqJN}OKpQ6t`z*heCv8rd*Lbsg75Jvx#~FQHb@am zekvho@1Cq*N1FkWMoA%829E7S7V90>YB1^01@>h$z1~`)ht(xBxCCa=V4mCgj8PAJ z)D#8pYnQXTq}_Y+5qOw_#dDt7xBu7ESw_XtL}?fX4S_&#f;$9vcXtaCG`Pz!xI=*8 z1b26L4^D7*m*DO$+0K4@_ROz3(^J#kQ+?}J-RFHr37+_4@kW6f=g&rb z{9C`;4%wDuMTG0}y)DN7U^s+TK{kN6wMt%xBAAHzb0QnXc2j5Xy!$uThi;5}g`AWF zzJP84n_yzW=Oe+JygtuMgSQ|aCZBPL%FAHp>5okj1+=k?$+h(MGPJ2)V+<3=R8p)S zrFTM)1%Lb(8=3H5Cl15#D?6b0hS>ASdcefwX>TKl8T=_`n4H?3X)Aeil_wG&~w;hqmrd1r$hLjDzznKPv2XyKy9?>m+2&_)VmrqGRQu# zm3YkPf2Dhc>kS=06TYY^f%Y0bKDU(R`{OYU10~Zk(;raVG}z;YWeMqy41GIh_K-G! zyw&{ekYVg=L>ocEz50x@H+8su**jc`%a+g8k4$V~ahU9uABtPDzN)uQH$4W?SE9!} zOs(o>Ka9X=F`Kf*T`hv4QsAFShjdL(dG$wq3f?yp47c~yj+}Cp7iSu0Rgzx)k-}c! z{Ap&?0-5OIASC6SsjDOp-gw!dN*sBg_e5U4|0hQ4)=_x>R-38yrA(*Jl!>@81^6u( z!G&LazMvC$T&8DgZ3PN3Lr7-icPR3q^H%;WAryYQRH8d%YmT!;%r_%pK8TV-gwN`z zCb-N?{ibL3Ry8Wh(FfWNLZ*mID#$fYayf86k2kncKR%Kd+V%MLGLMOYb@P^9b@94F zclnxonqJp+kjx@>IUz-DbTn3FbQxXd(4CUU;gf}@ktz#^UMf)xnPMWbRztbv&M#pdfV-N zBZFa#f2JpY$Juy_KedRYjbmbU9*n3<8^1|Z*p|;vP=t5r_x)zzTp;h$rcGV6AY47a zNPlVj!*M4Sd79bJ8C-qDh@KaTzeYF8IJ)9&Q|7bCu<$x9G;29tun5-RL|oH4W%`^H zqo+;-lI!bHWX|-62&KCd{;@>Ly5(9^1R`$_D&t2>SK`IM!hCf3)@vDE=2j*bhfug1 ztFVRr$~}q-1m;`=>S#-v!AK^YzY%Dink;|Y)P?W4Z6+9n;|#$ME4OvkcGrsie_Tzt zi-^}Vo?%jdPr9!a;w7FcO0G_F|9ZJYr`OH$8!AE-|AJ|b2SH!_@m_OlMX8CT(mvvn zeD%3XsRq1ahkUfuot!;Mg4n`75r1tvgQd6%<6f^^Jf+e>ax)tk4E((g*gIdv|WIi+D>)ceHH>^$Gp zZ0Tn1bi7K`(&C;h;QOvaubB|sZd4;cj)N-jMrIy5W4%U(I7SWw-d1P3WLV*-_tks~ zYYt;juyXCF`MLh2+t(1uKB7f4TwF<|S*B4j$`*&DKAI8?Sfb!^SYZNH)8Kz7n+!)( zZ*ir|bmQ|wp?nTy>t>q!?aW1DBfZ`ss&ILdO$J3!HrJwYnLRDSnDj3#5IWuxGd%k~Zi zS3!Iekxa{EXY#Hy_S+=ur>*#5jyPV9AbFeit0&T*J`p|qiLVgWq%sRXX;#kDDUumE zxwGABYy8u#b=@9)WrjSl>tlp~uA^AV zy9LDMvR$f1t9ci+0Hp{H2-+|AJP*<)Y5#~5A=YJDZp0Yxyb=kw<>`osm=$JMI)iyeMw z4|h@Ghtc^e`?%wT?|uu;%*(?f+XUv$o$z&caf>ciQB7r<^3{)79iq}APwZM1tqGjk z`};IgnskJ@ zOQXB7_3>#gbf85d8j>5&jhaxgg`D4K6kkfqDD)=$bpwS9u7xzdenLkMi$cU;j)=^j z{5iUi7Rq}Qr$iR>7RnvW4v&r)`;I6TuKd|#IgCndJRdfb)b_i~`@JAGGyUUpnkR{} z%>LM|!t184E{r7OMaoaLqaP=mdEYefQcjYv_eV_HX7EZ&;cn7j(b%oEC$#T*UReu^ zuN)=BSUhjlZD*GiwV9P4Z>KVmg387251Kg(Q$Yb7cys+l<||+5XtI-ZN1Kx6ogDu9 zX7@HXF5WnUej6IFAFpkO`1|wH!54>6{dlJ-0ktcWxFkVLMR-gunUc55kW3&+1z#K; zdu7q?YYtz*wLNXm(>a#vJbXeS(UwTPR^^R#H4NRubFLS4l^?!zm6yL=8aqBRMIT(b zvx7md?hax{n03;V`Cv&YVU?o(Wr=fNFk|7|+e>oXo)AAZe#4ycB09G{{!0{>ci%~S z-11g_{*_}!$I(h#)^JfHor7@rc|lBK=KPWDn%uf~m1jj0Ln9`47$%x=3?`B6VcpUN z{9?Ve(5G8ODSCwuMym~T{0^8hM&Xs>(EcLAUqjoN!Q`>n9XlTemHfl&o}D@v|LuNU zmMn1+vmqV8#T1gzvZh~uWVPu3=|nqVsONncjdvOKJ*@sxBdm0SkRw?R1wc_*rze{Yl+UkRQ%+HK5LPdF^&g0 zpbCP~KnBl~gj&gmVaQ0%)NQY5>SxtTbaYaPu?G(O6~uU@9#EyNwE!C-Md(?vuF`dC zRE{WAfXw$=Kv%QwJm+$NtDG%vC2Z2nN6Yg&AtmL|YU)=CA2a#KLww)Zoh>9Ap~Z|* zcOs`e^5O+NMAS%!q9kxTc~V5w0Jr75vBaXtoU@K_aVq1TjX<7X%pf9c_fp#=bJ_}h z;t!PhI=kgF0&*l8AA_yYt#0Tu-JRA(yK%tdsOf`Vn~O-^1<9)CbLUjOCB}Wk52F4^ zwsS5W@Vd{$_p_b$6|~h~e1CdG*K`Tall=oe4;#ZK8*HO7vJavJ)UR1!4S__l#A}L< z$l)To;5TwjU`-(zguWGVmV}_&OBv!@!6Ac_EAYNqk>dxw8n+5OY3uoNk^S+E8c!v% zr9J%l(~&i%lm)5r!uA+wgN$LoD^LBaLcA!m?xEx#}CQkEr z5KN@xWRVhaoz4Ae(?-ET?H1X(c6Ba3}KmNQupIWXP1X z;(Ffmm{mkdu?Ov7a0by@(Koq&hF!(xn3I_(05;uswL5Y-Ojp(1FnyGusUKR^`Syv0 zg+&*U1^+z`qqwYW7*CPj(YpISVSK!UDUIyW@@mL?Y>kPOza*(&g&)2n+Ltx_>n{!S zr&tQ|TETgVTG5~O`*jEg;%e^?RIYIUur1%)In8gg9|6e41j^|0&u_C!z_`V8p;FIG zPRU=)Rt_7@Y6YX_wB}zwx0qjStKNGV?IvK`9=GJX;02HA>J_vg?PIX2(%P`f4&iY zadDBSmcwuxo$Y71_esa-B#eYi825+ccX*UZKoQsX`?vq|Sn~e3`T5)CcT^L7RuWZ4 zW&$>0Wxm~BG-X;-hq%1bKumno8!2Q;9rKJ_zr3g8g6VKwJym`GNcne93j16{`ZXRgMY0JZ-lkCHBx%l^z`FU5RRBlCXEl~}vp!Z7m{-~N}Oiv~WMSp=9_ zuI%Teg5g%OfA`kUIZ)4*^2nY*B@Ga}4&PF(D_N#9bEVgP|9aP3FN{m;Ohd&>5%1qO z+O9DBytvgJe{*zXJ?nV3dpWiD-7cRiTrJ|ptTP)E1>pBFaZ2c5NT)I6j;b1}c(wl9 zu%=I%6%O1@#EmYEZCe2z|J|Z7V}QTT8(b6|POtKqSQ@(}x~iI*-ZXkjR$aU`CDEcg zVuQ8Y()=tH(m?^k=g+L;VhW7tVmRBH@-2;Nb6|b_OcsP8{_IfcRwBSBfgQA~HVnAH zeQq6A9Uj}|jFBHMlE(B-Ww$g+?!-tvI6t>PQk0ix-&+N?eDP03Z3ea3I0QlJrq=lcoz^zimluJ=?M0~Difw+Om0m7nkgn)jq3u+udwMZgGRST1U z?~SE#lwpxeMld6bFE}eMhaOc{ApdrHcz7lUZ;aLa$(7uIND+aim z0JT%DLZUIn1U#8NB1@3bDln{B<#m`kU8e=6(fZI}iJmM9%b+ChN1!XXZ)3=%{s6;t zpk+VP+9y0^(1e0HcE|#_kMOwchoOq{+XEMs18;A}w8|+{p3FTnROcJMHgsiG?N2{L zkIox38Q`M$kfg6Lpbp^vUJekmm=0rl0RrxRSc?90?ud3gzq89XL@mrGUe7z5!S$D3 zRH0;6xKFGMHz!Loe*LPI@g=w5o%p>zYK)9rXNZYM^^oeOg>sh_qSW`&#!(LiXoAgtk%6BO8l&}?fE%>Elw$3u_w8=uPwGXM=WSQ2;3-l?sp|QS1`7?zgF+59BWC{YKM9I~)ohXlOdwK?!-!>Xm{TrKKenguqI>Xv~~eSq%VwifIdM1m*l?F&&cIV9*m7HQf&~G&|tW zjT5M|0(#WoU>(UI913bt>t1( zxDd$vrLnAfNIc4eqIQfHvuF{o`+XMm59Yx=7yL0I(poFf6ATkI8`u;R{B%G z8P<#@(OiKAWktq$`S-j*GD?3$&7r<@9>{39`ECPnyQS3tA~~k8n4xN2S$I@?4^INx zQl*;E>5bQV%{sVtk0|7E1SdO$d^g64X|MfTH;8=68a)-_D=HcQ4XZcP6JzuQLS*nN zLQ(JA*(INRRpP%4e8=W&)+j3n*bgy{am*DpE&B^oo*n6~Pc zMIxVjl9X+wlB^`GkKe&jp7z|2FOis<<)qvfgmeiFSGB?tJT(G%Tp{M9TMSPvAbK)e zm1~~zN*+G2~?{n@#a%9Qui>;1 z3zhlA|Jup}{{Y1(0p=Cm|H$$@iGODtUfcM8ZC**B?R?_Dw5RgwtlxufcjD)s{J7;Oo+jG9g%lRe%6Z z#asKU`lnEKOhW6LU&|fxjZa5@B?sF!nE4w|-1Xd;V?M{ub49%3eBR-E964=Q()hEg z2imGn_f)pYPUTNt#f{p;JvlTcRXRunfgq0S~r1Av^T?bJ(O^DYIw=>>wQQl zj~NnS8U0$G-pZHz!=fy^FoEZjIu^@mioA+qZxC|A`RaDON(&0F%aKF=V(HZv!F})O zn$4 zrGe6-);qdrm(gRuSi)>H*?)k@bJW5$%li&b@a@5PG@S!0C=Utf_Vn>WE`>DC-?2FlW9YRU5G<@F|e1M_e6VJH6Hf(lT5q|P>& zF32H;lnmyRV#MUn_w|X-Ol0{y4HdsF82Glk`|nC6b66)D!U7lZ8;};;CFkpVobUYF zF%mQy{;)VW>(q~D{tZ~o6#inH;GbcR;DPMnQc+NS<*CrB0Ugg*uq}sEOap1)Crd37 z((A-zRWmcH4nS~`r+47PLeJ6)Xct7Biv@rkM^-?=8eS86$Sbq(o8R;~aY$@AwDLS# zqt4^j3dYLF!swULbZ*?$yL4=NEoEpoMfdfM^;@?r%?zueVf9Q5Mec5X7)u#m{Z1bN zMgB{1c8kd_>20IMqoPKtIX1^(XN%Kxofbz*yXLLnuu{9uR~KE!AC)i5VS<-Ocx}V%8MF(lKuO#FNDID z$_>tSx7+|0ll)hM)0KONY|PZ%kQZ1?u^*!+WvHATYd4KM(bj3yF5CdWZ5WtX->T%Dcn63mxLzJ7d^x)wPwFNW)tPUnm}dfZVV%eVN|t1R0H?p&uotK&ewpE#BPSJ;*<LSR7T5x`^h$BNQ=O&OH)>yCDlZlzxhHmx>rR%!PqR_8XNn(tQfK98#?C z6nxnH{?~qgUA(}>7W~S^=CPNdGVgxm|UY4Z*L?aUNRGMOv

    jL$ak{^MQ`e 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∨KAz6=Zy;J zG#5~ao~cGaxL{3Kyk%Y!u1(`4TkLQ&(?ol|?)RmHfU(sv=*l3NMNwQ|X)9WMgD+pt z@cd-Vr@PH?JEVA9^5$@PJV-zPGx0|L+D9Gc_lvi4U#3;C>NS|Tdo*8Go)z1}}?%|V1S@v{11MdHz|t;y0g{9`-s&FIEWM6ecHJ~3{hWKLA{ z@2j(`M3NTZZ}9RB=RfYjDQPd+Lf__DasF>d=t+Nvd;mcCdIVVI#s7*<`#&gI6S4O; z205!f3_bUyY!Lnm?=Oy4d7J`owZ@fpm;VBw8CiMO^!79dpex^H$q2Wr6HN0cq3B-) zV2Ti6F*EB-C4x-Jl20#iIkfA6w0Y(qu`P!$YOSjSV#nK@xn0DI9IMO%F&h>cFMB9n zgvI9LzIM#P;pZ^%jLzk{Xw5r}{MC}LpY!m@NIu|X;}NAg`1q~Sab>4V)JA48ErmC| zbdZvcq6U>%*{LC*dj_Lak`b&DX~?L2Q3(zeqqCJ zOkcb)VBFW9mGM`e?^dl2yYAM9Exd-@e0s0-!0F5!-`;C9pn0oxPlVr=+2?F^m6l3} zq(pK*S!!Dc-Pp5hx&u;ZeNxyDR3%Z6%Flz+4mJaJUM%!yNP5B=4>RjyRG3&u3Znt3 z?KvRo383XxIKI7reuP(44Y<*B=9Ww!nI+jQnmw-N0#oGzDr+iQoYzXWSj z!^Kj_BsH$VAc)gK^l%dCzQ2yy0m*-cfA>EelWUcz%TaVa?dL=P>i?H^H1ma|5_OLI z_xD57-qx1$BuIv1eT2I8APB{c6@Br4IA&FlxZ) z>=k>4IBd3ZtB*#r%&Nxx0_U}eNx^i!3@Ev~zD%k&Xt65Xtlf%}bNy5=chT(g(Vz{| zniZe0EmrohPMBaNfnB%8Q&jK1vH* zO0fLmy;w2XN~ycdZ8g{QT_cqKpH(i{K z>x^SYxP=gzf3;4e3D4fW04$gQs2zpjLCz5|nCwI2;bD(f-Ee9pB^C1|a9>0UrW*hAC#XO?reorqL z)sv)jL3?T>xEAwca5oO2x-j+SPkTAgz`d;c^@n(V?3}Q*|0KYnAfega$3ixEC`{y& zD7hfb1z1CLR{laP?;{oM7}R{@P%kGTIPE9CX?89})r)}O!QS^|UUs5^YOM}BUoOWT zb1xHpQyDA{KRFYlK~jK2TUCyIQTwN6s=TMnHrzpi6!B?D?gk*#?oRm;r(Z(%$&z8bgbZd`}7QKWU>)T%4Y0 z*~##u(NLgw|Ke7~hGbf>Au_xtBVVe}3)X-+SFUv_`5butTnC2aw04Ih9>JBkq==*P zlwP~QQw+kiA8NIf>AcGCuh-g8cAsSx2*@L|M=-Ec2>8o$w_WZ{xJT?cdn~@ZBOa9xvmjw4{IKv)pH{nx zVG7iv#XXC)Ur#^|C7DGIL?JEBaycRs>@_0HDGaizo{B~S@0GjB?pR0%FZ>lh`a**- z<%LTLEC$q(2AaZ1c1vVIYX5>J7%B{_M(UzI#Qa}+8{h38j$IoKY<6Y*Ao2TE_fb3r zv1Z<$Y3wPcp}3jiNOcZPgpz;6M-Eaf#Ym?JwhCt?t*m#deGILncZpti)$`i5Ak)9T zD_W$&zBV>=B2YO!hQ04O&-t36BJ*2ef8mPnWvRU4#XoGu}{Zc&a5j1Z{(^Qik_B}L52!hzRlMlLV57nZMJA8SMDVW}smfVZuU zyiZ9%o1$(=6#2&)%wwfD`g&+UjV6pW#WszWi%CcVLHkaLc~KtrQ|}Smm9n7N@GXj) zR8Lwx|5PSWOQ*Q-%t8Md8LC@%Yb#_3nw&(Xg$D3^)7ySc72TB^KqTX!;Af(Jc62VR zKh#~E=EZ5!>L^T~0s<%Nsfn0cCs~un;@=jx3XsvJCD7hycym^G{tp;MM&JI3aTq^7 z{}ZJs$5WCcN~<*1&mA4W#yJ=kBkQ2tUv79*)B8G0N#UVX`>(gdMOUk0~vPggw7ZAWQUh3V+m@E$U36N2ejVw%Qb@FDmOXk>(4RRq{xoM|l zx#9}=frkjG2Q+V-CM(sdJC>UekPQN7cWtegA?~Ko{%jTsanw`*TkPO)B%2Sw-A<8} z6J+n-m`49||3MHn*eUS+m-gS4XD@dQ1R=xJL0wvU!SD9vd>}mz^hAVY_&EK^ZM6<% zg(Y9zt3ChD5C;l2@p4^U(hUf|u=LL|u|7lh!!CI58bTym@-0i2Bk@MyY#ZH1Z!##- zXiZtkw&%{)7Wb*q)3z0f*zq1s?*qxAG;+kyIe`h9Ptzf^s%U-q}0*nhU-}nZWoZ zRRmes{e3&d2|O8H6K*+n{U7mhYWlID=qiVX64_V-87yCUp_OS|B;E^}g_dkA>j(ka zt3yTufPrC3oMQ{H{5uHRlQMQUsVT!(Vb{)sJZoKIvWd%}%QvhD`K@$l16m3VG;&Oh z@ao|u6#i{Rz^kSN)C|Kug&TBwE8n7jio=bp6 z&H9wGr*>aT_$v~Ksl=>4U#K^E>VboR2*m5!5y7eB6F9)ll(e8!9mlw`i2ihNA<35Z zVaCIWG=Q^lK0C|PYdJq%+%lW{mOjdI2Hh593HCqJ|2Rq&W@6ZJb%H}4TGbdV&VATL z&Uz`JAFlG0WS6$4^}*r$om-_PyVJq%qL63K(YS9agfQ;WA;LFiFi(f0=5Ox#NKpT??2>tBvJPnY8r96&De7IT2en-`msXr zF9+w%j=Wd;g1H|pH5oEW6bW12qk40f1gV?yv~2@z|MzI`RT||zyMHgucovuITlT-c z|9N`P<~3odjiE(v1yBAfy5~XGb?BoC5MoGVpcUebzrn20=tvj}N!I3Jgi}S!`bxxy zGc#wNB;Dh_CR+2X7CYSOri>W;uhTw!5R@Xq^OgA)wC~S=+D%iv2g3d$P2E}m;Zyrs z*{6(8;_1gif&UL%Zy6Ow)NPA44#9%Eg#aOFaF^f`Ah^4`YvY#S+PDP^!QI_y+}+(9 zZ}jnnZBQr<8TA`2;o?fyWzsb(a_{Srs=Xd^$~u!7@$*(qPbM$Wfl28 zGa!nVS`f_aY`kr`Q@V%OcLV2eshGt|MMI{qe;FE&OSHTgW|ScSam~vFkY^TjFNOvp zo8UbskPG?ti=ml(fE*&;hGOCIejA4WC6$xfr0hUA{@2Ad)YVhI8+ETxC=bq`8ON;K zGiK^TmV^eYBrAR4Goe~)R1azxlBwkDC9>dgpEE|7wJX5+XZ4K(c`_?JJ~GO-7QkKtH>-XpgIP`0f?!7i2?(rg0=+8MvuR#oo-)BXS(4Gz7`BisxYxMm(^>O-%loaOAgB zjlB%?+nS0tLe*-H%^#iouECm0>GDuF8VBbKw zEb>j--3RfM5_Zonk;t;{{Pn*&yT7CU61WC>=e`yA_W(j(17n3|vg0ssWes2{<)U0> z!=DUYbCEqsmu)C|zqBaoQDMTpptkIFgnEuhbP#@G9cd*$Oo55|&P$21gZh9e1G8)uRlQCN%1Zu?ct_^iTm!*bI|c$qceC6<9{`%V@?I7H)9Pm;;TAiA!| zUp>SG{uHhwI{C;JKw)awa&EF2>A6e~ohJWnLIbed@coMD`zYv2CPs8CwXu7pf_S~> z9mIXOM2KRrE7hKd+6H%olxLvxbi;6}c zb$j#Goxnvx8dmy)t7)tOPCPrdnUxK5O+c&Y zEspOqZ+~`J2h6{P(dG?Uvvc^_%n%;KlP|`9OC%SAENyk{!1cf}ChM9z!V4>O#W^yuL!~Qu`4RMcX@J1v2(9d~{$1G>BpmB8j!Bwv||Y zrz6@7kT~(~9{FCBLLvlkbD}lm4=%Us+dcbTBcJ4jcrQ;MU3GPwt)mx=bs=eOB%yl( z_4ggpNWp*s3Q(0Xu3}o{G}bdjbx`ZW?O%WhT}I?ld4|AXi>M#u0Xfo{_S0z zKD$00tndSK&k(rsIlFImkhGif;e4jYzD^ZSIw;q6&NIo>h|{%C)KAa6ZL)*9A=VU= zn4FDZ2NSi=^NQcH9eV&Xj8_L125`JjPLG+A_?K^`%u95zBgg+Eh;*>j*fJxHb6x&Jy z2g>hpcOj+5+$9*lZcD6RAPEg&xQrL#_jXVRBz~?lJ!|G2)o1>X|cAL&2lK14ZUYCa`VMytIca*rq&~|n=@9kzf zL91wR{R5gk#-wux#)K7?q(I%MM=k3$f{+OD%-141a^T>>LyU`eHq3PzIQ%BS$wmMA zN#x%P4|;473d`heEu`D7j~@~eY{1CvG@kJ!uwFOH0tvWwTl$yBG=q)E)OF)Ee)O)( zdguF})iRLJIYEO6aF^0;|A>8(jGIAPc)LV=;O)d7^e5l<8RTW@(JKZaEwqthcn}+8 zghD*9;y62+B5~8z4u>S7D!KhSd%VrJXqnP2G z@C&OHh`MYz#(N#b!O;~6V-4BHLl&N_()eBJ_sK@>ZaW1u9YfZCT`_dutx>M{$K3GSZPu1yJI+mDQt%B3;0W9_xSN4@&M`$9Sh z34s{w=CEELJj+Mg2+!V zE4Bh;Qk^_8wD1o%sboFrs2uBcySZ6v%{$9uwQF&F+_%yB#3m+-m@mf%TQlaw3fzdV z=T1Ll2l^bsfaZHNP1L|*zN3{SHUuDwb!igkNnd;9=q7n})OSB<{u4|M3|H@sJlci( zEKEYn0o1LrZ4m3u+nM)ot}njNxd1*HpI>j7L4F-1-O?7XE4NneVli^ketFb?_Jk(A z>fED+&N^q!wRfmvOToKv0~+HdUIy<(+b(|<*l0=T@`H{4Pb`Ko&bw!k5hbgLUjZ>Z z5>+7sQ#xY`(8$@=pEt*d8P397K7XkbN!{77J*MJk$M8c3!dvd)@}|FETVRzzzGwF% zc0F1EpROFLh217nDs3S%<6rCy_A)_Xf4gBI6i@5hpCr$A@qT~(^GG&IG(fmnKckZ= zZ*yT5tX&y{uLIe&C9_f&B?{;)7Iu-@=*7G|@p%+uJ4D*4 zW^WS>)_M0)4|h^*_M}Vxk04!FxFT%S$?^`-$eo(4+bkL(lDGjDvbNLb2_@fD2me8= z;AM}gsa?2ub9uftrqzb+AH{+T{e^SHel|{*)9OHKzZKb;_e1){=hODl_f^-QzFGg> zcEur=kRw?~9J&W(8GmWblhEcPs*qQOa`UWCoDCQMQUP+6KYzgRNhgkgzCZVU%`y~h z78)$;@C*v{`dEYrAS>6;3YO1})2aT)lTXrIrHVWFm6t6|&kb}xbU0Qb!YA%Fn*{8( zI#xUnc5HNRq6YDFk3(Nrq1<&Tk~W^@{k#qb3x~_(cKU;@P`>| zo)d44!0yat(PR~lZgo@Dmhd^QD0^|($`c!t33z}kyR;nqD=iQbTd-ZT{NoCGH(2f^ z_-F+64Pjok2oRKb!V)g^9p{Dk9pdX0t)J%D5~ELxX1Fqqm{QdxR7hG+q2m3$;p!Pn zY&=(9rgkrh;WHdt>?zq`7~OMqtu3`;j@u%J&N7s0!H&@C2X$cVq*vPE^VOJz_polK zk_#yB#$>g^pSOOyY^s>FXge@vDuz9Iyetq%YdwMcNiip9OJMX`!={OZY+Y&FH8$fk zVG2?r+y-0|HA-@5jfsJG7iH zFQ&q4$#uoIfV1ArlkcDHuM$%xI9z50gbwgB1u9axV!>dFI-1~2;q7iI5A#k=LBT)1 zBN_(&!LvFmqD-?SLYv~p58qhE!|UAE{^spLiDUl?kp9^fZ5Jx$%gT%G5pxTJy-hRI z$k%KaDMdgJ!ZQbYdi}tFQ(SlQ(e~+!DJg!OITXWF;A{B&+u|zj{1VGMM#x#H!uR>9 z^c=*n**MfTET%7{guhiO+7j`&$Tt(g{WjIHsbZv3_x4tYGEv~>vBc>X4+F2Y2Z#~7 z5^(X0*zzK_+=KAiZDgopu~Z`M@ccksEwlgSfOP=BSO1^>h`TJM_r~G%Z0fSKO0h601u!o_s28K zY@jV6M!+OiltPQYURc<*oAf-8={mMaJKb~0ugiBc0z9*Ezs#iPdOCIV{bj>txZf(K z?PqY~GC%H4=$l>`Ysc3Ylg1eN;-jiz4RSA@c8nlZ-#{3O!Q*NSd_8ztX&lg7o}+ze)l>!tO_CVgIv4r zKiU-4hB;(byv?!9xRRX`uvsfBqF?_*K+H+{F<~D?LWF3FfG@nx!(t#7+z%(BSwXUzsF>fB zxggobvt~>7_3;tzzEH>Y<56m74oO{HB=VrIjqv`I#!MNl!58lBtwBKc%mcC02bKYZ zF=Ccg4e>#%CRn4P+1s&{qCyp+4u9{58aZm#Y`%@jd_cF_(S_2F63MlCQnY|#8NPU*O#0)FKS)WqRYnmk=;Mkn2HC! z#+|MZ$Y7ydhbNHl?N{$K?}~kPkzH8b7e)S&;cmFY z8Xf+1Z^`=Ktg3g=y$kj2CFq|0;=Xj0;tmMrd)ZvL46lz7D%e&Y!F&1T%J# zmm6Brs2cU!ySQb>{NrhSiNP2g z#zh-GWizYHyGTE+LnTJm%zTcpP&?0gwJVekL)yiRb@`D!T$S#sr~4zjMz)rm&4xBD z_zsu)1&%;BuHmVdV!FcM`BN;6nLf}i-NlUuFCg4- zRs!cGfcaU#9XxiFp5>0waX`PeBK+{PV4{y;;x$j#v$~zlN}0HM<=o2VjHqMuAV!hE zdTA9%G~odq(Gc_S9?tOC6lRgZ3)QRL109`lZ%98qKNPDu@K04ihFw=$dRX?-92fLT ziW_tQixDvT0q^7XOD22CJAR;s)x)B4eON?Wnddh11cxxWz^Zq|Puah=(=X^CF*e#;^5&0Fv9_y>TR)KBSQ|Q^lnC zrjv`|WCuB{O@QPfuM!66Sj9-&plOgy5?n42P$qyp#jCS2}lEbuMd z>bATg#^T|7Y+mDYz?@2>SxU4cc)SH>P4XjjVcT3$Lr{jB9~@k3)k4gC+x+@%P+}cZ z*U`^g6p8TsIYmV^2BAdiVm%&jZ*hADso03`uUn|!3pJyFxy|jj`?Dpr_JwPKTftot zK3hNhVu!LRTh)PoLf9(h7k^duU@B$RFH|D;CcH`-q;8wRu8LWF9x;5@Ng71z|M@AL zVCNbD{t4RLu6Oq9N8F)Q`TL^$UqqQg_;n4GS-N3P=2Urtyk9C&4rgg~5{?*=1GB8P zza8m_5Q`Lj=%e`|K1Y;ZCWv3iO;n|r&~w;#%5S^AQY-pPn@ zEpfaS3zpOLQ%i+?!xbCC(H`O3Pg0o{Aky-7sI^AJGQMOrpEO(&4mx)5Iy5Sos507ikR^1pNkMsNZfbg z=T3+7aWj`@Zup#ztG6?ehDz{fR^Gmgzruy_2PZ|cLYuDnpO_%nOPXMB5Hh_6~Dyyjb zGn-oZx%KTib$Ml*EX-GmxHy&TS1wq<3_*aUQ_~6*>v1?`1BrQG-mWkcp=@NnjNStV zo?#onEtnU63rMX!_ZR6dSFifT9wD__qvMcRcAy^@grSKY(YZIpeyjla)wPVTd0I{g zDbEyeRKhlN;)YTGOJ9Xy?|VI_Okrs67myS_N$!Hx0NKGT*w5uJyw>x8!DT8580KSa z)qk1ONI>tR{m^0+3xwYaItnu*=}JmZr34T7vN)9*)7VYem$V(~#JF2Gp{0||p4p?LO;J9?x>yePhQMDkL}`wnS4OGD$6M9L*uaW zRibn*t|1sv#?AH_slQ}y&l})CLZ_H?1BdkakG$rmJ_iJUdcfe2c8}q{hwagFcB($hRl;E8Bx`!QQ z3UG_gr))Ppev_86*L@PZx5EzK!nJ5_tEXCWeh_o~;#58!?kYbpRkihud90U*`)4IQ zQ{wja?tQFql7fG$hAa+c$g2#=!Nj1m^Wj#yjp_L01P#{Xhe~BNH!Tk03OiPRrZBVX zm<@Bet)d^M2)j#HD-O;dd>a-}S!Cg#FAi8#6dEQn=)if`O-t;BOVqJ)40Xb58dc=4 zPAACbXP%BH_)v6;m>Q*v(OHfX|CO`pyT=o}OjF{-so^3K%I+J)Gv5H)o^di~#B1x` z7&6A+nIeMzz6W;nAAf%T1QGqGt6GOEMG}u6OH_Wnrw`1u45{{m-Em&1D`5i|T3ZDy z4YI25`#zRrE>`LH#8nEqs+Iqwe)-FKQEH@2p6e?Ra8jN+VXe!h#P`Sl5)Sde`ANyx z&BRMCK1Kux4%9dV4pniC*0Qey#rRBYvj&>7uey8_Sol@g5>yJib6560jW&ix;Hxaq zo|um&xxE`3TIA+Drv^WDTxTa$Xmjp9(D}y-pnDP{=fNBxWlp#ScJ3X7eSx@Nlk#o->EProkdSHbn_$-GV$LUxwOQ?DNd+OQ}^p$tf} zb79{ew-s1J)P3vHS0V^5zkj>^++xO%3@)4ej-mw+SrXN>Bs*Hd=^)?Py{u#}APFFu z)(6nzzy#dEiTtc~Cxa5nNgVW8808!oBprE9#Yh2r9?@$)7=ncYD(U7cQ@~;VsFTX3 zJOPyon&kROELU;>+bF9=rStxT>pv2aLq!YvAn0I1!2y9-;S59NNENdl4J<$r6_B1t z?l0fNppgLXB&6jBF(v1$G$dwhVdnfH%Ed;3s3?CrXjuIc&YTT0l9<$ zO)}Ia#3)wGClc&uik||NYK_N2+gV0v^l&G$>M58>KL8yTR4;pAP7joyXNCaI%LnkdVu%2L&8=FM!P^TSDH!GwbgjAT)6M@SjOSfeS9bh%z* z9>^=-(v$9vuKP{C{fxHRXGs)}gITFYBA)Ow<&IG^^zWn67z=8)r*f0T zRv{oS77mRRlgK7t5CyT^dlySaVim7bKKb}>FD4vw;@3l$xy>RY=HmtefbrWzxD1;} z76l+Td@LKCwW*b&t+ra8==ysdh-smqq^^AjgC4+o8*ZOfNcpIGF-We#>L7o@<@_z6 z(p#@(2nLYZ{|Wf|Zw4UgkK|a&`}o#>=;3s!MZDifldC{Y>3{=BDW9UXp?zh{Pv=bJ zhrtC&`7i}%M3gM()AyCRRvs{Zc0+R1fn&pV9Cz2X)NFR=TATjlwiCx)@C_7$gC^K!R;&6~!=QW1 zB(8Y!U3a{FTyOUaV8AJ^l10H7U(lpwzF|_dpH}Hxp)%@A6f?c#ABFfyJ8H&O z4;`tNYvqHRN-!iN#(O3rZ9cK?iUx0>ZF+lz!HnRB3&}-yMvnO>Ym&<}D90a!BOH@b zluFPUPTqnB4KKLpkt$l$qz}au5Y~4Ko$Y|CHxs^i?e+b>a@iSRLw3fxT<7(Q7|rC; z^KN^~c7+bwuwh?2Y+++fBLHaqDOM>$Zpe*>rD8|00_NexcJ9xUFWJmg_meAM-@{T2 ze>6lJHP@+KA?sFb8Ci1;)R#F$_+t6AO~ZzFPF*qZtVhhpTVib=sH?ArQX`oQu?~JJoEm0DEi;AX?!j~(2{Kc5bC0`N8SSr^n$x^IdJ5f z&zfLbS;FnEOa-Zneul-aX6dBN5`Gl#=DiM=sX`2rWdscDPjuvh^~AL9n&q~p$J#ey z;=BO#jhJM#M89_Bc&*U&j~Uk7if3V~`drH?Jf&&jIL?r5^7lQPg;2mH%`8GJYE_@x z=)1y9H9t7JB=Y9?($+h)s8ufx3O(D zmc7&E4$<2C{x*(^eyiZv^0BLUtyFy^!6GMd<(+7%fU`LdGau{s zz4?@`u8#VWll44p2H7&|hG3_otn#&(jC;<3aX{(wfal*?$ ztt5$%eQFeCCwea@T>}`y2K+8& zZ7h~S-%roqB$McwLaBRq6RL6EgEifBO}g6TxKx#C2dGS(mvubSrBHI=o(bSdYfU#@8t$ybO2`vD~~DoJ)hk6~%C&6edk&~Kxxc@?#aS-9(37Vz@L!7ZR^4p6~txB3x3x_OWpj+O3RpEzXqU=J8=b{xK+&4e)65;WFE<@sF2@n~W%H5)f{9xtf?U8k*vL zRS=R=Ny+KZ@-wDKTNMz-_Tvj1H_j|-xgsj=P4u9xZCRgtIf~LZ*!gmu4)T2@nc+&x zM<H8iEgTt{qS00f$D;#D0n%Vst1B>N2$u;ojx?KOBf3+u>nP`;k(@2pbV# z(mniqf3?i!>ZbfbXZw~dog_9$QBiMiDXcx90r&+3-jkED;MjMFY70S_%s zORzemFsrDKY;Z|GN%EBaw$b+|JMP50y~y*XzGvT5!QUw=+wtqxC9Bf?uiR#+6_Ftc*?>T9udQJMc?AE=zQFqdYSpIxaK6@SEnZ^tJp}Nt z6u*R^Y@Um*>DAL8E3QX8y(l*yJ$Kj)&yUI9USgHsf@2Aj={{|$UJTO}ZfsiY;xY;y z7^9r9Ob9SFMiG_D3GNu>ala0ne16%4p;u~&O0rQxHN&5zxquBYf(i?(#^rzQLeI~S z%l8h)@GhN?_oS+H>~%{B8Z&4j^kQ2a83`O7pL26C3 zlv4Uf04e0KuQ`;tzv{7YYVmA$2z~mE1iF!PafvVX#VC{fh{KJ%e1!oDQ{~zWU5H%R zF*KdN>wX4T?YG%w;bw=5RWYq@3km@dQ+|MX#>sJ1OR;m?R0Mngk5p$!TJf}Ii0Nj! zoUc{C7cn1mcmvy?I_otV8wJgu=Q%Z69R_V8Y&)@AnEN4YzbD5;;1`0ct7G!j3+ll3Z!FoQ)1r}yDPfEQF zA%fUvVt*vEm$&l#EG|=$Psy z%6574ccP~M#YVxPilS)87kc%YB;9DI$CF;nVG>M1#wiU0dO>|}iGN*uk35X`|GN1S zO+meQ-uKC9KxxWNsTcKVm?k7+671ala13i^22{{*LLaNRlRCIK`j4@%jp+DWop(o+ zp(h;4+hf{8C{fixu|h>a&!G?rx+kP=m)Cd)#7E&3|BWP>tCt{N%QsJ^&h0U7Bo9(mlH~1%sYvk@iKyke2z?S6OwSaCWXaXooRsMg=B)@dj(4bn&bosC1?t9)AHBb zQ!_>#MHBS7cmJmF@$~&lqZK^k$gI9<;go?XmTbuV8-f6%u$)@p2Rpp%L?P58-+5-v z>v9#6bh<9LEg20;k9l{s6rj4$^7!y8=n!>MD5)TKjcwlc@?LL$LiSfU#)vH6jUH(L z)1cAu@6R`B9}>{+u})`Zs;6}JJJyt&WZhl>BEka&Dk}}rYJiji`NJX_A--zEnWku} zADg5RUECLGb+}7#8YoEnOi2k${)wL~H+p{P$T|&)g6|9}3c$afm1|D=ojp%IlxM6^ zTZ#|iSpBdWm7r8*c%awoe3@^WiBE&STUjrb-*RI6n<#Vje?0eY9 zCMF$;j@lwUl~x@I(9CEz$4F0*#Mcj{Y#nj;UDMF3dTTkp073hK-p6Wz&QJ=RVAY&S zOr~_~#2KUC4U*Qq>1Fb+9H6O&_&#ctP`SNkyJ;DKdBc%BEQq|8Sy!k6o;*-|9o|x= z<0&tHQtVxr{l(rvVJJI(~r6n-r@)*7;b& zZK685l*v1I#}q`$e*n*qKrm)p>XDKbxX7Y4|0(d{2Y=P>TY-20<{A?r79$@VzR;D14^#3wPi!W>r*6t@|#v{fJe7@|Sk)XJtk;#gO_dGD}S6nYXhX z3a}AC;p^v-ovVkIaBSSoOyrlb*F%%$$1IN|e^s!$^FokB0-$vM-9?32h1^H=y!*<6 zd14*oEsE`dS1;%X>&4Kh)py|riC(~QumkL~sd`|&fl-P=b1zV|j{3KH7y? zK2KK{8vR9D$vy+22dr}Kuu&$pU8|KHJNV}$fBqa8)%}a|+sx!#_h@%;;YdBV^o108 z=*Flm5&C4BFZ)EFAIf_UySUYcFg>>Q=|k7x*8s0@9z@>C8j37Qy)USh*92d`NEk}U zx7_r`(x9o|@JV(>&KDF@+2mc}y}aZI-VX^#3*Q16 z%}q`eC=ZUOdp_pJ%GUG0()udBu`g+{5xbxZ=f*G26X^K|*NIT@{EC&zhx}xKy|(gs z&^J%xTIa0x_zx_xsW=-jk)DnAZN!CS+ut~ioZs%ExU$ZbHst8N z-6K6fr2-EMjATGj_%g#EwzY9tz&A)9j}|0ZbT2o1t{8uVQNH{D+Le+%4tLFdIwneFQuxYL8Yx#tJ##SJ0*#~ z@7EX{`Z<)9VvVto1*7=GYwP8)uJ}S3;o`-RGlJk3nQvvUk|nu$@`dTSJh6{c>emd(Tjzh zxZhG771_LvZ51ISSpT&Tr-tkmO|jVGux4imu!Gh#ceRGJOQklLV9q@IN;DsbWaQO| zQ-Q|SO~>%t-PUTw{c&)D=)v~0?zqq}&5f;>cIklK`ezrR2c=oWk511`o3%DH1Rr}i zC;*JuJI`FsO6vRbYnRdKXm4dS1dB=XeUUexzX4`Z z;@ntt@kxUxM&jINr*pXh^6+oS%=UeYnjth3Ek8d3`f$Q`OmTX)JudLI_d~md%@MhX zOneGN8cB>GKBiAx^zgY1q_4c}JKISNFtc{-7T_L-aJlCSJ+}9ZfW~Y`y#&TT7XhHa z<#$Ar_UpCT?3|PrIc;Gqlb&8iW;q6FGv7manbWG5j~DHumvF$+?oQP{LRN%u$qqHj zj(PXrpEc-m)mcT;4>g>BOvyx+q7Qp_aga76<=#vx+SNh5L9RZGT`G2OYtCpH_a8Yb zU~P&Yw9e1Nq zvVqj`uv>feX_*_dfZQ+aWb>7TmY4PE5gqBD=3ty_-&p#?OK~rEX-&hcObuS*VV?!? z_zlK+uIa%$&wZV_yCEq7{YS$ErdsSKpt_J`XEV+GiA)}Z`Aa;jFb1nboC4EXf0UEh zIk@?I%icnFyspY%N1Roi9NvN%pr ze=!t#9KB+&(xDGkyPqF>*qYJW1)g1)ecpVvgpYOTL-uZ8 z2BXv>)wJWu`Mkbc8@k{=x)(4bGk-iRNLE&@T(2Q>?(=)dwhft(3Zj|PYms%*j8>+z zKT!hp1M4tKr)mmuOH`zct06-Jc{xKRbt==RVI)-?@8Y-h*7PV=M>S~b)~x{L`oKLa zod87bihQv~zdGM8ABWQqPz12m%w~A4Yq5f}p+wnf&P(Ym1*^!574|NV-|nwnq{V48 zlHL$rb%Ss~*tC=h$^+W!BUp8**0S0b2$3j>9h1KjwZKWA8^pN0HaiGHP=>K$7A3vL zzA?`rK(eg6EpIFk&nGNFmD9WX9!g22qGasH!0Ji4?oVeb@s7ehUxMM*e9KB4tKKW}jF%pxkTg=r5 zINZ`uz6+h^@AJKtTVe0!)vPy>mUa9oJ~@CY9Jo?r?XaR^bCIF{R|^1Ulpp3~Q@bjN zDLkuj0^pkFF{kEyDV?c$vG`Ng2ET0MqY02_@k zA9ZdaNXoF@=YeB6hwRtz$CoD?=Se>jX@}GEa(aMOsW-lFa1Sm_3|oY#b1iPkixa{r z^;OxU;{JrJ=1~sMB4NandAVhaio)?f@+A)?bLQ$iT4#q^Uo4g8tRjMg*?^>pYXoBsgEw6&$|+gyormm)MKxrN@8dWboK z#?$4z0H9Fog@n*x-?Pp;C>>zipx|txjSl8!JT%#mH=t>(36B3c-uc<(^h@(&L5$hW z2BWP4KIX^kKS<>xtj=i;REs_!fY|Iv1${W{ocbQ!2M!`wK*mRe6`Va3N0h!{XXC0JKzmW z+CnN?euoDso?>Wa%^tkWa=4aOZ`q)yqwglO;rD0BU+q{D%nuntyXdee9MjjFT9Xhv zu$Ky|%btw?mU*seyjf@Q=e=@5V2T1yyTa4c02?0mx-2%6u_lPZ3}^Oj!wzYYA9Q}M zFU@qlS)m_3hd~@kOXVAS4(xwcWOq4G$>hvk`l#W%0B?^upG&cVqRH<)$2|~BO)nu- z8$pF~+*rAoH&d8lJGvAuUUZz8FxbId-Q*!D8ltYYu| z{sPfTae^0PH)qqw+cQ>JjeDIpQg*OlRbsdnW_6~DXEzISGqNU> z{LNQ4b-@=i519Lm)odj14r9*9hs$WW zWt7FH6VdECft?&3^5O>(e! zWI>##$N@eYFim!Vu3vH&Oj)!T&W&SM9-#7T79t2K@$w8xFeBK*fuPN&?XC$tO` zWcwX=*W!MU#f-${c~hW?<6ft3nkNaPELpZJHnJV*n@;H(u7e8RKlj z&Oece36MFGg>I%;D7;x~BIEd2%H!6w-IZC>STy6stV|S1lv)g?hZj-RD8}v)Hq~L_ zQ3hL6W!mET8qrJ5r%CQlhr%2ZdIArRc&7crau&eVdwaig8<3QZ)N7j1b;86L^`hHf zZ`Rd^*2>}FEp_1-7!Fv*jETGQuA9+B0yeFo4d1c0HcU1D=w62(d;>yyEVxA!`f;~v z?GTLrd^(5MXgQ4y0OTw-ubQO3ws8wm?6sHACISnk8j5_br<4+rUES3&?j+Ia@|cgS zT^lsm3iPQ5A0E%!^8aUR^1ZefmV!G^d1-SreFp;JO8Hl}-wv0$kVMiBs(85JF_$zG zO8xc#9C}>!QV?s81?Xa_sll@;sF@QmOi7iAteXYUMfV32qQFg@6A`v- z^{{#g0RNla7hX=7-6_AotJ(WcV0Y6|&K&A+1AHm_nT_x|SwgSp&Xgn)>$luRu$=#}qdlkM|q>_w#RU3CpHr{EPgO^4^QMOMiK|@ez-# zM(3D57d)o{nbW|$gW~~I$bWU6|64}(K)|u-=QP2PJ1ih?bL7>)8nNBMHN{xIcCcp) ze5gA0k4I1VCR{eoW_U#Kvmz6>_v+eqWWe=(!el!;!&B8}TGfRl?qi7B5aqj$pR*?A zA2e8}s09v29DiP9rz9FWr=ub`AeFwT62T^dx#-mc524||0Gi;S?jbNE+3QPil9sl4Dd_oG)*Ip9CvsalnPV; z{W{QGdYGc_g`zXdC1TtKNHTXKpjt&Bu#gxTUt8K`**IaSr$%QDP+m%?h+#dW|KHpo znFSk(LwjQbsyZfJ%+WJn{SD)2p34JY4Y5DN#0Fiw;*JvS&NExfu|r#MR=GX?LC1j` zWf~C8Bjp4%I2L>adqIRB^`&)g!XVZV#RU~r*@0g`C=d0~n<{n4NsarNgvlLA8$a2<4rw2u1$y3>!-hAwR`{(qdu_9Xs) zkhY;(#D{l(2XX5A`cMRZ_MG-Qw)LxKXme#Sq>2?>dLI|+Sk&>04<$&Dj~%lcXKfZ% zh||g_j8iMU_rN4!p|0_Ctckzn(CTeNn%xYI*fA(87Z}(qy2*xbJ7#{eU9$$qf9VP=TBWV<69(gl$HYTrfEof zL#POQkx_X)T1e6C`wC=B4qCKNO034@MMgR9_o78(-4n@LPrswzx}1Y|QTqq`r~ON; z$d+CL37oM$SGGQro^30fbYOg886*_RoEJ7E#~i}GnrA}AL1@RM0(X{W#3_4Wy%#+! z)?M5L2dOyU|H9OMHa482@V3T`pFzF*pWEw_8OyBJXQ267GyScB-K+@D+D{|vm0ZQR+*4^9(+HD5~#XO_|MeJWjIII(l>`%xXwba-T$BJPH`%rL%xLv zVZHT_vo50CfnWEtoIcO>CFQBYpV$RY$XYX-`Zgxn6^mJ8#7EWDTK96&Zsz`m(Y5XG z*--cKPNgIr-M<3(e%WpCo?k5ZxGRQ^)2Hb|?F|mwuC7Oy{9@Gd&LdV-W<2-4fsw1yx`^lzNUXTHRok?Cr;R{7w3Hyr$oim&Y+awVTARN1hw8b5n=NVC4nDx z0iN?`huLd2ZjevIBcqo`gjQv~Mu|4OZ@28wAk=ObkWD7kD9p}h`_d@w_O;a+yA@<$ zxzYS_tL#TC1rKHGd>I7X?I^sJ&i2{Zep$eue8BDe%-;7N_wj}6=1pal2Ly5o?OtEKYQDOhK89!c`_C{@rxYAlogTX}Gs2Uy3Hh+jl`fCh&;Ki<%tm}BcI?D$f%)Mdq|3bOwYKo# zD?&%qeh;1LY^cy9pV?J^nyc%A0?}GcvMG!R<+n_KV4aZH&EF{7WmV-^itE8631*~u zu%=zD4Ty;taWFI;l-epl56fpkY40h1d-PpXpn2B%Li+*&W^841q2RRdYw z)299}KS6VH)J~i21N;l;j`}(IDs)*8-WHeQD2Fv|WgHyN($JH!P}0l>V|)g7*474u z0(7;oJ=CZLV4yGDC{vb!a0M%<-S&pitJD`sX0bJ;aXAG=wvaX}POGw$_BQ}MZDF9C zwxtWsEEfpB9%Hzw=nJLM`evSV#i?$zxby%Jxs{21C99yVd39r*B1FpkLBS8GQ*7Ro zjOJX^%ye6DV>cg)%xWFl`jNqr741?~TQ_R6H4GTzeNBvc+-I6!nnw5)f5LRF$*=Pr zb*4c?fKVqXTdcm5hRyFQ=2cBJ4&ZLw``S%LFg~@B%l*o%uo!&mv7SJ^(qJXj!cV33 zf?}B%G`HbucdO(@iF%>&<5^~2A+4+ivi`q-n-}7$w7pZh(pon77`cqFOQ+AQ$F{T= zBmWby`Ok+X26s1|p`+y`gTv?_0|ef3$^Q{ zW+h_+wD)&7f16z`KV*u#PEH~4GF5hxqaZ@G6tWGj3B4_R*+6=m42fes<)@ zNJ=9hFbs`S(&5lZHw+*mA%OlF zCzOH~D@4VfgufCz2SRcX^xnE<3SMZ-&(aP_?>`SsmB1f^n}P8)PbO5l0f`?-)8?ZR z0#~TVpks5oe&6|MQXwHQ4xDcahdTqiU18?E)*K$mEp_heg7ZNeFv=OcdMS`?mAa$YLH9e|HCbQ2cvs|Q?9a3?% z)`2o1Of4Fu_+r2Ohc5inMc2!KnM-FA3nH4?{(dS7dMX9V2=S84Mu(H=XI}7wSXSBB z(M^-8Iiy#}c!~NQ9^6{YX11Y#6S^%R$%sB0K-SMM#e7oILYqGb;DSj==+5%}EK^Qm zet9dvDzkt1QB52UolxHQ2n>2id&a>mY#$uD(4$O?7`t-fa5;}mcJ{m{__f~I^@y?* z@}IFIpcTWJ(o+aC?Nt|f5PpLA+5i6sw4a|g4ysYenMfXeNnMQ)Kv&+F{BTR-`VpzIIU=@`K`k9FIW*M#h1A{-ht zaLBkhL+k9IucV$>*#JVx_3P_uO85hEH^LkDF_Tfaq5j;zkUe5@^>%D}0DUa1O4P(>W3;X_wxoXg%y>rBiss49~|4D%#U}VTPi`oPm6&y;$)@X4emSG zABAJD%rK<$$LCN_)r$@LROV;lr_CXeHP-DYON7O%i#Y6#QlYtWks;QfBTWQopB8_( z7cDgu+CdyhJr3ipu9phjECMVZCSn&)W(*?gPV^cxeu^=2^r^*6X=RJ=Y6Z^*;~X!CgsjcwF;6Ks zJ!?q#t4Sf{;J?d7Y+9QqceQcI1b%_WQ)cUbwZ;0zV~@^BFC%}(46uLx+lzKDhcTop z{%jW*lgcVXJ7bvF~Fu6Mn3*Y94FDmA`jsl$?~un*rMs%7Z2p0 zPkmfxz6jHuI(N+A2H!VpU(_uk;iyy(o6t+*` z0`Q3~IHv}1n>1M_;SNnjP7{*p0fC~UI=+vI2}N28bA(N@L!ZjIt+5ImMpw{Ouxm74 z>6VkDbST{JB9gX8&bNl9MO+vUk)oQJnnnZP=f~d)C~Oo^WA}1h?)wS@N$YEFYrpM9 z%>qB4wc53_EY;$C18~dt=@6*U0C1isz5r-Ih#qmA?Bz6So=dYzs< zO_Nel{7V(IG5J<+py6=3gPyVna-)Y_g0#z{-EyyhM>F;p9QdE83dPHi_+OT)B9wyc zxb2BBZ2i^#|H9xWHJjJv?VVmg7)+ou;b0U@*y$@Kyuw*N_9u1qxYDA2RI@c~j4uM} z(c~kJvxxHg)YYJ6%El))e1(T4Ng4};#_NK?Ggk@_t!dhKNcNi?sj}I7sWQUUn42mH zbFQ@o_4flV-1hVRS+LhK7Fxf2UG$GwCUKN9Isr@GB?XxlvZxOH`%sTKCVWt#GIUD( zqvUXA=%FMkO+4+k+i+~*3M(Vgt1X&(0qCjV)cxuN-aF_KNXC%P;T$Rf2m{PfRG9&~ z42SZq?HIfb#1Q4-M4cf`z;ARW#cIh1=`GZn5<=c$vu=ztu1S;_reh>MH}q%8j}aRL zO+^z=C72Z&y)`^9O%d1YLs=-ok*;(Y%iufQE4JiDwg3j!+FGD>VCFKU3L|VbMxuqu z4JQb{3FArY6!d4r=>HC#mC*OmmlWe*4WbUJnqT%-ND+Y~)zV&i-~A&_MwJ%DYaY^p z94QzrplRocy;4s0ct(pu?}(QJ)xNSSCq+tQD*f&)@GhD+B6z+FnQLVPsQLr`G+Aw6 ziL`k{M+U4pC7&-f%iP%Y6_*cuFUX9uc5LCk1#lng*J}2msTWS}lQd#M1l<}AnkzYF zCMrrEx91&TnSg)Lu<$sN8|t~1#yhG_&82sMW>7Z1nABC$0jjjfI-K0^7VzU&WFeEn zOlhq54!y9aB?6+Wf`vjJ5G3M9;xPx6D(r_y|g zt3ANPTs`xVT9|#M{SwA@*2~n2VpSGTanZkwqQs4M1Io$Nb``h&QuSS&7h z%yGI{6kW~6w47BD2HIbA7ah)ZVHS^ZeGKdh$&+92i=z!N@NZnhft<3Xgpy@#7SF^u z14`6bG7|Vc7b(61gji-3yagc8UvJ3)DuGcP-{oqST^F`_bQRBw5oi}^3{lvlxaguj zQjd|J!mJsS0rq1ZHWMmPxcht^4$To}63{HO`L#2^RzGO0{n|frk@1BY(n*pqGK8ct z)&ac{Im?cyiY*V5YH$(hMzouXcl(F+0{9n z8WI<7{v<;|#XnGQDB_a6_4D79p!R9&`7%<@WC!2TZT_oz7>dMH(AKw#YFcFmVy?9N z@2J=G0{}_AmaD}){PA|*?IwAnQVauH1$vWGBip3EWhSJ!l5CZ|faA|UUx`^@0Hy@_ z^KBPaOTTPH6qi9ucL+cs5O58Bm^o)&q3fmAedB z)96E>r3N3RnA4X6)X#OwK7-hUbUM2}3VhKvD=L4&r&6TUUDAJ~OeXTsWzQOO`_b!k zEC0)ab54|wK3++uh%vFFKz-ay6Ynukb0kHx(LsVON~Gt%L3k_GiB!MHxt5WVRJkQ9 zOS=oR^tY`U__@SJGT;i3GIN3S0d3=M24wG^vxmZtkQ3f?pLq51>}|V(j2g-R>M~BA z%Q3hw>Srb}ZF>C2xWqq+Mi9CYK-IcsEWdRL{z9|)7Pb+7y#$>`Vly(+SuAi1ms?ct z5Al&7e%QRP-|9rX-g-0e2?e0j-Z`dJ7exFbkIh$=4zR;mk(LK%eUriG*;qzJnj64u z)9-VfIJV@aL(qvDjVgMxEF6hK5&lbKGz~fn zNGpTXesuxpJ1C*NP6>m6z$0<;dM)X|X@3!AuP*#K}A(T!jLR(mY<8eTxq5z_T!6fKqQ)C?e?>vx`*{ImI1 z7v(CrSJxI&oM(2vjg%zi?_G7Ek!WNG$w^DehnTkNkQ`y2p@<`tq?lNUh$#)ViUjO_ z2Pnf*UqLT(zdzo;%HulJwXQFI8~f_X1CgTy)6u)&`lC*_s0j9!@B0;SzV zN+zjV?urBsClzMhzi-aH1;`(wd>a>y`=(^9%Rg;BuQ~y6rq^rwR7!6>`z6l}WQ}=} z2C8Xpy$Hv{%vvBltnm5EJ{YbRZ!2}He70)HRpM$rdp`lf*{dX)hQ6j<^*CcfInMfiVkh@<GFFeazfV@+chn%yHTYXPamUqnE=vHTpsL3jefn z<0a(Z$W4b#NBPSOdKrEyqwfI8R`?^gHFmF6WN2`AAN+OK#ORrHc+aLyA>8a#4qbm43Y9+ipi} zkA4W)d0TLzVTvm$p!+!Ak&`HA1P45n2hunt1yH*mGF!eQq{UD)q&k=5AGVfn98rMw ziTV6zUq^1$kqnY`-kS2A6E41``TJdQ5IckrvlDv53tIV38uaTqRH%Gc_7t1UUos<- zr7_^(@XpsOIPtojh^%Dv8Q1wanNOdZT5Z4kxxKFO@8o~|>wmm!NNn_sf!`3|co`z_ z^3MvE?HISiEV9Iv7VI1B_uFF|zlC7K6si)q>%1Vw_eBpqWdbQUZIbesvM|^iCHLa4 z-imOdF?!(fI_;(53|dqt+uG!ws$TY$je2-5|MuSa&ArsU0TqCV>GSU|I(N}jY*J`6 zd6wF^y2d0A>;&J6IgOf%FWcnmxC~fd<>Xp!BX~g9yORR+Tzg%`IulIQcq?&6TVsTo zu|VuF8H)ZAeSSdE30or;KyauB7BqxknMRk8k;42vX=f}p0Y^QLxdWYhl^Kg>vKq!6 zu#5pIv$%97!S0Yl5o!_s6tp?z4*8^4v5~tEv8nJEV8?omeL}@a6aUJ$@Kbq){P>SD zyplVs+(Zl!HkiFS9Q(Izzh)U@$1DfR&Xe~pT!y-u`PuWcXxCZ=Bh)UF5HgVWiu3Fx z`x@Gx1Yv@NboGM2nXB+hf$N0a++qCG$G$f;d}3(4!V^nxM$8-%M1$eFk$`yD2sbm}xuA#h*)}!UntBBi}*gnB<^K}dTYl%Mb z87)SI9U|0p+Ux|V#WCBx{|fA{TA;^VWT#YE%L8j735ml~*vCY)>)?=23svebgI(YI$EBjgie zD0J1!f_t5iy(?exm7$^@OZdE8!!wRC!W0^6L)4c{H2LQ!cewWlv)8zpR1v1WRilil z?Vk2+A};%xZDi(isG23dA|e>Y8|g0FL*9^#!=nbYI|+8;3J2x68jT@ims-^94g5)0 zwwUCjdO5fOOXa8gsZMkM_CjSSdgH(TV&OBtngNlg^d~~zRJj>y(Wh*WxWRnk_cz|8 zcQkzpj6xV3KjAk8fI4X5Cp|vi?X*G3Un(0>-Er?#>Q)%WpMX;4+(QXdf|Vpo@X#vY z_uhTZn(IpIc*e6hf2-&02`p;kSiaMD`M&oAIN#5Vwt08b9wCq1_~y|Gs6^F_9qNhX z=8T_=<#R>+7=Zh6;f>#WTqy2daL6rwa`r9B&wXkUhGBgnw0^ zWpsss6n3kHL1Gy8Evh2gf@+8MKOED|@i?HbdCd0R)_nEkY~z^|iCP!oAhEBdq}Xg` zY)+D0k;yK~`mOA4#sOL*uRm%U5XPn@-{gMiy>t)k!F}$xS@&KH z^X!Jsn2BR8u#n$BO%{$leBSPw`Nijf=kI#-fX(6UetG+l%}t9pp7ABgiS!9AoiDN^ z8zby{NRULyGx3uV-Ty)xq#(5%X-exq5tfLd=wEiq2=2wqulGUp`)o!(n0y-rwZUij z5vUzqFvp2-q#c~tcWPXi;RAl0o_8rqg(fK|3-La+5j_YqB?%KcSoPuDR3aJBv3_T1 z%SZzku4m!$A4x0 zfb3e58{OV~q>#k6NHNQCDfZIthKgj+E@DO5I72>i8{sEE$A-V=>{^;#eM(SDX&nn( z6Z_!L!t+SXd@NB!9rv9fEdl*G@C5>@1(FCNO6dK$8N(CE6(n>~05nrQ@>Gq<#&UJS zc%2bVZSJ>{-GNH*GkQrl*it7r!9us+0cL%@#L%7_i7@v1{9$VWP|yYVpoh^(fckSv zFw8#Z%KuOL`01?+AZls*n4vBZe?Otg*NFh6;ZP;JHnIG(3J^K&Sd(GJ&y5L=I9KLP z5X=%>x84!LA#*l7ZUbo&>13I_`BYg^?nv5#qTyl3RL69W&PMNQj5D1BYkSVNAOXk$ zU$k?+CEgs${x$KpXhcXk=L-a{qU!qnmCjwFGJr_!b-J(1BsoG0&}`=Qe0J8ggDYQh zJ=J^+wQ6|3<9mMy2;;Zt32MYi;{S!(U)C9e1zKBsHDrC&sH^Qq0{UBSOvBMz8R>`f zgtZkP4p>oouulo{Lg!;c3#&?@3kRTorO$rWAVIvfPXCtAqaJrRJjDUn2ITdNpXQZ( z7j#}5dNo1$^cG07EozkJdPI8vBQTi;bB<7*j7f8G^#G4wmz+M4qizx8g)Hr&QQG}2nzN_Lr_i|-*$ql*-KIA$7TQd>vH8z-Jor%U|pHf%A(wuW| zA@tbGmvGfz0~JHi&aPqqXR^8Pw10%$$*dNoHdHA zKJo16e_@Dvwjm395yC5Mkt1Plvb%tD$E-Nw2%t@yYE+mEF+>%>b|dYPI+kdE67TuDR7umv3Dqj(==YKSfHjDlXGINs2vhUXOg- zkv)iCH`SQKpDsj(Mi~j;3{hr%;}B>)^Csh+kh^Q78R^99G@mL z%JjGwvXaQ5qePn&+uw1}N(SYh`e7VZbkcgp_K@&r_dN@f>gyv+&1;GB_Q$I)dQ}!- zWL-Y%_VA{2zG1(yeUob;O1?);-^3@B(S^kxaph`EWAo+CiJ;sY{f}$igXAas)u;$w z#lC(+jPAwj7WZX-$r#j0^n8zHOb|5%ePnyci!E@2nBCj7M730*k+}U?TE|@%Ml9!b zD4TA^!dh_Rwkkb^;{(^P-uc#E%<$bPaC_DL;e0(JUQM!aF#Z&B3xFEH-xC%cx%SPR zy*Rr*lK3C1Jrb_q&OjuL-)+<1{<5UI$JM^0U)_ZQy)b;N30NZ$Uv6{!P*cqho-X*U zBk}zsUmgIWEp!d+y)NEaJ106;)3>3oqfEwazCVAlWQO$}3bC~C93-j2B@5dB(X#5p z{n1EYWqgj8KI2RAOgT&JzY<#P-qd5Aa)xjaz5!L)Nf=Sn$w+3}{B2Tc`?6BH^)jxwf?oRD^5S3M-> zhfNFqdxyb!o$+eF1$D>GK3a-h_*u+r&$d}*E4N(VQL+sA+)HSZM5t5qYBbV`2$sKt z_bqA=k=IMH=^Y{XCUOZRmzJ9+c9&muUSnA{ea!t0H=_kK?PvPylT?bR10nr2C;U=r z=uHEJ$(|Ondp_hm`yKk8*F8XIqDL0g4)I6U3{+m_A_Cj#TG`M+{~LqFTHoXcboz{2 z+fN#RU#A;&RBigpC0}&vOyj9Ee^>U1s^=P9EuZXeTT($!kh2E*<2QO}3eV~7gWN;q z#|S18vdoZiA?mFWm|<$&ZQubag_y$Xq!51A0Uc*xhS%cd1P&+?*pJ+flU!1-$Vz`d z&~KHW?uHfb3Q&Pcqv=$jv;PkE&`aX3%fE2k(@5pHQpQw(>-9S|%NJgiyJ8XQSfd^K)t(*f^6zsW86)5`LoHvRV-hmK;ub=G%Kb$EIK+ zp=3`ZA0Qd{t8~J&Q%{IpsR(a}C;n9RB_{5gx!C(B*v^A=OiZTe!U~JqfKx1WWz=jE z^*^<06e@jVrSx~8E|gIP^EQL~K^A00Z)M!(kj-Nu&&27-mppXdA8S_S>BZWJJCEh_ znK13V*@`xyHSvVRB!Hfu_c~@Fx?EXEw8m951I6>g&X80eXRgcF`xvY=a?a16w4W|W z@A@P~YD`<}c%(cl1yY5Rr)N^JV)AbnoI8jj&0R-^jW>DMtQQ?W;XDRh;*Mbpw~G=y zR*z6R-c`8%zl&N`wA(b}MWlCO)S0XF{C5KXgddo6Iz`Zy@l`y{hhAg<+mA=>&;GaA zq+zitl6S$7PRHxWf>+k)%%UVLFYY_vA=e|!c+G?xy`FuiV-s;;;Ee)XwB<{OsvC3N zs?#1)wH-e4KK>T{TAP&qBduFS<|%Zm5;gpBbBLzet{@xJJ5==9p*Qu@pd=GK@B z9Y-*=YR9N#86cv#%9Gx*6d-EXub zhLRw1ckX#hq^(q3$=J5kP1sp93>Nfw2^jSlsR$dhe6+M0In%6Be6#Dm>^GDM!G0v2 z#*`jlsS^G7dFeuKKTq?b6dS}u(%G%q>b)aKi1u_FmJs@RHQLX6FiF4G@>I-mU(++_ z4Ln8o^h>9+p5Gwd;>#NIwza=SN*bKRf;nz$vveG<$&Nlt` zl2=M{=GV4)?{JFMHE>|8h4qW{UZ08<{WBx&7j^sG?HCeQi>t{W0C9U=j>e1k$MNNR z)A5PR1Tc45qxd*LnbkXY)YpB{C$Siz-yMghAKr$TYNW3LQM^ z)NgE86VZ|r6xCbo7`XHK0JDJhViaQ(Py)*1GLKKL-_deGidE!GjYK{Kw11{gusF=mjD;RJLNz%ppKi2$qh?d}X zTK)x%{_{N3wO1zTO0{)!!x`%D!Y_T2o-04#F=NbPo+^+iv7#c1-eE2GNx+?p9!IJQ zhPWumh#Mr7Vx=6N*fj&p?cTfLyVKK0d9+dfTpzt)3^GzN=^gqwfq!>3ZrF3Vh+|Kq zh*1Vc!TSPf()yt_3ub)rVD)?TtDvfJo11A?<#wv$ zBeWdKQlZ?;6XKO6$+`7= z*~mxb?P`_jXv~|l$*eCXfj)};qkx+@u0>XP|AUz?@@N!BIM>wZ0<#JrLHh&x=YxKx zcM288m<+)vwRSbHmPX<4`*4mMJ3pVcwbr(b20OsQ{|(r_=iCatK4$5*l^cu-!A-D) zDbU5r=C=R6G8T5=oO?#WUROX-C)Tx_V4wa8@%0R*cy5u;Y=5kra6v%|OnDu)@Vq{k zswXe?!8ZT$Umy<}@2mSw;n~YxFrRo+ld|1kir4OGL>An$RgLyxZ1UFPG1|0seo zivszhYFRR?oS9TjRO5Vcq1+;q*&`qQpE;nhuZch8F8klBAk9XvgR(4XrG%Bw-}=7g z3XQ*J)5^CJ4T9$?L_=vkDfWH6WMjZ5Rn9Z?dWiYPUQO z8Lh2{YD!PK?P{zinP2EP4mTP$yYmr|Du4QvAg@*C<++nr%SPXWeJqf`&$4Z0KdLCw5%fwuQm-m`!6sQUtHGU`aC!bo10C3_Y zc&Ntj6wXOQXp|R!)6@^Yk95TC-%oIQ^B`U8xNMYSvMnDs*zN6pykxj(>7!2YR9U(+ zV>O)g?rfNUOi@7`Poq8<1AoGswk!H|=YHdfgcdfTvyNFK>SO~wMX4(PNr&1>^GiAk zWF$D6*Uf5Zt@553XInzGVbZm??`=V11u>T^<=CBTmvazk6%lqstoSf70^d(~Lkse} z>*y3&$ByHda8vY(?rz7M|K2idv~hbmV{9uT+;tcD_|w_{d_;Hx(EJ!nUYf9bwerw1p+xHb%gehlN#DWz7CXW#i7OqbpCDcM-D)Q%9zvwVuT=HFF2?_foND03 z|5qJ$PZ``^e&mz?rg6;OezgTG*<5d%!Nq+t+_3QU@}?ug=c{&pB+YZ{!=vLh=v9ku z!0je%4>z_f>p+AL8&OwmMw&Cq^<}`brN^Vc+Q3hI`2?P_>Dsy?K_y}Yg*VbOf+FpeDt0+qR{mP(m$*G`3`{Khb=w; z%>S)%{x zECAK1W?CsYgq5rDH}%=|C2PCxwt>3*naX=lX-SN4E|igC4PLQ(n76=!KXePM#qTd< zqfu5He14% zCQU>1U*5fOB-sSM_%~2Tz+)&sMkMbhadb=w+Ju(o(Jtlg*iN^mC7=Dt1dHBF`C5>u zum77Aa=+2^VEdyIDD$FL($1$=UQVy&>uf@Fk7T)9k6`z%8H5|<9}D(e%94LCvt+8V zm;TW#dAW9ZLPQfTa7B$)PXs!8cH$?1G9C7e&gvlu7{4=Y+N1e!`$uEy`tbVOd@muD zjvs(1<1w6%UlP4Cszj_`QmYbwmR=7!wAS;k`RZdHSbyu3vQ%1lU`Gp|GdBH7_%Uzw zF;u&#_av}e(hi;aInul_d%&=6l$au#29cKeZ+qA~ZWEr%72W{>&UaahIUGPU(sInh zE&n|fT!x-ZlP}zvh)&Ynud_i`(A8Wx@4-1l7I%4H2!iTe**dPUN~HEpL|HhZ*c?{+ zyp%RSB<+sw0qg0f8h`Z@z4h$;kL=L^g7JRr#LE_!v!lSo5)JHUq@2dRz9x8 zWwD@0p{l%$i5Eg)opA823=a?2{Bsh{QT8Xircd+d?qq@O13RaW_0L7j`kX3sR%{%3 z1qEC#ao~lNQbjEvodHkKbKZ7_k1Cz_&YKrE0w@GRFp0mMUzxD0uLqXWT;?t zIqW9l9m9HubX42sG$#I@igAl@Xm9L`f+GTFDkm11DtX0zx**l}905@Z_W zBK)w)5<&L&Rj0-DDT8n0@!w+DRcPNYYTGYWqplIs6-m;pfuHw&RB`LB+|MbV++58& z&Qz%=xHFrKxR*!poT5YZGOsIIB1Ez5j4fCGYi0T00eb(L>L(zNTl)DOKDdPL?o#m$ z=SaExW^)$r!`6E3BBA>W1cKbDjc8KA*jN`^ewCpqcCd?lhi*AC0mD=1k~Bl>Oa}cC zYl_H#!MU^F1MQucn)QAqW(_N6lt>d^s)L&H8#v7uYt%@E?_TLnJ7 zE(Q=shqMT1W(JD*H!Vig>`1Le2uoO&KAZTwGd~lmmvVq)j<_;;n2J?dnusj(iinHU z63T&L2u*m7L>K5|5#6G=*9gAnKW*jy?T_`6&O0K~G5n6PvUi?Q ze1WQY;h5$!A6=**1g8V-JHGW*+hc2=%}LAHIE+_hpP@ni_@8{x+s~<^8mn?g<)y44e1ShxraDKQ{oUJ9j-of;gGBJuDvw%yf9X^lh*0g;PQU=ZLuxR;- ziQ11fk;yOOv1&EQ^9Y=RA2zCgfF|?K;UBb-t2zws0$0WXyz?3Rz%C;*BaFA;gB9Eaj zr|fGSr&XY%$%R7sZZAxmPIndNDJ)Z-c9o?2rM!YL-TIr72_CPv$A2~3?fnMyah~gE zy1@D+N4@%oOlbT(=8~$sZPIlEz!@jYvXTt>oBvER*#o2BNCJAyT%@WEhWgt+NW?r` zy3O_ONgIBl{mp}#&6(;FRTmmRGx4(Dpzq1%oY~9gKa%(V=&XH<7-mK4HOF>z`we^e=Jte_jWdWdvi+(HK#baxNd(oH0pJLpfdx}E3JqdJWtJDR9RIj#ze~DDw1$9 zSyKXSB{Pm2)(L7?7)-3SW0e((mGxtfFn%h5J_g0t04$RkgM7XQ7NWeT$ofobe2(4V zhic4(u8h4jArnizM^s@>dlPJG`De_+)wRZ<>3oQzoHa`!1N~H&J}6FkpZV!q4Axxo z<~|e5Act?H?+hE&zUFJ5+uJ}rlFh@p+G0F9ab2t@ew?8tjxftf$zumF?d_)eqmHl{Ex#?%z5=ansXZ zE0s9ZstQ~l`( zxd`KFn6$H&ctY&c?N|W23BvY@Kd63q%$>m#_&RMm9o=0|=!3Dob3%uXt3IUa$+6}1 zhdx|WimT%LkV@}KFwi9FHJe|N{ulQS9d!MbD#z1eRJYyO|2rg~Y+E5ES;bf4=5Rh^ zqjUWX@$|}s@b(LbrOqtuwiD;v^f=F@-xRMl1K8@enYOm$7DQ>qtSf!r)HSpWbk+># zMjut=zirHv7Kl3awPJAukS>UbIozujx+9w;@U*`s{2Ke_fRGd>+_V<|W`1@%B;iA! z#C>Aq2InD+90WRKOyw@}e>Rasl53+}5h1gB_(ba5$BQiwO{QLdBH-ZU##ng7?kuQm zEzY&3GES@Bq>4A=wnQ1aT^1VM+2~gL4QV_0(lBV|tDzX>jgD9!rRK~`1y}6f!Hk%H zVHmM7rC+0H_e@Y8n}9*)ccxUh>$M@x-M@30J(gZ%&D4iSYDeGCyf;fRG->bcF>iw zQT;Ygcbcy2Ujz&G&el~`JzCSyNs@uRFq%DdH{Y`b6xeYf(uEbgR7G*uMbrt_O~D+) zhEXAvS1D4Ea%5ImwZWNKfpMejKuf+${0x_EmT%|8clKr6>;FWQlutk$EixXNuneR4 zaXSuGb_A%*jS*J63j1Q*zgWiO;$Kf-zprFwRYLT}GAQivebcvyYq%vTy;H>a;mr2_ z13#+6-N_G398_y`dTV>7m zofkp3wWSho^a^F+`?i4z(L}OvbO&s2lHcomHc(p#%RW-%+o64yqpFDhtcd=FjkHv@ z@nxzB>!sYj7Waz+N*-7;b)_o$QG?vV#fOswIWH_m&24l-v`Y{q#pHMF(~S)C);PUC ziy0cUTm1f@8t40j4D2#nIj_~{nw`SNi&7p}Hz)z)I|(lnKoy~mys}>w^4H-nvi^~Y zw~%H*n51KU((M0wiI9{rYqfea1iz?3hcoepK~v<1)%=;!5DZqfclnp>D4wX<+AR7^ z9xn!lB?pD7p4Of!&HaD|GLC)`yTA<~Z^FiOD26BI|E1twt5L0-$39;1$*w%!Ch2-3 zgBs3imchaS5o3pG3=$cz!7tBg%7Hxfcwgxx)pq(gZ+;(RY7;3$sP_eiNzR<4JSetp z=2S$PKX?tmSZ*d8`b5O_2sW`pKQF;OnT2zB6w!vw?K+Z7XgpRM?*wsM_LIJ4_{=Z_4ImRlT=!nHuTk2z5xS@e(O?pb?=qXYnBU zKww*JU}<{GO*zVTOxPDv0&;jz_x$7ASR-}sNl9T5gC}S9Jb0{oZY@_4+|B>V*o>@x z$AbLH`t6$ro97v*s@tU5l`!@-nnTS}H-NLdf(B^NW2C+ny0^}zJgahBE% zko%2$!pC-~yfkHwzNDw$>A{3pZS|q~-gy=ikay1AKl2GpD6&WUkKDP@W1C-Z%=yx= zaq?!}dDYrlS9mm7UXm7rjR^vSk`u|(ec0sP3Iyx;U$Hz_9M4bjrdMpbFoz7)ho{JE0; zNdF5xd`B$~Vj{rF3M6wRgP{*u1qz$>V6_J*T%)~(jZ!xZv_$f&$AP&WnXTk)Wx1P~ zc}ONHiN1b4ZvoSx7H?2T2H`uaemcTVxLsI5rjpTfpE4VxHln}9o_l9p5jMJK!ADH= z)o95DTZoIn8m>f3@{j*5Pf+!SZB>fhku&Ng$O}s~u0#@=0T$-jf`8@-J5$#NA?~Hk zZl{U^tL<1&^Y98(JyjqH4j99sEM7?j$r<(*hc=L!X63zv73NNGG0&#JTf!#FiqKd2 zrj7EE1(zIQGbQo0D93ux#V>RjrFf`)8)Ij)0d2wO9@WF}qohpBlwk+DrP}{{3&eOJ zSEUcP&gdX;d;hR)``{7m^!&8OQ<$E2B6Rh2`3clu4B9VE!;u+##i?}Coe@AWD%!BV z;6)Me#^Ubee5D$jg4baE+ZJwX=7rx}eclZH58R=h?bWD;4%;BCsYQ{Nj?C5kP^sb; z+?PCRgSeW;-06m_(uoB!Pg-GV?sZRKvxfQLv4Mk!3%`MF!zYHuNh=5~s^<@7@Gjb@ zvYKeY7<%5l$Aa_lPBZ@8@)9w3jbxy6mC?qBrIxMCU%=@!Cs)(#rXOw3u&{M0T?to7 zXZEXO8}Q=_s$uisUCQV$NEzi}^VOx)P@j(n?bF{Y4_H9w%mH)&OR&afZNJ@0)o0<}WEpRo z<}{*I%TZNUm^$XFU&vV%S8x9sXzNy^xK+(N1hk#6U-AYuOf7a;R>#y>o*#Kvst!B4 z<;KrhL6*^tXC35gO7c{F3AJ%=|T1$R11G);Dq-vcmaawrUTihNj z{U50matX7IWFLcdm7X#%e&`ugxHwq5MBw%zx|}!Gb9w0S&o0oFXMct5`&!Lw+Z_ah z3B*&IjDO$8`BNawa91fC1Yi~GUVG7!YDpoM6=#RH13YnBs$`O!^pF4#NlfJE&1oPo zPPouC%Jhp+IGRk|`?2ml3FXB`ch~OL^j@r&!9^ zePVD2-r17+Ea=gA0JaO5js?(o3~+~T&R!s_-2}3t)cO`$b||!!E2A4GDb%7UuRb`~ zbW75PfgSZl_H@lsw*v(}(@ky#F19Cp_lrBmSopR*fb8hVR;9g^ux+}{9WL~ErU)Hf zt8n631ShyYAe#ITPvd+~leas<$?Hxf|>L&VkI$x59fZjAd&3tp_Fx(Z`kOjyIi!izkP-=VMaE#>(7* z%ua+A_?2uS=f`r>rYTLDA!t06!VFt zagap#?Ha*;nzFO_jt#elNhAz?3+uHrDiaSHINbtg$*ECb-TH;CKU1kB`Z6o&pkdVG zjgvXUUA9F!)`oNU8`L69N`Bo)Q|=ApNPdm*$gko(}&gU7h9$E zOGOVVba%9Hf5n7QXWcKvczMs-&_TYPAIS1dv?7kXkDF0v7oE3j5j=N~rTP*B(e@+0 z^5`6m*`$7uC*-F)*JsC2_v*2kByS4?c+og`l}BKO@3ZJOQ>cty)_5=OS3dC-xWlTX#8@=7=xIZ2L>3~}~=Of{!g#o*J06j|k0rvgX-TKAbp1|9Z zDE0^!H6!#QDBd$wvqu4|BdP)8bnn^8(g*{tJZf<}6?41P10=FT0eM@gRBf|(=wH_| z1<#@@SD!jK8$5(fJY^dZr9#q)0Ex8JxKk^yfD7(z-I9n8druUga0e_5biLjhy z0uP!Fihw`9c=-x#i$7B&WAd4~V8 z&cpNg&`kW<{m`)h+*FMGHE)mjYAxDNjE@eHM_pU&6UV`wiA@okjYg?|iZc7CL%#jF z8tJS);SItZ! zc$4E8+j^%s_9vgrv+0Hn)p_dK!(FjBlyY>d)0eq?NVN?*aoQcHf4|^OymHeKyPlX^I5FHZHG&sY>j%B#IR=6mly`yJ(&jMaBjfXZ{6nst z$cs^pdWzQ=A`SKb@o3z>GzagdIu5kJo|yCiWV2nx$lSH*>{0RhbIrOvk|P7JUl}My zXPChG`+>H{vk`n@B{J9W&PY&DZC)ciK_XC1oLS5GK6;qyy>x!dUAVI6#)rvi9PYC8 zcZPJ*_m0V8{TrtD#dMKa8Pv>u3RVG|V8sbr7vilB*QIXjVlG_r0T?o5*!;L%jd^X! z%jBFH?H}JEpyK;&_YWnZK7n$#1l@#&9F>jQ)J^VaL7c%zexJP*18 z8B$lRwXebphv~tU*ljI^U^b5jW|O{O{;t9FsQMQ|Oyk}0S=nU_g91Tz39>ohx2*)b z*-B=jfx@VsSHWc&ER2i;Ss#KU4ZIngN#Ck|W~nHWTOyzp{hnV=+LJ5pO~~$a?dlDV z`y@2xmLgK1=OUqk+I>N=sh?6e!_p}o6gFcV&^itiQa_27X0Y#F+`%3i?@q8B-%spu zQ@~cmxLoB!Wqj0hQ{$@&n9WJ~x7&Ej*&$0PXh(UISck|-*_Ix|_E+&&2T&WzC$%vYZ{&AO0>Dc&+ z_EO(cbUswv#&-HNCxV;Lh(MX80r9H<%VfeRPk7mPvg8HAr4HquDQtx`(7KdfSZ^&~ z`5=OEE+dOOP;)Mgt|ZMY@8QQF^3DJ6y7VO{Hsy0lK6ER=Wg(ol3k0| zbv`wm8KBW+?koQef|DUUt(y7fRg*_725swhdRL46#f$VNieH?qaRAMc##`-O=fg|+ z&*!VlY1zsVW8xgQV@^&d3(YrD<%8L@VSMOg33PZnV3}66fl$UvxwA$bbUQS^QG=7w z`H8zb>b%p!f{?Wu|AouPy20g?#YMJ`jbPOmB?tRngV~2-{yAF;N1H zN#d&nHVm8NS2I`dv+0O(nl$JTI+RU5ZsK##Bz7fkR`)`baen|b+P8L~yd^knEz-n95xZo+xFsLLipdqs zsgIdq+cJ)6`8!Hk%g<9)(@$W@LB-{0{$-@m4_<{sg_Jx#J;`JH663WL!K%H#&jYRB ze2Y*W*Get~!g$tO)ylh!=s%wj{}vI)|Ln0RE8?v(+d?p-s?Y6KGynP}{Z^jM`rQO> z>V2Q7f;aSIc<3R*l=fe6vCiNg%m}K5*}|}?Ul*I?S$-T>fW1`tOweB{df3v+*|n=E zNir$p{#P>UJ&C=%-{IXmq!2?tz=c@UQ)qRQ?|rOa;PtXY2VarLKDQ4_WWKqGsP)D> zdQ>{T=e|_uSUlQ)ps$fLgz@S0?!cIkQ zMY;m|kWhGL=O-}A9elYeC851b4652!Gu+V|NS}2pa1U}t_y5cA>92|3u9uQt4O6G~ zkcGq!5L8jgonIA$6<)OcHc*+VmS>-4O2JDYZ)6u6DB*hedhVyKKVQ^RE@Nr*micc$ z`Bt@;l|osF5|LIXNG#pWLS#zg(6*qu%?>>GvyS;)y5*Vvxq86ati4j|Y087&{Z*m@ zomUT_=YI8(VKHkyzP5Aca+&jE1duPs59c8c)*_Td=Tfa ztDl-RyJ)VI!>*lP&e#3p$DUvK_ z$$?`UzD7+4X9zf+WK|W}tS1H)1e}Gt`gy9shfs|!!}vzV7msiGc>Hsd#A8}b|L6qq zOm3b;$C%Dmy6#u&S5Sqae~GM4iNynyH_}lBhI%Kn15oF>w@nqNXheMwJH6Cm$NgjPmBM#f+6!MSi+%pCdHH6$tyhPjws@Ah2cT?Cp(` znooLny|5~3dIU$eNEVu;fD8C^B|^-;_bNAvdIi7f6-eJc`J^$yc0qB^gmhFnVZKPm zu`psYR%ke#W4=PfN-sgD;G;|7%+NfR0JbWY7&#B$q$a;9A|Idu^A}RJLHTYK@rbF| zJX0RONy^;O&G|XSY+5sdf0S361X1uyW@EqGdgUBx9&b94DLr-^9$ai8H`@a*PTS{k z3rQPBCF_fkjlxRMFyZ>4QNPziG>;N&-ppHkfO(8(kjvD|OIoEzZO_1sm4l2P^Jp^u z?&3G%WmI6NH!He{Z@Z9|B3f)wp8ZX`)lCm zgjqEDjdi6>H8sM&-BPBH|G<3jTpUIscdN)BS12}WWbdahVX(PhlqSV1PX7;CUl|lv zw{6>a2pS-`JHg!=5ALpM+#LcmZUI7YcMI1W@hjRqk!+F-{|&E#8x0X}m42{VDbu`x zgci!dc=1^^ICu=+uik1%jknIWl+xJkcGXy7SY5S1x?Une@u2eN>$bD|m&~LPeUIuJ zuNV6Z71<;rq&?YEB&OJc0|YY@5+Q1W{js?E2PyQhtL3hg98g+=vIt|tNrj8`E_&N~ ziQ!wRHTy{ z%xvMq_k_Ar@W8o`lTWhXiV_mM2QMZIH{Q2wPIgj%K zdsK>#frz<{7S^O9LtxlXHRDO&rVU#2_bGR7ge;W9^-^bOF~w#pBQ8nW-bjq`L74U1 zl_oFdYc=K)+lfHe`Lb2u=Y#jf1N(yatVLNiM?w1=T1%h46qO#g2PsJ*ZP=7DW6nC7 zc8?_T^-q3lw+;H*UGi8L`o%VPv^jPfq(ndQ|EL(;K+lpZi6gx6JB} zJ$SD3deYkTditiIywgqT1{$DGTQ{1`cN%!FIZJK6wS46o%Vb<;y=KLh4dm#0W@r~D za62TAaL^~W6(=Z%*OK|!O4X-Hv5sfDzr+dghHx#O%l0q(D-aDDU9D^deVH#-Evg7D z+TQmbk51FDjb|$|1`)4Ogu#?va4ti&_Zi!)2i-sL7@*|o)r~^)Seu%fOY^pcaP=jZ zeAvgGGON?)TJpu|)3aTXv>4vaicC<2YS6Hrm|zg9#%8DIme2T-A5%zlmd3epBCf-4 zT@c@OF3td$Pe?A-B5pYQ6_F~NQ0X8lFf-@QyLjbu4w^dI)6KgtZ^YyE;Q2M0khHPp z3}837-w<4X+LYYEJ^TDQ$64~GcMG@w83OtzER68EoC_ACNakup_g>Y#mTn8S6LRgJ zYtitqsTb>xAN*khuP?S(*C);#cmyPYP;Tl!9D2pp!poLQd1j- zUp9w3QeG1TIVzY-+M(OAG;Lf5>VFcPPBuq+Pi->I$%1(h76-_XSg3yxgH}@W6Ma0TwTZO>?pO|0`B2fQ>3FFI(vulb0~ zDdb}Kj(GG3JeTpr>xAv_pY>pfUFB;ZM(+P2GPRG)FG!e`L44RIEZA8D?HI$Rb2-ug zMQY(STo5}^oh!Y09iX7U((paSgh2;Bx~Nb(j4}&=VfqRA376ecCV`%Q^jV+0_fyKY zZ(CGvOCl@aEpnTzlLwS;%<7yEuDJh`8gV_kE}wKHpC%pWJeI%<$;w(s@tUWf_VwXK zb?EX`XPMj+Px4TBS;nl{j8mQ}c^9w2Vg8%7=8$0m_`qRM@$R&_J0R4z@yN`@E*mL^ zcJy`C2#Nt6mVEQ@n~d1~A@~r4+NBoi6atmK{O|~?9TuT3)Hf`173{+Hc|#3Vn6sjl zugx8a4q>7Ks;$1ycn+tVIhID7itOvV0bzI5S3i|FlIKzf6s0L900pK2Ou(l+c>v>= z*<$)psDKKOTdK#a$9;K%!&;(K^!O|&z`iVi)lzHE@9Tv9((L@eCeiZfZ5dhMEo@J_`H*D8i&Tt$?(FI_XW!!*Y_!4_pq07w9+#HYgviAf4RHXpgz7Nq; zMCDd4oa!TwH^6@HH%=*Wzh9+Gc<>1{e>|~eh^qgoKy~3IgbKI=0WR{tsKukPZrjp# zne6F>FFMCvVCayr&kc#4N!l(;Z(W&E%l8K}ezVQ`(Sj=U4vjZo9^uZs)Myfe-2aSz zi0-$viGu%Oe%Z!DgX9t0+w80gP0XmE?Xo2t9#Lf)6jzh1C%QOm6gGHb3xJcUnUF)kGq$Vah(&%gC2kUZ_PXYCeZJ0V>9Lh z=hn*5HZD|W%y|Kiv4C@pRkySeA|P$N8|E3m{<>FKd>=v-@=fI@@cIbPcO{)ECbu;P zRgll*1+_8bI2mA&=9_DbS`TX>4Kkt3RCvmg3_wzg|dDV3hAp0x8lx3mJ zu)Mk6Q{&ia^iFd@^ebcp?4>poygg-c6A`HKY~K$i2==tR+LLF4GWNRb1Vsx_gbe&< zHI{!n4?&AN2i$O-dUD*)_u3(wN2I>us{<(|~n zh#pR;dhO5hZi(+sUPW~9Jn6+rMN^@v^R?UYRO?_m8`0k2wt!j_`W+*EXQvXR5LZa} zEHP-^ZfVu#O%xs$pXJ3>IW!S%!94z0>D65mIG>tyDT6PGmzNFP4O0v5D<#~WkIu|Y z7p&-FX#e;qW^`?x(!ekqLQ2OS(Xts#U@}{xPR%_3w1Wj6nglXL?R(e`YkuRHsUx*# z>AbRpz)x53rWToT^7}BwdpXFn>bpTDk{Yjr3W(O=p0YrWiKOsqgx^igwE=}cTC z<3zkjJpWl(tI*x+-$0VN4u^N=^?T~gN1wAVgMu2ooX<*gb;Lcs7odJ5CF~i#|J}G2 zub?P|pv1Ix{UplAjZVf$d>K>ceccIP^>Zi4^f)B|N3%j>t<8-D23+sAPZG+P;WM6P zLI5ne^M09_}xM)rzX=0oH+LHYjiPMdVq8;gaM$7|0lwzJm(f2Jd|PJ(Vq78-1d z%e=Sco6$g~D=&E(!y0BSJOYh6Ps57Ea`ghID-U&uF|JFnFbl_8sSgJ&)!-H^IiafKfsZk8)4cb;Iel&uN&}4K;H)Wt zLz%=f#+u)uYRB}wjuWRaNM+~%=*bNxL!k=@FMQ=eNt=URMV}`1sR=cevMj7tnJ2PFsZ|?YHKhMUmm_b&Awx+X$|(mX`3Hr z2Wp{%AsrLd_}g;%xL^_*vLtTq4X$k3eS7-ln((3bGm*rLA%HE-APN=?8f$s^c!(n0 zBv1f3YYp+r;I!sDnSg$|@;J;;`=7>8fc8UrmOoP*7y%Ly&<9C~=-#|f-)9Lbl4>Uk zrTeRicX5&fR#ApI2p3am+23D^R#4A7pJ>+$TrT#2JnpG_{B{ACXQdBgt!%h!5v}jv zD=!Jpyk=+3fc8%gAD*P6RZIGYjL*U}2pHV2V`aPeX>!6;Zc_145d&8RTA1MGayfBGtzYsN z+M4&c-&FqQrrxJWCf*l|07n(5^NyR5d~TC(@x$*X;C+n>O??j?wu4@Q{a#<$`y8=+ zq^A#u7p0+W2rV3N6NCtbL=C>3_U4A#F_BXzCmmOV+UBWI?7K?DE;TNuZ-1ovU0DHh zl##|_ZEhQ4GKKW-27g5P2t zJJZo7u4*2yH|3XRSLiV}$y%U^54_J>WVbeAwA)R}^dhU}n4hnbEvsNpd)HB}tC4fw z-W(W;3r|xT*O_?f2HB9kc{Wo^^ZC!N(`}V?QUDjVq3J^Pe_iZho zx?r3xJKLW1h<1DlWbyA_`&D!9IuDjpRM1G4mNvzPg%4_Ry1b$1q!|sA+9k1~&zK;f zCjxfY=2iFVLO>dMXw3WDAu_qFJABS<2?9U7l>x!1Vv zJ3N0MJnD{_(lf9 zx?0m`xtc$0tSFV%u`lJg-ouIs(!TML6G#ouIA$Hi0f}B->`_eAd{E`l?0k-lw_R68 z-8A+{c|tTx?MN&qc87(eZLx5sUW_-63sN*+_%T3j{D_uE1qxDN99akpECjGM!|;a6 zew?t5V!r1E2*>~|HgwYSvkO(2b~FxFy5+LQg>@L)swm=iazJ(gu{!=-p!t?jF|v|d5Bs=`ke?}K!|X@Dcv*d4 zc^PhNJHPO$s?%!1an!P~rLO|A@)B5y&Ma_u1wtv3K#J^`Hn*twu18Tn|1c<597zJg zN1L{pgQZax*S`3L*Y)v4&5io`jVJ5;{`Pz0Uo2hH3#ddXL}Qo(6cx9N&dkC+iBW<^ zeiMWjN7M8{Um-cf}GW#R61K36adc#X;-2wjaf24omj zi_8*}z38_a!u;X}Mf9>@f2=OjCMGl9N97F|HR@k+OxqC&`3}aPO1!}+LqLyf?~_{& z#&OyjP@X!zq|y38X}e;O*pKVB-cJ2DEN|0v^@Fg6jgqDnG>{wC6GByad|eN#irC*)TpZDQ~M360;nsW?g*q)1->b?@6JX00e5I7#@-+k z2B|jgznjE;D0@z5#L@B>uB$L;$_=YAmg`bp{PqH-Oejjn$Y88SYwIiZGIdouoti&> z3w2qMm~ps=1}Npw4Bha4kqlXr#agc9*4r8jyd+g)GJ;}@3Yf0q6w0DSTAL2EU>_UeN{qoUTTe*KsbGBF` zJbY@TK~ZGdQTtN^oqjSNWqksZpu>@{!o<4O$PT)iqad?G0U%uhzlnu%OxhjL&^^C%(}@HhEsvpE47g+3WP&=7srh zjrB5uJzNA>(TFgE-^^5s15x|%`I9$-Vt*lm`=xLMwROH(g#^w@X1d<2!g zf$y0ob$&n{G44}^=%0v`=_Apy;uWUdb1Eyh3KYC_uC*h3G}=JH52PD}t^5#+UPV@( z@|Gdjyr9 zri1}BB>j;q73ny8T(_SrD=i`VgKM-F8gxz`#zZAg7mx~wm)1c3Aa$+{&Djq{jRiCD z$MUzAW94dLRC=Uke(^=eHrB%fTYf&Q$#jP+iAoAXRAzJr=%KNUgkNK8V^Y22K1GXu z21c!k|9Zb_dw04(26~I99egLynCk=zLzmmY59s6S5TIKIdQybLp-XP5S%{i`*COSS zU&vz=9>)i0v2W1fBSei(T|nn&szZH+h|*Wgcqk1P0$C z4NKaH5fPo(-vo?y*C1UUVA{9^yygM_W{l+S@M zH=oi~$dlD;+*NsrHda|F-jmaB-N0}?#!-Loc08i%ED-_ultev@n~Tj4XPzb~eOC%{ zQ0Lb*+_Nd$S=rXD^c8=nu;k^R?|+zEefCodaXn+ZrOdW4+5GZZm}xjvBK=IxuLd=d z;J}}XSoAe(8{1>r=M+k=B53~F@MH~i-eR3cEeMSpP(B3HbbLF}6C29f`}|j1QGjum zHS*qY5EfaXRv4~=%JhCok-Yy+te*4dM~&~ zy5Qc1TaaRWQNTR}9G6CiYxe$nq+q zaiPE^(oeOvON{3kpi1%NG{Kg^LXV7|9kcBVXO21@=g)5ts#V1a$lamWn1dls$s$0$ z%uH>oR*WUfpG%cr)GjCjU#^17Zyy>TcooR%+F@pJyN)h*3n4B)lOYcV7Pj zRzf0fd%YA1A%WS`^GK;13@`^SMX!~!0z_mJ7|oZMgGu?9k?swAz~sH6_GiU0eNM{E zO!5IPdtey$L%kj@7|`&!6+$|bLvI3wuUcvEnYQ~!XaAX0T*zv5wN3oa{tcX70FIc0WG=%$ zGrffzRJ0_wy*+2Y+Yzc0a%YkW$9+UAj9~7dv}Ha!5A}5-v9k=LtEcQZ z5p{GXKJ3ET$-Myb{U{!qJ(@`z)8elLauF>y`iRjY%_FP`*!y2DY*F(`Rz;E@cFozU zG%(H;{^*}$1&HaI`Py^YZPtDgsGHg-{tl};aJZP<#y(lbIPhh@d&a4QgM>HTgzG)$ zUg=QgGn5e+U=Bs#AN5zq!s`%!))Xa?P9|8=e5j*67Bxc)&4b^*vRucN#*py$Mr$d=R;sd$55NhT zz7%UeZ4`CRp?@{Ngm2Jx(U> zJLzW+MvXzf;a#%eZj+K;xv*MqL5W4}iX_G4j@p`nd!ZGMvSR1j@RxRAhie!N2>DKY z^mR)@8}S03D6Cqm!H@#X;L#eP+2fufachcJfUjAhi|~~F3iYEGTCQBJH8G|hm4vpb zd1+@JloWBinQ;U&D^cab!qv?wL^vaK4_y3(;w=OC`|*n?9q8xmldH{MUyS3t&?EO-A);9W14 zjYC@aL7(1zy^Q)|FMaW{l&c6XiUjfHN+LnM*a=_WKCu0U3UWQu zd!S7gJI;N|(1ojQ2uf*5N=G*k{%=TOvw~^i8DW@svoBj`j-Lrd1 zN%YJVdQ^p4K>Weo8$$KKo9d{S13!qss9a|b)@wDpujLT&Q@W26^Fp6bpK8h0KV_Y< zlKi3#<67o5OYEF{ zhnE@DNywMz3C7Znei2k=nfk_y^y12B;2Zx-2MJYf-6idwO>zwSe=ngFxCioLQc2|> zN3y)`DT>z;znFi?rAh8CI_~8!M{U$PMu+1VUvBk1n=x`RlstyeS23B^dU27^E&Qxs zm1#KkKkOg|$Bv`F+R$?a68?0#gV+D=-?m&6qh`0XAU8Ry2kYcvR2%CEKv^%csT5q@OcFHk(TS1X+;6Frv2GV+T2>Q;CBnrpC1kCSlw($ody zNkWa4mvU56>?N9Lc%HWj7}F*BBj6WSyt$mvlT9`lt)%LGh(LUFH)$B5JT27}RtI_~ zw~%6jq#3rg-X45P<&MeqzEQST(Ba8)n$GdQ3dIHheG42vrthh?zCIiW+PZ!*$3G?v z#W#5E9S`iT-0{Qa16a5!lwpTx7FIIR9X zb=4!R4ehOx#9ivh5M`e2LTg9uGElcM`?Yis2cTZ}Dw#1oYy&%+t!mDD`?zm{D?EAG zxI&4-#G`Lzd^)L@!9)lv`6BWO39Xab*9VqBKZ98;d%y7@Dhc{;>3p|t<@KNWclr=k zm;25OEdsAKUN!*WP$L{sWkJwXg=LJl;Y)Yx7>OkE=dU&)E}F|77r;JAflB+XxJOSL zjJ^)7YFFE4_+gAP&kYegS3@B@*v!w#AUSde52%8ZD0tc@kt}vo;Zdin#d2=EuDI;i zpQ`*r3)Yp1$%%}nd|aDPRl1%wJGH$#a8s1FOs#Sox-^W{c(?+Mh+}Lg?~D62Z|mcO zX%`Egor=tO$mbVC!+;|gbbbPBNhUwH>sEkYRDQmcIoR<_(J^8}OzMWXZWAWUq4IvwX_t*eI4@H6ig=#GgiadOU zHqReAUB+P^fBQ+r{5xbPqJ5@IC<=?6TOe$;_8rwaMaS^>a|jx+U0U+U(iO!Hs?Syx zmEGf5OX0owE5VGdfG5RB8>8l<;Upw+ftHoao-p&thOe@BD0}h!}?%E$oyVm&=Y|jqg z5XfhjgT|mcU68;&7ve=hc)@QWv5VLHL_#QbmoEXz@yylY=;vDhI-yC`GPyIaz4D^VWJ6!v~lX^m9X* zZ=oNgi;>#Kzo5IAE6s=|ZNFgSgC{mD^nEU`>!v30D$Zl*FmV30pIt$e|B9(7<7U%l z1me*aK0wLB*`&j14JrI18wV0fx6&@wu-zU#5GeG#+jjpbz`P0dmAQ@W0-H)LI<$C6 zawA8ZxJz@e#&qP#X$2U%rg136&b%`bWg<*)^aN#X>~A32XDiO=lD4maWlYZf}SB#%U+)I89f12g30RwI~I z8z$~(KFFVI-+fN(rF*aAer~F(pw;D;2cy_2@C{CMf4PMJ1l2 zz^L{i!J4W+u-4d7c6JqI+5h}`c7x?dUa zxC%$Z5o*_oXR<&SE4ZatPvUrh-|>UbjJLUKuFs__4mn4uA1a5WlkSt26U#V2oeW~< zh6Uv>9t4YT?Gc&Xcow?0zI&Sj*&Jv5?zqM*Oa+HY9Ns_fw%TV7`ri)cqrB6tXKG$g zFBQL~6aqwuhmF>9n>70qn1XiE+VVNIu%GeH3G|?IvHFZ4g~4y`4t|< zP7Z+z?`0YhGo~lK{M7mp?_|@;_czbSJ4Lr)&MZYX0HbLQifzh{?BAz(eLatkl_qfh z<(Iv4qr5i}A4omXSpdhQ@z_>R%nqP7bZ-CnaB4~zdT(jBwt!3$qRFEdQ)7=%=dU-vRUC^Gyz9+b6!e^E}rWNW{S& zV;t>~*jM>cXn*z0ZT~h^ld*h9@0wr;_yES!VBz?@p(qdLyZzC>KUuDu=hJuwc4RUK z-4$bs6mtMD(l+wvKB#6QGSPwm-29G5cK;mUFkm;81cw7^6$h2(cNA@Sr?;fNd| z$4~x;_c0*a($G{Tel&Lp*)7vy)btCT+-6}=c;_Z7fWzmvK%P4(F}4?=qiI2&MoZ%6Aa5 zDUxG-4SGDJ^;*7a2;Tf$iHu@Nk38>Es!v3E$6@R;Z~5()BT)V0gOI2=IlBfcSKFi` z;jwa|tj(KJX#8j||Nj8T{|#cjupnJP#WP39+%@+t=3GGz=8;0?tS=M;xozD_kMh7B z76>`OtIzB7${D{>2z>6KW5+0`T}T4`ClYa$|8Yf9MHvmDBl=?LM zokJ!?x5jVKY(06FM5jR4=G1isZc_^)+dYCcF{nkE1076l`;Kn(B2fO42hM8IWIh+YLXFXiQR4DIc&+Gb&NDIdnfFQ*o$xbLI3_V6;nA?B2G5@e+co2~mnDsYnJ|_}0p>5v;MS!4X zo?;8@EmX4xaiJAvaF8sN= z=v^H9VNG=vV)Hl2@Ib>az`gwIT*{N`J?2#mv)-D6W5;!W@}WmT=ohH<1prU(hvK?EUjDw#(54 z0GZ+Ofj2-l%d19MBT~7Q@L3P9r`Iwy z7!GXn>OicancK~qS+eF)W9OqVi9>9_FEayzj^`o1W-+utV*7_eb)UDfJH z0ig#dOQ8EQe@+qe-TCtUov}+Fm;LRfB=z!%J+gQGhq$tD zeVF&V$||1u86Cbq3t?8m$-fxPs@M~SE!zjjL9c8$+XhhG>~$;}QIkX&|ATG+@5=9& z5md+bS1e|oZXD2a3#|ujRL6%@Yr!Xs0V9J*Fx^cLCiFX8t$`OSZ<{AK*Wa!Jretao zXcX^>Z~_8Hb9+0`CmDL$js4~;+sq(&J1%32i13|UCm@Y(YWkw?7XyR`i!P$mE;nd< z%!CZBkV?L}wJ*2BN0l0vr?@joZ(X03rLW%xltfHUac`~yf`AsrTtY;{*LMm0{BA=Q_Zf{1gPWAWE57Lj-MrM@rU0c2t+NFDbtGQVUuq3 z-L>8Nlp6|CESHARsb_HfkpXU)W4w|LW9f7ujrK_lCmaeo{I(@t_kGrE_dPi^CMUQl z++N9YJx@*q=3^}UG;!vTSi~!bK=>M)6&{dB=GR1~f{G3WE*2tb#a-s;v*l?lbRcPj z>k1@2XM~7NYY&--LAKAA8llmizDgu)DMXm?G?)3IguU>IfH<%A1pVQL+|nGnM}muOA6iWiR+pMdk92#SUq8}l-aU4Y8^;j!3%APaDVQ;gZE+m+e2t1; zFw5_*2X%q;NQ~{a<_N2;fG~o6U$C-oHwmu_#UxJ1f01p!$T;@}1AD_;^xZe~%7@(){UThHEDaJACjLX#3Hu?d4)E+sIklgw)H06mEGqi5 zC*!}fnSXXKJV@w2=sv%{^LaE0uEtE^V+Rvh?Pvzv^fJFO^ zBBXPTq)_z7P38GKqZT|^fC6U%-47#*-bcQ+AABC*;m_C?C!5R1dK>q+QRdyRjFL@& zN0DzRE3=W7ACF%x)g1my|4!OEU!KJA*EFx-X?jQ8irb#r4e&$mV2)8Oz8uL#DFe6C zw!pEZmdJje^-!ZjiR>-3p~v;a_2rhaQAWq<7X!gVMO)m2a09^0*mLoY?reGR<}+9H z5~}J_#F~`(Bjp}NR&_l|Y=}YT5zk|r5Bv$WP?G3|PeVoh`< zi_teVCNWi}(~!2A-kF|X<}HKU!-DC_wCmrzLKlgW@(-tw3qTuGiwzr`k28HaR}9V6 zjA~{bwxmB76{}Eo=^Y;thL}TFJ+9|$yHRI|c}fqNzW?XF@-WMg-KqH~r29#Ia`Dm* zCjTe36XO2|V*Q1N&G?Xq^?5?grU};OruJbfC##f*D+soS5SMFwjyUK*C3$rxtCA(0Hvp|Za~PX)rS_IG zGY>t)Lx;^Em6m||Hoq993*qDzk@xHT^H;Q=8@_)}8;4ZD(p)D~y$dw22cNV#T}mi( zm~Q!85(^45zw}mfK+&=DYR`?NQXwdF9j{)!AdM7|+w`xT*l35;WnB1`FEv%y@f|a; z&w8$=pPRn#F{+fg&`%T79a_d_#C{p|xQQ;gJ%)L><{IaVjBa3*StrwLb{@qa^e?+A zREwLv7>S4oOS;1W85~8K@FJs4tH*T)MNH*6j!#oUk$N0$ib=c+lxl==$VKyBY%yZv z(Bw}REn{}fd3hjCMGyuIEVY;bp$C$VtRAfZD* zPj3I|BCXe~(m>&>U{zz%GgIBsj&vMWR!H%mzQjLk8k!8bu8M@J;F=eC2PhVSx6-ZM z_5ZAm!WusmL2PleXniQDQ35$M5WRlhL}F5wX43LK@Q8eO77Q(##wwp0H37RTb@M=> z)MtMDH=%TApiwpNF~vNUqg4)blO#FoCEe_W$*;KQcur<)lG43Xl}4MxEPRvi7pJr& zG^F(g*)G-^bWn~xC=Q2NjdMvv#8`KOPMfC_c0A`>k`WHCB~N^t2l??#6-2;e0WMnPpf! zRhJ^yapH6cUNptfPmHS;=Zkvz+5&RSr#xS0h7rW0m%KZTQ(0&#klN8yI6j!g(46R| zVCHD(#yViq)Tan|Z@);*Lnu_KHeyvNbrfs<-qm^~T|6~KxE5Lu@(?#`yQ(oEZ_sAQ93 zX9Giq5&+jrJrs4*`ANl6(t7nOiGtT1a|QaI3UP`v`Gp6 z`ypk9U9)&*|PP<^Ui${Icj5YI8myzQ;4qDi&+kvSeIPk3&_Yt`!@=ZF`>I|fTv9YtRF^CesRPF(0&vMF>C zm%B#`Suxs5r2m%8K8#2XDdZSF%Q{R|$3?^P{XG=1(+NaqEV$U;i1k`#TB5H6Jfc)Rbt10px9L zyI^j{SFM$vNjZ$6eH~Di&JdJM?YK1inJ)-v-)fS91iBa$V~j1|oLWz9S6kl`C98bH zZ=H|(J;;TujCS1LY&U*xKC3AoT4sQY4o6aYx*iO) zGiEVRE^X~$RssYX747W!<%Ok`yVkOun*n4v-UGgn~Ae#=0tq-Idkh@9;%1 zw5tdMl6U8+Q|&}$Lw?hI^RVGvtWwz_E%>493|!B5wOczb#gxc6Fgk?w6kX7BT`gK~ zHD!N6n)1QG*>L=9>aHUskJCo0K!u9e&vKipn|u4TaaYR>zF@Xr8oq*EMKyfoWMm}3 zP$Mzrqg|Ik33fBDe1}6wIE7^1qP#DMFzvK;qa6h|48xehcr?WUy>^N=+vQ;AFe|v5 zI0(UR?`}0V!buitXB7TG>U<3ukdRNW7R@ljNxas0qU%19W!sxiuz+*LWyjq`GhJl0 zKa7}Yv`qsZ0<}8D#fN;mgL$F+y9Vn=9s1aWE6Vhp#{=B>ciT%)*yaS=wBUN0hKpdB zn{c5+Y~k37%PxRoD_iq&h*PXGddZ3qfwEyc>Nraxod2IvmIo=l4(`{Lb9t0GK#bzO z<{E(mL1^p@;WTf#%k-Zv|KFbv-*kk#WeLz{csPH8tOvlE$DAS({0h??CVbzTNAzoSUoKSij}w zSkSs+Rft^~Xio466tzBfF~2!SD=0-fCMYot&WLC2$d5=9XEz}Tc|3+&l14Rb`62@v z7r=jli6`0Py9nlo!~JoLm2*JoL6aDVKKb>j3q z;>zT9ect)4%tSb-W)bkXfURe)4mAmkSH9dr$|oq8)wG_K2?o&GO|>?v*)Mz$hxuEo zuej8e#*Qg$At&c}=sIkxf_NMo>#^n)#vycU!(dsZwD-+1=;U#EeJ6H5D@ki6YzA7j zH!O((u8Ui8iL4zXk%aVf;XMtIWn0$sIKS`l)WD6X2aJKR6!NNkJl>M@#~0OaUQfsN z1obI4R8*)`ueE`f+dIXQHjOWu2kYQgM!f=ToztL5LiU7c<)laUsFu^+52Wd*nZFs&u$tc2MWsRZ=hg0&7(Sw3i6sXjHIb z(^FF;nxKejC8SAf6uioP{4bRStjL0X5?fSRkd2BG>U7{sL%4xY`y9~2HpEqjhiIVG zgVvr$P#0~tc6icxw3H2xWM|%*pEB!P@ZL1I9rcPO(_~nMxUZBrb&Mw#WPQ-PN5Yv& zHkcSUzMCN~Q@ec&Oy;J|I_&0|6~A7Z?{>bbD$7P59cEEU9r~D5VJX7v+J54-`M_B@ zTIh&xCAe$YmS|YG=u+nnu5jlh*$yxnHcB0u2F93J2<{&JofZ1LT;TkGOvYBk%dE!hqew=Kdb=lR$Iz=O+VO?C$8xA+Kj)JLQL-l86a|M!84mz>bD{gZBeY{> zBPGW6BrMq7!vLhO^t#M)+S5J9->9R_bTr*8)_M&-Hsxx=vhI8=fVwd=#P=8tdnPy^ z4_v_gnP)3PN{{x0@pYD`2mKa3N2AEqXY{oi>QT==*6+j->}^IQ9Sih}?}h669Oh4$ zmump{4dPdUp5HFhDs52fx;a+^x6Fx4@xhPx8F!2pbKBeZPRW0E9;Zd!%cTjp7upcKh&BHfMvj=xW3d3qQ94;#jgHg;I5 z6>TY*V>g5hrd>P%(U0G#0^}p=SP;y9B$du(y@u|?`dDWy%DO@@5>BNgExqktStn)v z)ej|VL02{AeGH5BJ{dE1Q-8r7i9v2mZqDlhuIbv50E-$q-$SaH0u+=gPqFSbifb1aCYxlK|_s*qX^o-q9?BBSSrB`4#Mi9m^xg%$Z5= z`0TD47sfMZ`-kUQqC_&tcMfLZ<|Clxe{?JQL(pJRR&jSj!_Haa%6nf~Mj zKFWRMth@e9HMUm`>A+)-%7CegViAqk5D=6rY7{5Dv68bgxZBIstI12`&0#)f%f@|k zEYK98vS&9`O{1V>`aYZO;6+JY zWWCNzUme2j4`!>j;wg$lM<#h)6%UG5ia+Rk9DIfroWlg86w)mI`Q$G(h3Kxt{De|6 zO3RJX1+k_-=T>cu!(mQ3KYx#7;A_!j%50qilbIQ}@I!{xO$5z=)+xa(lQ{(e1k%fa z0(sMI<0$FbI|v$-|dg(jS&#bBIj}Xv=lA{ z&k3=31Ksh$%$O+8+eBjSb@K9R_X&s|H<5djZW&$mA`WLH!OA>?BB}klBF~^XAgany zWl^x561&Ta%{BX6$nrN7pcFuQD&&+GF3dlB=2k&Svw*0p2lRq%vlBQUWZx8j!W&v- zyI=(Tbac8yAd!J7bCf|(dX=}uSK;`BXgkKmFX6|IGJWxS&o-pQQqzyZJ@D{&y`%0| zerwk(eFw90`miR<`+CP8K7${DFS{)H&OpIZ-=Q)esgkRU)_gEKPnqM4Q@pk>hxdWB z>51xfl&=_4TnW<;TI-H?v}vHJFbQlthIt4C%matNKBrG*`|Aa`XwVo*`!^p%IjWb+ z?4{E=Aue^o7xZTd8u-a|c@)<2*Ts}*UDp{Fx|%Hs?#4Y-8$8w>s(c*g{V+$&2!B6= z`IOOr6!~AgN~js`h8rP^V<&niim73UKhJ7Z|ZU4WQ z{IlC+$Rb#QGuBoqK1V1zHy`R6C0_CmSW`p{;fTi}jMoyt!v3n_N_lqW%YRx0G$i(FbF3YVJh>OMPLr_YdNFk(~taKl-RV-D1fVeF|U-%%) zz4qAb*(1yB`AMrJa2se{S(8|rf~1su9Qx`o|KZXiNjg1k8#7zZRy|HLF!Oe_-#;eJ zO?OE13|b;||KFE{e&x6!DRu`96paf(*tOI0S0tLi*9~M@9k^&wLu_%gb4OX7Oeo>s zJ?{4ZBe#Cb^>*zPoYgSvgrJURP)Wdki2O(q-eFM!eX^b{OTFy%Mx4{PS%h{cTET`N zRzLbzC@K!e=$-i1u56y+2CJh&b@oSa^ThCb}%kkJ&GH#8l4*i76|$cA0=5Qk7;LY@7Fnso^^ z1_8;9)Br1q4;dN)Y^ry;p)tde-{Bk?97Ua_(SatUfUE>ON%W&K!@ST}Iag8t>8ENv z*3}4fA4IGyiN1gq3ry#*Ody_ko$+GtagN_P)<__dN*beGDh9~WhoY4MO*xOc?`uSz8!?*3+wg$JdUEW%p? zusJ9_&myHPKESqqs>9uG=s-hwpOf35h?{20~eXy@f*)Avd%q}l!`1WsJ% zx~rr>`-)?TaFN2QGui7FuD(0^2QUD({je#s`|KFgpXA^niyK@yl3PaUI_lCuEFWRe++#yj?u~}6I;{<+T(s}WOWV@z$w;FGor}z=8-E6$YKjVVqHy095-#Bv3`gG9y$!y{8_T%#xAm#IIqF9bp7s>X2yk7qww&~}Vx17|( zCY{|eUR*eY5Wb;+67}#`Sz%p8A}rCSBm%k2QGCQrNtT#IYdGYSlU3)hOt2Nrtd32M z6TgHZrJSH3j#U0tg{-jHHz-pvNx^vGE>&FWdiD{$k>7G-fs$)zr1_thgRo>8`KdHx zN;nXI)rov!`$zNcl$H?{K+yn}7F%JP%y}y)>{sUWW@F!b0oGh@aoXwti^8IG<oe!jlhw#%1LyW6kxQUvZ5GmDllS$2^s%*o{ zU!sAwR&OcEq7BJM=E!|E0A2kM6Q2 zr$?P3w*r1syv8i;_kn%rU)4p?M#$Obzp=!g#Rl>>w;Qsj9}iOSvTZNxb%xnn%VMRd zB6e7wvujd34}aXf$Y?~hgw5wd!K|;R8Vy9h%U2lL3=I;$I*+K)V>h89y;vZSLebVk zc)4%zs1BzLvnQSt!x;aqLYVryO3-wHT;q1{q_3YNHaEx?*k2Z&2w%Nv~gHqFTXUgL%`TJ@K0*B88UnI zX=`x!3OO}+9Y85i2#b-P6iUZ=&fb?P=_ra8pqd{me!pnmO$#1Q`tNr{uj&0%C~J^c zz3(Fbf&j0w`}>#FW{AVlbDp!ZHk;gb@Ik>oW<4aWwcQ~>TwT5il_Va>j_)-7*@MGG zJ6ojiy;_B_9cllCZoorcWt?@39z|YO1pi#q&!XcxD*b@?pVC#vtr!%&kDgl)r%fcK@)Cl8ILxK)pz`O4e{-lzbbuKKVfCFa!Xi$s z=Br6T>zQmBQ%}}ctZPRkU7Q_V)^Pg$WD?>IbG;5rjR)g{J68l^{$#i6Py9YB!8}Tr zPe)YOfS~_;LbiWM(#dn7jVLDOPC=pkNTITlw)gi7nUuAaoe&)Q{o%@jlVVjVoAoVn zJzpD)M$QTY&v|g+OH47r7E!(U@FQ2Lo#0}%uv?^>DcQ6WYbEyQt|F%ZNFP-7ZJuXr z$N_M@vH$89@9g(&bO*D8kh}5#XZ9lr`-sj1 z+dwreN?`@$x^xY64Zyrwn3;dP#H{hs(#4)7sF9ZG?>$gaYz}OiVAn02bwK_bQy4C- zNYZCB_(akAL!lq&L1DzH>QdP;W`DGE6%ctY#a3%-|CD(BOwvKg8ZpeSV{ZA}c5P$h*BcF2SpqjdZdWY%JV7V(16T_!RR=hw9c5;5RAZJ;Ti*-VFIpavl4qR` z7CYn%pi$1T`bVPUXNLjVc7quFUgsb~uu&EaT z)%f|}qQ3h@aEW|nbRoqD>MTR|5TZ(#UkOf#&xOlpa!q6qJaUhHTN2fl?YHw{4r#uR z-8e4u!<8hWx&g;Ua3#;D`)Y}&Z=0zrsom3A3P&;2_Su(H1Q-O&w*&=~f{g=tzGTlD zp)3aOn8wdBHJY-Vl}TOD70eHS0A=ih5+vHDmRw<&V(n%S_YrSmUe3kA6&NdkSs4A0 za(Y>%Vd@AvJEn3^bM;v84}m1s@)TB8J5-uSZVouVN;ub;Gl1CYj;iwE2MH1XXWS(M ze>vI7#1?scHC_?7(SSW`x@+Ef$hX}>$|G{mivL3NorG5h5ub+ZrFZ6@NCy2odQS=6 zYYzWA%$+?B+c6`3S+D!!;RGMnb~R=jr;R%2?8ZWWoO@F;XBFBN`xdf_a}SxhT9>)- zWQst8+5C~GlmBL(Hwr3)w@^&!veo_ukQ#qja}az3W2NVk5!<*FT^e?OI1Q9jPVKwO zXV-pMZ8iN4l#aDFHlN~p^q$HMaUr7m?40r%k*mYOY;r|(ie~iQEToLMD~du%Zv~g5 z?*&=+N5F;!3+fe#2F>e84ik9MNdtVOY~j|ghA6!u68`j0Z%LxZ^W@BzUQ$oxr4{iw zFkkggNtM)k(NAa^7E`$IPjl|!_DSWx-zs7$;WWNQYRGZN= z3d)Qk*ZlPGC2+AA#5THKdN31gCj*e4lShAz`#?6($uD|BmJvYdVeL}(WISJ!jbZ52 zQ-2~8&F@t|xwNYz8~Yxil4IJW9#j+6S%+Pudmj!PEt|AQg)yzg&S)%dopO)IaRCay zHp_fn6OptyqWWu-y+NIPTE?t~Ex%nT(ud<&4hVG$WSpwr?9ayvJRNdBNTEEYozY&5 zwLO}zGWIAp!T^^UmPC7$tyIMl^%Kopuo(=#3hXZhV15qU5miloosz2(_+@UP9#A=_ zu(RHy4(fD}y7zdE5hI8;hXHp?)Hs-lcdxZd6?#p_4)$^Rb5Av0tN(~PA0cGkz4?a} zY4GB+kEr1K>~UJCTx0R%%WTwC!+Sg-{Yj_H8VMyejA!9bjTwc&Jbd(<=JeB6)maIq+U4z-$uvhV$7^$g1S3O7`* zgPxQYKPN}tp2gJ!WKBEHd^*S#CxC7woIjG>L<)KG3l+V%2Ydt^W14TFpI@7Pp>mtY z7QI=Q#uViv&sdN?;I%uMpC(JE$=m4l4VHME+oT=3&Vda1F-iBch=Ll}joxV;OecQg zvrX~2(MB{WB@U^x<$SjXS({Y2ys0f4;D@R*x}Chc0;a@nIIC`IUeH}`@=aM~pWqd! z%~ceeL>x&z);dEjAg-92_WYz--dRX1W>`<%;=VsdQb&l%cnov)}3IP~uw<9A@-5KR&y z^lrf$h^L8O$zhDL*Nu|@GM@RX&8#-pW5pR329MoD!8>NYjUTqbp{(MU1#gAE#T?XM zyw#$*OX;9Ck-ywz@ z&tcd`V%0zoK_xA^u5-xUomk*2t~{WKD5C5snD<>DRb>EQX&O|kVE-sKtS6tER!Ngh zxAqIVOJFpVPi4%@8~T~Vp`W$`4)thD)lN}SD#FvXdg2LC`>A0xc_uyZOcC%&htouD z2tS!IC3;R@n}NzdXrF~vy|r+W#!I!ZTnp}CvY^;cU~#BB{iHE{-rbro>|@1E<*V19 z`}#_KxqBNcxz7b!86~R}?KW2XPpy49z&hxg#e&!65}bbXS6Z~Mr17l6z5Iu^`M1}n z8k_-V`tH+JRc`63rv;2WFs4;F?bvVq!lYvdFN#4Q$cgr zW;HNkUzd6pQ(Ttw94~TXY#!}$49$g=F5Iu&`0Pw(?!6|voYQPY7{-e$QqJf(@aEm?j6O5yxbAGm9 zn9gAsUvQTrfKP&naI;$577ZYkzir~reA@Hgngb9#1Hn%M0>(W+F?nK)zJ~t}M}#h0 z?;V?t>gNiy4`;iv)q^0m(qbx)L;L7nR&DrX95Yy zA1SDdji{Zbfgf9dwx?+FyaW>6>`-`3Bo=q3L~+)2`g{<^umnKMXJ;(2{uD1}X4TgW zvA+%XIwK(U+1{=RIRBdJHrm7u3giyY9^7K2il>b4h{X+*GgB2-ekp_uRoI+fUk40^ zHk5G)7b+*B1^8+9ExF|>41ZEg-&_Z5so$K$ zzLCZluB!D(FTlpk-m5jnhutH_uxTkb_c6djaICDsgnwgkgJ);&KKHA2`AZWxO>5~4 zv9Jbpiq(DTuHN9R9mNp#^@k zkW@{IkBwu6fKnDaynf#Wa-zdYQ91o+3z3x+XHgN&x9@oH?;c#N`|(MC38@NpBva_u zOYJQAl>%MO%oOFrFSacv^{j?(I69bwfh0ALH$b{~4zd9xv$+Kk?OX5ihIJm1gD+(P zd^X`E(V(*l(@m?*aVN_m1Hi7fD$IV8|HYf?#OFj~^7YS+B;ZpXaF6}Lt!yIw_(Q&; zaO(4Ad>m@@aSmX+sO>RnP;Si?_e8llzq_m4V5^p@h|DwtHC}HG2j} zIN)!>kr?EcS-{6f1x2~2geZ;<*6ne*+cH&hsvg%Ty36#Bfxkn)LDF1C3TnEt7{pH7 zZnhi%BXuKU5#J>NFIIg6TV1S`v8H>f)Nl9YH9k9Tq@m8u<`2+#xXUD6>RsO9k zS3tRN*{zLWk711;d{gcQ2uL?QeZypTQj|3`0lK#U1#XAL#G+<%zB1*S+KIUPh)>5r zrWeokmLWIObz-%n4`T!97 z@pG8#IxK5ZXRmEQtl`VG{YzB$1Hb=u>Jdyp6`$RRimWFGo9H~)FP&R~M{U&Sxqrng z&_IpOp>aTP*O%jGAgUxTCVKZTYD{!Dyli<+tGOeVow|dkoxnh#tL$HFbb?nP%onia zRtC@W&9$IANuMi6<3`0H%p2xvSt#xPa*bUI%!t-}zxy-Q8wSD5{;+;H<8eBEbJcA( zvm|(3cffP-U#|zKHQ`{@Uff}Ga-|T@Yf5Z?dVZyO<5%xn)*)d-Sf+-F*C77^+T3FK zBBI4}?^1aF8K>B^i<2Pp49OFwRzjZ&i7ZGB?@T0S&q{wd z)^YDQ!|0=kn?C94>*aCn!}70q%Uez1EnFy=th1(Sa!$>#Nh-pANPw^HJ7uztbsuJ` zzF3n38V!+68&R;_hUGp@tbzku8(+@Sws&5ml9Oa@O^uYgW8vBFiyewz7YwXRw{Ens z@^naRN?=?-n~=54GU}fLHlo1!qAY(~IZF2gzapTi6S_j%s|)VjnlP!j?ar`^TbVJV zw^6-!?wb6&J+aUsYX6EPl#yuz9)|lpGD_CTk=J*W2oMoKsiXqflCjdQG7n3JP2R}9e~F7cihT)xPu8fSI#UWApgb980+P!5Vq zI)#EKLHESXIY&heGHRl-h+vJmu;l;TFGXNt{%xO~co`47;1T?=q7iEYH;{9bO5r|D z;g4$>NsQhmO4Z4&?`O^4)Jk*h=QT0E-98)2&V|o~@m9n)R%|%u|ErsL$XAp@{2TMO zyNh3`n|zv|pO>Uc{w^{)_D^G%FU>If{emBdX#PaU;HLX|9R(IBqDa*+hn$1oeZL@i zVURJ4Vl=a3Ze-Zw@s@92giUUj5|5EBa9;gNZHS~GELx+^e3zSTf~7(r17T6;mjP+b zurj5%58}A8JW44J`=Rb_XQaA($uWt9J9kUB>8)b7AiIMOD0~O5nE_2Jy!< zqiG(SOd(2Sru+5S1@XxjUvV81z5Ye@;C*xA5Rj~c5C83-*G${;kB(#aA%-oDoosh^ z#ly)qE`q8AXA@}g0X&(zXqDy1eGnn*MM`^#b$|DwNs)ISQQHQN^v<^8IYxHYng{N}&4y)@WQcPTG; zKQJ72E}-<$Wg={MjDm;kR@H{O;VCU$X{CR%;M{-1uXoD3@#fLZ3+tcnLE|8uFj8N| zOFn(5=%d+|!?dP^!WSTQ0tQL{>rZ=&bH=sEG{fY#B$&HK`=(8OKbju1>N;NREVpNW z#|c$&seVG=u%~iOF?8Iw8wt7p(po63&|Bq^CjIX8VeyUwm;AL^8N)FSEMQ{v%}=d5 zH`2wMV8UZ(ugK$~?Ty2ov0Bo~R%<(}v3fR?A3N9t2BqPa^S|CG1*sQX6=Pk!l1>iT zA9F2Nk3P|s8WUGUW$`Wd-kG}9mm70POe4cYP-9F$n@Q zK&cHs?Fw(P8@7IIL=;C?)A2Uaj*d}qx@Au43x8H@o_TST z0p$^`mi1b~S46_+gTVD3W-t~aRoMRrT&>tDYstTbj|ebhOlAMM{hiG5KcCb3_=MR* zqIYotbsVaZM_o6tle?m53LE$BkZ@yxeZ6-vqfPhlyWK}^If-cj6k=Hu&qT_FQcpmM zs*lVu?XpBYs&OkbxR16j6A0k16iyfC9l(z(2O`9ntK9$lfmYLz`+XOPGP98BV~l8D z2a5KT={GX@66_n*JI}56lz(rfFS*}zz;CFQ4n@#dU4GhUG4lICP~9;eie1V;(24|s z=NnL51EA|(`)2(Z%}E1`_6K2*#eymSnqT#YNA}M)3xE3q>%GIzt|&bh22tLSKW=ka zTEX5dkiEJVzWzY&ye`uf)KCHFp7&-CJUh$h$V&&AH#ne;mnW!D&AKj4Hi;WJe_zg8 z@`--KWHR9mHhI&U)v)x^X!RH5=et#@+5n`uW=Bx;btwtJ5D zDX-a~DA-yTm7zBI9j^kP7!$Yhdau*KBlaNAO;ppYANQzPDt$!FM&C!<_R%A6o!iFZ$FjNF$!afe=DC!%kr;1!G>>>n6GChmak!^klY%Z4~WY>(v$ai_o&g z)+V;u;jlXtdEhSGZdCY%Z*L%W;`84zM<9~%{vuBC7-b}NcmPzl&(*(RCNw(v4?3%GC6$WZ?ul3wA zTDCniwJX`RV*Z;XFLv;@;9F%(#pM2nw9zrEQe()FhtCk<8>ji>jhA0vZ)l>3eQ+P$ z%2MByn{U24Y7us-*B13g1uMa743=D4_|!VmJoi`avtb>(!zZPO(2XGQppWo`_SJ%C zRhKvEIPw9ftPUm@2J?)XX}x@X=rOkpbw&kiVo?l<++M;gk>}15eWE|LUR^E1aXNG= zTi-$B;9~a~1iJxjm2G`}p9JtwcWOAMH4SLa-R2$7?e-gh55xgyRL^o(GC&jX}Ej*5bF zNxS6gHuefBMxzR@zq$yYm6S2iU4;ICV5>}b?G#O)<+Xi6 zlWt$`NU>tj)jO(pE5ja{1XN>$l>#=Hw97uHZB_v@LGyy%Jz^%{5PLh-&P$otWd#NbeLJwokL>};)-Dmk4&$Me&D5G7PtX80FuN`Q%+RN>I$$U!+{v2sz&J^T!~W4nlu{-89dZM;ZmNEl$ekEypd_8UV&PGX$g zv5}^gPC_mLM~(RMyM2a+>H+R6d`P#;6xW)#5G`bl&2PS+@wPMQqFC6Xayp3M%hsTR z|K!L-FzGyS6}Y}wZ)cTk;_uZ6X|zXB?j{_!?&bAU`IKdPEZAJviR7Xm(qJ1DP#Lqk z2);rGBgt$sig}Jl5EtXp;CTY&1?c{drH28zOp-%k&l8GHyInBEEzmH-hiY^^TnzJ{ zH}u1uV02uv^arz{7gYRf!0s;0hkYpzdM8b>eMb`dMBVLG^4~DphV}b}pZBa+xg)W( z(xi9~YJ6*c2Xg~*uq7t4)7?wZsSj0Bl-!>Vy5I6fMT$x(<~dNl1T_28WD zHm>lgy_a)2@y}Rjg<^mDHY}niLFKMu7d0;!pVTU6&*jtTV1w~ZUKd-M-Z9Vpf|$di zH@r%O=bq!Ze4#50(fD0;f!Nz$P|D27A1H@VSW8ujX2zc&G@tULMg3xhWq@Fv*Av5b zP+x?wbS84hPn}qRMVJ8Q>F!_?KFhl8Iq2g#2+XwYVel}+WFYS9{VB?{GLBvaif7p% zYN)7m18!cY+P~LyT&bF{g-r1Qa<<+xbTY2~u~^6X2Y^ADqUS_8qD9P;u{2p3-fYtU z_E%)_BNPpgKy$DB!t;H^mN5x?>h*}*bI8Tp0jC1De7xjCf0g^S5CI)(NZ>`rvLh?jKWZUaauR2Q|h8=i#JY3{NQ&>}%?l#9FENDIY|qkdIyu`MsW*Xv-Xt zE)%VCk3v@sp*bsiXZw}-34HDfS3qq|Ws~s9*2ktoUBaQ~8ue;Sw7;+8DhaTY#IG=B z;m+}aBhZa?zKE~w=~6Bmd2DNyR62Ioaeq8;v#JQli8GKTvPY@<7LT0lA~rM8Pc1sE z!KkZJe*ax!`#(lJ-bL!m8-?NYPDv-_G0}fl;Xw4)qTNOe*5bCY@ld5#_=M68DZ;NZ-4QMHBWG!*Do4 zBU5;x9R)X?YB}WS`_PR@k5Y%usOT^Yznx>)mFQI75xEyV%0$#ehS;8*yM{AdUxK5~ ze*-GKHQItg@avY+vBk(QKSqp#m$@}Ng~cjbu$$xpyUyf(3fG{toGuAVm!6Fu-68Ss zU9e9vZLppAMm^?s@$Sci>F12oe*H`p%DRX)ueGtT-qvRx|B;yF<6h6zHxec*JA)wj zH+n&n;*sj~KYXS7J^bP4F%Bq0e>lpL+9ZEljnp~2_##+DD<&HMi=c@P)Q0EliahWf zemnI8V&eV7x%y|S`Ul5F17pFwrQ!F`qDoq4>bdZQabU(?a{Eiq0oQfe37*~MQnTeg zw?WB`pgSM&4-pe?p92YKY!A5p3yMX++i$DKQ|sD_Q2^DwmGo&_7B&Mnd(xX1O;tDD zWOsDR#O=SSB5q5Sr+l(+IEcst8@qQ*&y@o;ggTBMA`e_@Jwez`#0-Ow17(VM{?>up zb*<3k36tO@P}(_x)NW_|t5b1+a4RcK>p4LGtncfjfUM&T6k~nAv62dqO5Mj8@bnGM zq*Hq@FIuc7bU_>~5HLpmllz?FQ6Gf9dw*z&+6f9s`W&}n(W8N>sw$Ft)`wzh{@Sma z>H+ZV+bv{|sG?@1rX|$qDA6$4>VIDweoY1>GX3gbwQ|JW)Tfcu49w=8dg7dp{*Gsq zEe!>>123VlZ)QJ50NDWyThKmWFp4@#1-0;suM(nU%Ak3DY&?j7DtXS>r`*EA0!=U$ z{6AP?Dbl)-xenDVF^?%_fe^UJXAnsz_3lYi00|cWrbt(nqJ*P}0du?ugszHEihl@|sC6&*_feMj-Y#^-Yj+;F@QiBEsfZ)d zf6QsF^rR+sj7fl6ER(rh`rBXH2lU4bCjm|TKu!!vSo2v`L=DR>UtkX~-WxgN0zAT4 zt8Q{k*z)Y;WC86afKo179hm}`p|z0QXV_?(-3&HP_N@9&qZ3>ln0p3w7*quW7qql;#@uIlS!T~C-+xxbCbr9pcWT?{ z+SIehb{9WUs`sCrsbO00*jFDreq4?t6^ebJK$8Phg7A|V9I5_< z?^M;qLymbE9+x2_GFS2j6HX-p@E;X`AEZSwkmc;*SZMQrSgXojB9dBso9ASse7?=+ z#f9p zSL}yMm7d#vOiMDdCn}znT;=g4`VyGlcOm62ehPlD7`xn}w<7%dj7ToV<)j z$1MZ*&vJb)$8KeecoHT$16t5qRZspwyA#)k*P!3Z$)58@#&wsZ ze6oR58Mg>R{;P-;k+n_6lbsX z0H=d?^(J~lDK-ASc?$qh4(~k)T6O{qDz&iR)Yus;zOSt57OLr>P_$+qS(m!l1SS+o z(1h2RwW^$75$<=j5d)cH>m_1)e`m+gl`xujzGtaqIMsah$i-|m)~kbRuXW_{%Y6!i z7obRC&7{@z37fiPHosbQzAE;($mi2pRe|wFwVvV!oP$kKtWuYpNlHS8mpw`+lvfg8 z3Dt`T?#1ID^6ng+2*7D>+3tQmrv2$=%l6`fLEgQitEfh;ToDnB$)Mj2wJ|{sP{*Mb zCANv$qQTH9edD%#{V%sFKOtHj&vqK2k4dmv`ky3!k@XjYGJ}wy?F8B7-P`}mun@+u#_gIKX?2krYJldA(B?`;spEgsi&-K^ zAy_H6U*ZwsE43QP4Cec1c`AG5^4iWU25-GgLX(3Y|9CmWYk|_D-6e&d(8ZY9^71>- z4LOxv!ki{7(P~I!&RuboJiQz=H>e)7<$Bb%J$Zey7P|w6voj!s93rSP8lORyq40i>IA>U<*Z@_Z zJztEfj~>cjDTK0=N%*t0&rP*Z7=>*-ZwFGoIfGgCIWY|CN!dt9O%K6hFp)h$d=Zp0&d_xop2jt>LJZ z^lqBnf*Wj)c>UCd{H>bbAU1dRQC;wGYt1RA{JSl-ZDvB1DI8lUF|mb^zo$Pg4&7Fkwo}QCC9nvhNI%51od)vrXTj?Y=6XN%K?8~-PWZFW!w>O|d(bhitHOIk) z=J2P%UpX828;@5iVdx^apV*fI(*s$Q zQd0L&EIFwG*aXoTAuCp68qj~|1fL#Eu7*Of(c!qLrNbYQVWj@L;$ELdg``5r9)&ZO~064X{DZ1Pi7{nC>^B<5Pe@6_$m;*2e zmIfB-nE2Hjc`myHKfa@UxXoeQ+Q_(Yr8P&*1}?pfg~cB*gO_^THPEv`MBn@ejCLS5dH-Gp-KS&9zsXO2$c4~SO=;Pgq5S9)+Qu*=kXk1 zF@2{1=mrA|rFgC^iNyv;=_@9$N%#+SfA6y2ziDKJfU31oc_f`aKQv4S)G z+hT>l*P0lRy%{CUKCtIN{QBe5T9_C!vB3{R@iSCb)R_E{b1{$41TARADA3;Pm}5LP z3~aFF#$6W%1a&;azgt%dQp5_q_m45gm$S$3BweHO190=O1KD#0+#*)OrJVqpdkGM# zU?~0Mu2(n!XgwKlGS$2R`{T zCQ}OT-$UKvqmW_`XFb3Vw8v}>ORxL1`WTUpi*SdYMj5ZxYyfqi54n?GpXvf=Sxsk- zYm)*%{!#4U=V-MC66N^6=;{ovz!yfP;(lgv^bH@BCust{1;n+mmvaJqj|uXRNC)Iy zzPjut`Ls-$zMqE>F+Qpz4KCaJpi;r%)(KV2%2__^lzHnWM~Zmo@tXcJDq{~T{p->- zJbR^8GcQ^l9sbE+s{PE&cWoQ>2LAMTA7NS=VFQJ6#2j7b`^+EoF^_b ze4rw#QTvlh(H==(UxF;>5vCt!eo&-;gjAtpFe%^R5FH&pWF2U+JV?`8ukCRI3e*}*V= zZBX6tk`fS4(9tF4zbENq#1S>ScZVhjjJvCvE+sayPETg9j{4QpLo~*~OuuDeB6*D+ zG8f_0QR{+~@H=<89i_ie9lw3bKN0@tpFskhjB1KK6v{fubM23cw@T=I8+jOfAM76{ zW)a;ShT1a04=50(vRl-AM#|MSJgR<}NW=U)`f_TR+SPRATka3=ewNOAq`tn~0?B{3 z=JCh(-}7zx1`$i*wt7GX{doEB!+Y-}w7SZ)<@8;}cmUFW(O)<~zi5N0PKBEllb zQAqq7PuQ()r{A+yR!Dn7Q4Wb`Ttr};un0gygHJIu#ohkn;hJg6Ud9jWPeHUoqV3#l zsCBz;>yy|8JgK3CX^+QC2~|EzX#SmlpqQ4=Ro$zb720T(wk@5<*J^NxF) zf(HI|7vrAMkn3X}abMV16rW*=iP^rYxjZ-AvC8eb&rd%+v_IbXh!vxrZ3U~q2P=RP zc6E+sCdM9WXF~K(VbNPZ^IyD%tUyVxx}yDPxa?SG-G5ByABXV^%XOa1{#YHu)3Tmq zU=?{z?f=d0rxHq4d}#6`j#rYob|QnH|16Z;_1Y3`{rr#6dT8Jk|9&J**Hnt##c5ST zZ#~LSY=?S(ukONzKYs1ge_9rZjS1Wmbv&xR4IIM#YIlf!zg^Jru7*|CwW`N^RjXVc zd54%C6cHk2S=jX|Kh@~SrVZ8L!P~71y*y|~*ye*sgu$KmQgxrBKug=U7HGm8#xJdH zM^MeGX9k!2b$T@gFUt)hWbKZ^_*`Y1t%h903*B%|M75@2Z_T*PJ{Yb$Z}g{KRXUwj zRAu55@Ml~09A{s+KiOS@me7f=aQ-f-c5TzO_?wGx2UI-yrx9_bY4=>!D4$cK%`#i< zq>;o35iE$;V1c%Q;@Cf1JKVJmwgW4uZc@3%aQiei!}XClhpvdp1kiy(;*3on$zjAW zG|6mMe<6*=J3BIC$O6rNpTU))mIzijzPTYk`t3Se<1{8F$3d%tz3-=lNBmUeves|= zLw`Z#huSaj`9z0U%ky(t-yq|@jQUp!&lW=w_g{&xnMV4%sGZs?dBePS65@Np$}tyb zNFdx+FSt0q7+(41R8KwP)fMZ0rj}t=VMf!yFX}%rNV4Z&=2cA;i%@OU&D*uuE;Cpp2r>Ki#h{7o;`v7nsN_q2_dEZ4}dWs zYi#+>`nhNYE=AAOCGJ#9brr!gv@`RI->{g-@Z)UBAQVmsF9K(!*7MihqoobsEkHp* z(Ohc_Z`}*Owisll#(J5E?^$wPTmJyuF^lX0U=hh`9{VsP=v}oYMLK{fpM6Ub-!1%D zP3($6ycv*@jq8RF4HAnHP)SfA4U~YQBKdroKrQF+llj`B%woTEKsI1>9~{Uz9bZt5 zz;*d*1;qu`eHITi6Tz*v7(%%P_2xZib94{jK%xLjfm{hAiWpmr;O$9b-3>gGg*|8G zzcG{?voD)o*6B>4nq)dSS{Ed2^_j`NZf*1*o7?f^~IkRddL`QmFgS@GU$8z=bZ>}P;T zPrT3>s4h=@e3vAEG%FVCD=ykup3;z<3G|PKRiEoZ#&xeFun_o(`S&p~2{Gv^K*Y-? zP^DU6;nz7zn_1Q+_<|;WZgnQucQ3SgLZDjlpa+L2(B7Cm7JdF!uq*5wW6aE+AeFk3 zM}#zNIX#WovvpwY=ZXz65?5#k4XtKsZa*MGG2*z6xRb$nW;*0)Oi+UJI$$$W0G*r; zZ}Xi{*YCH&Ju5a~B&LYX`vbX_b>&|$8xX*;Z@kgC6_#oy3wl<-`*_T~+%{=JYs9-U zwz&>l*?dHr2+Dfh8;Km})%NLboU?2w4$*@bR&DpFVsf{504Bk!A{r~tX{IOakCz2W zito?Ah&G1}nhX>@Jy@KI@hfu(&2i}2S5iP@W?I)CIA0lEUlMw3_0vjmoBujUmMG!+ z9Iuk;fFu#J*&6t_99Bip;yEZE;+x^T?wA7>rYj)P#_|NE_4l2csMjg|_l&K_Tg?30*(T#e6Z=!}tlK-pd-a{>^6p{6citqMAu$&DUmCz+V*O-B;GPkHoZP zvBvm14ppja%(P26y&vUo<;Io_KA!=LJQ|YSqxB!=Ifol8;wilkGvUdtN7{H^i}0aX zniz!rgI5&rNXnt0GWqJ_s4MKW;Xrh>D#WSH5@W?%M>h)3Bx^dX7wPIZGwxULNDE$k zztJ(5>SsT%2r7Fz2J4dpu>oy%j1MuN@QQ!30d3C;98WwzQ71%6&*dZ+D%StXQ8Ixq zNU>IMb*mTN+_dTy*f}GNcR@v|{|aU3;th+rTpQ1HDCWRpg@)YwY8@j3{w=M;+rAcU zY1f}$ZkEcyYreLME6+?@na?Yq?Iv3)k3BaOy8JzAX7XVryHKw=KhITh`^Q2yxXUGp zk1PkK?Y>vTvew*!QvH03x5DRRsrin;%r|4=Hk4vYhybQuJTb#SdWId#VXM)qxjMd& zgHztRk`9on?B}3aZ6Bhy?Ajtr95~5LHupx~azsl6yKHo<7prQvUmxXr1#-8>(1^HB z?C-dT6PGwtH21BDT+#W|xRI8A^9CCrr+Kb*+8Q}HB<#2EAq%s^m6&(8i=2Lc00lig zSC*n2_T$5Nj7TCRVA|gErtP{0CYYsefO1j}-DEo~BrYD873Hy4LJ2Vhv6nw1|8Y`L zPPc`PK+nC%r!N5S^UA4rLm#-F|8#gsL{LWr4-L(EY z_5(iFqP02@7l-2jIHI$!TzB&Wtt$j<@o^DoiHQ+egRllB`#aB7RMmmmhLyJ)2YadB zTfu|Z!3XM6?hE`{95jDyI4tXHr;uDd_KG)CgvqbwC$`b&CwGLx;W#T(pR|np9?vi! zRdR>3^+>;aO#3;0Gct^pO9)d;`H%8KO1>xbxXg{zUwqkFQGXW6(`W-q{k23|;B3#g ze!>?`$z_)De&r!BqgAz~8zZ4oBOHCLZD6ZgZkvwX)ARP)?ops4Bi(iS+viNA40m;d zMEPU-qRH08Lk--uxYXG>)-ptdWh-|mPYdDEji z+Y9<|Q~xnvSz&p;H|u+rHPI4zsDYm4M@g0fwX=f0LSPPmj7{j}AdR)Nh7PfZtCzD9 z2OVl_RBpGaQ}mpkz>#<^2DF`c?_JjX#)QoTFh6Mr09{|c_PQOYmmcg7o1y;|4R{aQ z-TG=QPCVxAZiZi`!)V=b!$Xs9qD)5lVayyo4LAL8<5xHnIVQb;5iBnRn8P~@V&V${ zJOIWqiG=W$Vksm1m}pWSfC1N>GpS^viU%Sf5FjGnZR7*YzvyE>c=FTdLaiy-2ejUs67RC`BG4T2LLFRg|i1Ic>rYqwaCNd z955EIQqJ*q#OsK3P<+sI{ZbZ_f9eE?jJFq{43lcoMCC^&pFA_s2FOMStMqGmFN(i0?ttfJfGo<9w-C-(YFAvkbxgy6JRL76m1PpWTK7nAjE5w zJ_Z;>KLohE>#n;32*#3SfMNi1FC$(3DVz4Be3yfIAqRZ{+39ltMgViz8hxDpk8Wun z`m6&~=!rN$M)=|}2!IWL$jjo~!>Aj5n7v&A)Q}n80G8MQU_W+GJ*WfnARFKvL>|0# zu`d8I`YpD_I0XO-7>!=g9kL@UUg_*7OdFtQ07!srcmje0MABD#YyVH$8qhGVKdz|{ z_6jd_M!Ps@k6uWFED-hOnmVCth?gzAgu(t@hx8EuJZu=dN5_C{#Ctm-H)W#}4}%}x zntWJ@mrUphTk~?!C${TABld*-aHM?!-LX;MACr0$hdl#?k{>{k`oWue111v3k#+-2 zrH=f(j>rXZP2J(iO9b>x-6#jX$O*ZPxIKEi6Hb`_NY9V@0FLv5ftPCZ1NtT4Bk{BY zKr1>XEiaU46UGdHPr{HFosx!p0HN?C4Ia|;1Nu3(3Sk%IWz0b5v>9dj%RqDlxa}Ue z-UmbG%wI+We{c%T5C{cm^3`zdMT{G3NTJSV=S-<`C4l!4{Ct2C~DaqZRC^s>uS z%kJP2#$NT)&-H9j|LT4LaNAXFC>P4s7}8t!zkFJ%4cGub#*Q;|&j+w8Qmm+T`Fgai z*Rv{sMCEI*v+`Olm=k}zub2!XZ?}gYwz+ztvrc(iUv-UD)U#J0#jE1h@~m?Lctn`X zlSmNzSK2(vLz&rnanwh8SR|R(>IGmceTW4h2x{8-Xsa%{7wV;Ikqa)eYI+gK-j&D` z2m3;Xj8Piyz%<@QIE?u-tS?eyIn{@vwU%O6_1MT50d%Ipx!ynH* zzz#eyi|W2pP-E_ax7;axZnI)~sZ>!q1I(Zf{=~+|QmW{>gr0i?p%;~(69E#De{Yq0 zmg>1!I)Ezglh_jBB#qe)C~cSgot_@7y#^D2Pj%h*`$})~r~PCbmMyo2#~!B->(mbU zX^UTw!l)N@I{d7&Z6EFN%6^u4&*)_p1;RjkA9|QofA7Q4mR#482k0r%Q(oQE0X)vo z($e{pCWj^MWd+);S+P>f`-@tcs)g)q>8+@21S+Na_nI;}94YS{#o>ts712`LQ+}GN zy($v`^u8UBvKj*Q7U}sLfUwxA9}>->wKBazUO`)&3=tfFf<$`Nh|QM;!T@X-?WuATr^k_5Q{=djR*< z^W{9XK^}oT)dXs~9qq4Y#4Wn-JMAO=*Jibf0!ru^s>cT(+uX^M1Ujy>_BY*PW%aU} zIJ`s-q&bZS4wV}b&5^(jvK7HjST0imJCmpyBz0A~+{vm3v z4-Snu_dJ`RXPCyC*YS*^LD2XxRiz)s*@}7dEvs46usod?4p0|?c(kum+kQ6%+vd83 zoGj|LZWBBn3kv+IT6Zsv8+yi6TxBii?xnfdT+Q$3FC`v&)Yj_RD{s<17$c4y`J)xl z91RM3P3jn3cewZd)K&oj45d%0{;khB*BW%_Xndhu9nDJVnU!$^q96OYx04JyEXQxG zeppR=vL@_*ofQ`-$=A%GLp080X*}Pc=jvK|CSL!~2g0@wAyR+5#K}7Dcw10NTfS)z zQYcN&58=q3=>Pexjda~mxWIDyf6rc4N%I=0wqDGip}x6j+H|WZ-4s_}TeDnC^VJqx z^gLWfX5PHdneueaXBF?bQy)jF9BhMS|2;IX_<7=lu(xi7flo=ND+3S=-z59tcEBnrz42=;)7Uy86O0g0$5k6w|9u+ zk3DHOEm~>^-}ir3NOK6RDW%_3s>BK5)Sr*_o%;HlHfN$f9;z2P)dxKjyjOi}B=y*L z-?5Dor&^;U5`Zi(b!cmdJ*+G0SWI)5K;oI9KfeYNPb<0fuZRi~BeI^mczg0&hz`Pp zl1U$vK^_db*)YK)oCzIa{Fr#-k;lXp@_Y=YfG_Z1pkgu$;VH)ioH)F0m{d{*@qiOd z?%4;>$tU;#82}wDH)oGF%B3uTMgS%zvV=3KhaaAhc#u&Z-~*FtJ|Yq&2NejT&d5m^ z^+RsT=11M=$b?a**O59PKXG`$0m>2v2niqfp$F2SJM;yB2B1K?bc7tFBM;yL-bPHy zv$C?nWEziLC%{37JWc?8ct+s~hn$oHAc5?7mH|M*1CL53;DAd2&JgnM4!JIniR}Ua z16XED`{0Sll6-W?kF?YUJ<=we(;j$h4%SCh01g3gurJc!(S`oV2N6b_(tZHx09V8T z>fwFbQ{W-x01C1s9gi+N_h>V`KA}N_1_fK2sRvz_r&3?Qb;!NM088_JrUW~L0{P0$Dc>!5zYsyDF7 z;f;&{l(aqd0xYJV0~BI|cq9TA!vpVBUc3N&0g&VE%@S|)Nt;n;`0@h9m(BxjIV1jMC0k4G;0C3)j|gb(5LMTjH%=7_Ga8T3S*NlW`N7NCFH zj`(PP!o3XY5Y;h!(E)tnM>^Lde7N>@L9e`sV82g@7cbZeFH*>hwVk1hV5P3iwXjVyBGR)B`!aT|>sqUq%Ce5Dj3LIrZ6p zMZ`Ei=k4Ks^l-|Ep2y21y%GdZC{CfYBa9;1A8@svml~WQj~q?hLt5ocX46u_6;?~* zRTJG~LY(?@j-WzF!5my4+0l!UkY0b`iAv)T;T3hCciLp? zU*vWo4}42$Zon@cF>d?h*yG6qG1mVeFV@exbhU=(o-L21wjn&&LpaOf4;_f$60av= zRgxfu*STV?EQRjS4LYk^cE>Y$G%h5J{dqeA!dA@A4nI#q8GKBKpX;Nz?lb(jPRLj@ z0Oy{km_TL1St?yEdH;qFfNfQQP7v|YIMPymFcYf`4>Gk3o;zdK8H!;6x|Ew&M2U{&&F##B*WKY@>@r2=#$j`~kDw717NJC!$ zl;y`YY4~b6^=(7_U0eZfiD25Q%!+&X8d*#{Br9908G5F0X**_Z{An*qPmQn zD5`6&!gC1EpEVmb4!<2-6LMD4Gd|>H_!yC>LrJ|jNdWVn&(l#Kz&qc!lXz@`aDD)P zmDD#N&l4{AlMX8Ar2yn{ZXb*Rl{KdDixNC5$NDkxK`%+YmaSS%_P?{evbQID-qw<1 zic?|PiPJKHuETD<#b(Ld{kU)RjfjH!h}$mo9hJ3`UV!j(^1PMRH~BfahcnCB^Oh`P z_2g{eMBTXGVH;)ChWwn+Ddc?NdCWW)RHQfW&fCCc#V`nZz-fn<-EUa0ftx z^murY76RBJ%=bNHa!MGUFc1@5zy>CYOd7MYvciNDFaw-fs+u$jKy@<%s|$l!=UhPRIsPM}RXr8bm$-1oqft!VfWd zB@DojBR}#1IKl@|k0V}u$ib3h059U<=iYdvgV3o1ATCFUNdCc_i9B9lc!e^dW}*%l z0XZ-OZ}bNM!qIt=A003uNAFD9;Q079SeLbsF;;0hoB08Sf`4-Z<}8=JsRNDpX&*DRi#c#X1j6@UU8!1me- zGzBCiKR@!ID}K}s4=lh5Y>Kj}Bc7|+C}{y70K-Yg4+0n)E-(k#Syql+0qD}slo2KB zO_&46fFW#;01%8fAVfJHkB-od%ZVo_^`I>HV4swO9it;`9M4Dq5r}I5MV8%SH%{<# zFrE4V;sMC;HFI=?{Q{s6dm5aM}?*Bqb^vQeJf82}Wz0qAC2L2s0UZsA40 zr7XrB$|9{V6Q^Dfx*{Lo8eVwlmmju>ekqr&3IMF|TBLsfo}*jR;HeG3N11@n0Ls_{ zMA_I5eGMR#x^YcgLdZwEVaL2=pf91boQOEu5c09duW3g>I>sA3W+CcDJo|EzK57d9 zcgO)yhb^FE#tT4BJRH#rx#R~vz4t)b+Gj3o9l;cGCe$Ds+VMmn3 zb|HAwq65Zgz*fdHfPdNnd*nx%jG@FMv&W$;#w^Ab`XOTnb>l~w^aak*IYb%g7=581 z(v!~Hkba0KJVY7X6R~&fioT0yHup|+!99r=dfbZ`#~6RHvz|SBrT}KVM=67QgzF$M z&-`UH@CVTVw*PA>w>;k8<7wj8l<8sK2XmBzJ{mIQm?D5j>oq>IXJU;r&k7Sp@`Qeu z%+FqHZM2jd3KPoYxDWzz_;cB!yhuuTbi(KH2{|?9B@*SpH4z-;5lt9mP6njPP2UPemhcpY>dwa*xZ%Iq~zfNz0U+s*~=Me5hxe zK%(LI-ea>Lc*r`x{F3c2ASLi55jxaGTHPodzL3ilF7y`-Cq?v{G+cWgk0U%v9`9kE z&cnHm^NC)2K92Fc>EgVO#BmOJzHpJlpL<8|t8F-zKlp&n{{FiFhAMNaj`6HfIlYW% zz5G0y&(jgMyYbOH&dc*g<2d(lPsh>Ic${CS(kLIgN@y#QKIk#zO}xZPrS%h~=NXZG zNVD1>X>I;~z3w9k&*oq7_dJnfuCj`Ed>mq%2%Zah5mvg>@!B$Da{y^m&#I~ekO2gM>O_vWI9xny$Bop>BM>g{25$&!1m6uzmq3;C%A3mu(Q6XaIq=}aqYLKkQ zY;B{_UXCVpwT(dFiLS+i^I}!1+0mN6)e%+zXzuZ`A~KhMLJcHFa*b^=hJ?lMx{~>N z_3hiQW;K1)OUC09JI}^M3y@}aj22HEzzP6LmKHLhWs=9_jtMbf7L!Fhq*@E$fG1_* znaAWAZ~)+h$t;skyg1>*vPWN9$-Zm=HB9sY4FN9r5;#Y~SPD-$l*{B3z5rbSXMhHH z$T|4JQdcIrq@|8bRPg|GFo&hY@P(L!qZ=md)Dr?kzz#Zzuz=*y8LjWTH4*+zK1Au@w!gH2=2x%vl!Q%aejOY`&01-F`q<~jWHbOW* zChq`XfGhwVfE9poc!08`81Fei7uprQU?b>`d;r+Uh!-05MV_pztkA9i4}g?>U zV*_~p0eH|B{?&Ty7@&!^!Q+rN_qHaCHUw+}oCdrBn8cRoBglZaBS-YaGHzriKW*#v z=15%#r~c%{(-aU2APsOE{=PpX%jW4Lq{9Oip0p2X0Hn|p^~FmRa5pCq2A~VQ0-RHS z^p7VewgS+OJ^?OSK2F`JC!j8Rq+d`5;2!M`08D?PJqX7u72pb;(Kdj1*b9I;U>?_u zH^_kP0&uZZ8n6}n#-;%Rd04;W_CM8$%ZC6g>cnp$GJYy)e$9Bg!KVJ>hK# zfJl1AD(sxTPZ{Wjc0&&O3H6{&u{ZLgM?4Q_3Jk?_7QhZWBp+>utUU$Bkrq4Vn)2d$ zM4#xNI#L#8)7Rn85&Ht9X8gP3jynQ>`Z9VZEpk%^V?QziYy)aiFY1m?oakrXZ|K9= zIdz~;0IKduRu1sufV2?(gEEjE zJ0u8KTfn3`yXH>^%8>TIi6=2E9jSu zH}qZlbOYU=*f%sxs*-Y&28zn<=jq}?;^?3Fc>0}#6XDd7Wq)3$F#g4K{l5gOZOcUCX`*Ac*=OInx7?|Yp7f6Uv-8@&|;)sUdO0RQQ15^T{@2=eNN=O{OIX+_uA8O zbc%-gxyOYzPL3<_I8T%IDE>h|I%ervu6mP%4xLB3BYZsEdB(3(?M7N`Dngz%dW=N> zg7@ykMS1zT=Zjx^I7g4;=-1xn(YWY2bz@74s=9v^DXDLmU|-4nfyplZqP+Y(S@=%! z5a-XwczrgO=R4l2P`o_n6Fr86iNwF)>v7Ta(Q}Vi#+~vKUPfD#w9qp=A6>(f`M1`e zGMs8@p2vR05Tg|`K^$34P*%_T*we2qGcd{g{oyn~UjkqU2f*2i=Q1BVuzlvf%*AVH zzQi+}tnn9h2q6(063fI~>Wi;NZEtnpLE!;lOF-;$Px{#i_Mak2-&S)bD`2jhQ8Tp+@G%x4ybiD!8VZZPo z4{=_;^G9y#fV~6!0R-Vaj9h^3fO-JXfUC4Meba#!c)|ys@w$7M_eX4>d=R>Wkc<4t zz&SkN$vJhPPZI|iM>_9sA#<9)bnQt#e(*#l>VO`R3%hWAQ66=p-o$f+A3yZy?TifY zgBNjLZ?7-%!iytiVH@-f_z~vmqT+iP`QS;tDUWN{vB$Z7$iq4I!jZbTeRGbC#8Gc- zhcv`{K90L0WPy(pb%h@~pbT#p>IS(!Tqk}F59H?;*D?8!6C$7M0$mXf4{V1vA&#<0 z=VgS9$v@9aKFUIm$m`>t%K=a7KstWt27a^^X(*3=Ls)J$M?Cz=Ls>qylZQCuaUCF{ z%SU>`{hA~7rOoje=Oq>|xpe$W&0(e4?_XyA{m0s5ZzKbMp1DEYM?iR0X_{XDlLeU5lP`Z?GBct;@i#|qL`rdl=JN7=r1HL%Vp~-PWg@&;Mnd6f!WZ*C`LF$p=h|6&32vSyU0iPEc|Owo+PpdO4Pd7jN532$P3vXusq&({igs02v=`r8acoN1r54KdEhgrfPFND(PnRzp zzbNncIs52yrlIq8vbe%>aeg;(?#E+T)#NAsZ{@AFYTM$@5F z(z&goO9x1a%ZcFOwnATv>o+GGfuHNfd6Sm5!e-cm6pvE8RRKfc%L@w1b368Y&WE^k zGa*yvFQb9~Aq{vxq96QLavpl>NA0=#AOCVhZN zAFd&AH;MZ5QKEvnhwLfxaXOtG$hvuho>%heg`Dn%>C)^eIp7n(=wzEk%VIp;tg*L{ z?k`g2o-Fd;&TB{eZw?=z@_KT=;G_QXNaywUSS?9TDgFfYT8 z9_L5rYN^Vro9-mWLoy6&R+y!?0=;r{bDuA_Q&T_UqnI({CP(=n?1sEl5I z^yoaIVSes$@E{C5#%;mV#E;G+dW@&xn)oQWyl#tr9ZeHIcO6FaMEOR~;YS?+JXku* z$EH}W3lPIzi+q*a>ln2i@_9M&@}t+JqdXpxA(!9V)OmPYla_Ori#l-a>7u;Tos&QM z^E57RR3{$itSGbb6M;qZ$S`6(BjaPDQ1E}lMmO*+pT)v@OzZS+StdH8vH&Z9g@la8YK zkk#w#{E3JB=z1g_Y24;KU%E0p%w-}EKWH1VXg=AaZVU~IVT@^2-_Wfq$@iUmeIh!Qv=v-)TVd0 zkL(EE+QhZ5`zKD3?4z9?!3%Q%X+akX3R(F7x6U(`|wFX7=(z zxYEFXy;f)p?A*y-zx3kprD4J(P{_NJyp$(7oSYYOxsHMdswapalXcuE;FbOA)+}2Z zQUF|2=91a7?8URrw6!Y}`%J?#&d+t1E-qf2UxzxVuAY};ltQ@5+PWptHao>BU#MHM zj#2xK>)d7aG@lO|X!i>AT&|Tv)Q{g>ZPoLtRugU6xFOXRp3mEVvR<;&{?oPZ9(e!> z)%Z4|{}cAoX+7*|?e9KQ`U`Q%dU4K|wC_^BQG=h=6-||BLyn6Tw?JP-_cr55c+eyK zlawyZE!>mM@qek|Q^Dh!S75c3X6vdVH#lX-C>WQs-10Jc-*)iq=1&#~q&V^r_k3 zeH;1~MX^Vu6E8`EoJm z9j&j+O*(IHmj_u0quhDe>gnBWuvXp-d;BrI+|gVknFqd6UB$1Xd_%g3zN2xJ8*fMC z`csqo#vg)W&jEi3nKDBBsWlL})9gk)6RG!zXK1vv)B|QRFn*mbZBA(j&n+&>i}QHe z__e3WDct$(Dn7S#9_L5r<;Q4vJdC(>VV)J=)sK~kFxOaAQ?Pu zIQbQS>Ew^fGFBnoQ`oG@pN9dcsSQN<&EZvriq@%`4N{}d^~;hI$9p#>G(v$ zc2(A{;-h6p&!c6;!_&pZ!{XQQ*xj&rd48QPzsJRo>C(o-(p|^H;(X)Rq>tvw={#M& zco={uOEB8EZ=d2xn6Frf@+NQmI?gY8oi6R}gr!Rp<(oOrXyA{e0q$2(!hYjn(Rj{1 zKDV@2n=NHP1d2xAMNErX45|kOsv%5TVsw3g(A)U^# zvsC+QS>RrmU8?pkl#*=YT73{vdzNJ>ml;>&SWON)@fX$}6q?vLWo(>;C1S0am?CT~*5)Q5Tj zVtQC^x=q_RV2Ey#<&M;MDrP@)uXP*sUGVCp{G5m{X(MZ1Ev)I6hsU2DK6rMpgYr$WEs8?lltuf0LR?(4?FzLH^Xva0Ke$iFlNA$)>0oz zD_*wTjy*=>0_d800(9V+HbWmrWBG9{ee?~01^}W^@e)>8zv`>k*m!+d&i6k9u-vGn zu5%_&vV6shS&QC%tgyBVg5bAM9{?&C~e?vEGmoK;Z@`PPEca9aW zP|<4YBXmXd(rC%gb8Y3KMYc>I5iP9inez66S6KmP74)&9W%K9Rl6mtjTYGtxlGi;T z2K)fU7R{Wgtpo}Q+}du_zaDLs^N`fni(a#ariq1Ep(y zTvQp5X^FgS=ln3vN>#36wNys{q-{y?MJ!Vk+E3{LgcnLLr3GHEQCaiVF4eWNr<^_> z!dwfUYqX4e*0^y2IIY>Jp_LG5M;jAI9cSx9e{1BCSX6q=)<-rAYx@Ff##RQiCr+?c z(rqCDmswgZR7^VFAo=IY3pc+&<`%tsOYhn)D#?!z1(omE!8WS>=4yYn?P}|6fo3fK z-l}KInaYP3Vas#RQ~OsB^$GSZSpcWZ^noem_a*Y^F~96;o<4fHRiVdwZ}WgXF8FDxRoSbq>>Ir&z^lM8 z`IqV&s{bst#rjpNt(th0(??MNH665H^u_0P_zQ1XRoRSFoFBZxxF9{umG|m$*)P7C zGk+Lwo6!IA6;@XtE6iW8fR(9}W!q&#)4%=3)+||Kd+D1RMcIl=>D@Md))rPpwUtgy zeQcBZx!zaE({!%dapQ{RR;G5HU^|Q#-p14Qfi`t|%hE?fiz$CxPM4pf+ZOFVOFUPr zj}_DhcRWL>OrW3I-C(%MIvK8_vaBrEBc ztKO>@FAD9GPqv4y*|uPb_|#B80KA_kn_DhV_X66^1D+)r;}p)=!mldZ5y`%kIU$GS zT&g;=rA1}w5}wQF&kud2vR3QR{*1#jWE(3qw$R@yWU1|vb-;sn_K%ZnnZ`Z(Lk;z* zvidM(qL!hE%wI+W84dg|Ye0j6?hHW#dYzLQ8vd7UAyfB1o(7oAFfrli=ix#1k0)wI zqKpRqv>Ncf=f^16@u#hHreYZl`~ftO+x^ag4HwhXdN{}2$m2skCf>V9A9zL503FN` zP_W{Y;kLH`N{BInWtw`RHkOLdmbd6W?T-j?iAU{ly3-$W03@tly2PG6uB&}{=j}G- z?IHGvChW81SxOv$CA2}Fep>~wq-0mvMtOsM|H#Ak$;B7iE5{uhz=)7of1f{azYVUH zWuKhT)n4x1%RapNN-ZI7XEOxGP~}Oly<#J-yV{8@wyEaXf#(#?B7$}pYM5^Z4xNAT%gOa6OOU90yCBjA8w=W zzRN~ja*@p!zzZ#&GS$Z1ev7>>K<ntC7U^Nq~i9mA^Nc0YK23QW=UNDnvNYfz{U!!&R?anO&B=PM&5Lz zeJhzZ2v7qMT0efGz5Le`Z1g|wwhyG2A=m#sfG&8~eLwVV8zi7;wm`PGRo3fQT^aT| zWp7Hn5u@bo0Ra2SZMWK!<;vQ-7oBfk-+ZGzArN?;Kr?J&<~QHi;{vO`eeiz!T6%q? ze1!lu1t3WP&x@y>YNPJ`hb@p@jlA$2TOr^RLhr9#bb)<(?KQSQfY`_j&z3Hygt(2X zSE>CUv`-ZO>IvNfa0fpKdtCPJd-h`YQ|wF0^41}T+B?$0_HBvf*%M!U!A4wvZL<9n z`~apPdJjP9MgiEV`X>3#RvUbEOFtVdy-XZFjO3x67O9?}o^z(HnfjA09yUzvf2VzM z{yDZppz!8(YpuV4sOjH~3F-O!;hlHvWqIx9@dor`GKKezmD0_iwujrW3(m72UwqCU zQoD@*;Qdf0AomZSd}8YbAkWc~_n&0jV|Be+UmA8jqzV-Opc&QwN7??@H}$jp*;zK_ zxo2(UEjQcOH(Y1y1bpVrm*2K6U1YCyJH|%be3O0F{e-X;#s-16wBwjpU$Op@Ve-o_ z+I#H|vS*J!&Q>j45XzgOHXhu(u}#zm*=CLS!bab9yKPV#QSKs*2?H9`vT;uhwC}a8 z!Q%q|7$=Z3rQ?*am~vulp#5J}-9P*LwYETj_=wYvv!!}@fQ^rqj(>czzvZo1-o`%u zsC{w$bv9o8hd6-CH?$1?8>N}?@kchGVIv!v+}40`k2a=Go2JcD`HAt8GLiNBcZS*^ z0phb&hamze-<0ib*2=VWvbxRCPoHSqB)kytYJ8~|Lhs40dHFC?F9BW@06a@JGwRlx z>u?5IB0iRL&UN4(Ix%Zw_(4>z(wD)?PEN`yF^7%KezRKP`wzJKV zm;Mvk+4jB0PwIe9Ke*=}d%J5Vn?H7}eRJDQHtWlgDL&qBQvsQij0Q3q_|s`1F|zHV zTFDJYdc8 ze>@F1Q2A>m`!$nQPfJ-oIrp%1<)&ovC*|E4ZvhYl7C4Q0Zm>1czTU+IYPiP~-fGbI zZ@p!81niX;7)B2F<_wYk2k?+gKT1os3keK7b-~ci4-)`)+S~7>mb&K2D+pEq zmp`uap_k$nD_A#yu5%}hw-*~V7C+#cM3Rkb*IBhIudo)~yW1lIDS8Uns~{k9&Y02i zbUV;G-f*L>TfWR5DO)bcA2R9>udX%1%W8G*YF+x>XiMcG`Ix|;#R6bz?Ypn-f86nQ z*y%k(oz`D-o&8hwpCPcJp1`)clcw1lH{NJP<)PUAZ+{D*3?B_a2Ya<@W%Uj}+&Z_8|0N(Qr?Eu z1+E3%Iu9MuGhqefsn_|=y8~!6Pyor1x87!zq|*&+RtGQJ74l$Rt8~pzKE>L}Gjg^( z%LX;s*N(XIs_?;~w>uwgCy)Lrc#E#P{7QRFb)4L>qcu9BeXxOaGbUb?0bJ*kT*vkw zXe9-TKBYZ3zY};|wcg(Lt^nnR0(FkOt2sAq|am8{zn0|GGrlDvtdIz<;NmFDp7 zr`f@(=WMmnQv#z7IPG+K-8Qg}H{KNXynRFfZPc!nC72jsY94{cZf}5=lzE^&D%he& zcN=*8@xjBC^mxVA6QJB+)YtZ!_U@z(N2s3co13p7AKx@YBlRI`1#<0J>u{-_U@4#td@YwDFV@#j2dC<=l*Oz z4|&rzOr0Vyeu`C+{!1k5<$T;W@)Rg&M_hG{t&u173?IFJQuDk7ta^>yoH{Y^$+FN{_ zYW9ebP=)d!{^*SYJ6?YY=P^}SA=?XU7Q%@*LjR{i)f)p4qHzK`mRt)#PmJUwf+ zX>AkVe_!@qUm&nv)Ledr9XWJpC=2h<%LJ(A*M}paM_aWFTYpr@%1+sB7$=isVo0RF zd}^=!h4rDcBn2;9dB>vH@t=HbGw%L}ov)WM1+`~x?KW-geSKS@o&e63c}Npa@A-c3qsx`9944R0^?S<}mmz+gPPW{_ zcBNzDy^PEt6UCw`vq*B-shXn7vyymE^pjmO9HMdO@TPRBi6 zubj%sEgTuV4%Er(m0O#GQ=7ftcu`Z)Whp3$D z&gc#N!yG^7OL9-Kq^|5b*s@Qb5XL^AWsCE%sO90B zI*u3Mu~go0d={yoLN^OY3M3#Eup&=#*==5Vgas1M0OaZ-pMaVGO!3^11i&_F3xEUf zy*B`%q-RO*LV=M`VeMzSZT%WsEALspdcR!Z6?&W&;=-TC-kd0urN}%2PN2L3Rv_9+ zRkDLm?H;a+mo8(#Lz}f6881I*&e(DKFxK?ofwoE4OJ>gr;WFg#tQn4pkktPs0d$86 zATOz<@=$~BJ?y99!?o0Ro^AMHf;H>fHG~5)HWx_!a`XMe68M_ho3pH926sEonrgqq z`T})I%X1E@kX_sAwry?CAJ)?LyXAJPFVN9Jc7Fmu&$Q!C?XQo}R1)sAzrZi3vX+;Z z*Z$WlwRD-CKGA)iWIp8#X=rb=v?Wg{U_*Y!$J8Ek0K{>ukQt ztfa`g2OmsQy$C61`|IOnKLPj&3~ki8v%R4DJ=>?3H97IG;RQg^0;S}!z1Ws~KHTyh z)y2Mg`Q@P3^#Wa2e)p})OVrhmfpO%oVx2lx=Wuyow>{LF-EecV{S#X+kSuSZ#Fy9e zXnB9Qmj0 zEZG0txkRH>{RQoYOT0z5st#-;(d>c??X%;Kvv%iSU~}cg%1E=9wxxgy$`)p5tBX1E zATCg%WEeBHY3z#<9;)cUZH~A+VI$Op&4p`~mrwb%kC@GqCvc&ITiADcaYDPTp1;7B zDbXs~c%`f?JLqqh*_&qTIx-bS)n+#<%kkP=ONCQc4PtAf= zDaNcEh$sds(ax{q5@CP%e+OWE(#Qb1D;+KybU-H}&63&}r^p!5V zqC+0yoq`_G6*|^$TXGqQ$M4BwkPF#jKQAL1PTRShoO|1nmUEAf=HcAKo#(&vh+Hmn zx^j!?A+=EQZEw2xJr#xwQGMkm)9#c>{&YXjOMfYaIi1EwIjLPWgLL&X;7UyZw9pUX|rHlV} zgdsy=P3ntluQP0fcJn&_tL2m5b?5ErJUJ4EP12^`)?OCZNtBI%HZV*7kR)GR}<;j$9(;H1hv4XpE!A*uZuVZ+Bz z0B-Pb898 zT6_2Ao9z|tZ(35IUbAD42|ROaTN1EmAm#hMmtLmemN4;QZ}yN*b%d}hmKZZB%B!!i zo0fNP(K6@O0=)K40${=RP;MFmxC!2r$vAlNAt&U-KKvP+Xk?)ryeHWowwHj^rstk- z6XdDbUmkw5lG_Wgmtyy~-nLc`J!aE~4Y&TavgA?sX{eX?1Fx6Y6aG+v#?4fbq^#US zmT4KdUn@QZU?$r?#ZPfz-$&UTdo4m zQ?vy_?Nd$)KrecU<|nQ9CG^HqO|)L&nTI?i6m1U;EbrrFn*h-74$&V-nLqiwm6Ab) zhjvNcPqBZOajRY=^pm=JUFWyaF7kxhpC=v+dAwMOW)n{{STH`0Cm1i z8`Z}bSsi&Ww*9~RQ+0J7E<4xEeb{rj$J_7P!S_92)8*yZU(44gOMl)D*myoEmiBU@ zUhH4nUwf-A*47H8N)}gtEg~H$S5i-;3BWbSNM3A~xIFUWOxXWwYa{!|n;c5WNs1)F zPi+nXd;*-#9P_nJ`rrd=bkj}2bDp@*2M(||TODEr)%VMkDq#f*6->2zUcBB#!Ru4; z(e{=KQf)*UbQ$`SB7*0!%15W01b`MRRnm@s=|wA+^nC5Ak6?AtmJKWB%(bWGxjOQh zr(qH55ZW|c={kAL95NcnXyDJGfg~2X>&k?IfIp2hyH5SbO2I(LM3h09hZ!cLeB>fJ znTy_7NDmPPa6=x>0Sov-H^dDVK!?3c{DB3Y5ZA=w_r$cF}^gdW(K zyBhN5dgo!p+Y=taj&&z1pqqIBRQ2Ej@R?s94k)BL zZzjFc6;PT@n-dSd(XxN*d5~T0kBwj#T!)7>$&8-)5l7wmL8NgzBaU+jJ0cBvXfMcZ z8lDigMVRvnfxqJ)vby|~?G!D4iuSJ}O-(JOLw1iNFT^!6dmWsI*Td6$c)BBb`H_b* zqSsyyM}E@N4-riua7-c)Uq${*Q9YuS2yDM5f?4n!#qAJ zCo(XO&6qL6Cg|a{jh6Lb10GJ<$i;X={kcZxt_SKu*m_N>>EjC)EU;0dMp=gr9Ri&K|v8Q5!7eb6Gflh@t%$PY<+f%f}Mzptw^Yd0s_(?DcyMq zk^lEI=d8o#aBhGXg#T|3oV)j)ShHr$n%OhoSu^`(opE=XENQA}BD`GIG33X)fHK3M zzq`l_nIKE};2s|MK`%E)81$e;T|p0r&c`m6Kw(NC+8*-K$@DxuF6sR~8Yh!qrns5> zoQ{X_cFn!DBw_;&mQb5nE0Ap1#TQ$9Z9B)o2fyNKqkNcV=yOlo-j`hw03jd(^2p1T zEL+YhX&XD|Zt_u@sDmfKr2y1uY*Pd)u;h3l#G{QvUw}x_9c3*gu*F>wjfn^%|04Qe z2@lW-mR24~Whz=rU*1;wlv6z9q1eHTD}0nx|FTT|-Ov}i+Escl))pu^Ph027F$E;) zzPR$5qrOo>33&!!Q?kLrOz2xUBQnSY%fsZhey&kDLC>u?$MoxGjW7I{wY>O}@Ij`! z`fy4~Ekfb)mTUsR6^?6s)P|4lgmQ-Wj`AaYl0cA=o+)RQowJNtLH5Ci*iL)wp}X~t z@F<__LlSqu$ZL~x3eIyKeBs%)?<3Dzuks5iP_e`|GiR){=Q+v96!yEz}>;e5_= zdVjP~pWoutQ|yTY53*ms`qFmRmV5eqKG4%lUwG&Dn#Ku(-g?`bioaz2by$?q9`}vj z!;lgq-3*A7q=Ym>h=8<6Ntb|tbk`sylG5EN-6AP1-Q78W^dJrI>}Q|-p6fa1|GBTZ zW3Bb;&zFST%aW`_-81lJ*P!at5H@=*`{$MsMg3P+Uk`P(og9j8xZf#z5tl!jQ$Y2l zSHG#*3++!sfGi^95gY5 zFCH6}N#PLrLywX9SMO)ym4(u@y*h;p5SA9c$uES?NPTCu1t>gOI_h5s4f)T%RT1YN z3w7WB`yyPbmv#c5^jQw|AM=whsdpt!uP?kr=j$9bIzxN*1?}lZz_ebgtqqHsvq5t@ zPRGd^<$2^>)nJzp8*;m@tXw{0_sP?z1q(E$+X8PsXV$$@i5;z7Ww2k__L(caK4l47 zW&aWPyI)35eoZG|=O<5J;u9tAywk3%3G!ZC;`(7)_ZC2E>6-<+?Ok0Mb)Du-c<#35 z$4axF(o_Rje}PomE1agFsW4d69?bWNNdo%D@xy9!S>p(WfrM93#>t*G1A}E8LEFhQ zU)zczfnyK-D!d~o$hdW6j0O2AM3~;d6t_}7OGf+uU@q2`fyk&vXI>)oPj(oH|V@#TS$^( zajQl*#j*7#S~G~)O!XPw{qXg*{;<&n)u6yc16fCST-fR{@BHI_X{GJAHm~_Nv<8ah z3%u2gOa3kL6i?TVxo3YbFeB?nyaRg2MZm>5m|EQo!JftW= z{jSy9|W$iq{Ah!)K1k?&xPjp9(X;BVbX)_<~Z4Vrz@Fu-bUt%8qVBA$ST-|kPn{63DNu{4% z2;{S4`1uL=e8JCnHB0rr)GG9Nx?|utyEf!?^7KItvE`g<+{Xn55qOKq@Qa4G9n54| zQh!|UUHU2Xn!N1fwN*Oy&Zcet7CBDZuH|c`3c0-Gn;DO?-k+vF%dz1vjR-caB-b5wU-YYfQh!YQ6dK2{%QUSfZyU|@2I;hn zqJULFlo($*zhoh2Cl>!z&g@UCX7`1yZDWQDG~Wd@pP`u`oP~H|*nXhjb^sP#l_Ga5 zfB(`Ty4}P{cw(gG#!u*Ich9=&gBUSEmI{`5E_c(z%|Usv)+`$f!Nbkvu|3RsaGNH> zxuLcUEoxG$Idwr=&RdRaOjdA={Z1i}W<=uF)?3>0o$LM-+ZHwU=Uyn{H;XnB==kTM zyYqf*U-Y5NmFEFMg_=Vqvq22TFq-(*$;Px1wvFlTN7hAB3Dlq_cNSjjsy}Ubz1zV+ zy?}R5?wgeaPQ>ZT!G#ARqs#Rg30xrt?%m=0pqlmFCOtOrz4$Bihs>??T+Stey8=iZ?{Y6 z#k0|3jzCYuqo(XABlYUj9P3n$l88lKv|i3ViPRt)O1E5HaVx!lR~k~^dauH6SXeep z9In&7KIiu&8#_B7!8Fb+y4sN3(0E!vCFS~todM5=*S)zxbZ8|$c!}MIwDqlpjhC(5 zqJia8f(g&>ao+xRwf=PG>>&pc8g|0HA{EyumWyvQ48C3bXx8*1=i1d*Z`$AAy)p2u zAHkne2fQkMSx!Dd0(!lzBNSmRZ98HYH`Q{&k=p;-wm*7nANMjZ@Y*PnFOEaRNP6SX zNtaK8a*5@bYqX(6>vHNmK_pg30EP16(TaEFYt~`bA5iBoC z9al)~W?G{fLw^_UcX5+0(%N{tB8u}sZdG0N3BQs&GDUrg%ToncKe`=!E&@>vMowU* z`L*qGm3mLGfmlBj1rHfO{l$Wm6IGC{8}CY;_xC_CdMAIKXZ{UxOV1tO^v`s#(ACmR zpLfpV9-3v;e`xwlE1ElKp!G&CjGR-?47yV6tmXxo^tDUQ~O)#I>LO_e+z9Vo(eQ{^7*pR5=E9dO3`!GyJL1Jg-i?|TZ zkxkjeUA5ey%&Djf^yWj689hP!?TT@5@|eHmbWl|GqfdWvMaSvPi$~FkBJt1d!ykPf z!VcAnDh`XzH5^yN=%8e!D8O!m)y8wsPpl7|!{ZKz=x^J6C2`XV-9OU?=p;9)>+<%G`}RTed`~ zQwC^Rz>Yr}rN4(HqEw-nh=T(n)E zVw*Ym%U3qQU{{_WwUB(}q;11C4+@&F%x21i?OUrNY)z0HqGKCeX|5G*{xPhu^r2weeDB?(HGyX6rUM>wC(Fc8E)yT9}w6(Rff zdpF?Q67M&KXQwHwH|Ug#Y?2{KhF)xN4FJ;qX}ZF3v-jV3f)?2IR@@;<$f!`V%5`o#tdcGGx>SGUvq(b$=O0~*Xn*lJx=bNC#h-(Rd6Rm& z&EiyZ-x8>N!VhU(R7N^ZC(8~WC92B`1^y1!LTYaWe(1ET#~toSbREWu*Zys63NITh z-g~vb)K#7dpP|{t1#Y!z14;L>dA@ojzp_hRE-2TZqBRC*lih%I+`I4v=T<~o=)zkK zkP`(_S>8uYfZy;~2T5s%n^!fo1Uxb>01yIEk9#znWg}yb8#K96BwnF@_Is}su7US} z6&tSCAR6diilSH{1W6A63ztRki>UaX4YfAvE--#;pNu zm#b8pikIe?Yj0MREpmvM6F(ip)%(NPJ-sux!Wrr8V*Y`CJ~@$T7S@1b7&CwsTfh)8 z9$@4I3mZo*J|Tg0&B1#;)}`25iHr=R2KEhbq5` z*J1ta7sGYcG?+Tr&IJQ^`VZCL9LD(jDGgQp)0jbO`xZh3>>fD@>MvN*`HQAhBl+b> z9qXj3WaJ>mFG(U6gA$IouebYlHVHLwo43e0Mz0FQ+Q{BTHo@(Xf*hS=uukGLbR7&l zro-vfzctvB6n2=D;|)uubh->0lAp*r5nWXa7B9aFO)>uZV%X$k0P%3UmYEFDGT*xR zdKV^_JPBvM&gIjw$rbEANA?q@pejp`b?lWbyz$Ym(jEwgRaZ{%jSvglNk()l)+e&< z;|lbDJ~tnJ6!$__O{&7gTapkq5BJCZp=XS8HzXaCB^pJT*)%Y??tp5_8niFLasdAM z{9*IHqm%L&wqn!5XLY{SF8nLjNO$w((VIxJJ&&t#w-|Q$@b~gT$)Dt6Q)0ib^$}ZU zw`qcI%=(zU#cs^Ly|5By?zX`MWP*zfb;=b&6tvv4P&d6s^ zOMSYG#~W0(XScUH@X>6Ij%qo&d7XTA@sB(~>PDP19+G4%+hDq~1jx@!qqi2`TsM6< z$&xVnlAzTbltgn82+&1hlyRtMiRt39rWb@w@4pQu)yTw86Rg!mah9U{<7YPZ@GgFf zmO@h)Kj?HPkjo26EmqBch`3m$N=Q2ArT$yn$Z<6=|2)!q|7c>$ZA{!`a%$IDS64m! zbNJNi-M>jm5t6};)?A57rGf!1S=8ZD^*%9W!#xE4p{**j)t;|3GlSM@NSm_Suxq-o@Aef)Ebbmc7Kaq^uy(;Wr!7Db2ZjtNUk1wG-q0 z2p!BYv7@x?i^N)7dCgJwLoDx`^FWr~xRcwR;K!P8XmK?cT#^>Sk@{So^KNla({?-SujQlVOO6;^c3GmOHm z7o)elnvcU4Af4`yQM8ulnnwN=x_0#fqa|!Sb*<~eFD}}n_uC^P z$&H5zxJJV9jS^;4st^#%92K>>f%Jq}*=t~gVfBO8u3BG5s4P=f3UTkE%jrc?1lbrN zV7Ek-(R^*5QqvIKi*3h0vRq|}t^VppW>Ke@JAn~gM(6%6Lqjz)F%LSX3cvY5#a=W! z`Ud?}er7#2LHK6iy0XD|VamAa+vh4-?;3Lr_auXY$GXUbT-e(iBkh|?TInCVC8>-f zl(3N(Q)Q(49#|Z_J$(_?X10aB1~&PN1^imp#j*k4&>RBw<2j*>X(CQR<+19o*?i5* zGLg}43Dl$U;#2KphT5El=(}3(qpcBb3`p@8Snxfk@lBgU#d>4ICO1x->}#>b$M1vu z^>Lqc=e3ltIi@$T$d)+_mBuKPY2Oz6--xzt7^<#WU?dRYx+DFg_6hGRb&5V0%OM#A z4fo8}F$|=W*x#?JC)SCEUR_PDuvqyu>*GR^ICazwJ+m<@Loq`;@~`^8x5Y~o=r`3Q zmP2I5X9`QXQ^c$s08VzCzxvGg<(yq~4MV%w`GswX#hk|YMtLUSMP1$jb-f$qkQal~ zSrrBar|}z;A;OFNTjf4v@|6hH8i7QM_2-Q>7{B}cO>w%3t`?crju)%<`Mj(NBbHsR z)|0(oVs#-P5y3C@KS%imOR%rcSgX`^enXN|EPN6L$y9#>SN&aS^{)D9=H#>LdpTcP z*+vBr0|Xqu0Kyc6$-RIsfJUsv9OG^6;FQr86&g&}(RV%ygLYtYR2ZUE1A!V-yVQcD zTa-v4ISSO6P?Zgmw?K;$fNJGpQDNcQ#$__*WuQRmY<}k(1_`mYqy}KzLBHE>Gg|?o50>NT0j8?B(2~*A zx@w+QVUJ<2QQ&gLcmI9@U_$$M3;F%0v&fZn%l99JKE;Yp`RBKK%$S5+?7VGt0P(R5 z>sooYAnWl(nj-~l7CJDaXzV#NZ#zSb9LqG>jC#HB=`6jcN=@7^9(uSI+rwxZEM@e5 znHD5)=`QVX4_4!@9GS^YEslo_f3R|Q5gXfoUt;V`#*G^o{2_sT&a}sF@xr;eT$cD? ztfho=QnG@lLv2gCbpjX{Fjrb`$>Ylcc#Hk1m}i3F2hBFFX@5vz9hi~{uk04k4SGb5 z;x$xoP(Z3AuubEEP2w2ZbA~q6t%pw;TL}_)pZL}2x#3(c+vLjWKnp~pK-jsUG=7G*$nzKoIesHL@O;OBQci056~xN1QwDBLwV zqvh5xUpO!pDt7~LIDw|uAc}7n9Ck-CWPF%o>!>HHih=&uo@=<-D5Zz7aF06o1&FIu z6Du^T0mDi*OS&yS$!#?R?l&P`&H3k6{Xtz`xhy@=ADYrdXQuSIcYd(YEXDLop>LBO-%Hu zM?+wD4hWmQXkc3+UK8J!{9c%>R@=Z;Ge*!37funbv-Rckm>Yr`bXqloP7#9n>mP)6 zCt83no89$oyOP%yE}+ay|7=YHi5EOHVSFK_(wGihNOo zD`w`3%L0|as|Xg5Uo+kK z2tkt^R9FNx9ISHE7iS))z=iJV+2kJj+-Vb5$)lRJM>Vmn-lk-o>zA*}9T?hW^Z5e8 z(}VT1ZL;N1&=-lgYjQ605jfNzq*pE4omJw6Llz9Y{4jEo$PFOpHd3x#i(bt0uRVuJ zGRa3%1_Co%H4_(*0FICwJYoIbq5~0hj?2KzBnJdNP9kik+QZ>GOAdI1RxX)&qz-`RX5`BnALCTXgjZ zP-JLGoOJ2c+Lt|ua))4jSB>4?Q4nC)ne-AkY&J3byPCCEmKNLd_cC~}>|M&l%-)zU zNAnwtv?dXgWjU{(Tp#L^#aZBWhbq%$_bd2zYtP^Y8P$1WbEZZG=_3xL4na(auL(9~ zpPIH_fb0jPo%mTh*!Hn$^HyjO-B}Ju5+E8%78Qq)Pl#ECm5kpHdO9#;)FDX4l#Rik z!Nq?qOKs0UYQo67;^=_kvwQazU^|*`z~#aAFY5>BbEDyD-Meyg804tdP3)7)wLl1F zHe@W!3G4z&1R>E7bqTV}ye2vDIw9_&Npu|vBcn@mY5{=q)JpZPs=p^S&JApZZP#E- z{Kg32c_aEe_vvNU;~fj!WK6ORa5A*4Q}8gS7R04)T=BB0y0LKThE$On6J!(ku|kan z?(r`|g6S^aHW%XMhtd&L;il?XqZvMU*~{pt*u zFLjtEcfNDpnOj;GNvdwtjpv_U%o;%~uynfYHE1jxfZx$flG<$KHE{Ay5W_mg81%s6 zM)Az4=@*xCPnOpOk3HWMn80&fHAio&N1D`_?-Y1HkEXH$cSnHz`e|sT<@OkxUG|71 z#~bSei0MkSZ+cJ$QxFL6E~;1y@T1{Lz+k%3gum%9*blBzw{%}}lq-e;sy9~VILh2z zh5h{ETxTafYiTuLA&qv!Sw!MsLRb=emFRPL=I&1Jaq!Z{Pk%!fF_ljZ8E5@IGNPSm6|rS9 zkjK%;b=wx6^_>hkJq_M6#_L}Kl2+Jp{!+9pXW!+S_w%>+hOx`yGEl#R%s&1 z3)U~Y-GBY&1}i6V^M`?r=#qFcHGB}lC+S;?0p``2Mdp;eiHx!C*(ni&zDP>kfz8{0 z)nTI&)}yp;#jRr%w$4RZ!z5PGnvl1fPMhCw@4hpqaOt}q-+ccDjV!I(N+?oHoZR!e zV~wusQp@_&B9m+-3h}Sd8`bV-rzA@#Nn(^GUm>ORN36BnT@?+aX@xvBS`RSvvk0{M z`yU5yj_Y}hM=8tAi4SFAU<|QOyed}G*UQF-0yDEOedehOtn|!rdL7RtpEJAb+ybV6 z5?aUKlEddgeAXW)u#LYIK;l+V{2QL@u4mycSO*d0%x7>yC6DT7O!)M8i3zqwJsVFP z5_kZ5I39Bn098t^7V3J3^@tK~^Y~L-YjK8$=rX51L4zm!VYNrx&n~qiZ8U30ZyHWm*YnH<>(JJfD&j*`lHgFLMS86O2apeS6i?B zO8n1snm%+GpXUA>aqlX>IPg784Ros?bSt+xPaOEAaH`EFW;vZ|E)u|GgMpqf(=c(W zahOJldl<`TZotC1qYvd!RwS9!P4s`>U{y&Pz*0B-kUwByJ6iT3m+3&JhtrmP;C0h8 z*k~Z6)Oc@@%-#ietsg|yAeyHw!~ypFUNkGmoT}ojXz+)4*_a^!>NS!Qk9aDHhvEm} z+l9MfIK1|8=b(3)AX~K!aMr0e%?TyQ+eC=Yxa3PEIl{jonnh<7cLUwPY7s{$5V=8Q94>#uPfUqch(T!2LT6R=_QS4)BAm(5M?o+qH#L(>3wz z1&stNdv*i39>H@nK!Dj{Y#tr~`gn|z|3<}m6*X7Ars!wut)1S{!IFq706s#f(kwNq z@6O784H7bwinIlN$iXo}nQ=7=qJG84JS6T9XAEl_i~`b-wm)tM<$`%TrQ$K%iz1HW zyuvWx89{_slh{z!`7f`mXB#Dc!B_D;)^f+-aJwsf?1`Eu=&>IY@aUKuFGxCZ*VlpC^mo0I7uLWvVgqQL7!Tu+4 zays9PHlC70n##yH51m>CM?LP9GZf}ofk_o^LdNKJ!W9K#)HLf83+04i-}Ckmmy>U{ z3R(G5lzAiD;aNor^^@{T?^~X?veHfzcwC;KozR?Vh~2~@Z`|o7&m+(FEEKmhlt?Dj z+{XXizQ4}U-cHGS5QUVKPEHE!r}w?5jP)dD836aeJeFc46u4j5Z!{~OM)_oKUl1xJ z=}}6z{rTnKbt;f3$q{g9gm%ypzP6nn4UX>I1HWBt%bPUq9C}VY7O21l#Muv<-@Y(W zdXds$+7t|KS!e2e9IgCkfWL_wc7>pXQDf4EMMOVDtog$;seCAPSUR)f!)?6Xb*kbg(A|>C}T_ z0jsCbF=9z9IPpVDKKKofiRB_A9Ahte3vBuu25?vogU*2B4tDOolP{4iIuqAiQA&s;gZhzLE73w@HqSM zl#iUV-pu>nc6X}8l(BHQL0!XAPZq49tF7Enx~;d_bVk}Do`DG3K#;RGNsZLcw^z&?Q?v*D9xrISk^ zv={I?;sRYzgT5G6($wqoSRYOlM2AO42(~3z#%(s%lwYvYs= z-|q-02E1ZOI|!n#x_^I)b&JFhxCh7*f~4GQI50tapvZo5HNyK))s5GlyOdbUovpTX zSpEZDe0I1tq67W4=Wn6XdOZ*uHJUo0P53e43eYq#$B-9~i>jG;qp;~ZN@@UlYByP^ zT*cA*BKB}m2l0-sY#CHL$2{8L%drW{9Os)rAesglQ`=C5`CBxNNncI9b%XSY+0O^PjwEPHu!U*0l79aM0 zD!%3`MYVv59dM}Hn-_^ck^dxHWoGnU0DbF{nFqt4DXy!H&p5WITRSN?H15e_#nR9$3(Is*T9!>-O z;UAc@Gth^4LA|l0PrF!hP%cMK_c3!vq7TS+9pfP-y$hyCQ%!^>)B%!qc%WXCSOIV5)qHl{f-6S?Y&6rBn zOIli&yubSLJ0|)*qp$n8ebyj7Q_vH?ve=)<`T|bIrBoA32^i)?-=RRlaR5uPB}ZDA z%yP9R6)tC4tjv5I{kzzyRi^+;GHRf3OuOO)lVWrG%-b@?>q4kb4@)Zs(gFMRjSl=O zclQox$d1;(lQFH+Eeaf&^s6c}w-F~e5I+0(9qk0aNjgrryDr&8MH}_6{XSs~e@b=53gPCJg z;q)Vb(xhe26Lyz1&`Ip3+L*f43BiWgJCE0WATdzRJkcoD?TKXo5R)p4W@W7WtxZi8 zg-^`PhS(#XUvf~z^tRKh3Rg??WcB<)1uLu^IImslmf7*JhVQJl$qArrE8R)afqYKI zcVEtJHrSN@%C!FoUdp9PdQDr0v)LCaVcO_PBzZPfFpE1{;oBZkSPK9Iuk`BEzk{~^G;l4PW=X$)=Lndc`@~I9e zM_!>Do0~>xern)YP1ZLh#4zY=!t!8%AzmBZEZqiC)5k|Mfy{XoFw1)Ov~zP6cSy@-s~sP2+y2+y`Au!uI;}Op z(DY2}W92Jn>nLBCcbNF)CIz*yO-{yCS+&7*o5dV~kxJ-RR5Jf^=JCp+gm8O88->xI zUzgOMv|G;tGyHUjy&sESzS7-|K2mS~L0jokz;C^-uim)88o{g44sgOfBoKjImvCibY6*}YUmPlEjl(tB?_5y_%__OOoF4if~6k}Ou)B(i;MxJ5whFF@vp)k69 z6V)H)EGzsCj4MT>1xGUWQ(S{zydyAwEzzGYVw!^i$Mr+h_Cr%9v(y^lu3O~W?B?09 zu{C+38yzxrg!{O&Bwy6?0G177LREjT)q*mvP(P;dS@pC1xCfk*NEf6(4bJbT3LpYG z9i+i^16x3IRc=}jV_)MWkl2gc>ziwfauq)817%L(Oqa&8n(W_)_E>nIJUKa zY3m&ta(m8|Q`sHutms)7Rb9-&wdY{_vS>wjsBrh6Tm#6wC2ImzK5$+!PC z6snELW9;74|IqQC>>uLR6GqQ{wPvf@#+6m@Pukasi|Y{dP@?VE#)#%A3o@Rcz9OBj86oSb*$@<(~ei&|@eI!aHHH?hi7 zd7b@E-Z|AJcDCMZ+C!HA-K(g^!91e=RlWy2EdfS@3+Y@I0) zybM^y#zZ*TU%-Ej7scS{VpXK6K9f?>Pj&s%{YS*MWZsI&KWOGmI*+h)Ry6Y_Kb#}0ALwlM1mr&KJXB`b8G{v*@yfYjhwE*p&MxQyBzwT0$J|p zm@zoccMz_-Neah-U_M@J6+8eLl-Lh zp=4L!?c%3dTVMK;y7KYs942(;A)}fhzYJczz5NftHKKDT3VgORUzpZ?#%XlFJF&V? zKiMvmo%FhS702;AR#MRYFdNtbyl)q5-`0?3FB6G7`uX;Fl2MsS3qm93#0Eq(4p~Q6$e6LFAO{n+^5x zc=EW^p>`!XlYWj<_2g>6bK>N!p%9YxVee8mIs4JUAx)$J?+#l|bv>H*8M{^Ux#d9k zUwy4QJ|sD=nd+|fh7Cu+a02kF57O$4(i*$Fo3BKb74-995V_-J*rd20N*y(`;Z6Hn zksE&+Xp3igpSN3&Y?6*aeOd)~8^uCm^mjzxo(B8AcpsXcaOC|Jj~UK&^xykPUnd>W zX45~W>;`Sp{g4G@v_{B-Y31MAN`sfqA(~~n>_G)1wYb9ntv7x}Ii2+XGp0|TfqGqf z_@f3NZO*VT%IF1CCCH!Y%eoLW z@lY}Fgo(k|1V%|VxS5FR2z3pd&hvZaV4#D?&ip|r?^#~N$(OO=Qrzb^{=8PPWS_)?gL&CP@VkdyY6JX9HG9BA=5o| zcIdH?{yB?Jf1;r(P6~fF!pT(8hnrGx&w%$dp+|_rq1uPZUeW{c4#g~Ev>y$%ykbnR zae{CTNdH=*aa0J@&&IMFIIyE{bgA~|2vGhGc=dAs2p5yffZZnW9No_!-@g?)9Lu`@ z28BT`E!(}kWmgq84+&fS5AWR%-v;EkqKCAx^0?G5Kzzv^_v8>sXcRBP&z&XelBdyNUj<6>f?{qJtK( z`-B#c^hKR%Q&cfK5BZ|}>nxWWAniBHOgNG90LNjG`k_00Pa|dd!b5yQq>P6_9OP5=sVZqiSD|XO{}7;#^+~4U0SY zGRx_yKC8all4mbe8LqSk5^zxW`fbMks9-O50=8FI>%D$Hw5ZF!WD&UXD$2Fd``$E(6l>x1qNx}Qd^6=K#NWh1>&aKr>j=p* zRJpTCx{?``QZJGb8adzek!z4PJN@bkHP-o+E+%2k>Ms&47DrI3Tef?>FO*I0r~u~f z2iPSgW;S<|3(rX?6wh{i!;3C?xFMtcOSED!kW=S4Mw5Q-4F8FBegCg=4-ZNdep`K` z7U9tLVmdX*2(IfE|5tL?e#}tVY2MA66XVct;-hOR)CG66-Q844&id6KgULIcoDrN_ zg*qS4Oc)2qhSR*c(~|z|$!^OehwdhlKja6~X`3T>9j^bHfAn*1)PyYJ(DXX^s8O`0}{F!`~`O+(X*sn5__Hk=6fW#~uHa!~Dz)^Y-hN_l4 z2>a<{pSz@`Tc-cNr{I6G^#55j*&RdxdFRXUQTw}X#|u!wT`p$o;uJIsz3aUTS|1=R zauwBk&I6^fd@H9j1-%(}*)w@aXQQ=tcFrG+gfw!|mrw3W49FRpjPs9sqzy1y<4%@* z%C}B&<%GvSiM`U?!QUhy>4@mXt$CregDW|Bz#{(1%h$u>mwzGh$_|&na2)D4qQUH! zq0ODv*Ry)u^eakK9CGe)bd1DSjVAW}cL#(xp#Cvq5?R`JVOg<j&e8dx`>~8T<=MVM2}k*1wb#ien^p|u@fGTZrMqUX#EF|`4~;9$t~#@|Du>;iqKm!a!V_UJem6$ z$0lzDsHXSi7K3hOHw%u4m6 zQ&7Rrn}5!u?i$u0B4DYym5=ss5E(ryY{Tw_-LAD>(@wAd=ZCbe%bl?W8p=4xoUpN1 zr6-rRR&|FQbG(T$Drt3V*vgIgd|hg816L<*T?xW!gi5o5xBjSOl7xA zAtN^R@|s^DI{PA9cg}ySM%!jOgGat$J)f@pStdRE=htIW1^1lcW7D6v`!b4SQMc>- zpuXt;pWEVm4l-I9u@fU7enqG)XZ}Wf^Y*`YL{f;J9C1S#bv?cu|MPnMTi5^IwejM2 zNy84~%xsH%>~W7hq}Wh^T5LgISj-dp!qD4nVOpAtD<48sGnk5?=bW1F_GsCI-)%7c z1cv1M*_6%Rh1|x~dS;PZ%H6x5&@0vdg+Ko@Mjd1clAkw!;`3fe8BOpVItx(0UM_@E zSfSO}?!)8wn*#Wng0~aMP5dxd3$ZyGTe7Q#O|DO|hm{ki3*%YZe-deAUK5WbZ8>j# z$H8*sZBqM(7m$A%_O{R^czxH&{lnhqRWo!J;q3ACj$z5)&?u9c$gXs?6MVO|BPN!i zota|mK<6p)9uf_78PtZ_@9-Ai;IXYpmFSAmT{dy_)nvO0@#hTg!a}D5U z)BN#%joDW%Jxq2ZrXpYK^4jru@8?;dVHgq9E5Pq-C}V`!Jtgc2qVm z|B$PretO(HdJsm>cZcInzQy_&`-9sLhtk_>-S9h`Ynb9mtjC+30LXO8!3)1B($Rc| zCEClY+i0&HI=NIUebril3cr8P@A6f7*2A*~;iaNi+bR4$J?XbC1Cm0Xrzloz&xP1o zvox{r`KH7RIfS=iJm<2fe$jV2I#}QTT$CfCxpWD!ds^ZGg4UMK$;~%ed@JWS+u|87 z9a>qg6FCe9$nGAR6Bese58bB58J_y9HrxJ2tJP$;%ZgJaFlm83892B}-}wLEmct3~ z=zXHy0On#IW?YJ{tS|_6krfM;srfEl>l0alYi_(A-`XM zweA6(w+BN-7*us!hAl!E#zZ08RPg?#CTF`8t;d|>y;GL-mVQ1eOmTEE4I}bkf>C{E z=b)F8A(+=OS(BjzVnhvAB^ld#RDCkX;ioNHTuC{^?}^#(XCj zq!EfBwDxf8^Hot%*$*X%iTcHazwWu0ILY?chBCwV%}O0LGL(0H4P&K6TrJZ|=oDaB z_1vZPzQ35&=x8ruYJH5Mw4f3A6o0=9d#q+sOhjKm-0z4~Qd^w{l6eY^4bw&@c`K^X}~peRgF>yRtRuy;=q+UOV9U2S+kj zjw`-w?bG5i`b=swQx|o)Y_I9Iz}C$YePXC(&LJzh@@RZjOjLJO{O